Get started
The form (React)
import { useBugReport } from "bugbottle/react";
import { htmlToImage } from "bugbottle/html-to-image"; // optional
function ReportForm() {
const report = useBugReport({
endpoint: "/api/feedback",
screenshot: htmlToImage, // leave out to disable screenshots
});
return (
<form data-bugbottle onSubmit={(e) => { e.preventDefault(); void report.submit(); }}>
{report.types.map((t) => (
<button key={t} type="button" onClick={() => report.setType(t)}>
{t}
</button>
))}
<textarea
value={report.message}
onChange={(e) => report.setMessage(e.target.value)}
/>
{report.canScreenshot && (
<label>
<input
type="checkbox"
checked={report.includeScreenshot}
onChange={(e) => report.toggleScreenshot(e.target.checked)}
/>
Attach a picture of this page
</label>
)}
{report.screenshot && <img src={report.screenshot} alt="" />}
<button type="button" onClick={() => void report.pickElement()}>
{report.isPicking ? "Click anything to attach it — Esc to stop" : "Point at the element"}
</button>
<ul>
{report.elements.map((el, i) => (
<li key={i}>
<code>{el.selector}</code> {el.text}
<button type="button" onClick={() => report.removeElement(i)}>×</button>
</li>
))}
</ul>
<p role="status">{report.statusMessage}</p>
<button disabled={report.isSending}>Send</button>
</form>
);
}Call report.open() when the form appears, so the screenshot shows what they
were looking at rather than the form on top of it. Anything marked
data-bugbottle is left out of the picture and cannot be picked — put it on
your panel and your trigger button.
Every adapter also carries contact and setContact, for a form that asks
how to reach the reporter — an ordinary input bound the way message is:
<input
type="email"
value={report.contact}
onChange={(e) => report.setContact(e.target.value)}
/>Nothing validates it, and an empty one is left out of the body entirely, so a
form without such a field sends no contact key at all. In Vue it is a
writable ref (v-model="contact"), in Svelte $form.contact with
form.setContact(…), in Solid the accessor contact(). It is personal data
once you ask for it: see Please read this part.
Other options: initialType, screenshotFor and consoleFor (which report
types get a picture and the console; bugs only by default), extra (fields
merged into the body — an app version, a tenant id), headers and
credentials (for an authenticated or cross-origin endpoint), timeoutMs,
onSent, parseError, and messages for translated strings. The defaults are English.