Session replay

See what the user did, not just where it crashed.

Replay records the DOM as a stream of mutations using rrweb (the same library Sentry uses). When an error fires, the SDK attaches the most recent buffer to the envelope. The dashboard re-runs the recording so you can scrub through the last 30 seconds before the crash.

Enable in the SDK

Add the replayIntegration when you initialize Sentry. The canonical GlitchReplay config records every session at full rate — the server prunes non-errored sessions on a delay, so you don't pay client-side bandwidth for sampling. See Why we record everything below for the mechanism.

Sentry.init({
  dsn: "<YOUR_DSN>",
  replaysSessionSampleRate: 1.0,
  replaysOnErrorSampleRate: 1.0,
  integrations: [
    Sentry.replayIntegration({
      // "Balanced" — change in Settings → Replays → Privacy level.
      maskAllText: false,
      maskAllInputs: true,
      blockAllMedia: true,
    }),
  ],
});

The mask flags above come from the project's privacy level — see Privacy & masking below. The setup snippet on the dashboard pre-fills the right flags for whichever level is selected; the SDK re-checks /api/{project_id}/configon every page load so subsequent loads pick up dashboard changes.

About the DSN format

Copy the DSN from your project's Settings → Setup page exactly as it appears. It looks like https://<public_key>@glitchreplay.com/0. The /0 at the end is intentional: Sentry SDK 10+ requires a numeric path, and we authenticate by your project's public key (everything before the @) — the path is informational. If you see a non-numeric path in older docs or copy/paste, replace it with /0 and the SDK will start sending again.

Why we record everything

Sample-on-error replay (the Sentry-native default — record nothing until an error fires, then flush a buffered window) misses the most important thing: the first error of a regression. By the time the buffer is set up and ready to flush, the user has already hit reload or closed the tab. You see "something broke" in the issue list with no recording attached.

GlitchReplay records every session at 1.0 from the first frame and lets the server decide which to keep. A cron on glitchreplay-consumer runs every five minutes and sweeps recently-uploaded sessions:

  • Errored sessions — kept for the full event-retention window of your plan (14 days on Free, longer on paid).
  • Non-errored sessions — kept for 3 days, then pruned. On free tier they're also subject to the monthly session cap; if you blow through it the sweep keeps a small sampled fraction past quota and drops the rest.

Each decision is recorded in the replay_priority_decisions table with a reason — errored, within_daily_quota, sampled, or over_quota_dropped. The dashboard surfaces the keep/drop ratio so you can see exactly what the sweep is deciding.

The practical upshot: you never have to tune sample rate. Set both rates to 1.0, ignore the dial, and trust the priority sweep to manage cost on the back end.

Privacy & masking

Pick a privacy level on the project at Settings → Replays → Privacy level. The level drives what the SDK masks before recording leaves the browser. Four options:

  • Strict — text content, form values, and media all masked. Maximum privacy; replays are useful for layout and interaction patterns only.
  • Balanced (default) — text visible, form input values masked, media blocked. Good debugging signal without leaking what users typed.
  • Light — text + media visible, form input values still masked. For pages where on-screen content isn't sensitive.
  • None — nothing redacted. Internal tools or dev environments only.

The level ships to the SDK via /api/{project_id}/config and is cached in localStorage for the next page load. The setup snippet on Settings → Setup pre-fills the matching maskAllText / maskAllInputs / blockAllMedia flags so first-time visitors get the right defaults before the dashboard fetch returns. Re-copy the snippet after changing the level if you want new visitors to skip the one-page-load delay.

rrweb masks at snapshot time. Changing the level only affects sessions captured after the next page load — replays already recorded under a stricter level stay redacted as captured. There's no way to retroactively unmask an existing recording.

Per-element overrides

On top of the project-level setting, mark individual elements with the standard Sentry data attributes:

<div data-sentry-unmask>Always show this text in replay</div>
<div class="sentry-unmask">Same effect via class.</div>

<input data-sentry-mask />     <!-- always mask the value -->
<div data-sentry-block />      <!-- drop the entire subtree -->

Per-element rules win over the project-level setting, so they're the right tool for credit-card forms or PII fields that should always be masked even at the none level.

Consent-gated regions

Where regulation requires explicit consent, gate Sentry.init behind your consent banner — defer the call until the user accepts, and pass replaysSessionSampleRate: 0 until then. Replay only starts once the integration is loaded, so this gives you a clean opt-in.

What it costs

  • Free plan — replay enabled, capped at 10,000 sessions/month, 3-day retention for non-errored sessions, 14-day retention for error-attached.
  • Pro / Business / Enterprise — replay included; no per-replay charge; longer retention for error-attached sessions.

Sentry gates replay behind their Business tier ($80/mo) and charges $6 per 1,000 replays on top of that. We include unlimited recording on every tier, including free.

How replays are stored

rrweb events are stored in R2 next to the raw envelope, keyed by {project_id}/{event_id}/replay.json. The dashboard's replay player streams them back on demand — there's no server-side rendering, so scrubbing is instant once the file is fetched.

Replay summary (AI)

On Pro and above, each replay is summarized into a one-sentence narrative — "User opened the search modal, typed ‘invo’, hit Enter, then the page froze." — generated from the breadcrumb stream alone (no DOM content, no PII risk). Useful for triaging a list of issues without opening each one.

What replay does not do

  • It's not a UX-research tool. Don't use it for funnel analysis.
  • It doesn't cover server-side or RSC errors that never touched the browser. For those, replay is empty (the error event still ingests).
  • It doesn't record cross-origin <iframe> contents.

Common questions

What text is masked by default, and what is not?

At the default balanced level, text content stays visible — headings, paragraphs, link text, button labels all appear in the recording. Form input values are masked separately (maskAllInputs: true) so passwords and credit-card numbers never leave the browser. Media (images, video, canvas) is rendered as a placeholder.

Switch to strict in Settings → Replays → Privacy level to mask all text content too — useful for compliance regimes or apps that render PII inline. Light shows media; none masks nothing.

What never gets masked regardless of level: element structure (tags, classes, IDs), CSS positioning, click coordinates, and fetch URLs in the breadcrumb stream. If those leak data — e.g. an order ID in a URL path — pair replay with server-side PII scrubbing rules so the breadcrumb is sanitized before ingest.

How do I unmask a specific element I trust?

Use data-sentry-unmask on the element (or any ancestor). The Sentry replay integration scopes the unmask to that subtree only:

<h1 data-sentry-unmask>Welcome to GlitchReplay</h1>
<div className="sentry-unmask">Same effect via class.</div>

We recommend unmasking sparingly — every element you reveal becomes a place a future engineer might inadvertently render PII. Test with a real recording before assuming the unmask is safe.

How do I block an element from being recorded at all?

data-sentry-block drops the entire subtree from the recording — the DOM mutations, the click events, the screenshots. Useful for credit-card forms, auth dialogs, or anything an internal security policy says shouldn't be captured even structurally:

<form data-sentry-block>
  <input name="cc-number" />
  <input name="cvv" />
</form>
My replay is blank or won't play. What now?

A blank replay almost always means rrweb couldn't serialize the DOM — most often because the page is heavily masked (maskAllText + blockAllMedia + data-sentry-block on the root container = nothing left to render). Try playing back a less aggressively-masked page first. If the issue is a true render failure, the dashboard's replay player will show a small "Recording corrupted" note instead of a blank frame; that points at a different problem worth filing a support ticket for.

How long are replays kept?

Depends on whether the session has an error attached. Error-attached sessions follow your plan's event retention — 14 days on Free, longer on paid tiers — so the replay is there as long as the issue is. Sessions with no error are pruned at 3 days regardless of plan, because the on-call value of an error-free replay is essentially zero past that point. The decision is made by a server-side priority sweep ~5 min after each session quiesces.

If you need a specific replay longer-term, export it to your own storage from the player.

Next: configure alerts for new issues with replay, or read about PII scrubbing.