What is rrweb in one paragraph?
rrweb is the de-facto open-source recorder for web session replay. It is a small JavaScript library (~50 KB gzipped for the recorder) that takes an initial DOM snapshot, then streams every change to that DOM as a list of typed events. A separate rrweb-player package consumes those events and rebuilds the original page inside an iframe so the recording can be scrubbed like a video — except it is a real DOM, not pixels.
Who created it and what is the license?
- Author: Yuyz0112 (Yanzhen Yu)
- First public release: 2018
- License: MIT
- Repository: github.com/rrweb-io/rrweb
- Written in: TypeScript
Because it is MIT-licensed, commercial vendors can embed it without restriction, which is why most of the session replay industry runs on top of it.
How does the recorder work at a high level?
The recorder has three responsibilities:
- Take a serializable snapshot of the current DOM, including computed styles for inline elements and the contents of stylesheets
- Subscribe to every source of change (DOM, input, mouse, scroll, viewport, network)
- Emit each change as a typed event onto a callback the host application provides
Sources of change are wired up via standard browser APIs:
MutationObserverfor DOM tree changesaddEventListenerforinput,click,scroll,mousemove,touchstart,selectionchange- A monkey-patched
CSSStyleSheet.prototype.insertRulefor stylesheet edits - Wrappers around
XMLHttpRequestandfetchfor network metadata MediaQueryListandResizeObserverfor viewport changes
How do you start recording in code?
The minimal example from the rrweb README:
import * as rrweb from "rrweb";
const events = [];
const stopFn = rrweb.record({
emit(event) {
events.push(event);
},
maskAllInputs: true,
maskInputOptions: { password: true },
blockClass: "rr-block",
ignoreClass: "rr-ignore",
});
// Later, ship `events` to your backend
// stopFn() to stop recording
The emit callback fires every time a new event is produced. In production you batch events, gzip them, and send them to your backend on a timer or on visibilitychange.
What event types does rrweb produce?
| Type | Description |
|---|---|
| 0 | DomContentLoaded |
| 1 | Load |
| 2 | FullSnapshot — the initial serialized DOM tree |
| 3 | IncrementalSnapshot — a delta (mutation, input, scroll, mouse, etc.) |
| 4 | Meta — viewport size, URL |
| 5 | Custom — user-defined events |
| 6 | Plugin — events from rrweb plugins |
Type 3 is by far the most frequent and contains its own sub-source enum (Mutation, MouseInteraction, Scroll, Input, MouseMove, etc.).
How does the replayer work?
rrweb-player (or rrweb.Replayer directly) takes the array of events and:
- Creates a sandboxed
iframewithsandbox="allow-same-origin" - Rebuilds the FullSnapshot inside that iframe by setting
innerHTMLand re-attaching stylesheets - Walks forward through IncrementalSnapshots, applying each one in time order
- Renders a virtual cursor and visual click pulses on top
- Exposes
play,pause,goto(timestamp), and a timeline scrubber
Because it is a real DOM, you can right-click and inspect a replay in DevTools just like a live page.
Which products are built on rrweb?
- PostHog Session Replay
- Sentry Session Replay (forked variant)
- OpenReplay
- Highlight.io
- CloseTrace
- Many internal observability stacks at companies that built their own player
This makes rrweb's event format a de-facto standard — replays can often be moved between tools.
What about privacy?
rrweb ships with built-in privacy primitives:
maskAllInputsmasks every input valuemaskInputOptionslets you enable masking per input typeblockClass/blockSelectorskips entire subtreesignoreClassskips capturing inputs on matching elements- A
maskTextFncallback for custom redaction (PII, credit cards, etc.) - Password fields are masked unconditionally
How it relates to CloseTrace
CloseTrace uses rrweb under the hood for its session capture pipeline, with PII masking enabled by default and custom event types layered on top to mark form steps, rage clicks, and dead clicks for the lead recovery inbox.