Vue.js Integration Guide

Install IssueCapture in Vue.js

Add bug reporting to a Vue 3 app. Works with Vite, Composition API, Vue Router, and Pinia.

Quick Summary

Time: ~10 minutes (incl. Jira OAuth)
Prerequisites: Vue 3+, Vite, Jira Cloud account
Method: Script tag or main.ts inject
Works with: Vue 3, Vite, Vue Router, Pinia

Prerequisites

  • Vue 3 with Composition API
  • Vite (recommended) or another ESM-compatible bundler
  • 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.

1

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_")
2

Add the API key to .env

Vite reads VITE_-prefixed vars via import.meta.env

  • Vite only exposes variables that start with VITE_ to the browser bundle
  • Access at runtime via `import.meta.env.VITE_ISSUECAPTURE_API_KEY`
  • Add .env to .gitignore — don't commit the key
  • Restart `vite` after adding env vars
# .env  (Vite — recommended for Vue 3)
VITE_ISSUECAPTURE_API_KEY=ic_your_api_key_here
3

Install via index.html (simplest)

Hardcode the script tag — fine if you can paste the key directly

  • The widget is an ESM module — type="module" is required
  • Vite does NOT replace tokens in index.html, so paste the key directly here
  • For a runtime key from .env (recommended for repo-friendly setups), use Step 4 instead
  • Place the snippet at the end of <body> so it never blocks first paint
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Your Vue App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>

    <!-- IssueCapture Widget (ESM module) -->
    <script type="module">
      import IssueCapture from 'https://issuecapture.com/widget.js';
      IssueCapture.init({ apiKey: 'ic_your_api_key_here' });
    </script>
  </body>
</html>
4

Or load from main.ts so .env supplies the key

Inject the script after the Vue app mounts — keeps the key out of HTML

  • `import.meta.env.VITE_ISSUECAPTURE_API_KEY` is replaced at build time with the value from .env
  • Adding a `<script type="module">` programmatically with inline textContent DOES execute when appended — this is the supported pattern
  • Putting this in main.ts keeps the widget at app level (no component teardown to worry about)
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

// IssueCapture — runs once on app boot
const apiKey = import.meta.env.VITE_ISSUECAPTURE_API_KEY
if (apiKey) {
  const s = document.createElement('script')
  s.type = 'module'
  // Inline ESM — runs on append and gets the runtime value of apiKey
  s.textContent = `
    import IssueCapture from 'https://issuecapture.com/widget.js';
    IssueCapture.init({ apiKey: '${apiKey}' });
  `
  document.body.appendChild(s)
}
5

Trigger the widget from your own button

Skip the floating button and open the modal from a Vue component

  • Optional-chain `window.IssueCapture?.open()` so it never throws if the script hasn't loaded yet
  • You can use this alongside the floating button, or set `trigger: '#your-id'` on init to disable the floating one
  • aria-label keeps the button accessible even if you go icon-only
<!-- src/components/BugReportButton.vue -->
<script setup lang="ts">
declare global {
  interface Window {
    IssueCapture?: {
      open: () => void
      close: () => void
      updateConfig: (cfg: Record<string, unknown>) => void
    }
  }
}

const open = () => window.IssueCapture?.open()
</script>

<template>
  <button
    @click="open"
    class="bug-report-btn"
    aria-label="Report an issue"
  >
    Report an issue
  </button>
</template>
6

Pre-fill the logged-in user (optional)

When the user object changes, push their name and email through

  • Use updateConfig for runtime changes — init should only fire once per page
  • `immediate: true` runs the watcher once on setup so currently-logged-in users get pre-filled immediately
  • Pre-fills are visible to the user before they submit — they can still edit them
<!-- src/App.vue -->
<script setup lang="ts">
import { watch } from 'vue'
import { useAuth } from './composables/useAuth' // your own auth composable

const { user } = useAuth()

// Whenever user changes, tell the widget who's reporting.
// updateConfig is cheap — call it freely. Do NOT re-call init().
watch(user, (next) => {
  if (!next) return
  window.IssueCapture?.updateConfig({
    user: { name: next.name, email: next.email },
  })
}, { immediate: true })
</script>

<template>
  <div id="app"><!-- your app --></div>
</template>
7

Verify it works

Quick sanity check end-to-end

  • Start the dev server: `npm run dev` or `pnpm dev`
  • Open your app — you should see the floating Report Issue button
  • Open DevTools console and type `IssueCapture` — you should see the widget object
  • 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`
  • See the IssueCaptureAPI surface in the docs for the full method list

Environment variables aren't reaching the browser

  • Vite only exposes variables prefixed with `VITE_`
  • Read them via `import.meta.env.VITE_YOUR_VAR` — `process.env` is unavailable in browser code
  • Restart `vite` after editing .env
  • Ensure `.env` lives next to `vite.config.ts` in the project root

Widget loads twice when navigating with Vue Router

  • Initialise the widget once in main.ts (not in a route-level component)
  • If you must run it from a component, guard with `if (window.IssueCapture) return` before injecting
  • For per-route tweaks, use `IssueCapture.updateConfig()` — never re-call `init()`

"Domain not allowed" error (often shows up as CORS)

  • In the IssueCapture dashboard, open your widget and click the Domains tab
  • Add `localhost:5173` (Vite default) 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.