Server Setup
Hosted and self-hosted database server configuration, app provisioning, and backend context setup.
Hosted database server
Your app ID namespaces your data for storage and sync. Click below to generate one on the Jazz hosted cloud — you'll also get the secrets you need to deploy permissions later. Generated apps are unclaimed until you claim them in the dashboard, and unclaimed apps are automatically deleted after 14 days.
Or from the command line (AI agents: use this to provision your own app):
curl -X POST https://v2.dashboard.jazz.tools/api/apps/generateJazz Cloud sync URL: https://v2.sync.jazz.tools/
Self-hosted database server
export JAZZ_APP_ID="replace-with-your-app-id"
export JAZZ_ADMIN_SECRET="replace-with-admin-secret"
npx jazz-tools@alpha server "$JAZZ_APP_ID" \
--port 1625 \
--data-dir ./data \
--admin-secret "$JAZZ_ADMIN_SECRET"jazz-tools@alpha server <APP_ID> currently supports:
| Option | Purpose | Environment variable | Default |
|---|---|---|---|
<APP_ID> (positional) | App namespace identifier (required) | - | - |
-p, --port <PORT> | Listen port | - | 1625 |
-d, --data-dir <DATA_DIR> | Persistent storage directory | - | ./data |
--in-memory | Use in-memory storage instead of files; data is lost when the process exits | - | off |
--jwks-url <JWKS_URL> | JWKS endpoint for external JWT validation | JAZZ_JWKS_URL | unset |
--jwt-public-key <JWT_PUBLIC_KEY> | Single JWK JSON object or PEM public key for external JWT validation. Accepts inline contents or a path to a key file. | JAZZ_JWT_PUBLIC_KEY | unset |
--auth-cookie-name <AUTH_COOKIE_NAME> | Cookie name to read for browser authentication during WebSocket upgrades | JAZZ_AUTH_COOKIE_NAME | unset |
--allow-local-first-auth | Allow local-first auth (Authorization: Bearer <self-signed Jazz JWT>) | JAZZ_ALLOW_LOCAL_FIRST_AUTH | see NODE_ENV note below |
--backend-secret <BACKEND_SECRET> | Enable backend session impersonation | JAZZ_BACKEND_SECRET | unset |
--admin-secret <ADMIN_SECRET> | Required for deploy, migrations push, schema catalogue reads, and edge upstream sync. In development mode, structural schema auto-sync works without it. | JAZZ_ADMIN_SECRET | unset |
--upstream-url <UPSTREAM_URL> | Run as an edge server connected to the upstream core server. Requires --admin-secret. | JAZZ_UPSTREAM_URL | unset |
--shutdown-timeout-secs <SECONDS> | Graceful shutdown network-drain timeout in seconds | JAZZ_SHUTDOWN_TIMEOUT_SECS | 30 |
Local-first auth is enabled by default in development and requires --allow-local-first-auth in production. External JWT auth requires either --jwks-url or --jwt-public-key, but not both.
Edge mode is enabled by --upstream-url; when set, provide --admin-secret or JAZZ_ADMIN_SECRET. The edge uses that admin secret for its upstream WebSocket connection.
Cookie-based WebSocket auth is enabled with --auth-cookie-name or JAZZ_AUTH_COOKIE_NAME. When no
explicit auth credential is supplied, the sync server reads that named cookie and validates the JWT
it contains. If your app uses a separate application session cookie, resolve it in your own app server
and call context.forSession(session), or pass a mirrored cookieSession to the browser client while
the application cookie remains the transport credential.
If you prefer to start the database server programmatically, you can use startLocalJazzServer from jazz-tools/dev.
It expects similar arguments as the CLI.
Backend context setup
Every backend needs an app ID, typed schema, and usually a permissions bundle. If the backend syncs through a Jazz server, also pass serverUrl and the relevant secrets. For auth modes and upgrade/link flow, see Authentication. For frontend/browser context creation, see Client Setup.
const context = createJazzContext({
appId,
app: schemaApp,
permissions,
driver: { type: "persistent", dataPath: dbPath },
serverUrl,
backendSecret,
adminSecret,
jwksUrl,
jwtPublicKey,
allowLocalFirstAuth,
env: "dev",
userBranch: "main",
});
const db = context.asBackend();let context = AppContext {
app_id: AppId::from_name(&app_id),
client_id: None,
schema,
server_url,
data_dir: PathBuf::from(data_dir),
storage: ClientStorage::Persistent,
jwt_token: None,
backend_secret: None,
admin_secret: None,
sync_tracer: None,
};Backend identity pattern
On the client side, queries automatically run as the logged-in user. On a server-connected backend, create the context once at startup, then choose whether work should run as the backend itself or as the caller.
context.asBackend()returns aDbthat usesbackendSecretfor sync/auth when this process talks to a Jazz server. Writes from this handle are authored asjazz:systemunless you add explicit attribution.await context.forRequest(req)returns aDbthat runs queries as the authenticated user who made the request, so permission policies apply to them rather than the process itself. Writes are also attributed to that user.context.forSession(session)is the same idea when you already have a resolved Jazz session.context.withAttribution(principalId)keeps backend permissions, but stamps new writes and updates asprincipalId.context.withAttributionForSession(session)andawait context.withAttributionForRequest(req)derive that authorship from a resolved session or authenticated request without impersonating the user for permission checks.context.db()gives you a high-levelDbusing the context's configured runtime/auth. On server-connected backends, prefercontext.asBackend()orawait context.forRequest(req); on embedded or local-only setups,context.db()is the right choice.
In TypeScript backends, create the context once with both app and permissions, then use context.asBackend() for server-owned work and await context.forRequest(req) when you need a bearer-JWT-scoped high-level Db.
forRequest reads authentication from standard HTTP headers, so req can be any object that exposes them: Express, Hono, Fastify, or a Web Fetch API Request all work. Configure jwksUrl or jwtPublicKey on createJazzContext(...) if those bearer tokens come from an external IdP, but do not set both at once. Without either one, backend forRequest() only accepts Jazz self-signed tokens; set allowLocalFirstAuth: false to disable those too.
For cookie-based auth on your own backend, resolve the cookie with your framework or auth provider
first, then call context.forSession(session) once you have a Jazz Session. forRequest(...)
does not parse application cookies by itself.
For embedded or local-only runtime setups where your backend is not connected to a server, use
context.db() to get an unscoped, unauthenticated handle. This allows read/write access to the
database directly, without permissions being evaluated.
Attribution without impersonation
Use withAttribution(...), withAttributionForSession(...), or await context.withAttributionForRequest(...) when backend code should keep backend-level access but stamp row edit metadata with a user's identity. See Sessions > Attribution without impersonation for details and examples.
Per-request user-scoped client
Pass req to run queries as the authenticated user, with all permission policies applied.
export async function listTodosForRequester(req: Request, res: Response): Promise<void> {
try {
const requester = await context.forRequest(req, schemaApp);
const rows = await requester.all(schemaApp.todos.where({ done: true }));
res.json(rows);
} catch {
sendQueryError(res);
}
}pub async fn list_todos_for_request(
headers: &HeaderMap,
client: &JazzClient,
) -> Result<usize, StatusCode> {
let user_client = client.for_session(requester_session_from_headers(headers)?);
let query = QueryBuilder::new("todos").build();
let rows = user_client
.query(query, None)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(rows.len())
}