JavaScript API

BugDrop exposes a window.BugDrop API that gives you full programmatic control over the widget. Use it to open and close the feedback form, show and hide the button, and integrate BugDrop into your own UI elements.

API Reference

The window.BugDrop object is available after the widget initializes. It provides the following methods and properties:

Method / PropertyTypeDescription
BugDrop.open()MethodOpens the feedback form
BugDrop.close()MethodCloses the feedback form
BugDrop.hide()MethodHides the floating button
BugDrop.show()MethodShows the floating button
BugDrop.isOpen()MethodReturns true if the feedback form is currently open
BugDrop.isButtonVisible()MethodReturns true if the floating button is currently visible
BugDrop.registerFlow(config)MethodRegisters a released FlowConfig and returns a FlowHandle
BugDrop.registerVariant(config)MethodRegisters an immutable custom feedback definition and returns a durable handle

Basic Usage

<script>
  // Open the feedback form
  window.BugDrop.open();

  // Close the feedback form
  window.BugDrop.close();

  // Hide the floating button
  window.BugDrop.hide();

  // Show the floating button
  window.BugDrop.show();

  // Check if the form is open
  if (window.BugDrop.isOpen()) {
    console.log('Form is currently open');
  }

  // Check if the button is visible
  if (window.BugDrop.isButtonVisible()) {
    console.log('Button is visible');
  }
</script>

API-Only Mode

When you want to trigger BugDrop entirely from your own UI -- such as a menu item, button, or keyboard shortcut -- set data-button="false" to hide the default floating button:

<script
  src="https://bugdrop.neonwatty.workers.dev/widget.js"
  data-repo="owner/repo"
  data-button="false"
></script>

In this mode, the widget loads and initializes but no floating button appears on the page. The feedback form is only accessible through window.BugDrop.open().

Custom Trigger Button

Here is a complete example of using API-only mode with your own button:

<!-- Your custom button -->
<button onclick="window.BugDrop.open()" class="my-feedback-btn">
  Report a Bug
</button>

<!-- BugDrop in API-only mode -->
<script
  src="https://bugdrop.neonwatty.workers.dev/widget.js"
  data-repo="owner/repo"
  data-button="false"
></script>

A common pattern is integrating BugDrop into your application's navigation or help menu. The key is waiting for BugDrop to be ready before wiring up your UI.

Using the bugdrop:ready Event

BugDrop dispatches a non-bubbling bugdrop:ready event on window when it finishes initialization. This is the recommended way to set up integrations:

<nav>
  <ul>
    <li><a href="/dashboard">Dashboard</a></li>
    <li><a href="/settings">Settings</a></li>
    <li>
      <a href="#" id="report-bug-link" style="display:none">
        Report a Bug
      </a>
    </li>
  </ul>
</nav>

<script
  src="https://bugdrop.neonwatty.workers.dev/widget.js"
  data-repo="owner/repo"
  data-button="false"
></script>

<script>
  window.addEventListener('bugdrop:ready', function () {
    const link = document.getElementById('report-bug-link');
    link.style.display = '';
    link.addEventListener('click', function (e) {
      e.preventDefault();
      window.BugDrop.open();
    });
  });
</script>

This pattern ensures:

  1. The "Report a Bug" link is hidden until BugDrop is ready
  2. Once the widget initializes, the link becomes visible
  3. Clicking the link opens the feedback form
  4. The default floating button is hidden (data-button="false")

Synchronous Check

If your code runs after the page is fully loaded and you want to check whether BugDrop is already available, you can do a synchronous check:

<script>
  if (window.BugDrop) {
    // BugDrop is already initialized
    window.BugDrop.open();
  } else {
    // Wait for it
    window.addEventListener('bugdrop:ready', function () {
      window.BugDrop.open();
    });
  }
</script>

This "check first, listen second" pattern is useful in single-page applications where your code might run at various points in the page lifecycle.

Custom-flow lifecycle

registerFlow(config) adds a custom modal journey without replacing the default feedback flow. The configuration contains forms, ordered screens, issue output, and optional evidence, appearance, and content controls. See Custom Flows for a complete walkthrough and the Flow Reference for the manifest-driven released contract.

const triage = window.BugDrop.registerFlow({
  configVersion: 1,
  id: 'support-triage',
  presentation: { kind: 'modal', size: 'compact' },
  forms: [
    {
      id: 'request',
      title: 'How can we help?',
      fields: [
        {
          id: 'summary',
          type: 'shortText',
          label: 'Summary',
          required: true,
        },
      ],
    },
  ],
  screens: [
    { id: 'request-screen', type: 'form', form: 'request' },
  ],
  issue: {
    classification: 'question',
    title: '{{request.summary}}',
    sections: [{ heading: 'Surface', context: 'surface', format: 'code' }],
  },
});

The returned FlowHandle has a stable id and an open(options?) method. Opening returns an OpenedFlow for that instance:

const opened = triage.open({
  context: { surface: 'help-menu' },
  initialAnswers: { 'request.summary': 'Export is unavailable' },
});

console.log(opened.instanceId);

const outcome = await opened.result;
if (outcome.status === 'submitted') {
  console.log(outcome.result.issueNumber, outcome.result.issueUrl);
}

Every context key must be referenced by a condition or context-backed Issue section in the registered configuration; open() rejects unreferenced keys synchronously. initialAnswers prefills known answers by their formId.fieldId paths. Use a string for text fields, an integer for ratings, an option's configured value string for single choice, a boolean for checkboxes, and an attachment array for attachments. Both opening properties are optional.

Registration validates the complete configuration immediately and throws when it is invalid. Flow IDs must be unique within one loaded BugDrop runtime, so register each configuration once and keep the returned handle for later opens. Conditions may only reference earlier answers, form screens must reference existing forms, and output mappings must reference compatible fields. These checks happen at registration rather than after a user begins the flow.

The result promise settles with one released status:

StatusMeaningAdditional value
submittedSubmission completedresult contains issueNumber, issueUrl, isPublic, and optional labelMappingWarnings
closedThis flow instance closed without a completed submissionNone
busyThe runtime could not open this instance because the modal surface was busyNone

Call opened.close() to close that custom-flow instance. This is separate from BugDrop.close(), which controls the default feedback form.

The Flow configuration, handle, opening, and outcome declarations are published with each tagged release in BugDrop's repository. Script-tag consumers can pin or copy the declarations from the v1.56.3 public type source. BugDrop does not currently publish an npm package. The exact property contract is kept on the Branching & Output reference so this lifecycle guide does not duplicate it.

Configurable Variants

Variants are a separate public contract. Use them when you specifically need Variant-only inline mounting or headless submission; do not place those properties in a FlowConfig.

Use a registered variant for a focused feedback experience while reusing BugDrop's validation, metadata, auth, Worker policy, and GitHub Issue creation. Registration validates synchronously and stores an immutable copy. The variant sidecar does no DOM, listener, observer, storage, or ID work until a variant is mounted or submitted.

const review = window.BugDrop.registerVariant({
  id: 'export-review',
  presentation: { kind: 'inline' },
  content: { title: 'How was this export?' },
  fields: [
    { id: 'rating', type: 'rating', label: 'Rating', required: true, scale: 5 },
    { id: 'message', type: 'longText', label: 'Anything else?', maxLength: 1000 },
  ],
  issue: {
    classification: 'feedback',
    title: '[Export review] {{rating}}/5',
    sections: [
      { heading: 'Rating', field: 'rating', format: 'stars' },
      { heading: 'Comment', field: 'message', omitWhenEmpty: true },
    ],
  },
});

const mountedReview = review.mount(document.querySelector('#export-review-slot'), {
  context: { surface: 'export-complete' },
});

// Later, restore the form to its initial state or remove this instance only.
mountedReview.reset();
mountedReview.unmount();

The inline renderer provides accessible short-text, long-text, and rating controls. Choosing a star changes only local form state; it never submits. A GitHub Issue is created only after the explicit Submit button succeeds.

For a host-owned CTA, register a modal variant and call its handle instead of the default-flow BugDrop.open() method:

const providerQuestion = window.BugDrop.registerVariant({
  id: 'cloud-provider-question',
  presentation: { kind: 'modal', size: 'compact' },
  content: {
    title: 'Which cloud provider should we support next?',
    description: 'Tell us what would fit your workflow.',
    submitLabel: 'Send idea',
    cancelLabel: 'Not now',
  },
  fields: [
    {
      id: 'response',
      type: 'longText',
      label: 'Your answer',
      required: true,
      minLength: 2,
      maxLength: 1000,
    },
  ],
  issue: {
    classification: 'feature',
    title: 'Cloud provider request — {{response}}',
    sections: [{ heading: 'Requested provider or workflow', field: 'response' }],
  },
});

document.querySelector('#provider-cta').addEventListener('click', () => {
  const opened = providerQuestion.open({ context: { surface: 'studio-upload' } });
  opened.result.then(outcome => console.log(outcome.status));
});

The modal traps focus, closes on Escape, restores focus and page scrolling, and settles its result as submitted, closed, or busy. A variant requested while the default feedback form is active returns busy. Opening the default feedback form while a variant modal is active closes the variant first. BugDrop.close() continues to control only the default feedback form.

BugDrop's merge-queue preview checks render both this CTA modal and the inline star review from the exact deployed widget bytes. They assert each normalized draft without creating Issues, followed by one zero-retry rendered CTA canary that creates, independently verifies, closes, and sweeps one real GitHub Issue through the existing GitHub App.

If your application owns the UI, the same immutable handle also supports headless submission:

const result = await review.submit(
  { rating: 5, message: 'Fast and clear.' },
  { context: { surface: 'export-complete' } }
);
console.log(result.issueNumber, result.issueUrl);

The tagged repository source also includes VariantConfig, VariantHandle, MountedVariant, and the related declarations for script-tag integrations.

Keyboard Shortcut Example

You can bind BugDrop to a keyboard shortcut for power users:

<script>
  document.addEventListener('keydown', function (e) {
    // Ctrl+Shift+B (or Cmd+Shift+B on Mac) to toggle bug report
    if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'B') {
      e.preventDefault();
      if (window.BugDrop && window.BugDrop.isOpen()) {
        window.BugDrop.close();
      } else if (window.BugDrop) {
        window.BugDrop.open();
      }
    }
  });
</script>

React Integration Example

In a React application, you can create a wrapper component:

import { useEffect, useState } from 'react';

function ReportBugButton() {
  const [ready, setReady] = useState(false);

  useEffect(() => {
    if (window.BugDrop) {
      setReady(true);
    } else {
      const handler = () => setReady(true);
      document.addEventListener('bugdrop:ready', handler);
      return () => document.removeEventListener('bugdrop:ready', handler);
    }
  }, []);

  if (!ready) return null;

  return (
    <button onClick={() => window.BugDrop.open()}>
      Report a Bug
    </button>
  );
}

Combining API with Visible Button

You do not have to choose between the floating button and the API. You can use both:

<!-- Show the floating button AND use the API -->
<script
  src="https://bugdrop.neonwatty.workers.dev/widget.js"
  data-repo="owner/repo"
></script>

<!-- Your custom trigger in the nav -->
<a href="#" onclick="event.preventDefault(); window.BugDrop.open()">
  Report a Bug
</a>

In this setup, users can either click the floating button or your navigation link to open the form. Both approaches open the same feedback form.

Programmatic Show/Hide

The show() and hide() methods let you control the floating button's visibility. This is useful for contexts where the button should only appear on certain pages or under certain conditions:

<script>
  document.addEventListener('bugdrop:ready', function () {
    // Hide the button on the checkout page
    if (window.location.pathname.startsWith('/checkout')) {
      window.BugDrop.hide();
    }

    // Show the button when user opens the help section
    document.getElementById('help-tab').addEventListener('click', function () {
      window.BugDrop.show();
    });
  });
</script>

Next Steps