Installation Methods

Choosing an installation method

Updated 91 views

There are four ways to get BugDump onto your site. They all end at the same place — an initialized SDK instance collecting telemetry — and differ only in who calls init() and whether a floating button appears.

Quick comparison

Method

You write

Floating button

Best for

Script tag, auto-init

One <script> line

Yes

Marketing sites, CMS, no build step

Script tag, manual init

Script + inline init()

Yes, unless hidden

You need config that data-* cannot express

npm

Bugdump.init() in code

Yes, unless hidden

Any app with a bundler, React through @bugdump/react

Fully programmatic

init({ hideButton: true }) + your own trigger

No

Apps with an existing "Report a problem" menu item

The fourth is not a separate installation — it is any of the first three plus hideButton. It is listed separately because it is the one people ask about most.

1. Script tag with auto-init

The shortest path. One line before </body>:

HTML
<script src="https://bugdump.com/sdk/latest.js" async data-api-key="your-api-key"></script>

The presence of data-api-key triggers initialization as the script loads. A floating button appears and you write no JavaScript at all.

Configuration goes on the tag as data-* attributes:

HTML
<script
  src="https://bugdump.com/sdk/latest.js"
  async
  data-api-key="your-api-key"
  data-theme="dark"
  data-bubble-text="Found a bug?"
></script>

See the full attribute table in Quickstart. The limits of this method: attributes are strings, so consoleFilter and networkFilter can only be passed as JSON — a filter written as a function cannot be expressed this way.

Why async

All the snippets here carry async, and it is worth understanding what it buys.

A plain <script src> is parser-blocking: the browser stops building your page, downloads the SDK, runs it, and only then continues. defer fixes the parsing half but still holds back DOMContentLoaded — many apps hang their own startup off that event, so a third-party widget ends up gating your code. async frees both: the download runs alongside everything else and the widget initializes whenever it arrives.

The SDK is entirely self-contained, so nothing in your page needs it to have run at a particular moment. The one consequence is timing — see the next section.

2. Script tag with manual init

Omit data-api-key and call init yourself. Use this when you need to decide the configuration at runtime, or delay initialization until after consent:

HTML
<script>
  window.bugdump = window.bugdump || function () {
    (window.bugdump.q = window.bugdump.q || []).push(arguments);
  };
</script>
<script src="https://bugdump.com/sdk/latest.js" async></script>
<script>
  bugdump('init', { apiKey: 'your-api-key' });
</script>

With no data-api-key present, auto-init does nothing, which leaves initialization entirely to you.

The three-line stub is what makes this safe. Because the script is async, an inline block that calls Bugdump.init() directly almost always runs first and throws Bugdump is not defined. The stub makes bugdump(...) callable immediately: until the SDK arrives it queues each call, and when the script loads the SDK replays the queue in order and takes over the function. The same bugdump(...) calls work before and after load, so nothing in your page needs to know when the SDK actually arrived.

You do not have to call init inline, either — call it from a consent callback or a login handler whenever you are ready. The command form covers the rest of the API too: bugdump('identify', {...}), bugdump('open'), bugdump('setContext', {...}), and so on. Methods that return a value (getInstance, collectTelemetry, getConfig, ...) are not commands — a queued call has nowhere to return to. For those, use the window.Bugdump global (capital B, the SDK class itself) once the script has loaded: Bugdump.getInstance()?.collectTelemetry().

3. npm

For anything with a bundler:

Bash
npm install @bugdump/sdk
TypeScript
import { Bugdump } from '@bugdump/sdk';

const bugdump = Bugdump.init({ apiKey: 'your-api-key' });

init() returns the instance, so you can hold it directly rather than reaching for getInstance() later. This is the only method that gives you full TypeScript types and lets you pass filter functions.

Framework-specific setup — including SSR guards and teardown — is covered in Installing in React, Vue, and other frameworks.

In React and Next.js, use @bugdump/react rather than calling init() yourself: its provider starts the widget once and cleans up, and its error boundary sends the errors it catches.

4. Fully programmatic (no floating button)

Set hideButton: true and open the panel from your own UI. Nothing else changes: telemetry is still collected from the moment init() runs, and the panel, capture, and submission all behave identically.

TypeScript
const bugdump = Bugdump.init({
  apiKey: 'your-api-key',
  hideButton: true,
});

document.querySelector('#report-bug').addEventListener('click', () => {
  bugdump.open();
});

The same thing from a script tag, using data-hide-button and the queue stub:

HTML
<script>
  window.bugdump = window.bugdump || function () {
    (window.bugdump.q = window.bugdump.q || []).push(arguments);
  };
</script>
<script
  src="https://bugdump.com/sdk/latest.js"
  async
  data-api-key="your-api-key"
  data-hide-button="true"
></script>
<script>
  document.getElementById('report-bug').addEventListener('click', function () {
    bugdump('open');
  });
</script>

The stub earns its keep here: a click that lands before the async script has finished downloading is queued and replayed instead of throwing — so your "Report a problem" button works from the moment it renders.

What hideButton does and does not do:

  • It hides the floating trigger. The widget still mounts, so open() has something to open.

  • The panel's own close and minimize controls still work — after a user closes it, your trigger is the only way back in. Keep it reachable.

  • bubbleText is ignored, since there is no button for a bubble to attach to.

If your app has its own report form too — its own dialog, fields and styling — skip the panel entirely: keep hideButton: true and send what your form collected with bugdump.submit({ description }). The report carries the same telemetry. See Sending reports from your own form.

You can also open the panel pre-attached to a task, which is useful on staging builds:

TypeScript
bugdump.open({ taskId: 42 });

See SDK API reference for the rest of the instance methods.

The rule that trips people up

init() only takes effect once. If an instance already exists, a second call returns that existing instance and silently ignores the new configuration — no error, no warning, no merge.

This matters most in exactly the headless case. If your script tag carries data-api-key, auto-init has already run by the time your own code executes:

HTML
<!-- auto-init already created the instance, WITH the button visible -->
<script src="https://bugdump.com/sdk/latest.js" async data-api-key="your-api-key"></script>
<script>
  // This does nothing. hideButton is ignored, the button stays.
  window.Bugdump.init({ apiKey: 'your-api-key', hideButton: true });
</script>

Two ways to get it right:

  1. Put the option on the tag — data-hide-button="true" — so auto-init applies it in the first place. Preferred.

  2. Do not let auto-init run. Omit data-api-key and call init() yourself with the full configuration.

If you genuinely need to change configuration after initialization, tear down first:

TypeScript
Bugdump.getInstance()?.destroy();
const bugdump = Bugdump.init({ apiKey: 'your-api-key', hideButton: true });

destroy() removes the widget, stops every collector, and clears the singleton, so the next init() builds a fresh instance. Be aware this also discards telemetry collected so far.

Which should you pick?

Use the script tag with auto-init unless something forces you off it — it has the fewest moving parts and no build step.

Move to npm when you have a bundler and want types, filter functions, or lifecycle control tied to your component tree.

Add hideButton when your app already has a "Report a problem" entry point and a second floating button would be clutter.

Add submit() when it already has a report form as well, and BugDump's panel would not fit in.

See also

Was this article helpful?