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);

Edit this page on GitHub