Install IssueCapture in React
Add bug reporting to a React app. Works with Create React App, Vite, React Router, and any React 16.8+ setup.
Quick Summary
Prerequisites
- React 16.8 or higher (hooks support)
- Create React App or Vite (any React bundler with .env support)
- Jira Cloud account (Software or JSM)
- IssueCapture account (free — includes 10 issues/month)
Step-by-Step Installation
Follow these steps to get up and running in minutes.
Get Your API Key
Sign up for IssueCapture and grab your widget API key
- Go to issuecapture.com/signup and create a free account
- Connect your Jira instance via OAuth (about 30 seconds)
- Open the Widgets page and create a new widget
- Click the "Keys" tab on your widget and copy the API key (starts with "ic_")
Add the API key to your env file
Use the right prefix for your bundler — Vite and CRA differ
- Vite reads variables prefixed VITE_ via import.meta.env
- Create React App reads variables prefixed REACT_APP_ via process.env
- Add .env (or .env.local) to .gitignore — don't commit the key
- Restart the dev server after adding env vars — neither Vite nor CRA hot-reloads them
# .env (Vite)
VITE_ISSUECAPTURE_API_KEY=ic_your_api_key_here
# .env (Create React App)
REACT_APP_ISSUECAPTURE_API_KEY=ic_your_api_key_hereDrop the widget into public/index.html
Simplest option: a static script tag, loaded once with the page
- The widget is an ESM module — type="module" is required
- Place the snippet just before </body> so it doesn't block first paint
- CRA only: %REACT_APP_*% tokens in index.html are replaced at build time
- Vite users: index.html token replacement does not happen — use Step 4 instead
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Your React App</title>
</head>
<body>
<div id="root"></div>
<!-- IssueCapture Widget (ESM module) -->
<script type="module">
import IssueCapture from 'https://issuecapture.com/widget.js';
IssueCapture.init({
// CRA inlines %REACT_APP_*% tokens in index.html at build time.
// Vite users: see Step 4 for a runtime equivalent that reads
// import.meta.env (Vite does NOT replace these tokens in HTML).
apiKey: '%REACT_APP_ISSUECAPTURE_API_KEY%',
});
</script>
</body>
</html>Vite-friendly alternative: load from your root component
Inject the widget script from React so import.meta.env can supply the key
- A dynamically created `<script type="module">` with inline text DOES execute when appended (unlike a parser-inserted module with src; this is the supported pattern)
- Use `import.meta.env` for Vite, `process.env` for CRA — one will be undefined depending on your bundler
- Empty dependency array `[]` ensures the widget is injected exactly once per mount
- The cleanup function is only needed if you really want to tear down the widget — usually you want it for the lifetime of the SPA
// src/App.tsx
import { useEffect } from 'react';
export default function App() {
useEffect(() => {
const apiKey =
// Vite
import.meta.env?.VITE_ISSUECAPTURE_API_KEY ??
// CRA
process.env.REACT_APP_ISSUECAPTURE_API_KEY;
const s = document.createElement('script');
s.type = 'module';
// Inline ESM module — runs on append, lets us use the runtime apiKey
s.textContent = `
import IssueCapture from 'https://issuecapture.com/widget.js';
IssueCapture.init({ apiKey: '${apiKey}' });
`;
document.body.appendChild(s);
return () => {
// Optional: tear down on unmount (rare — you usually want it global)
window.IssueCapture?.destroy?.();
s.remove();
};
}, []);
return <div>{/* your app */}</div>;
}Trigger the widget from your own button
Skip the floating button and open the modal from a React component
- Optional-chain `window.IssueCapture?.open()` so a missing widget never throws
- You can use this button alongside the floating one, or set trigger: '#your-id' on init to disable the floating one
- For accessibility, include aria-label or visible text on the button
// src/components/BugReportButton.tsx
declare global {
interface Window {
IssueCapture?: {
open: () => void;
close: () => void;
isOpen: () => boolean;
updateConfig: (cfg: Record<string, unknown>) => void;
destroy: () => void;
};
}
}
export default function BugReportButton() {
const open = () => window.IssueCapture?.open();
return (
<button
onClick={open}
aria-label="Report an issue"
className="bug-report-btn"
>
Report an issue
</button>
);
}Pre-fill the logged-in user (optional)
Save your customers some typing — pass name and email through
- Use updateConfig for runtime changes — init should only fire once per page
- If the user logs out, you can call updateConfig({ user: { name: '', email: '' } }) to clear it
- These values pre-populate the modal but the user can still edit them before submitting
// src/App.tsx
import { useEffect } from 'react';
import { useAuth } from './hooks/useAuth'; // your own auth hook
export default function App() {
const { user } = useAuth();
// On login (or whenever the user object changes), tell the widget
// who's reporting. updateConfig is cheap — call it as many times as
// you need; don't re-call init().
useEffect(() => {
if (!user) return;
window.IssueCapture?.updateConfig({
user: { name: user.name, email: user.email },
});
}, [user]);
return <div>{/* your app */}</div>;
}Verify it works
Quick sanity check end-to-end
- Start your dev server: `npm run dev` (Vite) or `npm start` (CRA)
- Open your app in the browser — you should see the floating Report Issue button
- Open DevTools console and type `IssueCapture` — you should see the widget object (this distinguishes "script didn't load" from "ad blocker is hiding the button")
- Click the button, submit a test issue, and confirm it lands in Jira
Troubleshooting
Common integration issues and how to solve them.
TypeScript: Property 'IssueCapture' does not exist on type 'Window'
Add a global declaration to your project (src/types/issuecapture.d.ts)
- Create `src/types/issuecapture.d.ts` declaring `IssueCapture` on the global Window interface
- Make sure that path is covered by `tsconfig.json` `include`
- For the full method surface (init, on, updateConfig, destroy, etc.) see the IssueCaptureAPI types in the docs
Environment variables aren't reaching the browser
- CRA: variables MUST be prefixed `REACT_APP_`
- Vite: variables MUST be prefixed `VITE_` and accessed via `import.meta.env`
- Restart the dev server after editing .env
- NEXT_PUBLIC_ is a Next.js-only prefix — it won't work in CRA or Vite
Widget loads twice when navigating with React Router
- Initialise the widget once at the root (index.html or App.tsx with empty deps)
- Don't put the IssueCapture script tag inside a per-route component
- If you do need per-route config, use IssueCapture.updateConfig() — never re-call init()
Need different widget configs per page
- Call `IssueCapture.updateConfig({ apiKey: '...' })` on route change
- For full teardown (rare) call `IssueCapture.destroy()` then `init` again with the new config
- For light tweaks (user, default values), updateConfig is enough
"Domain not allowed" error (often shows up as CORS in the console)
- In the IssueCapture dashboard, open your widget and click the Domains tab
- Add `localhost:3000` (CRA) or `localhost:5173` (Vite) for local dev
- Add your production domain — both www and non-www if you serve both
- Subdomains need to be listed individually
Ready to get started?
Free plan includes 10 issues/month. No card needed — connect Jira and you're done.