Session replay
Watch journey sessions as they happened, with every word masked until you choose to show it and form values never recorded.
Replay shows a journey session as it happened: each page's layout, the scrolling, the clicks and the moves between pages. Every word is masked until you choose which text to show, and form values never leave the browser. It is off until you turn it on, and it needs journey mode.
What a recording shows
- The layout of each page, with its styles.
- Scrolling, clicks, pointer movement and the size of the window.
- Each move to another page, including route changes in single-page apps.
- The text you chose to show. Everything else reads as blocks, one per character.
- That a form field was filled in. Any value is drawn as
•••••, so not even its length is kept. - Moments to jump to: page changes, clicks, rage clicks (3 clicks within 1 second in one spot), and the goals and events from the session's journey.
To watch one, open a session in Journeys. When it has a recording, the player sits above its timeline.
What never leaves the browser
- Form values of any kind, even inside text you chose to show.
- Which boxes are checked and which options are chosen.
- Images, video, audio, canvas, iframes and embedded objects:
img,picture,video,audio,iframe,object,embed,canvasandsvg image. Each is drawn as an empty box of the same size, and nothing from it loads when a recording plays. - All text, until you choose to show it.
- Anything that looks like an email address or a phone or card number, even in text you chose to show.
The collector checks every recording again before it is stored. With no text shown, it masks any text that got through. It masks every form value, drops checked boxes and chosen options, and removes scripts, event handlers and javascript: links, so a recording sent with a copied site key can't put script into your dashboard.
Install
- Run lf.js in journey mode. Replay records the journey session lf.js starts. See Privacy modes.
- Turn replay on in the dashboard, under Settings, Replay.
- Add the replay script after lf.js, with the same site key.
- Check it. Settings, Replay reads Installed once the first recording arrives.
<script defer src="https://cdn.littlefriend.io/lf.js"
data-site="lf_YOUR_SITE_KEY"
data-mode="journey"
data-consent="required"></script>
<script defer src="https://cdn.littlefriend.io/lf-replay.js" data-site="lf_YOUR_SITE_KEY"></script>lf.js stays the same size, and pages without the replay script load nothing extra. The replay script comes from the same host as lf.js and sends to the same collector, so the Content Security Policy entries you added for lf.js cover it. It writes no inline script or style, so a policy without 'unsafe-inline' needs nothing more.
With npm
If you installed @littlefriend/tracker, add the replay package next to it and start it with the same site key. It loads the recorder only once the collector says replay is on for the page.
pnpm add @littlefriend/replayimport { identify, startReplay } from '@littlefriend/replay';
startReplay({ site: 'lf_YOUR_SITE_KEY' });
// Once the visitor is signed in, with your own id for them
identify('usr_4821937');With npm, your bundler includes rrweb's recorder as published. On a page whose style-src has no 'unsafe-inline', Chrome and Edge then log a Content Security Policy error for each style change. Recording still works. On such a page, use the script tag.
Choose what shows
Mark elements in your HTML, or list CSS selectors under Settings, Replay. Both work together.
| Attribute | What it does |
|---|---|
data-lf-unmask | The text inside this element shows in recordings, once Text shown in recordings has at least one selector, such as [data-lf-unmask]. With that list empty, every word is masked. Form values never show. |
data-lf-mask | Masks the text of this element again, inside a region that shows. |
data-lf-block | Leaves this element out of recordings. It is drawn as an empty box of the same size. |
<nav data-lf-unmask>...</nav>
<section data-lf-unmask>
<h2>Your plan</h2>
<p data-lf-mask>Card ending 4242</p>
</section>
<div data-lf-block>...</div>In the dashboard, three lists do the same by selector:
- Text shown in recordings. Text inside matching elements shows. One click on Show labels and headings adds
nav,header,footer,h1,h2,h3,h4,button,label,th,legend,[role=button],[role=tab]and[role=menuitem]. - Hidden elements. Matching elements are left out and drawn as empty boxes, on top of the ones always hidden.
- Pages never recorded. Nothing is recorded on these routes. They use the door's path prefixes, so
/accountcovers/accountand every page under it. In single-page apps recording stops before the next page is drawn, including Astro view transitions and Turbo visits. When a route draws its page long after the address changes, also adddata-lf-blockto its sensitive parts.
Selectors can use tag names, classes, ids and attribute selectors with plain values, joined by spaces, > or commas. Pseudo-classes like :hover, sibling selectors and * are refused when you save. Each selector list holds up to 50 selectors of up to 200 characters.
The route list holds up to 50 routes. Each starts with /, is at most 100 characters and has no query or fragment.
Words in the title, alt, placeholder, aria-label and aria-description attributes follow the text around them: they show only where that text shows.
Tie sessions to a person
// Once the visitor is signed in, with your own id for them
lf('identify', 'usr_4821937');Pass your own id for the signed-in person. It is kept in journey mode only, and Little Friend never guesses one. With it, you can find a person's sessions and erase them when they ask. The replay script handles the call and sends the ref with the session's recordings. With npm, call identify('usr_4821937') from @littlefriend/replay, and identify(null) on sign-out.
A ref is 1 to 64 letters, digits, dots, colons, dashes and underscores. Emails are refused, and so is anything that looks like a phone or card number, including a ref made only of seven or more digits. Add a prefix to numeric ids, like usr_4821937.
Sampling and short visits
- Sample rate is the percent of journey sessions recorded, 100 by default. It is decided once per session from the session id, so a recorded session is recorded on every page.
- Minimum active time drops a recording with less activity than this when the session ends, 2 seconds by default. A pause of more than 5 seconds doesn't count as active, and the player can skip it.
- Recording pauses while the tab is hidden, stops after 60 minutes on one page, and a session's recording stops growing at 12 MB compressed.
Sessions each month
Each workspace records up to 10 sessions a month free. Recording more than 10 sessions a month needs a card on file.
How long recordings are kept
As long as raw events: 7 days by default, set under Settings, Privacy and retention. A recording never outlives its journey, and deleting a project deletes its recordings.
Erase a person
When someone asks you to delete their data, a workspace owner opens Settings, Replay, enters their ref under Forget a person and confirms. Their sessions, events and recordings in that project are erased, and the dashboard shows how many. This cannot be undone. Totals already counted stay, since nothing in them identifies a visit.
Consent and Global Privacy Control
Replay records only where lf.js has a journey session. With data-consent="required", nothing is recorded until you call lf('consent', 'journey'). A browser that sends Global Privacy Control is never recorded, and a project in aggregate mode records nothing: the collector refuses recordings for it.
Whether you need to tell visitors about recording depends on where you operate. This page lists what a recording holds so you and your counsel can decide.