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.

Apps generated here are unclaimed. Claim the app in the dashboard within 14 days. 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/generate

Jazz Cloud sync URL: https://v2.sync.jazz.tools/

Self-hosted database server

Terminal
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:

OptionPurposeEnvironment variableDefault
<APP_ID> (positional)App namespace identifier (required)--
-p, --port <PORT>Listen port-1625
-d, --data-dir <DATA_DIR>Persistent storage directory-./data
--in-memoryUse in-memory storage instead of files; data is lost when the process exits-off
--jwks-url <JWKS_URL>JWKS endpoint for external JWT validationJAZZ_JWKS_URLunset
--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_KEYunset
--auth-cookie-name <AUTH_COOKIE_NAME>Cookie name to read for browser authentication during WebSocket upgradesJAZZ_AUTH_COOKIE_NAMEunset
--allow-local-first-authAllow local-first auth (Authorization: Bearer <self-signed Jazz JWT>)JAZZ_ALLOW_LOCAL_FIRST_AUTHsee NODE_ENV note below
--backend-secret <BACKEND_SECRET>Enable backend session impersonationJAZZ_BACKEND_SECRETunset
--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_SECRETunset
--upstream-url <UPSTREAM_URL>Run as an edge server connected to the upstream core server. Requires --admin-secret.JAZZ_UPSTREAM_URLunset
--shutdown-timeout-secs <SECONDS>Graceful shutdown network-drain timeout in secondsJAZZ_SHUTDOWN_TIMEOUT_SECS30

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.

main.ts
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();
main.rs
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 a Db that uses backendSecret for sync/auth when this process talks to a Jazz server. Writes from this handle are authored as jazz:system unless you add explicit attribution.
  • await context.forRequest(req) returns a Db that 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 as principalId.
  • context.withAttributionForSession(session) and await 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-level Db using the context's configured runtime/auth. On server-connected backends, prefer context.asBackend() or await 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.

request-context.ts
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);
  }
}
request-context.rs
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())
}

On this page