Packstub

Configuration

php artisan session-replay:install publishes config/session-replay.php; php artisan vendor:publish --tag=session-replay-config does the same on its own. Your file is merged over the package defaults key by key, so it may contain only what you change. A list you set (except, except_routes, capture.console, size.strip_attributes, size.keep_attributes, snapshots.share_routes, snapshots.share_paths, snapshots.volatile_ids, ingest.middleware, viewer.middleware) replaces the default list as a whole, so [] really turns a list off.

Recording

Key Default Meaning
enabled env('SESSION_REPLAY_ENABLED', true) The master switch. Off: the directive renders nothing and ingest refuses uploads.
sample_rate env('SESSION_REPLAY_SAMPLE_RATE', 1.0) Share of recordings kept, decided once per recording in the browser.
guests env('SESSION_REPLAY_GUESTS', false) Record people who are not signed in.
except password-reset and verification pages Request paths (Str::is patterns) that never get the recorder. The package's own pages are always left out. The default list covers *password-reset*, *reset-password*, *forgot-password*, password/*, *email-verification*, *verify-email* and email/verify*; a list you set replaces it, so keep those in yours.
except_routes password-reset and verification routes Route names (Str::is patterns) that never get the recorder, whatever their URL. The default list covers password.* and verification.* (Laravel's starter kits) and filament.*.auth.password-reset.*, filament.*.auth.email-verification.* and filament.*.auth.email-change-verification.* (Filament panels).
idle_timeout 30 Minutes without activity after which a tab starts a new recording.
flush_interval 5000 Milliseconds between uploads while the page is open (minimum 1000).

Mode

Key Default Meaning
mode env('SESSION_REPLAY_MODE', 'session') session: a recorded tab uploads from its first page. on_error: the browser keeps the last moments in memory and uploads only when something goes wrong. See Recording only when something goes wrong.
on_error.buffer_seconds 60 Seconds kept before the trigger, 5 to 600. The window starts at a full page snapshot, so a replay may start up to half a window (at most 30 s) earlier.
on_error.max_buffer_mb 5 The most the window may take in memory, in MB of events as the recorder estimates them, 1 to 50. A page that changes a lot reaches it before buffer_seconds; the window then shrinks to its newest page snapshot.
on_error.triggers ['error', 'request'] Marker types that upload the window: error, request, console, rage-click, custom. A type also has to be captured (capture.*).
on_error.ask false Ask the person before anything is sent: true (or dialog) in a small modal dialog the recorder draws, toast in a card at the bottom corner that leaves the page usable. See Privacy.
on_error.ask_again false After "Don't send" (or a toast left unanswered): false leaves the tab alone until its session ends; true asks again on the next trigger, at most once per window, and keeps the window going meanwhile.
on_error.ask_timeout 60 Seconds the toast waits for an answer before it counts as "Don't send", 5 to 600; 0 keeps it up until it is answered. The dialog never times out.
on_error.livewire_error_modal production What happens to Livewire's error modal when a failed request puts the question up: keep shows the question over it, replace shows the question instead of it, production replaces it only while app.debug is off. See Privacy.
on_error.keep_pending true Keep the uploaded window in the browser (IndexedDB) while its upload fails, so a reload during the outage does not lose it. See When the upload fails.

Consent

Key Default Meaning
consent env('SESSION_REPLAY_CONSENT', 'always') always or opt-in (waits for SessionReplay.consent(true)).
respect_gpc false Do not record browsers that send Global Privacy Control.

Privacy

Key Default Meaning
privacy.mask_all_inputs true Mask every input's value. Password inputs are masked regardless.
privacy.mask_all_text false Mask every text node: layout-only recordings.
privacy.mask_text_selector [data-replay-mask], [data-replay-mask] *, [contenteditable], [contenteditable] * Elements whose text is replaced with asterisks. Rich editors are in the default, so what people type there is masked like an input.
privacy.unmask_text_selector [data-replay-unmask] With mask_all_text: text inside matching elements stays readable, unless mask_text_selector matches it. Input values are never affected.
privacy.block_selector [data-replay-block] Elements recorded as an empty box of the same size.
privacy.ignore_selector [data-replay-ignore] Elements whose input events are not recorded.
privacy.anonymous false Record signed-in people without storing who they are (no person, no impersonator; the workspace stays). Uploads are still limited per person. See Privacy.
privacy.store_user_agent true false drops the user agent; the device class is kept.
privacy.redact_query token, access_token, refresh_token, id_token, signature, code, state, password, secret, key, api_key, email Query parameters whose value is replaced with redacted in every URL the recorder sends.

Capture

Key Default Meaning
capture.console ['error'] Console levels recorded; console.error also becomes a console marker. Other levels you add are kept in the recording's events without a marker. [] turns the console off.
capture.errors true Uncaught errors and unhandled rejections as error markers.
capture.server_errors 'class' Exceptions Laravel reports during a recorded request as error markers: class, status, method and path. 'message' adds the exception's message (URLs redacted); it may carry personal data. false turns it off. Needs context.enabled.
capture.livewire true Failed Livewire requests as request markers, wire:navigate page views as navigation markers.
capture.vitals true LCP, INP and CLS as vital markers.
capture.requests 500 Other same-origin requests (fetch, XMLHttpRequest) answered with this status or higher (400 to 599), or not at all, as request markers: method, path, status and duration, never headers or bodies. false turns it off.
capture.rage_clicks true Three or more clicks on one spot within 700 ms as rage-click markers.

Size

Key Default Meaning
size.dedupe_stylesheets true Store stylesheets once per SHA-256 instead of inside every snapshot.
size.dedupe_min_bytes 2048 Smaller stylesheets stay inline.
size.strip_attributes ['wire:*', 'x-*', '@*', ':*', 'ax-load*'] Attribute names dropped before upload; * is a trailing wildcard.
size.keep_attributes ['x-cloak', 'wire:loading*', 'wire:offline*', 'wire:dirty*'] Names (or prefix*) that stay even when a pattern matches: what stylesheets select on.
size.sampling ['mousemove' => 50, 'scroll' => 150, 'media' => 800, 'input' => 'last'] rrweb's sampling options: milliseconds between recorded mouse moves, scrolls and media events; input: 'last' keeps the final value of a burst.

Shared snapshots

Key Default Meaning
snapshots.share_routes [] Route names (Str::is patterns) whose page snapshot is stored once per SHA-256 of its content and shared by every recording that saw the same page. Keyed entries are read like plain ones.
snapshots.share_paths [] Request paths (Str::is patterns), the same way. A page matching either list shares; nothing does by default.
snapshots.volatile_ids ['fi-dropdown-panel-*'] Ids a script makes up on every page load (prefix*). In a shared snapshot they are renamed in page order, together with every aria-controls, aria-labelledby, for and the like that points at them, so two loads of the same page still match.

Share pages that look the same for everyone who opens them: a pricing page, the docs, a sign-in form. A page that shows the person's name, a table of their data or a flash message gets a new snapshot for every difference, so sharing it only saves space when the same person reloads it. See Shared snapshots.

Routes

Key Default Meaning
path session-replay Prefix of every package route.
domain null Restrict the routes to one domain.
ingest.middleware [] Extra middleware for the upload routes. None is needed: identity comes from the signed token.
ingest.throttle 240 Uploads per minute per person (for guests, per rendered page: a value in the signed token, not one the browser picks). null turns it off.
ingest.daily_mb 250 Megabytes one person may upload per day, as sent. A day of steady work in a panel is well under 100 MB. null turns it off.
ingest.guest_daily_mb 2048 Megabytes all guests together may upload per day. Guests cannot be told apart, so this is the cap that keeps a script from filling the disk. null turns it off.
ingest.max_batch_kb 1536 Largest upload accepted, as sent; a shared snapshot gets the same limit.
ingest.max_session_mb 50 A recording stops growing here and is marked truncated.
ingest.max_asset_kb 1536 Largest stylesheet accepted, as sent (compressed).
ingest.token_days 7 Days the token a page was rendered with is accepted. Keep it longer than your full-page cache (see Cached pages). Tokens signed with a key in app.previous_keys are accepted too, so rotating APP_KEY keeps open tabs recording.
ingest.guest_tokens_expire true false: a token that names nobody (no person, no workspace, no impersonator) never expires.

Storage

Key Default Meaning
storage.disk env('SESSION_REPLAY_DISK', 'local') Filesystem disk for chunks, stylesheets and shared snapshots.
storage.directory session-replay Folder on that disk.
storage.connection env('SESSION_REPLAY_DB_CONNECTION') Database connection of the package's tables; null is the default connection.
run_migrations true Run the package migrations with php artisan migrate.
retention.days 30 What session-replay:prune keeps.

Viewer

Key Default Meaning
viewer.enabled true The built-in list and player page. The data routes stay either way.
viewer.middleware ['web', 'auth'] Runs before the viewSessionReplay gate on pages and data routes.
viewer.guard null The guard whose user is handed to the gate.
viewer.per_page 25 Recordings per list page.
viewer.user_label email Attribute shown for a person in the list.

Log context

Key Default Meaning
context.enabled true Write the cookie and add session_replay / session_replay_url to Laravel's Context.
context.cookie session_replay_id The cookie's name.

What is registered in code

Call Purpose
Gate::define('viewSessionReplay', fn ($user, ?ReplaySession $session = null) => ...) Who may watch.
SessionReplay::recordWhen(fn ($user, $request) => ...) Narrow who is recorded.
SessionReplay::userUsing(fn ($request) => ...) The signed-in person, when not the default guard's.
SessionReplay::tenantUsing(fn ($request) => ...) The workspace of a recording.
SessionReplay::impersonatorUsing(fn ($request) => ...) The impersonator's key.
SessionReplay::propertiesUsing(fn ($request) => [...]) Anything else to keep on the recording.
SessionReplay::urlUsing(fn (ReplaySession $session) => ...) Where replays are watched, when not the built-in viewer.
SessionReplay::logUrlUsing(fn (ReplayMarker $marker) => ...) Where a marker's log lines are read: the player's "Open in logs" button. See Error tracking.
SessionReplay::visibleUsing(fn (Builder $query, $viewer) => ...) Narrow the recordings a viewer finds in a list. Runs as a group of its own, so an orWhere() only narrows.

Languages

The viewer and the player ship in English, German, Spanish, Romanian and Russian and follow the app's locale. To change a string or add a language, publish the files:

php artisan vendor:publish --tag=session-replay-translations

They land in lang/vendor/session-replay/{locale}/viewer.php, player.php and recorder.php (the dialog of on_error.ask). The player reads its strings from the <x-session-replay::player> component and the recorder from the page that renders @sessionReplay, so both follow the app's locale.

Commands

Command Purpose
session-replay:install Publish the config, offer to migrate, publish the provider with the gate.
session-replay:prune {--days=} Delete old recordings and unreferenced stylesheets.