privacy-first session replay. no analytics, no accounts, no cloud.
tinyreplay is a small, self-hosted session replay tool. It records browser sessions with a browser SDK, stores replay batches in local SQLite, and shows them in a local dashboard.
It is for indie developers and small teams that want to inspect what happened in a browser session without adopting a full analytics platform.
tinyreplay is intentionally minimal. It does not provide funnels, heatmaps, feature flags, user profiles, accounts, billing, mobile SDKs, hosted storage, SSO, distributed ingestion, or compliance guarantees.
tinyreplay is pre-1.0. The core local session replay flow works, but APIs,
storage format, configuration, and dashboard behavior may change before v1.
The v0.1.0 release should be treated as an early self-hosted release.
Documentation: https://tinyreplay.hane.studio.
From a checkout:
npm install npm run build npm run start
Open http://localhost:3000.
For development:
npm install npm run dev npm run dev:docs
The SDK runs in watch mode, the dashboard runs at http://localhost:3000, and the docs app runs at http://localhost:3001.
Run the published image (no checkout, no build):
docker run -p 3000:3000 -v "$(pwd)/data:/app/data" ghcr.io/kzekiue/tinyreplay
Open http://localhost:3000. Session data persists in ./data/tinyreplay.db on
the host. The latest tag tracks main; tagged releases publish a versioned
image (for example ghcr.io/kzekiue/tinyreplay:0.1.0).
To build from source instead (for development or local changes):
docker compose up --build
The compose file mounts ./data to /app/data. The equivalent plain Docker
commands:
docker build -t tinyreplay . docker run --rm -p 3000:3000 -v "$(pwd)/data:/app/data" tinyreplay
Useful endpoints:
| Purpose | URL |
|---|---|
| Dashboard | http://localhost:3000 |
| SDK bundle | http://localhost:3000/sdk/tinyreplay.umd.js |
| Ingest API | POST http://localhost:3000/api/ingest |
| Health probe | GET http://localhost:3000/api/health |
Use the bundle served by your tinyreplay server:
<script src="http://localhost:3000/sdk/tinyreplay.umd.js"></script>
<script> TinyReplay.init({ endpoint: 'http://localhost:3000', projectId: 'my-project', })
</script>
Or install the package after it is published:
npm install @tinyreplay/sdk
import { TinyReplay } from '@tinyreplay/sdk' TinyReplay.init({ endpoint: 'https://your-tinyreplay-instance.example', projectId: 'my-project',
})
TinyReplay.init() is a no-op during server-side rendering and ignores repeated
calls while a recorder is active. TinyReplay.stop() stops recording and flushes
the current buffer.
The dependency-free example in examples/vanilla records clicks, input, scrolling, route changes, masking, and ignored elements.
npm run build npm run start python3 -m http.server 4173 -d examples/vanilla
Open http://localhost:4173, interact with the page, then open http://localhost:3000 and select the session.
tinyreplay stores data in SQLite through better-sqlite3.
The server creates one database file:
DATA_DIR/tinyreplay.db
Local default:
./data/tinyreplay.db
Docker default:
/app/data/tinyreplay.db
Replay event batches are stored as JSON in the events table and connected to
rows in the sessions table. Retention is off by default. Set
RETENTION_DAYS to delete older sessions during the hourly sweep.
Masking happens in the browser before replay events are sent to the server.
Defaults:
| Setting | Behavior |
|---|---|
maskAllInputs |
Enabled by default for inputs, textareas, and selects. |
data-tr-mask |
Masks text inside the marked element and its children. |
data-tr-unmask |
Allows a specific non-sensitive input to be recorded. |
data-tr-ignore |
Blocks an element and its children from recording. |
The SDK records console messages and error messages for debugging. It records
network metadata only: method, URL, status, and duration. It does not read
request bodies, response bodies, request headers, response headers, cookies, or
browser storage, except for its own session id in sessionStorage.
Do not log secrets from the recorded app. Console capture records argument text.
Copy .env.example to .env for local overrides.
| Variable | Default | Purpose |
|---|---|---|
PORT |
3000 |
Server port. |
DATA_DIR |
./data locally, /app/data in Docker |
SQLite directory. |
ALLOWED_ORIGINS |
* |
Comma-separated CORS allowlist for ingestion. |
DASHBOARD_PASSWORD |
unset | Enables HTTP Basic auth for dashboard pages. |
INGEST_TOKEN |
unset | Requires a token for POST /api/ingest. |
RETENTION_DAYS |
unset | Deletes sessions older than this many days. |
RATE_LIMIT_PER_MIN |
100 |
Ingest requests per minute per IP. |
MAX_PAYLOAD_BYTES |
5000000 |
Maximum ingest request body size. |
Set DASHBOARD_PASSWORD, INGEST_TOKEN, and a narrow ALLOWED_ORIGINS value
before exposing an instance outside a trusted network. Put TLS and stronger
access control in front of tinyreplay with a reverse proxy.
npm install npm run dev npm run check
Common commands:
| Command | Purpose |
|---|---|
npm run check |
Release check: format, lint, architecture, typecheck, forbidden content, secret scan, build, tests. |
npm run build |
Build the SDK, server, and docs app. |
npm run dev:docs |
Run the docs app on port 3001. |
npm run test |
Run SDK and server tests. |
npm run lint |
Run ESLint. |
npm run typecheck |
Run TypeScript checks. |
npm run clean |
Remove build and coverage output. |
See CONTRIBUTING.md for contribution rules and ARCHITECTURE.md for the current module boundaries.
Report security issues privately. See SECURITY.md.