When a user clicks the report button, quite a lot happens that they never see. Here is everything, and why each piece is there.
Console logs, including the past
The obvious one, with a non-obvious detail: we capture logs from before the widget was opened.
This matters more than it sounds. The sequence in a real bug report is almost never "error occurs, user immediately reports it." It is: error occurs, page enters a subtly broken state, user tries three more things, gets confused, and reports it four minutes later.
A collector that starts at report time captures those four minutes of confusion and misses the error that caused them. The SDK installs its console collector at init, so the original error is still there when the report is finally filed.
Network requests
Method, URL, status, timing. This is where most bugs actually live — not in your frontend code but in the response your frontend received.
Request and response bodies are available behind captureNetworkBodies, and they are off by default. That default is deliberate. Bodies are the single most likely place for auth tokens, personal data, and payment details to appear. Turning that on should be a decision you make, not a default you inherit.
Even without bodies, status codes and timings solve most bugs. A 500 immediately before the user gave up is your bug. A 200 that took nine seconds is also your bug, and one your error tracker never reported.
Screenshots, and the blur tool
Screenshots come in two flavors, and the trade-off is worth understanding.
screen-capture uses getDisplayMedia. It is pixel-perfect — canvas, video, cross-origin iframes, native selects, all captured exactly as rendered. The cost is a browser permission dialog.
dom renders from the DOM with html2canvas. No prompt, so no friction, but canvas content and cross-origin iframes may not survive.
We default to screen-capture and fall back to dom automatically when the user denies permission. A denied prompt should degrade the screenshot, not lose the report.
Then annotation: arrow, rectangle, freehand, text, and blur. The blur tool is the one worth highlighting, because it is a privacy control disguised as a drawing tool. Redaction happens in the browser — a blurred region is never uploaded. Users who would otherwise not send a screenshot of a page containing customer data can now send one.
Session replay
An rrweb recording of the DOM over time. Not a video — a replay of the actual document, which you can inspect.
Replay earns its place on the class of bug where the console is clean, the network is clean, and the user is absolutely certain something broke. Usually they are right, and usually what they did is not what they described. The replay shows you the difference.
Actions
Clicks, inputs, and navigation, in order.
This is the humblest signal and one of the most useful, because it is your reproduction steps — recorded rather than remembered. "Steps to reproduce" is the field users fill in worst, and it is the field you need most. So we stopped asking and started recording.
Voice notes
Some users will not write three sentences but will happily talk for twenty seconds. Frustration also carries information that text flattens — you can hear which part of the flow actually broke them.
Performance timings
Page load and resource timing, for the reports that are really about slowness. "The app is slow" is a real bug report, and without timings it is an unfalsifiable one.
Metadata
Browser, OS, viewport size, page URL, timestamp.
The viewport is the sleeper. A surprising share of "the button doesn't work" reports are layout bugs at a width nobody tested. Users almost never think to mention their window size. It is one number, it is captured automatically, and it closes a category of bug that is otherwise genuinely hard to reproduce.
What we deliberately don't do
We don't require reporter identity. Name and email sit behind a collapsed section. A report without a name is still a report; a form that demands identity gets fewer of them.
We don't require a title, category, severity, or priority. These are triage fields dressed up as reporting fields. Users guess at them, guess wrong, and you re-triage anyway. The only required field is a description.
We don't leak into your page. The widget renders in a Shadow DOM. Your CSS cannot reach it, and its CSS cannot reach you.
The shape of it
Every signal here follows one rule: if the browser already knows it, the user should not have to say it.
The user's job is one sentence about what they expected. Everything else is our job, and the browser's already done most of it.