Capture 500/runtime errors with rich page/request/response snapshots + screenshot; open ticket via public endpoint.
npm install @dottech/ticket-snap


> Capture 500/runtime errors with rich page/request/response snapshots + screenshot; open ticket via public endpoint.
ticket-snap is a lightweight, zero-dependency library that automatically captures errors, network failures, and runtime exceptions in your web application. It collects comprehensive context including screenshots, network activity, console logs, and page metadata, then submits detailed bug reports to your endpoint.
- Features
- Installation
- Quick Start
- Configuration
- API Reference
- Examples
- TypeScript Support
- Browser Compatibility
- Advanced Usage
- Troubleshooting
- 🎯 Automatic Error Capture: Intercepts fetch/XHR requests and global errors
- 📸 Screenshot Support: Captures page screenshots using html2canvas or SVG fallback
- 🔒 Security First: Built-in redaction for sensitive data (passwords, tokens, emails)
- 📦 Offline Queue: Queues reports when offline, retries automatically
- 🎨 Flexible Triggers: Auto-submit, prompt user, or manual reporting modes
- 🔌 Framework Agnostic: Works with React, Vue, Angular, or vanilla JS
- 📊 Rich Context: Captures network activity, console logs, performance timings
- 🪵 Breadcrumbs & Context: Trail of user/app events and mutable context (userId, route) for "what led to this" debugging
- 🎛️ Highly Configurable: Extensive options for customization
``bash`
npm install @dottech/ticket-snap
`html`
`html`
`javascript`
import TicketSnap from '@dottech/ticket-snap';
`javascript`
const TicketSnap = require('@dottech/ticket-snap');
`javascript
import TicketSnap from '@dottech/ticket-snap';
TicketSnap.init({
endpoint: 'https://your-api.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt', // Show popup on errors
includeScreenshot: true
});
`
That's it! The library will now automatically capture:
- HTTP 500+ errors from fetch/XHR requests
- Unhandled JavaScript errors
- Unhandled promise rejections
#### endpoint (string, required)
The API endpoint URL where bug reports will be submitted.
`javascript`
endpoint: 'https://api.example.com/api/v1/public/bug-reports'
#### projectKey (string, optional)
Project identifier for routing tickets. Default: 'TICKET-WEB'
`javascript`
projectKey: 'MY-PROJECT-KEY'
#### labels (string[], optional)
Array of labels to attach to tickets. Default: ['auto-captured', 'source:web']
`javascript`
labels: ['production', 'critical', 'frontend']
#### triggerMode ('auto' | 'prompt' | 'manual', optional)
Controls when and how error reports are submitted:
- 'auto': Automatically submit reports without user interaction
- 'prompt': Show a popup dialog for user to review and add notes before submitting
- 'manual': Only submit when reportNow() is called explicitly
Default: 'prompt' (or inferred from autoSubmitOn500 for backward compatibility)
`javascript
// Auto-submit on errors (no user interaction)
triggerMode: 'auto'
// Show popup for user review
triggerMode: 'prompt'
// Manual control only
triggerMode: 'manual'
`
#### promptUser (boolean, optional)
When calling reportNow(), whether to show the popup dialog. Default: true
`javascript`
promptUser: true // Show popup when reportNow() is called
promptUser: false // Auto-submit without popup
#### autoSubmitOn500 (boolean, optional)
Legacy option: If triggerMode is not set, this determines behavior:true
- → equivalent to triggerMode: 'auto'false
- → equivalent to triggerMode: 'prompt'
This option is kept for backward compatibility but is ignored when triggerMode is explicitly set.
#### includeScreenshot (boolean, optional)
Whether to capture screenshots when errors occur. Default: true
`javascript`
includeScreenshot: true
#### screenshotStrategy ('auto' | 'html2canvas' | 'svg-foreignObject' | 'none', optional)
Screenshot capture method:
- 'auto': Try html2canvas if available, fallback to SVG method
- 'html2canvas': Use html2canvas library (must be loaded separately)
- 'svg-foreignObject': Use SVG-based capture (built-in, no dependencies)
- 'none': Disable screenshots
Default: 'auto'
`javascript
// Use html2canvas if available (requires loading html2canvas separately)
screenshotStrategy: 'html2canvas'
// Use built-in SVG method
screenshotStrategy: 'svg-foreignObject'
// Disable screenshots
screenshotStrategy: 'none'
`
Note: To use html2canvas, load it before ticket-snap:
`html`
#### auth (AuthConfig, optional)
Authentication configuration for the API endpoint. Default: { mode: 'none' }
`typescript`
type AuthConfig =
| { mode: 'basic'; secret: string } // Basic auth: Authorization: Basic base64(projectKey:secret)
| { mode: 'ingestKey'; key: string } // Query param: ?ingestKey=...
| { mode: 'none' }; // No authentication
Basic Authentication:
`javascript`
auth: {
mode: 'basic',
secret: 'your-secret-key'
}
// Sends: Authorization: Basic base64(projectKey:secret)
Ingest Key (Query Parameter):
`javascript`
auth: {
mode: 'ingestKey',
key: 'your-ingest-key'
}
// Appends: ?ingestKey=your-ingest-key
No Authentication:
`javascript`
auth: { mode: 'none' }
// or simply omit the auth option
#### transport ('formFields' | 'metadataPart', optional)
How to structure the submission payload:
- 'formFields': All data as individual FormData fields (recommended for most backends)
- 'metadataPart': Single JSON metadata part + attachments
Default: 'formFields' if auth.mode === 'basic', otherwise 'metadataPart'
`javascript`
transport: 'formFields' // Recommended for most backends
#### redact (RedactRule[], optional)
Custom redaction rules to sanitize sensitive data. Rules are applied in addition to default redactions.
`typescript`
type RedactRule =
| { key: RegExp; replaceWith?: string } // Redact header/field names
| { pattern: RegExp; replaceWith: string }; // Redact text patterns
Default redactions (always applied):
- Headers: authorization, cookie"password":"..."
- Patterns: , Bearer , phone numbers, emails
Custom redaction example:
`javascript`
redact: [
// Redact custom header
{ key: /x-api-key/i, replaceWith: '*' },
// Redact credit card numbers
{ pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, replaceWith: '*---*' },
// Redact SSN
{ pattern: /\b\d{3}-\d{2}-\d{4}\b/g, replaceWith: '--*' }
]
#### ringSize (number, optional)
Maximum number of recent network requests to keep in memory. Default: 20
`javascript`
ringSize: 50 // Keep last 50 requests
#### networkCaptureMode ('all' | 'errorsOnly', optional)
- 'all': Capture every request (default).
- 'errorsOnly': Only keep 4xx/5xx and network failures. Ideal for integration-error focus; 200s are not stored.
`javascript`
networkCaptureMode: 'errorsOnly' // Only failed requests in history
#### networkRecentCount (number, optional)
How many recent network entries to attach to each snapshot and payload. Default: 10
`javascript`
networkRecentCount: 15 // Last 15 requests per report
#### maxBodyChars (number, optional)
Maximum characters to capture from request/response bodies. Default: 2000
`javascript`
maxBodyChars: 5000 // Capture up to 5000 chars
#### maxConsole (number, optional)
Maximum number of console log entries to capture. Default: 100
`javascript`
maxConsole: 200
#### maxPerf (number, optional)
Maximum number of performance entries to capture. Default: 150
`javascript`
maxPerf: 300
#### maxBreadcrumbs (number, optional)
Maximum breadcrumb entries (user/app event trail) to keep. Default: 30
`javascript`
maxBreadcrumbs: 50
#### captureNavigationBreadcrumbs (boolean, optional)
Auto-add a breadcrumb on popstate and hashchange. Default: true
`javascript`
captureNavigationBreadcrumbs: true
#### initialContext (Record
Initial key-value context attached to every snapshot (e.g. userId, feature flags).
`javascript`
initialContext: { userId: 'u-123', feature: 'checkout-v2' }
Use TicketSnap.setContext(key, value) at runtime to update context.
#### routeResolver (() => RouteInfo, optional)
Function to extract current route information for SPA frameworks. Default: returns current pathname
`typescript`
interface RouteInfo {
route?: string; // Route path (e.g., '/users/:id')
params?: Record
method?: string; // HTTP method
urlOverride?: string; // Override URL in report
}
Vue Router example:
`javascript
import { useRoute } from 'vue-router';
routeResolver: () => {
const route = useRoute();
return {
route: route.path,
params: route.params,
method: route.meta?.method
};
}
`
React Router example:
`javascript
import { useLocation, useParams } from 'react-router-dom';
routeResolver: () => {
const location = useLocation();
const params = useParams();
return {
route: location.pathname,
params: params
};
}
`
#### app (AppContext, optional)
Application metadata to include in reports.
`typescript`
interface AppContext {
appName?: string;
appVersion?: string;
environment?: string; // 'development', 'staging', 'production'
build?: string; // Build number or hash
commit?: string; // Git commit SHA
releaseChannel?: string; // 'stable', 'beta', 'canary'
}
Example:
`javascript`
app: {
appName: 'MyApp',
appVersion: '1.2.3',
environment: 'production',
build: '20240115.1234',
commit: 'abc123def',
releaseChannel: 'stable'
}
#### onSubmitted ((res: unknown) => void, optional)
Callback invoked when a report is successfully submitted.
`javascript`
onSubmitted: (response) => {
console.log('Ticket created:', response);
// response contains the API response
}
#### onError ((err: unknown) => void, optional)
Callback invoked when submission fails (report is queued for retry).
`javascript`
onError: (error) => {
console.error('Failed to submit report:', error);
// Report is automatically queued for retry
}
Initializes ticket-snap with the provided configuration. Must be called before the library can capture errors.
`javascript`
const api = TicketSnap.init({
endpoint: 'https://api.example.com/bug-reports',
projectKey: 'MY-PROJECT'
});
Returns: The API object with methods below.
Manually trigger a bug report. Useful for user-initiated reporting or custom triggers.
`javascript
// Basic usage
TicketSnap.reportNow('User reported an issue');
// Without screenshot
TicketSnap.reportNow('Issue description', { includeScreenshot: false });
// With additional notes
TicketSnap.reportNow('Button click failed');
`
Parameters:
- extraNotes (string, optional): Additional notes to include in the reportoptions
- (object, optional):includeScreenshot
- (boolean, optional): Whether to include screenshot. Default: true
Behavior:
- If promptUser: true, shows popup dialog for user to review and add notespromptUser: false
- If , submits immediately without popup
Manually retry sending queued offline reports. Returns a Promise.
`javascript`
// Retry queued reports
await TicketSnap.flushOfflineNow();
Use cases:
- User clicks "Retry" button after network failure
- Periodic retry in background
- On app visibility change (handled automatically in non-manual modes)
Add a breadcrumb (e.g. "Clicked checkout", "Opened modal"). Included in the next snapshot for "what led to this" debugging.
`javascript`
TicketSnap.addBreadcrumb('Clicked checkout button', 'ui');
TicketSnap.addBreadcrumb('Opened payment modal');
Set context key-value(s) merged into every snapshot (e.g. current route, userId).
`javascript`
TicketSnap.setContext('userId', 'u-456');
TicketSnap.setContext({ route: '/checkout', step: 2 });
Returns a copy of current context (for debugging or custom UI).
Returns a copy of recent network entries (for debugging or custom UI).
Returns a copy of the breadcrumb trail.
Current library version.
`javascript`
console.log(TicketSnap.version); // '0.2.1'
`javascript
import TicketSnap from '@dottech/ticket-snap';
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt',
includeScreenshot: true
});
`
`javascript`
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'auto', // Auto-submit without user interaction
includeScreenshot: true,
onSubmitted: (response) => {
console.log('Error report submitted:', response);
}
});
`javascript
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'manual', // No automatic reporting
includeScreenshot: true
});
// Add custom error handler
window.addEventListener('error', (event) => {
// Your custom logic
if (shouldReport(event)) {
TicketSnap.reportNow(Custom error: ${event.message});`
}
});
`javascript
// plugins/ticket-snap.client.js (Nuxt 3)
import TicketSnap from '@dottech/ticket-snap';
export default defineNuxtPlugin(() => {
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt',
routeResolver: () => {
const route = useRoute();
return {
route: route.path,
params: route.params
};
},
app: {
appName: 'MyNuxtApp',
appVersion: '1.0.0',
environment: process.env.NODE_ENV
}
});
});
`
`javascript
// App.js or index.js
import { useEffect } from 'react';
import TicketSnap from '@dottech/ticket-snap';
import { useLocation, useParams } from 'react-router-dom';
function App() {
const location = useLocation();
const params = useParams();
useEffect(() => {
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt',
routeResolver: () => ({
route: location.pathname,
params: params
}),
app: {
appName: 'MyReactApp',
appVersion: process.env.REACT_APP_VERSION,
environment: process.env.NODE_ENV
}
});
}, []);
return
}
`
`javascript`
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
redact: [
// Redact API keys in headers
{ key: /x-api-key/i, replaceWith: '*' },
// Redact credit card numbers in request bodies
{ pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, replaceWith: '*---*' },
// Redact custom tokens
{ pattern: /"token"\s:\s"[^"]+"/gi, replaceWith: '"token":"*"' }
]
});
`javascript
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'auto',
onError: (error) => {
// Report failed to send, queued automatically
console.warn('Report queued for retry:', error);
// Show UI indicator
showOfflineIndicator();
},
onSubmitted: () => {
// Report sent successfully
hideOfflineIndicator();
}
});
// Manual retry button
document.getElementById('retry-reports').addEventListener('click', async () => {
await TicketSnap.flushOfflineNow();
alert('Queued reports sent');
});
`
`javascript`
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
auth: {
mode: 'basic',
secret: 'your-secret-key-here'
},
transport: 'formFields'
});
Full TypeScript definitions are included. Import types as needed:
`typescript
import TicketSnap, {
TicketSnapConfig,
Snapshot,
RedactRule,
TriggerMode
} from '@dottech/ticket-snap';
const config: TicketSnapConfig = {
endpoint: 'https://api.example.com/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt' as TriggerMode
};
TicketSnap.init(config);
`
ticket-snap works in all modern browsers that support:
- fetch API (or polyfill)XMLHttpRequest
- Promise
- localStorage
- crypto.subtle
- (for hashing, with fallback)
Tested in:
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- Mobile browsers (iOS Safari, Chrome Mobile)
For older browsers, consider polyfills for fetch and Promise.
`javascriptFiltered error: ${message}
// Only report errors matching certain criteria
const originalErrorHandler = window.onerror;
window.onerror = (message, source, lineno, colno, error) => {
// Your custom filtering logic
if (shouldReportError(error)) {
TicketSnap.reportNow();`
}
// Call original handler if needed
if (originalErrorHandler) {
originalErrorHandler(message, source, lineno, colno, error);
}
};
`javascriptReact Error Boundary: ${error.message}
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
TicketSnap.reportNow(
,
{ includeScreenshot: true }
);
}
render() {
// Your error boundary UI
}
}
`
`javascript
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'manual', // Manual control
includeScreenshot: true
});
// Only report in production
if (process.env.NODE_ENV === 'production') {
window.addEventListener('error', (event) => {
TicketSnap.reportNow(Production error: ${event.message});`
});
}
Check:
1. Verify endpoint URL is correct and accessibleauth
2. Check browser console for errors
3. Verify authentication credentials if using triggerMode
4. Check Network tab to see if requests are being made
5. Ensure is set correctly (not 'manual' if expecting auto-reports)
Solutions:
1. If using html2canvas, ensure it's loaded before ticket-snapscreenshotStrategy: 'svg-foreignObject'
2. Try for built-in method
3. Check browser console for CORS or CSP errors
4. Some browsers block screenshots in certain contexts (extensions, iframes)
Adjust limits:
`javascript`
maxBodyChars: 1000, // Reduce body size
maxConsole: 50, // Reduce console logs
ringSize: 10 // Reduce network history
Manual retry:
`javascript`
await TicketSnap.flushOfflineNow();
Check localStorage:
`javascript`
const queue = JSON.parse(localStorage.getItem('tsnap-queue') || '[]');
console.log('Queued reports:', queue.length);
Ensure your API endpoint allows requests from your domain. The library sends POST requests with FormData.
Ensure you're using TypeScript 4.5+ and have @types/node` installed if needed.
MIT © 3bbasDev
Contributions are welcome! Please feel free to submit a Pull Request.