Current section

Files

Jump to
Raw

README.md

# PhoenixReplay πŸ“Ή

[![Hex.pm](https://img.shields.io/hexpm/v/phoenix_replay.svg)](https://hex.pm/packages/phoenix_replay) [![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/phoenix_replay)

Session recording and replay for Phoenix LiveView. PhoenixReplay records what your LiveViews and LiveComponents did β€” events, navigation and assigns β€” and replays a session by re-rendering your own templates with the recorded assigns. No DOM snapshots: assigns and events are recorded on the server, and an optional browser recorder adds the pointer, touches, scrolling and form input.

![PhoenixReplay replaying a phone session: a swipe scrolling the replayed page, the mark a tap reached, its queries, and errors on the timeline](https://raw.githubusercontent.com/elixir-vibe/phoenix_replay/master/guides/images/player.jpg)

```bash
mix igniter.install phoenix_replay
```

The installer mounts the dashboard at `/dev/replay` in development. Record a live session:

```elixir
live_session :default, on_mount: [PhoenixReplay.Recorder] do
  live "/checkout", CheckoutLive
end
```

Use your app, then open `/dev/replay`. To use the dashboard in production, mount it behind your own authentication:

```elixir
scope "/admin" do
  pipe_through [:browser, :require_admin]
  phoenix_replay "/replay"
end
```

## Why PhoenixReplay

Browser session recorders capture the DOM and ship every keystroke from the client. A LiveView already knows its state: its template is a function of its assigns. PhoenixReplay records assigns on the server, where they are produced, and replays them through the same template β€” so a replay shows exactly what the server rendered, and a 30-second form session takes a few kilobytes.

See [Why PhoenixReplay](https://hexdocs.pm/phoenix_replay/why-phoenix-replay.html) and [How It Works](https://hexdocs.pm/phoenix_replay/how-it-works.html).

## Recording

Every connected LiveView in the live session is recorded, along with its LiveComponents β€” without changes to your views or components. Sessions without user interaction are discarded. Sample a share of sessions or tune limits per live session:

```elixir
live_session :checkout,
  on_mount: [{PhoenixReplay.Recorder, sample_rate: 0.1, max_events: 2_000}] do
  live "/checkout", CheckoutLive
end
```

In the browser, `replayParams` and `replayMetadata` send the viewport, so a phone session replays at phone size, and link sessions across LiveViews into one journey. `replayRecorder` records what only the browser sees: form input, and with `pointer: true`, the pointer, touches and scrolling. The installer adds all three:

```javascript
import { replayParams, replayMetadata, replayRecorder } from "phoenix_replay"

const liveSocket = new LiveSocket("/live", Socket, {
  params: () => ({_csrf_token: csrfToken, ...replayParams()}),
  metadata: replayMetadata
})

liveSocket.connect()
replayRecorder(liveSocket)
```

Mark the moments that matter, such as a signup or a checkout, by collecting a telemetry event with `mark: true`; see [Marking moments](https://hexdocs.pm/phoenix_replay/telemetry-and-logs.html#marking-moments).

See the [Recording guide](https://hexdocs.pm/phoenix_replay/recording.html) and [LiveComponents guide](https://hexdocs.pm/phoenix_replay/live-components.html).

## Telemetry and logs

See the queries, HTTP calls and log messages behind each click, listed under the event that caused them β€” including those from `start_async` and `assign_async` tasks:

```elixir
config :phoenix_replay,
  collect: [{PhoenixReplay.Collector.Ecto, repo: MyApp.Repo}, PhoenixReplay.Collector.Finch],
  logs: [level: :info]
```

Then record every session and keep the ones that matter β€” every session with an error or a slow query, and a sample of the rest:

```elixir
config :phoenix_replay,
  keep: [rate: 0.05, errors: true, slower_than: 1_000],
  max_memory: 256 * 1024 * 1024
```

Any telemetry event can be collected, and collectors are a small behaviour. See the [Telemetry and Logs guide](https://hexdocs.pm/phoenix_replay/telemetry-and-logs.html).

## Privacy

Values of keys such as `password`, `token` and `secret` are replaced with `"[FILTERED]"` before anything is stored, through structs, changesets and forms. Plug in your own sanitizer to drop more:

```elixir
defmodule MyApp.ReplaySanitizer do
  @behaviour PhoenixReplay.Sanitizer

  @impl true
  def sanitize_assigns(assigns) do
    assigns |> Map.drop([:current_user]) |> PhoenixReplay.Sanitizer.Default.sanitize_assigns()
  end

  @impl true
  defdelegate sanitize_params(params), to: PhoenixReplay.Sanitizer.Default
end
```

Values that only detection can find, such as an email address typed into a form or a card number in a log message, are masked when the session is saved, not while users wait. Use your own patterns or [Obscura](https://hexdocs.pm/obscura), an optional dependency:

```elixir
config :phoenix_replay, redact: {PhoenixReplay.Redactor.Obscura, []}
```

See the [Privacy and Security guide](https://hexdocs.pm/phoenix_replay/privacy-and-security.html).

## Dashboard

Filter sessions by device, browser, where they came from, marks, errors and time; every filter is a shareable URL. The player shows each event with the queries, logs and assigns behind it, and draws the user's pointer over the replay. Restrict who sees what with an authorization module:

```elixir
phoenix_replay "/replay",
  on_mount: [{MyAppWeb.UserAuth, :ensure_admin}],
  authorize: MyApp.ReplayAuthorization
```

![The recording list: sessions of the last day by device, browser and where they came from, with the marks they reached and their errors](https://raw.githubusercontent.com/elixir-vibe/phoenix_replay/master/guides/images/recordings.jpg)

The dashboard needs nothing from your asset pipeline. See the [Dashboard guide](https://hexdocs.pm/phoenix_replay/dashboard.html).

Export a recording as an MP4, from the player or with `mix phoenix_replay.export <id>`. It needs `ffmpeg` and the optional [`playwright_ex`](https://hexdocs.pm/playwright_ex) and [`muontrap`](https://hexdocs.pm/muontrap) dependencies; see [Exporting videos](https://hexdocs.pm/phoenix_replay/dashboard.html#exporting-videos).

## For coding agents

`PhoenixReplay.Trace` reads recordings as plain data from IEx, tests and scripts, and `mix phoenix_replay.list` and `mix phoenix_replay.show` do the same from a shell.

The package ships two agent skills, `phoenix-replay-setup` and `phoenix-replay-debugging`, as `SKILL.md` files in `deps/phoenix_replay/skills/`. Point your agent at them, or copy them to wherever it reads skills from.

## Storage

Recordings are kept in compressed files by default, or in your database:

```elixir
config :phoenix_replay,
  storage: {PhoenixReplay.Storage.Ecto, repo: MyApp.Repo},
  retention: [max_age: :timer.hours(24 * 7), max_count: 1_000]
```

See the [Storage guide](https://hexdocs.pm/phoenix_replay/storage.html) and the [Configuration cheatsheet](https://hexdocs.pm/phoenix_replay/configuration.html).

## Example app

A Phoenix app with recording and the dashboard wired up lives in `example/`:

```bash
cd example
mix setup
mix phx.server
```

## Documentation

Full documentation, guides and cheatsheets are available on [HexDocs](https://hexdocs.pm/phoenix_replay).

## Development

```bash
mix deps.get
npm ci
npx playwright install chromium
mix assets.build
mix ci
```

`mix ci` runs every check, including the video export tests, which film real replays with Chromium and need `ffmpeg`. A plain `mix test` leaves those out; set `PHOENIX_REPLAY_EXPORT_TESTS=1` to run them with it.

The Ecto storage tests run on SQLite, on DuckDB through [QuackDB](https://hexdocs.pm/quackdb) (Elixir 1.19+; install its binary once with `MIX_ENV=test mix quackdb.install`), and on PostgreSQL when `PHOENIX_REPLAY_POSTGRES_URL` names a database.

## Part of Elixir Vibe

PhoenixReplay is one building block of a larger stack β€” tools that make AI-generated software checkable: structural search, dependence analysis, duplication and slop detection, session replay, and ecosystem-wide code search. See the [Elixir Vibe](https://github.com/elixir-vibe) organization for the rest, and [Building Blocks for the Future Web](https://github.com/elixir-vibe/building-blocks) for the thesis, architecture, and roadmap that tie them together.

## License

MIT Β© 2026 Danila Poyarkov