ScaleBun
Skip to article

Session replay

webDeveloper

DOM-reconstruction session replay with privacy masking, capture modes, and checkpoints — opt-in per session and always mindful of PII.

Updated Reviewed

Session replay reconstructs the DOM — it does not take screenshots — so playback is crisp, searchable, and small on the wire. It is off by default; enable it explicitly.

Enable replay#

TypeScript
await ScaleBun.init({  clientKey,  apiBaseUrl,  features: { replay: true },  replay: { mode: 'sampled', sampleRate: 1 },});

Modes: sampled, onError (keep a rolling buffer, promote it when something breaks), always, and off.

Controls#

TypeScript
ScaleBun.replay.checkpoint('before-payment'); // mark a momentScaleBun.replay.flush();                       // promote the pre-roll buffer to liveScaleBun.replay.pause();ScaleBun.replay.resume();ScaleBun.replay.stop();                        // stop recording for this sessionScaleBun.replay.setPrivacy({ maskAllText: true });ScaleBun.replay.stats();                       // null when not recording

stats() is the fastest way to answer "why is there no replay for this session" — it returns the mode, the sample bucket, the adaptive quality level and the dropped/shed counts, or null when this session was simply not sampled.

Sampling options#

OptionDefaultWhat it does
modesampledoff · onError · sampled · always.
sampleRate1Fraction of sessions recorded in sampled mode.
onErrorSampleRate1Gates onError mode.
persistBufferfalseMirror the pre-roll to IndexedDB so a tab-killing crash still yields it on the next load. A clean, error-free close discards it.
privacyLevel'standard''strict' adds heuristic redaction of emails, card numbers and SSNs to captured text.

The sampling decision is deterministic per session — the SDK buckets the session id with the same hash the backend uses, so the server can re-derive it and does not have to trust the client. A session that is not sampled installs nothing.

Privacy#

Because replay reconstructs the DOM, masking is precise. Typed form inputs are always masked and passwords are always blanked, regardless of settings. Beyond that you control text masking:

  • privacy.maskReplayText — mask all visible page text.

  • privacy.maskSelectors — mask text inside elements matching CSS selectors.

  • data-scalebun-mask — tag any element to always mask its subtree.

  • contenteditable regions are always masked.

Cross-origin iframes#

A cross-origin iframe is invisible to the parent page by default. It can be stitched into one replay if both sides run the SDK and each lists the other's origin:

TypeScript
// parent page, app.example.comreplayFrameOrigins: ['https://embed.example.com']
// the embedded app, embed.example.comreplayFrameOrigins: ['https://app.example.com']

One side alone does nothing — the allowlist is what authorises the bridge in each direction.

Next#

Session replay · Web SDK · ScaleBun