Get started

One script tag

For a site with no build step — a WordPress theme, a static page, a client site somebody else deploys — dist/bugbottle.js is a self-contained bundle that mounts the panel from the tag itself, the annotator included. About 23.9 kB gzipped:

html
<script
  src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bugbottle.js"
  data-endpoint="/api/feedback"
  data-locale="da"
  data-primary="#e11d48"
  data-brand="Mahope"
></script>

dist/ is committed, so the same file is on jsDelivr from the git tag as well: https://cdn.jsdelivr.net/gh/mahope/[email protected]/dist/bugbottle.js. Pin a version in either form; @latest is a way to have a stranger's next release run on your page.

Two builds #

There are two files, and they are the same panel:

File Gzipped What is in it
dist/bugbottle.js 24.2 kB Everything: the annotator, the timings and storage snapshot, shake-to-report and the network log, all switchable from an attribute.
dist/bugbottle.slim.js 20.6 kB The same panel, the console, breadcrumbs, the element picker, the offline queue, the scrubber, the signer and all eight locales — without those four.
html
<script
  src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bugbottle.slim.js"
  data-endpoint="/api/feedback"
></script>

From the git tag it is https://cdn.jsdelivr.net/gh/mahope/[email protected]/dist/bugbottle.slim.js.

The slim build reads every attribute in the table below except four, which it ignores because the code behind them is not in it: data-annotate, data-perf, data-shake and data-network. Write one and it says so on the console once, in English — that is a message to whoever wrote the script tag, not to the reporter, so it is not translated. window.bugbottle is the same namespace without createAnnotator, initPerf, onShake, requestShakePermission and initNetwork.

The saving is smaller than the four modules weigh on their own, because inside one bundle they share gzip's dictionary, and two of their costs stay behind on purpose: the annotator's labels are in all eight locales, which are data, and the panel's own annotator toolbar is a static import. Every language still works in the slim build; that is the trade it makes.

Attribute Effect
data-endpoint Where the report is POSTed. Required — without it nothing mounts.
data-locale Language tag through resolveLocale. Defaults to <html lang>, then English.
data-position bottom-right (default), bottom-left, top-right, top-left.
data-primary Accent colour of the button and the primary action.
data-brand Name in the panel header.
data-logo Image URL shown before the title and on the trigger.
data-trigger Selector for your own button. Without it, the floating one is rendered.
data-contact Present, with any value, asks the reporter how to reach them; required also refuses to send without it. Off without the attribute.
data-scrub Present, with any value, redacts the report with scrubReport before it is sent.
data-network Present, with any value, records the failed and slow requests. The same switch as the panel's network option. See "What the network did".
data-perf Present, with any value, records the Web Vitals and lists what is in the browser's stores — names and lengths, never values. The same switch as the panel's perf option. See "Performance and storage".
data-sign-key Signs the body with this key. A key in the page source is public, so this deters spam rather than authenticating anybody; see Signing requests.
data-queue Present, with any value, keeps a failed report in localStorage and sends it when the browser is online again. With data-sign-key the queued reports are signed at delivery too. See "When the network is down".
data-extra JSON object merged into every report, e.g. data-extra='{"appVersion":"1.4.2"}'.
data-mask="off" Stops masking the screenshot. Only matters once you give mount a renderer; see Masking.
data-annotate="off" Leaves out "Edit picture" and its rectangle, arrow and blur. This build carries the annotator, so the attribute only switches it off; it does not make the file smaller. Only matters once you give mount a renderer; see Marking the picture.
data-shortcut The combination that opens the panel. mod+shift+b unless you say otherwise; off installs no listener.
data-shake Present, with any value, opens the panel when the phone is shaken; a number is the threshold in m/s² (data-shake="12" is a lighter flick). On iOS nothing arrives until the page calls window.bugbottle.requestShakePermission() from a button of its own. See "Shake to report".
data-open-on-error Present, with any value, opens the panel on an uncaught error. prefill also fills the message in.

The tag also patches the console immediately and starts breadcrumbs, so an error thrown before the page finishes loading is still in the report.

There is no screenshot in this build. A renderer means html-to-image, which is far larger than everything else here put together, and forcing it on every page that only wants the panel is the wrong trade. The bundle exposes the building blocks on window.bugbottlemount (mountBugbottle), initConsoleBuffer, initBreadcrumbs, initNetwork, initPerf, createQueue, locales, resolveLocale, scrubReport, buildReport, sendReport, pickElement, createAnnotator, onShortcut, onUncaughtError, onShake, requestShakePermission and version — so a page that wants pictures can load html-to-image itself and call window.bugbottle.mount({ endpoint, screenshot }). Leave data-endpoint off the tag and nothing mounts on its own:

html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bugbottle.js"></script>
<script>
  window.bugbottle.initConsoleBuffer();
  window.bugbottle.mount({
    endpoint: "/api/feedback",
    locale: window.bugbottle.locales.da,
    scrub: window.bugbottle.scrubReport,
  });
</script>

Subresource integrity. A CDN is a third party executing code on your site. Pin the file with its hash so a swapped file cannot run:

html
<script
  src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bugbottle.js"
  integrity="sha384-…"
  crossorigin="anonymous"
  data-endpoint="/api/feedback"
></script>

jsDelivr shows the hash on the file's page, or compute it yourself: curl -s <url> | openssl dgst -sha384 -binary | openssl base64 -A. The hash changes with every version, so it has to be updated with the version.

Edit this page on GitHub