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 your schema and 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, 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.
For forwarded catalogue responses, an edge server uses a 67,108,864-byte (64 MiB)
default limit for GET /schemas. You can override it with
JAZZ_CATALOGUE_LIST_RESPONSE_LIMIT_BYTES, using a positive decimal usize
value; startup rejects zero, malformed, overflowing, or otherwise
allocation-invalid values. This setting is read only when --upstream-url is
configured, affects only forwarded GET /schemas, and requires a restart.
Every other forwarded catalogue response has a fixed 8 MiB limit. Invalid values
are ignored when starting a direct core server without an upstream URL, so this
edge-only setting does not change core startup or local catalogue behaviour.
The edge forwarding client is HTTP/1-only and does not follow redirects. Its
transport safety is exercised with a raw HTTP/1.1 authority (including tiny
body writes) and an HTTP/2 connection-preface rejection; these are the local
equivalent of asserting HTTP/1 ALPN selection without requiring test
certificates.
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 obtain an admitted account handle from its JWT. Pass that handle to await client.forAccount(account);
forRequest accepts bearer headers and does not parse application session cookies.
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
A TypeScript backend creates one createJazzSession owner from jazz-tools/backend, with appId, app, permissions, driver, and serverUrl configured once. Select backend authority with initial: { backendSecret } or later with await session.becomeBackend({ backendSecret }). For authentication and account linking, see Authentication.
Backend admission requires a reachable Jazz server: the Node host sends POST /apps/{app}/backend/admit with X-Jazz-Backend-Secret, and proceeds only after that server validates it. An edge validates its backend transport credential; account registration and linking still use the core registry. This also applies to a memory driver; backend initialization is not an offline privilege grant. Browser and React Native hosts reject backend selection.
const session = await createJazzSession({
appId,
app: schemaApp,
permissions,
driver: { type: "persistent", dataPath: dbPath },
serverUrl,
initial: { backendSecret },
jwksUrl,
jwtPublicKey,
allowLocalFirstAuth,
env: "dev",
});
const snapshot = session.getSnapshot();
if (snapshot.status !== "ready" || !snapshot.client) {
throw snapshot.error ?? new Error("Backend session is not ready");
}
const client = snapshot.client;
const db = client.db;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,
storage_factory: Some(std::sync::Arc::new(
jazz_storage_rocksdb::RocksDbStorageFactory,
)),
jwt_token: None,
backend_secret: None,
admin_secret: None,
};Backend identity pattern
The ready snapshot exposes client.db for backend-owned work. Backend accounts use the reserved nil account UUID and identity { issuer: "urn:jazz:system", subject: nodeUUID }, where nodeUUID is the actual originating native node. Copying that identity does not grant authority. The secret stays in private handle material and is never serialized into snapshots or account preferences.
client.dbhas backend permissions and records SYSTEM node provenance.await client.forRequest(req)verifies the caller's bearer and active core account assignment, then returns an immutable database scope with that user's policies and authorship.await client.forAccount(account)performs the same verification from an opaque admitted user account handle.await client.withAttribution(account)andawait client.withAttributionForRequest(req)keep backend permissions while recording the verified user's authorship. Use them after the application authorizes the operation.
Request scopes do not change the shared session's selected account, so concurrent requests retain separate policy contexts. Do not switch the shared owner to serve individual requests. When the application deliberately transitions the owner from backend to a user account, the old client shuts down and the replacement opens an ordinary native runtime without backend privilege. The shared lifecycle handles detach, sync, shutdown, retry, and logout; logout invalidates every issued handle. Backend selection is ephemeral across process restarts, while retained local-first signing roots remain available.
forRequest reads standard HTTP headers from Express, Hono, Fastify, or a Web Fetch API Request. Configure jwksUrl or jwtPublicKey on createJazzSession(...) for external IdP tokens, but not both. Without either, it accepts only Jazz local-first tokens; allowLocalFirstAuth: false disables those too. Application cookies must first be resolved to an admitted account by your auth integration.
The TypeScript backend's jwksUrl must use HTTPS, except for development HTTP
with a WHATWG-canonical hostname of localhost, [::1], or an IPv4 address in
127.0.0.0/8. For example, http://localhost:3000/api/auth/jwks is allowed;
http://localhost.:3000/api/auth/jwks (the trailing-dot spelling) and
http://keys.example/api/auth/jwks are rejected before fetching keys.
Every redirect is rejected, including redirects to HTTPS. Use the provider's
final JWKS URL directly. This policy applies to TypeScript request authentication,
not the separate Rust server's --jwks-url verifier.
Attribution without impersonation
Use await client.withAttribution(account) or await client.withAttributionForRequest(req) to retain backend access while recording verified user provenance. Raw session objects and issuer/subject strings are not public authority inputs. See Sessions > Attribution without impersonation.
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 client.forRequest(req);
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 = Query::from("todos");
let rows = user_client
.query(query, None)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(rows.len())
}