How Jazz Works / FAQ

Learn the mental model behind Jazz, how backends and clients talk to it, and find answers to common questions.
Note

Preview: this page is being rewritten. The bullets below are the outline. The detailed sync model moved to How Sync Works.

SQLite and S3 underneath

  • Each Jazz server stores its data in plain SQLite: tables, rows, indexes
  • Large values (files, blobs, streams) live in S3-compatible object storage
  • Works with local disk and compatible cloud storage

What Jazz adds on top

  • A typed schema and generated migrations
  • Permission policies checked on every read and write
  • Live queries: results stay up to date as rows change
  • Edit metadata ($createdBy, $updatedAt, …) and row history
  • Merging writes by default, serializable transactions when you need them
  • Drafts and branches, mergeable text
  • Mutable blobs and streams on S3: edit, append to and stream large values like any other column, with the same permissions, live updates and history as rows

How a backend talks to Jazz

  • Your main backend runs createJazz in its own process, with the schema and permissions it imports
  • Backend authority (jazz.db) sees everything, for admin and system work
  • jazz.forReqUser(request) gives a user-scoped view: that user's policies and authorship apply
  • Backend clients (other trusted machines) reach Jazz through the main backend
  • Writes are confirmed by the server before they count as durable

How frontends talk to Jazz

  • The thin client sends queries and writes to the server over the same API, with the same auth and policies
  • No local database in the browser; small bundle

Sync and local-first

  • A local-first client keeps a local SQLite replica of the rows its queries need
  • Reads are instant and work offline; writes apply locally and sync later
  • The server stays the authority: it checks policies and settles conflicts
  • Details: How Sync Works

Sharding

  • One server is enough for many apps; scale out by adding shards
  • A stateless gateway routes queries and writes to the right shard
  • Cross-shard transactions use two-phase commit
  • Per-query consistency: each subscription sees a settled snapshot

FAQ

Reset permanently deletes unsynced writes and local-only data in the selected namespace. Before resetting a cache you intend to resync, confirm that the required data is already on the configured server and that the same account can still access it. Reconnect to that server with the same account after resetting; app queries and subscriptions can then fetch the data permitted by current access rules. Local durability and ordinary shutdown do not prove server sync completed.

In browser apps using the React, Vue, Svelte, or Solid Jazz clients, keep the client open and run this in the devtools console:

await window.__jazz.clearStorage();

If the page has more than one live Jazz storage context, inspect the available namespaces and then choose one explicitly:

window.__jazz.listLiveStorageNamespaces();
await window.__jazz.clearStorage("my-app::alice");

The helper requires a live registered client on this page. Do not shut it down before calling the helper. If the client never finishes opening or no live namespace is listed, this helper cannot perform the reset.

If you're working directly with a Db handle instead of the window helper, await db.deleteClientStorage() is the underlying API. See Resetting browser storage for how this differs from logout and local-first identity storage.

  • Browser persistent storage only.
  • If exactly one live namespace exists, clearStorage() uses it automatically.
  • If multiple live namespaces exist, Jazz throws and lists the available namespaces until you choose one.
  • Can be initiated from either leader or follower tabs; Jazz coordinates the reset across tabs for that namespace.
  • Deletes only the selected IndexedDB namespace. Preserve localStorage, sessionStorage, cookies, and other databases; default local-first credentials are stored outside this data database. Apps with custom credential stores must check those separately.
  • Reopens a clean worker/runtime so the same live client remains usable after the wipe.

This is useful when iterating on your schema during development. window.__jazz is registered by every browser client, production builds included: it resets this browser's own storage and closes this page's own clients, nothing more.

useAll and QuerySubscription return undefined until the first response arrives from the requested tier. After that, the value is an array — empty ([]) if no rows match, or populated. See The loading state for details.

Yes. Pass serverUrl: null. Jazz will not try to sync: browser clients keep their data in local persistent storage, while other runtimes use their configured local driver.

Yes. When a user signs up with an external provider, their identity carries over. See Signing up with BetterAuth.

Keep the result of the write and call wait() when your app needs to know whether the server accepted it. A rejected write throws PersistedWriteRejectedError, which provides the server's code, reason, and transactionId.

Use db.onMutationError() as a fallback for rejected writes that do not have an active wait() call. The callback receives the rejection code, reason, and local transaction record. See Writing data for an example and Declarative Permissions for how the server decides whether to accept a write.

It's fast, embeddable, and simple to back up and inspect.