Server
The payload
What arrives at your endpoint, with extra fields merged in at the top level:
jsonc
{
"type": "bug", // "bug" | "idea" | "other"
"message": "The save button does nothing",
"contact": "[email protected]", // only when the form asked and they answered
"context": {
"url": "/orders/42?tab=notes", // path and query; no origin, no fragment
"viewport": "1440x900",
"userAgent": "Mozilla/5.0 …",
"language": "en-GB", // everything below is best-effort and
"timezone": "Europe/Copenhagen", // left out when the browser has no answer
"screen": "2560x1440@2",
"colorScheme": "dark", // "dark" | "light"
"online": true,
"connection": "4g"
},
"console": [ // bugs only by default, newest last
{ "ts": "2026-09-07T08:12:31.004Z", "level": "error", "message": "TypeError: …",
"stack": [ // uncaught errors and rejections only, max 10
{ "file": "https://app.test/assets/main.js", "line": 12, "col": 9, "fn": "saveOrder" }
] }
],
"elements": [ // only when the reporter pointed at something
{ "selector": "form#checkout > button:nth-of-type(2)", "tag": "button", "text": "Save order",
"rect": { "x": 912, "y": 640, "width": 118, "height": 36 }, "attributes": { "type": "submit" } }
],
"breadcrumbs": [ // only while bugbottle/breadcrumbs is recording
{ "ts": "2026-09-07T08:12:30.400Z", "kind": "navigation", "from": "/orders/1", "to": "/orders/2" }
],
"network": [ // only while bugbottle/network is recording
{ "ts": "2026-09-07T08:12:31.004Z", "method": "POST", "url": "/api/orders", "status": 500, "ms": 812 }
],
"replay": { // only while bugbottle/rrweb is recording
"events": [{ "type": 2, "timestamp": 1757232751004, "data": {} }], "seconds": 31
},
"notes": [ // the library's own words about the report,
"Screenshot dropped: it did not fit in the offline queue." // max 5, 200 chars
],
"screenshotDataUrl": "data:image/png;base64,…" // only when attached
}The same shape as a JSON Schema (2020-12), generated from the TypeScript types
at build time so it cannot drift from what the library sends:
bugbottle.dev/schema/report.json,
shipped in the package as bugbottle/report.schema.json. It carries the JSDoc
as descriptions and the MAX_* ceilings as maxLength/maxItems, so a
receiver written in another language can enforce the same limits the validators
do. Extra top-level fields are allowed, exactly as handleReport allows them.
ts
import Ajv from "ajv/dist/2020.js";
import schema from "bugbottle/report.schema.json" with { type: "json" };
const valid = new Ajv().compile(schema)(payload);