Configuring Error Sampling Rates in Sentry Browser SDK
Sentry exposes three distinct sampling controls — sampleRate, tracesSampleRate, and tracesSampler — each governing a different class of data. Conflating them causes either quota exhaustion or invisible gaps in error coverage. This page covers each option precisely, including dynamic sampling by error severity and user tier. It is a companion to Integrating Observability SDKs: Sentry, Datadog RUM, and OpenTelemetry and the broader Core JavaScript Error Handling & Boundaries architecture.
Symptom / Trigger
The clearest signal that sampling is misconfigured is a Sentry quota alert or a billing spike immediately after a traffic surge. The other failure mode is the opposite: an error rate that is implausibly flat or zero during an incident, caused by setting sampleRate too low or accidentally using a client-side beforeSend that returns null for all events.
Sentry SDK warning: You have exceeded your error quota for this billing period.
Dropped 1,247 events in the last hour due to rate limiting.
At the SDK level you may also see a console warning in development:
[Sentry] sampleRate must be between 0 and 1. Got: 10. Defaulting to 1.0.
This occurs when developers pass a percentage integer (10 meaning “10%”) instead of a float (0.1). Sentry clamps the value to 1.0, resulting in 100% capture instead of the intended 10%.
Root Cause Explanation
Sentry’s three sampling knobs operate at different stages of the event lifecycle and control independent data types:
Sentry.init({
sampleRate: 1.0, // WRONG for high-traffic: 100% of errors, no cap
tracesSampleRate: 1.0, // WRONG for high-traffic: 100% of transactions
// tracesSampler is absent — no per-route logic
});
sampleRate is evaluated before beforeSend. If sampleRate is 0.5 and the random draw fails, beforeSend is never called — the event is discarded before any application logic runs. This means you cannot use beforeSend to “rescue” events that failed the sample draw.
tracesSampleRate and tracesSampler are mutually exclusive. If both are provided, tracesSampler takes precedence entirely and tracesSampleRate is ignored.
Step-by-Step Fix
1. Set sampleRate for error events by severity
The most defensive production configuration captures 100% of unhandled errors and applies a lower rate to manually captured informational events.
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
sampleRate: 1.0, // capture every unhandled exception
tracesSampleRate: 0.05, // only 5% of page loads carry a full transaction
});
// For informational / expected errors, use a manual hint to force a lower rate
function captureExpectedError(err) {
// Pass a numeric hint that beforeSend can inspect
Sentry.captureException(err, { tags: { expected: 'true' } });
}
Then in beforeSend, drop expected errors at a configurable rate:
beforeSend(event, hint) {
// Drop 90% of events tagged as expected to reduce volume
if (event.tags?.expected === 'true' && Math.random() > 0.1) {
return null; // drop this event
}
return event;
},
2. Replace tracesSampleRate with tracesSampler for per-route control
A flat tracesSampleRate applies the same percentage to every route. A tracesSampler function receives a samplingContext object that includes the transaction name, parent sampling decision, and custom attributes you set on the transaction.
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
sampleRate: 1.0,
// tracesSampleRate is omitted — tracesSampler takes full control
tracesSampler(samplingContext) {
const name = samplingContext.transactionContext?.name ?? '';
// Health-check and analytics routes generate enormous volume — exclude them
if (name.startsWith('/healthz') || name.startsWith('/metrics')) {
return 0; // 0 = always drop
}
// Checkout and payment flows are business-critical — always trace
if (name.startsWith('/checkout') || name.startsWith('/payment')) {
return 1.0; // 1.0 = always sample
}
// If the parent transaction was sampled, propagate that decision
if (samplingContext.parentSampled !== undefined) {
return samplingContext.parentSampled ? 1.0 : 0;
}
// Default: 2% of all other transactions
return 0.02;
},
});
samplingContext.parentSampled reflects the upstream sampling decision carried in the sentry-trace HTTP header. Propagating the parent decision ensures that distributed traces are either fully sampled or fully dropped — partial traces (where only some service hops are sampled) are nearly useless for debugging.
3. Implement dynamic sampling by user tier
Enterprise or paid users typically require higher fidelity error data than anonymous visitors. Attach user tier information to the Sentry scope and read it in tracesSampler.
// After authentication resolves, set user context on the Sentry scope
Sentry.setUser({
id: user.id,
email: user.email, // Sentry scrubs this server-side if PII scrubbing is enabled
subscription: user.plan, // 'free' | 'pro' | 'enterprise'
});
// In tracesSampler, read the current scope's user
tracesSampler(samplingContext) {
const scope = Sentry.getCurrentHub().getScope();
const user = scope?.getUser();
if (user?.subscription === 'enterprise') return 0.5; // 50% for enterprise
if (user?.subscription === 'pro') return 0.1; // 10% for pro
return 0.02; // 2% for free / anonymous
},
This approach keeps sampling logic co-located with SDK configuration rather than scattered across route handlers or middleware.
4. Combine ignoreErrors with sampling for volume control
ignoreErrors is a pre-sampling filter. Patterns in this array are matched against the error message string before sampleRate is evaluated. Removing known-noisy errors here is more efficient than filtering them in beforeSend, because they are discarded without entering the sampling pipeline at all.
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
sampleRate: 1.0,
tracesSampler(samplingContext) { /* ... */ },
ignoreErrors: [
// ResizeObserver warnings from browser extensions
'ResizeObserver loop limit exceeded',
'ResizeObserver loop completed with undelivered notifications',
// Safari-specific non-errors
/^undefined is not an object \(evaluating 'window\.__gCrWeb/,
// Network cancellations — not actionable
'Network request failed',
'Failed to fetch',
'Load failed',
],
});
Use exact strings for predictable message matches, and regex literals for pattern-based filters. The ignoreErrors filter is case-sensitive and matches against exception.value (the error message), not exception.type (the error class name).
5. Validate the effective sample rate in development
Sentry does not log the sampling decision to the console by default. Add a temporary beforeSend shim during development to confirm events are reaching the hook.
if (import.meta.env.DEV) {
const originalBeforeSend = options.beforeSend;
options.beforeSend = (event, hint) => {
console.debug('[Sentry] beforeSend called for:', event.exception?.values?.[0]?.type);
return originalBeforeSend ? originalBeforeSend(event, hint) : event;
};
}
For transaction sampling, wrap tracesSampler in development to log every sampling decision:
const productionSampler = (ctx) => { /* your logic */ };
tracesSampler: import.meta.env.DEV
? (ctx) => {
const decision = productionSampler(ctx);
console.debug(`[Sentry] tracesSampler: ${ctx.transactionContext?.name} → ${decision}`);
return decision;
}
: productionSampler,
Remove or tree-shake the debug wrapper before production builds — the import.meta.env.DEV check ensures bundlers eliminate the branch in production mode.
Verification
After deploying a sampling configuration change, use the Sentry Stats dashboard (Settings → Stats → Error counts by project) to confirm the ingestion rate dropped proportionally to the configured sampleRate. For transaction sampling, check Performance → Overview and compare the transaction count before and after.
// Console verification: trigger a known error and confirm it appears in Sentry
Sentry.captureException(new TypeError('sampling-verification-test'));
// Then search Sentry for: 'sampling-verification-test'
// Expected: appears in Issues within 30 seconds
If the event does not appear, check: DSN is correct, sampleRate is not 0, beforeSend does not return null for all events, and the Sentry project is not paused.
Edge Cases & Gotchas
sampleRate: 0silently drops everything. A typo of0instead of1.0results in zero events reaching Sentry with no SDK warning. Always validate the value comes from an environment variable with a non-zero default.tracesSamplerreturningundefinedis treated as “inherit parent or fall back to 0.” If your sampler function has a code path that returnsundefined(for example a missingreturnin anifbranch), those transactions are dropped. Always include an explicitreturn 0.02default at the end of the function.- Error events created by
Sentry.captureExceptionare subject tosampleRate, but events created by integrations (e.g. the BrowserTracing integration’s automatic transaction creation) respect onlytracesSampleRate/tracesSampler. These are not interchangeable controls. - The
ignoreErrorsoption is evaluated beforesampleRate. If an error type matchesignoreErrors, it is dropped before the sample draw and beforebeforeSend. UseignoreErrorsto eliminate known-noisy browser errors (e.g.,ResizeObserver loop limit exceeded) rather than filtering them inbeforeSend.
FAQ
Does lowering sampleRate affect the accuracy of Sentry’s error rate graphs?
Yes. Sentry adjusts displayed counts using the sample rate as a multiplier, so the dashboard shows estimated event counts rather than raw ingested counts. The estimation is accurate when sampleRate is stable but will be wrong immediately after a rate change until enough samples accumulate.
Can I change sampleRate at runtime without re-initializing the SDK?
No. Sentry reads sampling configuration once during Sentry.init. To change rates at runtime, call Sentry.init again with a new configuration — this reinitializes the SDK hub and resets all integrations. A safer pattern is to implement dynamic logic inside a tracesSampler function that reads from a feature flag evaluated on each transaction.
What happens to errors that are dropped by sampleRate?
They are discarded client-side and never transmitted. Sentry has no visibility into dropped events. If you need a count of dropped events for capacity planning, implement a lightweight counter in your own analytics pipeline before the Sentry beforeSend hook.