Replaying recordings with rrweb Cloud
This guide shows you how to fetch and replay recorded recordings from rrweb Cloud using rrweb's replay functionality.
You can view and manage your recordings in the rrweb Cloud dashboard.
Installing the rrweb player
For a pre-built player UI, install rrweb-player: (recommended)
npm install rrweb-playerFor a bare bones player without UI, install the rrweb replay package:
npm install @rrweb/replayCDN Installation
Include rrweb-player via CDN for quick setup:
<link
rel="stylesheet"
href="https://cdn.rrweb.com/rrweb-player/next/style.css"
/>
<script type="module">
import rrwebPlayer from 'https://cdn.rrweb.com/rrweb-player/next/rrweb-player.js';
</script>Basic Replay Setup
Fetch Events from rrweb Cloud
First, retrieve the recorded events for a recording. You can find your recording IDs in the rrweb Cloud dashboard.
async function fetchRecordingEvents(recordingId) {
try {
const response = await fetch(
`https://api.rrweb.com/recordings/${recordingId}/events`,
);
if (!response.ok) {
throw new Error(`Failed to fetch events: ${response.status}`);
}
const events = await response.json();
return events;
} catch (error) {
console.error('Error fetching recording events:', error);
return null;
}
}If your player also needs the recording descriptor and metadata, request embedded events from the recording detail endpoint:
async function fetchRecordingWithEvents(recordingId) {
const response = await fetch(
`https://api.rrweb.com/recordings/${recordingId}?includeEvents=true`,
);
if (!response.ok) {
throw new Error(`Failed to fetch recording: ${response.status}`);
}
const recording = await response.json();
return {
metadata: recording.metadata,
events: recording.events,
};
}The /recordings/{recordingId}/events endpoint remains the canonical event-stream endpoint. includeEvents=true is a convenience for replay views that need metadata and events together; descriptor responses stay lightweight by default.
Initialize and Play Replayer
Once you have the events, initialize the player and play the recording:
// Fetch events
const events = await fetchRecordingEvents(recordingId);
if (events && events.length > 0) {
// Initialize rrweb-player
const player = new rrwebPlayer({
target: document.getElementById('player-container'), // Container element
props: {
events: events,
// Player options
width: 1024, // Player width (px)
height: 576, // Player height (px)
maxScale: 1, // Maximum scale (1 = 100%, 0 = unlimited)
autoPlay: true, // Auto-start playback
speedOption: [1, 2, 4, 8], // Available speed options
showController: true, // Show player controls
tags: {}, // Custom event tag styles
// All other options are passed to the underlying replayer
skipInactive: true,
mouseTail: true,
blockClass: 'rr-block',
},
});
}Player Endpoint
For a quick preview without writing any code, you can use the built-in player endpoint:
https://api.rrweb.com/recordings/{recordingId}/player.htmlThis endpoint serves an HTML page with the rrweb player pre-configured. Simply replace {recordingId} with your recording ID and open the URL in a browser.
Example:
https://api.rrweb.com/recordings/550e8400-e29b-41d4-a716-446655440000/player.htmlThe player endpoint supports these query parameters:
controls=true|falseto show or hide the control barautoplay=true|falseto start playback automaticallymode=standard|minimal|captureto switch between the full page, stripped embed layout, and full-viewport capture layoutspeed=0.25-4to set the playback speedtime=offset:0|offset:-5000|percent:50|epoch:1702900000000to start at a specific point in the recording
The player page includes:
- Full playback controls (play, pause, speed adjustment, timeline scrubbing)
- Mouse trail visualization
- Event fetching from the recording events endpoint
- Skip inactive periods
- Responsive layout that adapts to your screen size
This is useful for quick debugging, sharing recordings with team members, or using the player HTML as the basis for screenshots and future export endpoints.
Screenshot Endpoint
To capture a still image of a recording at a specific playback position, use the screenshot endpoint:
https://api.rrweb.com/recordings/{recordingId}/screenshot.pngSupported formats are .png, .jpeg, and .webp. You can also negotiate the format with the Accept header when no file extension is provided.
Supported query parameters:
time=offset:0|offset:-5000|percent:50|epoch:1702900000000width=1-4096height=1-4096quality=1-100for JPEG and WebP output
Alternative: Using the rrweb replay package
If you prefer to build your own player UI, you can use the rrweb replay package:
npm install @rrweb/replay// Fetch events for a specific recording
const recordingId = 'your-recording-id';
const events = await fetchRecordingEvents(recordingId);
if (events && events.length > 0) {
// Initialize the replayer
const replayer = new rrweb.Replayer(events, {
// Configuration options
speed: 1, // Playback speed (1 = normal)
root: document.body, // Root element for replay
loadTimeout: 0, // Timeout for loading external resources
skipInactive: false, // Skip inactive periods
inactivePeriodThreshold: 10000, // Threshold for inactive periods (ms)
showWarning: true, // Show warning messages
showDebug: false, // Show debug messages
blockClass: 'rr-block', // Class for blocked elements
liveMode: false, // Enable live mode
insertStyleRules: [], // Custom CSS rules to inject
triggerFocus: true, // Trigger focus events
UNSAFE_replayCanvas: false, // Replay canvas elements (unsafe)
pauseAnimation: true, // Pause CSS animations when paused
mouseTail: true, // Show mouse cursor trail
});
// Start playback
replayer.play();
}Advanced Replay Features
Live Mode Replay (not supported by rrweb Cloud yet)
For real-time or near real-time replay:
// Initialize in live mode
const replayer = new rrweb.Replayer(events, {
liveMode: true,
speed: 1,
});
// For existing events, play with offset
if (events.length > 0) {
const offset = Date.now() - events[0].timestamp;
replayer.play(offset);
}
// For true live streaming, start with empty events array
const liveReplayer = new rrweb.Replayer([], {
liveMode: true,
});
liveReplayer.startLive();
// Then add events as they arrive
function addLiveEvent(event) {
liveReplayer.addEvent(event);
}Control Playback
Control playback programmatically:
// Play from the beginning
replayer.play();
// Play from a specific time (in milliseconds)
replayer.play(3000); // Start at 3 seconds
// Pause playback
replayer.pause();
// Pause at a specific time
replayer.pause(5000); // Pause at 5 seconds
// Resume playback
replayer.play();
// Jump to a specific time
replayer.goto(10000); // Jump to 10 seconds
// Get current time
const currentTime = replayer.getCurrentTime();
// Get total duration
const totalDuration = replayer.getMetaData().totalTime;
// Destroy the replayer
replayer.destroy();Custom Event Handling
Handle custom events during replay:
const replayer = new rrweb.Replayer(events, {
// Custom event handlers
hooks: {
// Called before replaying an event
beforeReplay: (event) => {
console.log('Replaying event:', event);
},
// Called after replaying an event
afterReplay: (event) => {
if (event.type === 5 && event.data.tag === 'user-action') {
// Handle custom user action events
console.log('User action:', event.data.payload);
}
},
},
});Replayer Configuration Options
Privacy and Security
const replayer = new rrweb.Replayer(events, {
// Block sensitive elements
blockClass: 'rr-block',
blockSelector: '.sensitive, .private-data',
// Mask text in sensitive elements
maskTextClass: 'rr-mask',
maskTextSelector: '.email, .phone, .ssn',
// Don't trigger focus (prevents stealing focus)
triggerFocus: false,
// Don't replay canvas (safer)
UNSAFE_replayCanvas: false,
});Custom Styling
const replayer = new rrweb.Replayer(events, {
// Inject custom CSS
insertStyleRules: [
'.replayer-wrapper { background: #f0f0f0; }',
'.replayer-mouse { cursor: none; }',
'.rr-block { background: #ccc; }',
],
});Using Plugins
Install and Use Replay Plugins
npm install @rrweb/rrweb-plugin-sequential-id-replayimport rrweb from 'rrweb';
import { sequentialIdReplayPlugin } from '@rrweb/rrweb-plugin-sequential-id-replay';
// Initialize with plugin
const replayer = new rrweb.Replayer(events, {
plugins: [
sequentialIdReplayPlugin({
key: 'sequenceId',
}),
],
});Custom Replay Plugin
Create a custom plugin to handle specific events:
// Define a custom replay plugin
const customReplayPlugin = {
handler(event, isSync, context) {
if (event.type === rrweb.EventType.Plugin) {
// Handle custom plugin events
if (event.data.plugin === 'my-custom-plugin') {
console.log('Custom plugin data:', event.data.payload);
// Access replayer context
const { replayer, mirror } = context;
// Perform custom actions
const element = mirror.getNode(event.data.payload.nodeId);
if (element) {
element.classList.add('highlighted');
}
}
}
},
};
// Use the plugin
const replayer = new rrweb.Replayer(events, {
plugins: [customReplayPlugin],
});Handle Large Recordings
Coming later...
Filtering recordings by metadata
When listing recordings via GET /recordings, you can filter by metadata using the metadata[key]=value deep object syntax. Always wrap both the key and value in encodeURIComponent before building the query string so filters containing spaces or punctuation still match the stored metadata.
const params = new URLSearchParams();
params.set(
`metadata[${encodeURIComponent('user id')}]`,
encodeURIComponent(currentUser.id),
);
const response = await fetch(
`https://api.rrweb.com/recordings?${params.toString()}`,
);Troubleshooting
- Events not loading: Check recording ID and ensure events were ingested successfully
- Replay not starting: Verify events array is not empty and contains valid rrweb events
- Styling issues: Ensure rrweb CSS is loaded and no conflicting styles
- Canvas not replaying: Enable
UNSAFE_replayCanvas: true(note: this removes sandbox security)