BugTape docs
Capture and Error Semantics
How the SDK captures, buffers, deduplicates and submits.
How the BugTape SDK captures, buffers, deduplicates, and submits bug reports.
Automatic capture
Once BugTape.init() is called, six recorders start automatically:
| Recorder | Events captured | Can disable? |
|---|---|---|
| DOM | rrweb visual replay (mutations, mouse, scroll, input) | disableDomRecording: true |
| Network | Fetch, XHR, resource timing (URL, status, duration) | disableNetworkRecording: true |
| Console | console.log/warn/error/info/debug | disableConsoleRecording: true |
| Errors | window.onerror — uncaught exceptions with stack traces | Always on |
| Rejections | unhandledrejection — unhandled promise rejections | Always on |
| Rage clicks | Rapid repeated clicks on same element | Always on |
Sensitive tables / client grids: mark with data-bugtape-ignore / .bugtape-ignore / .rr-block before any capture key. Prove with the privacy proof checklist and privacy fixture. Default PII regex is not enough for named clients in visible text.
All recorders are wrapped in error boundaries. If a recorder fails, the SDK logs a warning and continues — it never crashes the host application.
Browser crash and tab close
The SDK does not attempt to capture browser crashes or beforeunload events. If the browser tab crashes or is closed:
- Buffered events are lost (they are in-memory only).
- Any pending report from a previous session that was stored in
localStorage(due to network failure) will be resubmitted on the nextBugTape.init().
For crash monitoring, use a complementary service or beacon API.
Unhandled error capture
window.onerror
Captures uncaught exceptions globally. Event data:
{ type: "error", data: { message, filename, lineno, colno, stack } }
The SDK does not call preventDefault() — errors still propagate to the browser console and any other error handlers.
unhandledrejection
Captures unhandled promise rejections. Event data:
{ type: "error:unhandledrejection", data: { message, stack } }
Auto-reporting
Errors are buffered, not auto-submitted. The SDK does not automatically submit a report when an error occurs. Reports are only submitted when:
- A user clicks the bug button and submits through the widget.
- Your code calls the API directly (manual report).
- You implement custom logic in the
onSubmitcallback.
If you want auto-reporting on errors, implement it yourself:
window.addEventListener('error', async (event) => {
await fetch('https://app.bugtape.ai/v1/ingest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-BugTape-Key': 'bt_live_xxx',
},
body: JSON.stringify({
title: event.error?.message || 'Uncaught error',
severity: 'high',
url: window.location.href,
userAgent: navigator.userAgent,
viewport: `${innerWidth}x${innerHeight}`,
events: bt.getEvents(),
}),
});
});
Recommendation: If you implement auto-reporting, add throttling (see below).
Manual issue reporting
Users file reports through the SDK widget (floating bug button) or your own UI. The widget flow:
- User clicks bug button → widget opens.
- User optionally captures a screenshot (with annotation tools).
- User optionally records an audio note.
- SDK generates an AI summary (if
openaiKeyconfigured). - User fills in title, description, severity.
- User clicks submit → report sent to API with all buffered events.
For programmatic reporting without the widget, POST directly to /v1/ingest.
Deduplication
Server-side fingerprinting
Every report is fingerprinted on the server:
SHA256(errorType : normalizedMessage : normalizedUrl).slice(0, 16)
- Same fingerprint + open bug: report count incremented, no new bug created.
- Same fingerprint + resolved bug: bug reopened as regression.
- New fingerprint: new bug created.
Client-side throttling (recommended)
The SDK does not deduplicate on the client. If you implement auto-reporting, add your own throttle:
const reported = new Set<string>();
function shouldReport(errorMessage: string): boolean {
const key = errorMessage.slice(0, 100);
if (reported.has(key)) return false;
reported.add(key);
setTimeout(() => reported.delete(key), 60_000); // Allow re-report after 1 min
return true;
}
Recommended throttle: Max 5 auto-reports per minute per session. The server rate limit is 100 requests/minute per IP, but lower client-side throttling reduces noise.
Event ordering
Events are stored with millisecond Unix timestamps (event.timestamp). The SDK does not guarantee strict ordering — events from different recorders may interleave. The server stores events in insertion order.
When displaying events, sort by timestamp. For DOM replay, rrweb events contain their own internal ordering.
Session correlation
The SDK does not currently assign a session ID. All events in a single report are implicitly from one session (the buffer contents at submission time).
To correlate multiple reports from the same user session:
- Generate a session ID in your app (e.g.,
crypto.randomUUID()). - Include it in the report description or as a custom event:
bt.buffer.push({
type: 'session',
timestamp: Date.now(),
data: { sessionId: 'your-session-id', userId: 'user-123' }
});
- Search for the session ID in the BugTape console.
Attachments and replay data
Screenshots
The SDK captures screenshots using modern-screenshot (DOM-to-image). Screenshots are:
- Rendered at device pixel ratio (up to 2x).
- Annotatable with a drawing tool before submission.
- Included as base64 in the report payload.
Audio notes
Users can record voice notes via the MediaRecorder API:
- Format: WebM with Opus codec.
- Stored as base64 data URL in the report payload.
Session replay
DOM events recorded by rrweb are included in the events array. The BugTape console can replay these events visually. The replay player is read-only — the rrweb events are opaque blobs stored as-is.
Offline and flaky network behavior
Retry logic
- SDK submits the report via
fetch(). - If the request fails, the SDK retries once after a 1-second delay.
- If the retry also fails, the report is serialized and stored in
localStorageunder a BugTape-specific key.
Pending report resubmission
On the next BugTape.init() call (e.g., page reload), the SDK checks localStorage for pending reports and resubmits them. Successfully submitted pending reports are cleared from storage.
Buffered events during offline
Events continue to be captured in the ring buffer while offline. If the user submits a report while offline, the events captured up to that point are included. The report will be stored locally and resubmitted when connectivity returns.
Network failure events
Network failures are captured as network:fetch:error events and included in the buffer, providing context for debugging connectivity issues.
Best practices for auto-reporting
- Throttle client-side. Max 5 auto-reports per minute. Use a
SetorMapto track recently reported error messages.
- Prioritize severity. Auto-report
criticalandhighseverity errors (uncaught exceptions, unhandled rejections). Logmediumandlowseverity events to the buffer only.
- Include buffered events. Always call
bt.getEvents()and include the result. This gives BugTape the full context (what happened before the error).
- Set reporterEmail. If you know the user's identity, include
reporterEmailso reports can be attributed.
- Handle 402 gracefully. If the API returns 402 (plan limit), stop auto-reporting for the rest of the session to avoid noise.
- Handle 429 gracefully. If rate-limited, back off exponentially. The
retryAfterfield tells you how long to wait.
- Don't suppress widget reports. Even with auto-reporting, keep the widget available. User-submitted reports include context (title, description, screenshot) that auto-reports lack.