The short answer: the browser tells a page about every uncaught error through two events. Send those to a place that groups identical errors and counts them, and you learn what is breaking before anyone writes in. This guide shows the one-attribute way and what happens underneath.
Which errors can a page see?
- Uncaught errors, through the window error event.
- Unhandled promise rejections, through the unhandledrejection event.
Most failures in
asynccode end up here.
Errors you catch yourself never reach these events; send those from your own catch block.
Step 1: Create an app in SnagFrog
Sign up (30-day trial, no card) and create an app for your site.
Step 2: Turn on error capture
Add data-errors="auto" to the widget's script tag, and your version if you have one:
<script src="https://snagfrog.com/widget.js" data-app="your-app"
data-errors="auto" data-version="1.4.2" async></script> Capture is off unless you add the attribute. At most ten distinct errors are sent per page load. Before an
error leaves the page, addresses in it lose their query string and anything that looks like a token is
redacted. If your page has a Content-Security-Policy, allow https://snagfrog.com in connect-src.
Step 3: Read the Errors tab
Identical errors (same kind, message and place in the code) form one group. For each you see:
- how often it happened, and a 14-day history;
- the versions it appeared in;
- the latest stack.
Resolve a group when the fix ships. If the error happens again, the group reopens by itself. You are emailed about new errors and ones that came back in one digest an hour, never one email per error.
Doing it without the widget
The same thing by hand, with the errors API:
window.addEventListener('error', (e) => report(e.message, e.error?.stack));
window.addEventListener('unhandledrejection', (e) => report(String(e.reason), e.reason?.stack));
function report(message, stack) {
fetch('https://snagfrog.com/api/v1/errors', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ app: 'your-app', message, stack, version: '1.4.2', url: location.pathname })
});
} Keeping junk out
Anyone who knows your app's slug can send errors for it, as with its public report page. In the app's Settings → Error capture you can accept only the websites you list, or only requests that
carry the app key (data-key on the script tag). Monthly, daily and hourly limits bound the rest:
1,000 occurrences a month on the trial, 10,000 on Indie and 100,000 on Studio.
Questions
How do I track JavaScript errors on my website?
Listen for the window "error" and "unhandledrejection" events and send what they carry to a server that groups identical errors. With SnagFrog, adding data-errors="auto" to the widget script tag does both.
Why do some errors only say "Script error."?
Browsers hide the details of errors thrown by scripts loaded from another origin. Serve those scripts with an Access-Control-Allow-Origin header and load them with the crossorigin attribute to get the real message and stack.
Can I read minified stack traces?
SnagFrog stores the stack as the browser reported it and does not apply source maps. Set data-version so you know which build an error came from, and map the position with your own source map when you need the original line.
Is this a replacement for Sentry?
No. It answers "what is breaking, how often, and in which version" for a small site or app. It has no source maps, performance monitoring, session replay or release health. SnagFrog's comparison with Sentry says when Sentry is the better fit.
More guides
- How to collect bug reports from a macOS app
- How to collect bug reports from a Godot game
- How to add a bug report button to your website
- How to fix bug reports with Claude Code
- How to get crash reports from a macOS app
- How to publish a changelog for your app
Start a free 30-day trial · More guides · Docs · Compare with other tools · Changelog