Browse documentation
Documentation/Storage & retention

Storage & retention

Choose a database, apply migrations and delete old visitor history.

These concepts apply to your Go application. Matching and private risk evaluation run in your application-owned TypeScript or Elixir engine; this client handles the HTTP connection. See your setup guide. Code labeled TypeScript describes the engine configuration.

Janitor stores a random visitor ID and a short history of browser observations in your database. Use Postgres for Node, Next.js or Elixir, or D1 for a Cloudflare Worker. Cloudflare can also use the shared Postgres adapter.

Each application should have its own database or schema. Sharing these tables makes visitor history available to every application using them.

Apply the migrations

For local Cloudflare development, run this from the example directory:

pnpm exec wrangler d1 migrations apply VISITORS --local

The Node and Next.js examples include a migration command:

pnpm migrate

For an existing application, use your migration runner to apply the SQL files in the D1 or Postgres package, in order. The v0.9.0 release includes all eight:

Migration Creates or changes
0001_visitors.sql Visitor records and browser observations.
0002_identity.sql People, agents, verified keys and delegations.
0003_learning.sql Optional pre-login feedback sessions.
0004_candidate_lookup.sql Indexes for finding plausible previous visitors efficiently.
0005_protection.sql Shared request counters and evaluator budgets.
0006_evidence.sql Verified application events and device associations.
0007_learning_lookup.sql Indexed retrieval of login-confirmed sessions for Jev learning.
0008_api_activity.sql Optional aggregate API counters and private assessment cache.

Creating an optional feature’s tables does not enable that feature. For an existing large Postgres installation, read the index migration instructions before applying lookup indexes to a busy table.

Native Elixir applications use Janitor.Migration.up() for a fresh installation. Existing installations use the upgrade functions in the Phoenix guide.

What the browser tables contain

visitors holds the random ID, creation time and last-seen time. observations holds the normalized browser signals, timestamp and visitor ID. A few columns are indexed so Janitor can find candidates without comparing every visitor:

Field Purpose
platform, browser Find broadly compatible browser environments.
timezone, webgl_renderer Narrow the lookup when these values are available.
signals_json Keep the complete normalized observation, including optional behavior totals.
seen_at Select recent history and enforce retention.

D1 stores JSON as text. Postgres uses JSONB. Both delete a visitor’s observations when the visitor record is erased.

Choose how much history to keep

The defaults are 90 days and at most ten observations per visitor. Matching reads only the latest five retained observations. You can shorten retention:

const visitor = createNodeVisitor({
  db,
  observationRetentionDays: 30,
  maxObservationsPerVisitor: 10,
});

Expired observations stop participating in matching immediately, even if cleanup has not physically deleted them yet. Each saved visit also prunes that visitor’s old history.

Run cleanup from existing maintenance

Janitor does not install a scheduler. Call cleanup from a maintenance task you already run:

const page = await visitor.cleanup({ batchSize: 100 });

// Save this cursor for the next maintenance batch.
const nextVisitorId = page?.nextVisitorId;
// Next call: visitor.cleanup({ batchSize: 100, afterVisitorId: nextVisitorId })

Continue through visitor pages using nextVisitorId, and run further expiration batches while hasMoreExpired is true. This limits work per call. Scale and maintenance covers cursor handling in more detail.

D1 inserts and prunes in one atomic batch. Postgres uses separate committed statements; cleanup or a later visit repairs interrupted pruning. Include database backups and exports in your own retention process.

Delete a visitor

From a server operation your application has authorized:

await visitor.deleteVisitor(visitorId);

Also stop browser collection and clear the visitor cookie. Keep the application’s opt-out active on later visits so a new observation is not immediately collected. See the complete deletion procedure.

Optional data has its own lifecycle

  • Identity directory: subjects and verified keys remain until you remove them. Deleting a subject removes its keys and delegations. Browser history is separate.
  • Learning: pre-login feedback defaults to 30 days, with up to 20 verified sessions per subject. Collection must be enabled and allowed by the chosen collection policy.
  • Application events: default retention is seven days. Verified device links have their own expiry and revocation records. See trusted events and device associations.

cleanup() handles the enabled modules’ expired rows as documented in those guides. Account deletion should also remove your application’s own associations and any copies exported to analytics.

Search documentation

Search setup instructions, examples and the API reference.

Local search. No query leaves your browser.