Per-user databases
Give every user of your app their own isolated SQLite database. One call
maps a user identifier to a database (created the first time you ask for
it), and mints a token scoped to only that database. Because the database
boundary is the tenant boundary, your queries stay single-tenant:
SELECT * FROM orders — no WHERE tenant_id, no row-level rules, no
cross-tenant fan-out. The isolation lives in the topology, not the query.
One call
Section titled “One call”import { PerSQL } from "@persql/sdk";
const persql = new PerSQL({ token: process.env.PERSQL_TOKEN });
const { token, database, namespaceSlug } = await persql.users.provision( "user_42", { template: "app-template" });
// Query that user's database — ordinary SQL, only their rows exist.const db = persql.database(namespaceSlug, database.slug);await db.query("SELECT * FROM orders");provision is idempotent on the subject: the same identifier always
lands on the same database, so it’s safe to call on every sign-in. The
first call creates the database; later calls reuse it. Each call returns
a fresh token.
POST /v1/usersAuthorization: Bearer psql_live_...
{ "subject": "user_42", "template": "app-template", "role": "write", "expiresIn": 3600}Returns the user’s database, a token scoped to it, and created (whether
this call provisioned the database or reused an existing one).
| field | meaning |
|---|---|
subject | Your app’s identifier for the user. Any string up to 256 chars — a UUID, an email, an external id. Hashed into a stable database slug. |
template | Optional. A database in the same workspace whose schema (tables, indexes, triggers — never its rows) seeds the new database, so it’s query-ready on the first request. Omit for an empty database. |
role | write (default) or read. Governs what the minted token may do. |
expiresIn | Token lifetime in seconds, 60–86400. Defaults to one hour. |
Requires an unscoped admin token. Provisioning is a privileged operation, so a per-user token can’t provision more users.
The token
Section titled “The token”The minted token is scoped to exactly one database and runs the full
/v1 SQL surface against it. Two ways to use it:
- Server-side — keep using your own workspace token; you already have access to every user’s database. The per-user token is optional here.
- Client-side — hand the per-user token to that user’s browser or app. It reaches only their database, so an untrusted client can query directly without a proxy. A leaked token’s blast radius is one user.
Usage bills to your workspace — your users never see PerSQL. The token is short-lived by default and revocable; mint a fresh one each time the user authenticates.
Schema, migrations, and limits
Section titled “Schema, migrations, and limits”The per-user token deliberately cannot run DDL. Your app owns the
schema: seed it with template, and roll out changes with
migrations. The token reads and writes rows; it
never alters structure.
A populated database is metered like any other (storage, requests, rows); a fresh schema-only database is nearly free until it’s used. Auto-expire disposable per-user databases by setting a TTL when you provision, or delete them when a user leaves.
Related
Section titled “Related”- Sign in with PerSQL — let an app provision a database in the end user’s own workspace instead of yours.
- Branches — schema-only databases keyed by an external ref, for per-PR previews.
- Forking — copy a database’s schema (and optionally its rows) into a new one.