Zero-config bug context collector for the browser.
Drop it in, forget about it. When something breaks, you already have everything you need.
Testers spend 90% of their time manually collecting bug context — error messages, console logs, network failures, steps to reproduce. ghostbug eliminates that by silently capturing everything in the background.
| Sentry / LogRocket | ghostbug | |
|---|---|---|
| Setup | Account, API keys, config | ghostbug.init() |
| Server needed | Yes (paid SaaS) | No — 100% client-side |
| Bundle size | 50–200KB+ | ~7KB gzipped |
| Dependencies | Multiple | Zero |
| Data privacy | Sent to their servers | Stays in the browser |
| Output | Dashboard (another tab) | JSON / Markdown for GitHub Issues |
| Pricing | Free tier / Paid | Free forever |
npm install ghostbug# or
yarn add ghostbug
# or
pnpm add ghostbugimport ghostbug from "ghostbug";
ghostbug.init();That's it. ghostbug now silently auto-captures:
| Collector | What it catches |
|---|---|
| Errors | window.onerror + unhandled promise rejections with stack traces |
| Console | console.error() and console.warn() with full arguments |
| Network | Failed fetch and XMLHttpRequest (4xx/5xx) with timing |
| Clicks | Last 20 user clicks with element selectors and positions |
| Interactions | Form input, scroll, and resize events |
| Performance | Long Tasks, FCP, LCP, layout shifts via PerformanceObserver |
| Memory | Heap usage sampling, high usage (>90%) and rapid growth (>50%) alerts |
| Context | URL, browser, viewport, device pixel ratio, referrer |
Initialize ghostbug. Call once when your app loads.
ghostbug.init({
// Show floating widget UI (default: false)
widget: true,
// Or with widget options
widget: {
position: "bottom-right", // 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'
collapsed: true,
zIndex: 2147483647,
},
// Toggle individual collectors (all true by default)
collectors: {
errors: true,
console: true,
network: true,
clicks: true,
interactions: true,
performance: true,
memory: true,
},
maxReports: 50, // Max reports stored (oldest dropped when full)
maxBreadcrumbs: 20, // Max breadcrumb trail per report
maxClicks: 20, // Max click trail entries
// Rate limiting (prevent flood from error loops)
rateLimit: { maxEvents: 10, windowMs: 1000 },
// Filter/transform reports before storage
beforeReport: (report) => {
// Return false/null to discard
// Return modified report to transform
return report;
},
debug: false, // Enable SDK debug logging
});Returns all captured bug reports as an array.
const reports = ghostbug.getReports();Returns a GitHub/Jira-ready markdown string of all captured bugs.
const md = ghostbug.toMarkdown();
// Paste into GitHub Issues, Jira, Slack, etc.Example markdown output
## 1. TypeError: Cannot read property 'id' of undefined
- **Type:** error
- **Time:** 2026-02-23T10:21:33.000Z
- **Occurrences:** 3
- **URL:** https://myapp.com/dashboard
- **Browser:** Chrome 122
### Stack Trace
TypeError: Cannot read property 'id' of undefined
at UserProfile (app.js:42:12)
### Breadcrumbs
| Time | Category | Event |
|------|----------|-------|
| 10:21:30 | click | Clicked button "Save" |
| 10:21:31 | network | POST /api/user -> 500 |
| 10:21:33 | error | TypeError: Cannot read... |Downloads all reports as a JSON file.
ghostbug.download(); // ghostbug-report.json
ghostbug.download("my-bugs.json"); // custom filenameSubscribe to bugs in real-time. Returns an unsubscribe function.
const unsubscribe = ghostbug.onBug((report) => {
// Send to Slack, your API, anywhere
fetch("/api/slack-webhook", {
method: "POST",
body: JSON.stringify({ text: report.payload.message }),
});
});
// Later: stop listening
unsubscribe();Attach user context to every report.
ghostbug.setUser({ id: "user-42", plan: "pro", email: "dev@example.com" });Add custom tags to every report. Merges with existing tags.
ghostbug.setTags({ environment: "staging", version: "2.1.0" });Teardown everything — removes all listeners, restores patched APIs, unmounts widget.
ghostbug.destroy();"use client"; // Next.js App Router
import { useEffect } from "react";
import ghostbug from "ghostbug";
export default function GhostbugProvider({ children }) {
useEffect(() => {
ghostbug.init({ widget: true });
return () => ghostbug.destroy();
}, []);
return <>{children}</>;
}// layout.tsx
import GhostbugProvider from "./components/GhostbugProvider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<GhostbugProvider>{children}</GhostbugProvider>
</body>
</html>
);
}// main.ts
import ghostbug from "ghostbug";
ghostbug.init({ widget: true });<!-- +layout.svelte -->
<script>
import { onMount, onDestroy } from "svelte";
import ghostbug from "ghostbug";
onMount(() => ghostbug.init({ widget: true }));
onDestroy(() => ghostbug.destroy());
</script>
<slot /><script src="https://unpkg.com/ghostbug/dist/index.iife.js"></script>
<script>
ghostbug.default.init({ widget: true });
</script>Enable the floating widget for testers:
ghostbug.init({ widget: true });The widget:
- Shows a live bug count badge
- Click to expand and see all captured bugs
- Copy MD — copies markdown to clipboard
- Export — downloads JSON file
- Uses Shadow DOM — styles never leak into your app
- Fully isolated — your CSS won't break it
Each captured bug contains:
{
id: string; // Unique report ID
fingerprint: string; // Hash for deduplication
type: "error" | "unhandled-rejection" | "console" | "network" | "performance" | "memory";
timestamp: string; // ISO 8601
count: number; // Occurrences (deduped)
payload: { ... }; // Error/network/console/perf/memory details
breadcrumbs: [ // Events leading up to the bug
{ timestamp, category, message, data }
];
context: {
url: string;
referrer: string;
userAgent: string;
language: string;
viewport: { width, height };
screen: { width, height };
devicePixelRatio: number;
memory?: { usedJSHeapSize, totalJSHeapSize };
user?: { ... }; // From setUser()
tags?: { ... }; // From setTags()
};
}| Mechanism | Details |
|---|---|
| Error capture | Patches window.onerror and listens for unhandledrejection |
| Console capture | Monkey-patches console.error / console.warn (always calls originals) |
| Network capture | Patches fetch and XMLHttpRequest (only captures 4xx/5xx, never alters responses) |
| Click tracking | Uses capture-phase click listener (catches clicks even with stopPropagation) |
| Interactions | Listens for input, scroll, resize events as breadcrumbs |
| Performance | Uses PerformanceObserver for long-task, paint, and layout-shift entries |
| Memory | Samples performance.memory every 10s, flags high usage and rapid growth |
| Deduplication | Identical errors increment a counter instead of creating duplicate reports |
| Rate limiting | Token-bucket algorithm prevents floods from error loops |
| Ring buffer | Fixed-capacity storage prevents memory leaks in long-running apps |
| Safe teardown | destroy() restores all original APIs cleanly |
npm install # Install dependencies
npm run build # Build (ESM + CJS + IIFE)
npm test # Run tests
npm run test:watch # Watch mode
npm run typecheck # TypeScript strict check
npm run lint # ESLintMIT — do whatever you want.
