===PAGE:api-reference===
TITLE:api-reference
DESCRIPTION:title: "API Reference", description: "API reference for Jazz CoValues and shared properties and methods across different CoValue types. " }; Understanding how to work with CoValues is critical to building apps with Jazz. This reference guide is intended to help you get up and running by quickly demonstrating the most common use cases.
title: "API Reference",
description: "API reference for Jazz CoValues and shared properties and methods across different CoValue types."
};
# CoValues API Reference
Understanding how to work with CoValues is critical to building apps with Jazz. This reference guide is intended to help you get up and running by quickly demonstrating the most common use cases. For more in depth detail, you should review the linked dedicated pages.
If you have any questions, we'd be happy to chat on our [Discord server](https://discord.gg/utDMjHYg42)!
TypeScript Type
Corresponding CoValue
Usage
object
CoMap
Key-value stores with pre-defined keys (struct-like)
Record<string, T>
CoRecord
Key-value stores with arbitrary string keys (dict-like)
T[]
CoList
Lists
T[] (append-only)
CoFeed
Session-based append-only lists
string
CoPlainText/CoRichText
Collaborative text
Blob | File
FileStream
Files
Blob | File (image)
ImageDefinition
Images
number[] | Float32Array
CoVector
Embeddings
T | U (discriminated)
DiscriminatedUnion
Lists of different types of items
## Defining Schemas
CoValues are defined using schemas which combine CoValue types with Zod schemas. You can find out more about schemas in the [schemas](/core-concepts/covalues/overview#start-your-app-with-a-schema) section of the overview guide.
schema.ts
```ts index.ts#DefiningSchemas
```
You can use the following Zod types to describe primitive data types:
Type
Usage
Comment
z.string()
string
For simple strings which don't need character-level collaboration
z.number()
number
z.boolean()
boolean
z.date()
Date
z.literal()
literal
For enums — pass possible values as an array
z.object()
object
An immutable, **non-collaborative** object
z.tuple()
tuple
An immutable, **non-collaborative** array
z.optional(schema)
optional
Pass a Zod schema for an optional property with that schema type
You can also use the `.optional()` method on both CoValue and Zod schemas to mark them as optional.
There are three additional purpose-specific variants of the `CoMap` type you are likely to need while building Jazz applications.
- `co.account()` — a Jazz account
- `co.profile()` — a user profile
- `co.group()` — a group of users
## Creating CoValues
### Explicit Creation
Once you have a schema, you can create new CoValue instances using that schema using the `.create()` static method. You should pass an initial value as the first argument to this method.
CoValue Type
Example
CoMap
Task.create({ task: "Check out Jazz", completed: false })
createImage(myFile) *note that [using the helper](/docs/core-concepts/covalues/imagedef#creating-images) is the preferred way to create an ImageDefinition*
CoVector
Embedding.create([0.1, 0.2, ...])
DiscriminatedUnion
MyThis.create({ type: "this", ... }) *note that you can only instantiate one of the schemas in the union*
`FileStream` CoValues *can* be created using the `.create()` method. This will create an empty `FileStream` for you to push chunks into (useful for advanced streaming cases). However, in many cases, using `.createFromBlob(blobOrFile)` to create a `FileStream` directly from a `File` or `Blob` will be more convenient.
### Inline Creation
Where a schema has references, you can create nested CoValues one by one and attach them, but Jazz also allows you to create them inline by specifying their initial values.
```ts index.ts#InlineCreation
```
### Permissions
When creating any CoValue, the `.create` method accepts an optional options object as the second argument, which allows you to specify the `owner` of the CoValue.
```ts index.ts#Permissions
```
If you don't pass an `options` object, or if `owner` is omitted, Jazz will check if there are [permissions configured at a schema level](/docs/permissions-and-sharing/overview#defining-permissions-at-the-schema-level).
If no permissions are set at a schema level when creating CoValues inline, a new group will be created extending the **containing CoValue's ownership group**. In the "Inline Creation" example above, a new group would be created for each task, each extending the ownership group of the `taskList` CoList.
It is a good idea to read through the [permissions](/docs/permissions-and-sharing/overview) section to understand how to manage permissions on CoValues, as unlike in other databases, permissions are fundamental to how Jazz works at a low level, rather than a supporting feature.
## Loading and Reading CoValues
In order to read data, you need to [load a CoValue instance](/docs/core-concepts/subscription-and-loading). There are several ways to do this. We recommend using Jazz with a framework, as this allows you to create reactive subscriptions to CoValues easily, but it is also possible to load a CoValue instance using the `.load()` static method on the schema.
Once you have a loaded CoValue instance, you can normally read it similarly to the corresponding TypeScript type.
### CoMap (and the CoRecord sub-type)
Behaves like a TypeScript object **when reading**.
```ts index.ts#CoMapReading
```
[Read more →](/docs/core-concepts/covalues/comaps)
### CoList
Behaves like a TypeScript array **when reading**.
```ts index.ts#CoListReading
```
[Read more →](/docs/core-concepts/covalues/colists)
### CoPlainText/CoRichText
Behaves like a TypeScript string **when reading**.
```ts index.ts#CoTextReading
```
[Read more →](/docs/core-concepts/covalues/cotexts)
**Note**: Although CoPlainTexts/CoRichTexts behave the same as strings in most circumstances, they are not strings. If you need an actual string type, you can use the `toString()` method.
### CoFeed
CoFeeds do not correspond neatly to a TypeScript type. They are collaborative streams of entries split by session/account, and so there are various ways to access the underlying data.
```ts index.ts#CoFeedReading
```
The `.all` property allows you to iterate over all entries in a per-session or per-account feed. If you need to convert a feed to an array, you can use `Array.from()` or the spread operator.
[Read more →](/docs/core-concepts/covalues/cofeeds)
CoFeed Structure
```text
┌────────┐
│ CoFeed └────────────────────────────────────────────────────────────┐
│ ┌────────┐ │
│ │ userA └────────────────────────────────────────────────────────┐ │
│ │ ┌───────────┐ │ │
│ │ │ Session 1 └─────────────────────────────────────────────────┐ │ │
│ │ │ ┌────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │
│ │ │ │ value: someVal │ │ value: someVal2 │ │ value: someVal3 │ │ │ │
│ │ │ │ by: userA │ │ by: userA │ │ by: userA │ │ │ │
│ │ │ │ madeAt: 10:00 │ │ madeAt: 10:01 │ │ madeAt: 10:02 │ │ │ │
│ │ │ └────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ ┌───────────┐ │ │
│ │ │ Session 2 └─────────────────────────────────────────────────┐ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ value: someVal3 │ │ │ │
│ │ │ │ by: userA │ │ │ │
│ │ │ │ madeAt: 12:00 │ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌───────┐ │
│ │ userB └─────────────────────────────────────────────────────────┐ │
│ │ ┌───────────┐ │ │
│ │ │ Session 1 └─────────────────────────────────────────────────┐ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ value: someVal4 │ │ │ │
│ │ │ │ by: userB │ │ │ │
│ │ │ │ madeAt: 10:05 │ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
### FileStream
FileStreams can be converted to `Blob` types or read as binary chunks.
```ts index.ts#FileStreamReading
```
[Read more →](/docs/core-concepts/covalues/filestreams)
### ImageDefinition
Although you can read an `ImageDefinition` as a CoMap, Jazz provides an `` component for front-end frameworks which vastly simplifies their use.
If you want to use the `ImageDefinition` type without a framework, or you need access to the image itself you can use the `loadImageBySize` helper.
```tsx
// Component-based usage (recommended)
```
```ts index.ts#ImageDefinitionReading
```
Once you have a loaded image, you can convert it to a blob with the `.toBlob()` method, as you would with any other FileStream. You can then use this to create a URL to use as an `` source.
```ts index.ts#ImageDefinitionBlob
```
[Read more →](/docs/core-concepts/covalues/imagedef)
### CoVector
You can read a `CoVector` in the same way as you'd read an array, but in most cases, you'll want to use the `cosineSimilarity` helper.
```ts index.ts#CoVectorReading
```
[Read more →](/docs/core-concepts/covalues/covectors)
### DiscriminatedUnion
After loading a `DiscriminatedUnion`, you will be able to read any property that is common to all members, otherwise, you will need to narrow the type to be able to access properties which are only on some subset of members of the union.
```ts index.ts#DiscriminatedUnionReading
```
[Read more →](/docs/core-concepts/schemas/schemaunions)
## Updating CoValues
Most CoValues are updated using mutation methods in the `.$jazz` namespace. Some CoValues, such as `CoPlainText` and `CoRichText`, have methods available directly on the instance itself.
### Updating CoMaps (and the CoRecord sub-type)
CoRecords will allow you to arbitrarily set and delete keys, while CoMaps will only allow you to set and delete keys defined in the schema.
* **`.$jazz.set(key, value): void`**
* **`.$jazz.delete(key): void`**
* **`.$jazz.applyDiff(diff): void`**
```ts index.ts#UpdatingCoMaps
```
[Read more →](/docs/core-concepts/covalues/comaps)
### Updating CoLists
CoLists implement most of the usual array mutation methods under the `.$jazz` namespace (except `sort` and `reverse`).
* **`.$jazz.push(...items: T[]): number`**
* **`.$jazz.unshift(...items: T[]): number`**
* **`.$jazz.pop(): T | undefined`**
* **`.$jazz.shift(): T | undefined`**
* **`.$jazz.remove(...indexes: number[]): T[]`**
* **`.$jazz.remove(predicate: (item: T, index: number, coList: L) => boolean): T[]`**
- Removes elements by index(es) or predicate. Returns the removed elements.
* **`.$jazz.retain(predicate: (item: T, index: number, coList: L) => boolean): T[]`**
- Retains only elements matching the predicate. Returns the removed elements.
* **`.$jazz.splice(start: number, deleteCount: number, ...items: T[]): T[]`**
* **`.$jazz.applyDiff(result: T[]): L`**
- Updates the list to match another list, efficiently calculated
```ts index.ts#UpdatingCoLists
```
[Read more →](/docs/core-concepts/covalues/colists)
### Updating CoPlainTexts/CoRichTexts
CoPlainText and CoRichText methods are mostly available directly on the instance.
* **`insertAfter(after: number, text: string): void`**
* **`insertBefore(before: number, text: string): void`**
* **`deleteRange(range: { from: number, to: number }): void`**
* **`.$jazz.applyDiff(newText: string): void`**
```ts index.ts#UpdatingCoTexts
```
```tsx
// Use directly with templating languages (e.g. React, Svelte)
{message}
```
[Read more →](/docs/core-concepts/covalues/cotexts)
### Updating CoFeeds
CoFeeds are append-only. The only mutation you can perform is `push()` (in the `.$jazz` namespace), which adds a new entry to the feed.
```ts index.ts#UpdatingCoFeeds
```
[Read more →](/docs/core-concepts/covalues/cofeeds)
### Updating FileStreams
If you wish to push binary data into an existing `FileStream`, you can `start` it with metadata, then `push` chunks of data, and finally `end` it.
* **`start(metadata: {
mimeType: string;
fileName?: string;
totalSizeBytes?: number;
}): void`**
* **`push(chunk: Uint8Array): void`**
* **`end(): void`**
```ts index.ts#UpdatingFileStreams
```
[Read more →](/docs/core-concepts/covalues/filestreams)
### Updating ImageDefinitions
If you would like to update an `ImageDefinition`, you will need to create a new `FileStream` and set it on the `ImageDefinition`. The `ImageDefinition` behaves like a `CoRecord` with keys for each image resolution as `x`, and the values are `FileStream` instances. You can replace resolutions or add new ones.
```ts index.ts#UpdatingImageDefinitions
```
[Read more →](/docs/core-concepts/covalues/imagedef)
### Updating CoVectors
CoVectors are immutable. You cannot update them. You should create a new CoVector to replace the old one.
[Read more →](/docs/core-concepts/covalues/covectors)
### Updating DiscriminatedUnions
When updating a discriminated union, you should first narrow the type using the discriminator. After that, you'll be able to update it using the `$jazz.set`, `$jazz.applyDiff`, or `$jazz.delete` methods.
```ts index.ts#UpdatingDiscriminatedUnions
```
[Read more →](/docs/core-concepts/schemas/schemaunions)
## Shared Properties and Methods
### Universal CoValue Interface
These properties and methods are available directly on every CoValue instance.
Feature
Property/Method
Description
Loading
$isLoaded
Check if the CoValue is fully loaded and ready to read.
Inspection
.toJSON()
Get a plain JS representation of the CoValue.
### Common `$jazz` Interface
Every CoValue instance has a `.$jazz` namespace with the following common utilities (some are only available on specific CoValue types).
Feature
Property/Method
Description
Basics
id, owner
Key metadata.
Life-cycle
createdAt, lastUpdatedAt, createdBy
Audit trails and creation timestamps.
Reactivity
subscribe(), waitForSync()
Listen for changes or wait for network persistence.
Loading
loadingState, ensureLoaded()
Check loading state or load a copy of the CoValue with a different resolution depth.
Inspection
refs, has
Available on CoMaps, check for the existence of references/specific keys.
Version Control
isBranched, branchName, unstable_merge()
Utilities for local branching and merging.
### Metadata
* **`.$jazz.id`**: `ID`
- The definitive, globally unique ID (e.g., `co_zXy...`).
* **`.$jazz.owner`**: `Group`
- The `group` that this CoValue is owned by.
* **`.$jazz.createdAt`**: `number`
- The time that this CoValue was created (in milliseconds since the Unix epoch).
* **`.$jazz.createdBy`**: `ID | undefined`
- The ID of the account that originally created this CoValue (`undefined` for accounts themselves).
* **`.$jazz.lastUpdatedAt`**: `number`
- The time that this CoValue was last updated (in milliseconds since the Unix epoch).
* **`.$jazz.refs`**: `Record | undefined` (CoMap/CoRecord only)
- Access nested CoValues as references (with IDs) without loading them. Useful for checking if a reference exists or getting its ID without triggering a network fetch.
```ts index.ts#Refs
```
* **`.$jazz.has(key): boolean`** (CoMap/CoRecord only)
- Checks if a key exists in the CoValue.
### Loading
* **`$isLoaded`**: `boolean`
- True if the CoValue is fully loaded and ready to read.
* **`.$jazz.loadingState`**: `"loading" | "loaded" | "unavailable" | "unauthorized"`
- Current state of the CoValue. See [Loading States](/docs/core-concepts/subscription-and-loading#loading-states) for details on handling each case.
* **`.$jazz.ensureLoaded(resolveQuery: { resolve: ResolveQuery }): Promise>`**
- Waits for nested references to load to the specified depth.
- **Returns**: A **new** typed instance `Resolved` where specified properties are guaranteed to be fully loaded.
```ts index.ts#EnsureLoaded
```
**Note:** When calling `.$jazz.ensureLoaded()` on a discriminated union, you must first narrow the type using the discriminator property. If any nested reference cannot be loaded, the entire operation may fail (see [Loading Errors](/docs/core-concepts/subscription-and-loading#loading-errors)).
`ensureLoaded` returns a copy of the CoValue where the specific fields you requested in the resolve query are guaranteed to be present and non-null.
### Subscription & Sync
* **`.$jazz.subscribe(listener: (updated: this) => void): () => void`**
- Listen for any changes to this CoValue. Only needed if you are manually handling your subscriptions.
- Returns an `unsubscribe` function for manual teardown.
- For error handling in manual subscriptions, see [Manual Subscriptions](/docs/core-concepts/subscription-and-loading#error-handling-for-manual-subscriptions).
```ts index.ts#Subscribe
```
* **`.$jazz.waitForSync()`**: `Promise`
- Waits until changes are synced to other peers (useful for tests or critical saves).
### Version Control
* **`.$jazz.branchName`**: `string | undefined`
- If this CoValue is branched, the name of the branch, otherwise `undefined`.
* **`.$jazz.isBranched`**: `boolean`
- Whether this CoValue is branched.
* **`.$jazz.unstable_merge()`**: `void`
- Merges this branched CoValue back into the main branch. Only works when the CoValue is branched.
===PAGE:coming-soon===
TITLE:coming-soon
DESCRIPTION:description: "This feature has already been released, but the documentation is in progress. " }; Grayed out pages on our sidebar indicate that documentation for this feature is still in progress. We're excited to bring you comprehensive guides and tutorials as soon as possible.
description: "This feature has already been released, but the documentation is in progress."
};
# Documentation coming soon
Grayed out pages on our sidebar indicate that documentation for this feature is still in progress. We're excited to bring you comprehensive guides and tutorials as soon as possible.
This feature has already been released, and we're working hard to provide top-notch support. In the meantime, if you have any questions or need assistance, please don't hesitate to reach out to us on [Discord](https://discord.gg/utDMjHYg42).
We would love to help you get started.
===PAGE:core-concepts/covalues/cofeeds===
TITLE:core-concepts/covalues/cofeeds
DESCRIPTION:description: "CoFeeds are append-only data structures that track entries from different user sessions and accounts. Best for activity logs, presence indicators, notifications, and more. " }; CoFeeds are append-only data structures that track entries from different user sessions and accounts.
description: "CoFeeds are append-only data structures that track entries from different user sessions and accounts. Best for activity logs, presence indicators, notifications, and more."
};
# CoFeeds
CoFeeds are append-only data structures that track entries from different user sessions and accounts. Unlike other CoValues where everyone edits the same data, CoFeeds maintain separate streams for each session.
Each account can have multiple sessions (different browser tabs, devices, or app instances), making CoFeeds ideal for building features like activity logs, presence indicators, and notification systems.
The following examples demonstrate a practical use of CoFeeds:
- [Multi-cursors](https://github.com/garden-co/jazz/tree/main/examples/multi-cursors) - track user presence on a canvas with multiple cursors and out of bounds indicators
- [Reactions](https://github.com/garden-co/jazz/tree/main/examples/reactions) - store per-user emoji reaction using a CoFeed
## Creating CoFeeds
CoFeeds are defined by specifying the type of items they'll contain, similar to how you define CoLists:
```ts index.ts#Basic
```
### Ownership
Like other CoValues, you can specify ownership when creating CoFeeds.
```ts index.ts#WithOwner
```
See [Groups as permission scopes](/docs/permissions-and-sharing/overview) for more information on how to use groups to control access to CoFeeds.
## Reading from CoFeeds
Since CoFeeds are made of entries from users over multiple sessions, you can access entries in different ways - from a specific user's session or from their account as a whole.
### Per-Session Access
To retrieve entries from a session:
```ts index.ts#SessionFeed
```
For convenience, you can also access the latest entry from the current session with `inCurrentSession`:
```ts index.ts#InCurrentSession
```
### Per-Account Access
To retrieve entries from a specific account (with entries from all sessions combined) use `perAccount`:
```ts index.ts#AccountFeed
```
For convenience, you can also access the latest entry from the current account with `byMe`:
```ts index.ts#ByMe
```
### Feed Entries
#### All Entries
To retrieve all entries from a CoFeed:
```ts index.ts#AllEntries
```
#### Latest Entry
To retrieve the latest entry from a CoFeed, ie. the last update:
```ts index.ts#GetLatest
```
## Writing to CoFeeds
CoFeeds are append-only; you can add new items, but not modify existing ones. This creates a chronological record of events or activities.
### Adding Items
```ts index.ts#PushToFeed
```
Each item is automatically associated with the current user's session. You don't need to specify which session the item belongs to - Jazz handles this automatically.
### Understanding Session Context
Each entry is automatically added to the current session's feed. When a user has multiple open sessions (like both a mobile app and web browser), each session creates its own separate entries:
```ts index.ts#MultiFeed
```
## Metadata
CoFeeds support metadata, which is useful for tracking information about the feed itself.
### By
The `by` property is the account that made the entry.
```ts index.ts#By
```
### MadeAt
The `madeAt` property is a timestamp of when the entry was added to the feed.
```ts index.ts#MadeAt
```
## Best Practices
### When to Use CoFeeds
- **Use CoFeeds when**:
- You need to track per-user/per-session data
- Time-based information matters (activity logs, presence)
- **Consider alternatives when**:
- Data needs to be collaboratively edited (use CoMaps or CoLists)
- You need structured relationships (use CoMaps/CoLists with references)
===PAGE:core-concepts/covalues/colists===
TITLE:core-concepts/covalues/colists
DESCRIPTION:description: "CoLists are ordered collections that work like JavaScript arrays. They provide indexed access, iteration methods, and length properties. " }; CoLists are ordered collections that work like JavaScript arrays.
description: "CoLists are ordered collections that work like JavaScript arrays. They provide indexed access, iteration methods, and length properties."
};
# CoLists
CoLists are ordered collections that work like JavaScript arrays. They provide indexed access, iteration methods, and length properties, making them perfect for managing sequences of items.
## Creating CoLists
CoLists are defined by specifying the type of items they contain:
```ts index.ts#Basic
```
To create a `CoList`:
```ts index.ts#Create
```
### Ownership
Like other CoValues, you can specify ownership when creating CoLists.
```ts index.ts#WithOwner
```
See [Groups as permission scopes](/docs/permissions-and-sharing/overview) for more information on how to use groups to control access to CoLists.
## Reading from CoLists
CoLists support standard array access patterns:
```ts index.ts#ArrayAccess
```
## Updating CoLists
Methods to update a CoList's items are grouped inside the `$jazz` namespace:
```ts index.ts#Updating
```
### Soft Deletion
You can do a soft deletion by using a deleted flag, then creating a helper method that explicitly filters out items where the deleted property is true.
```ts soft-deletions.ts#SoftDeletion
```
There are several benefits to soft deletions:
- **recoverablity** - Nothing is truly deleted, so recovery is possible in the future
- **data integrity** - Relationships can be maintained between current and deleted values
- **auditable** - The data can still be accessed, good for audit trails and checking compliance
### Deleting Items
Jazz provides two methods to retain or remove items from a CoList:
```ts index.ts#Deletion
```
You can also remove specific items by index with `splice`, or remove the first or last item with `pop` or `shift`:
```ts index.ts#SpliceAndShift
```
### Array Methods
`CoList`s support the standard JavaScript array methods you already know. Methods that mutate the array
are grouped inside the `$jazz` namespace.
```ts index.ts#PushFindFilter
```
### Type Safety
CoLists maintain type safety for their items:
```ts index.ts#TypeSafety
```
## Best Practices
### Common Patterns
#### List Rendering
CoLists work well with UI rendering libraries:
```ts TaskList.tsx
```
#### Managing Relations
CoLists can be used to create one-to-many relationships:
```ts relations.ts
```
#### Set-like Collections
CoLists, like JavaScript arrays, allow you to insert the same item multiple times. In some cases, you might want to have a collection of unique items (similar to a set). To achieve this, you can use a CoRecord with entries keyed on a unique identifier (for example, the CoValue ID).
You can read [more about this pattern here](/docs/core-concepts/covalues/comaps#creating-set-like-collections).
===PAGE:core-concepts/covalues/comaps===
TITLE:core-concepts/covalues/comaps
DESCRIPTION:description: "CoMaps are key-value objects that work like JavaScript objects. Best for structured data that needs type validation. " }; CoMaps are key-value objects that work like JavaScript objects.
description: "CoMaps are key-value objects that work like JavaScript objects. Best for structured data that needs type validation."
};
# CoMaps
CoMaps are key-value objects that work like JavaScript objects. You can access properties with dot notation and define typed fields that provide TypeScript safety. They're ideal for structured data that needs type validation.
## Creating CoMaps
CoMaps are typically defined with `co.map()` and specifying primitive fields using `z` (see [Defining schemas: CoValues](/docs/core-concepts/covalues/overview) for more details on primitive fields):
```ts index.ts#Basic
```
You can create either struct-like CoMaps with fixed fields (as above) or record-like CoMaps for key-value pairs:
```ts index.ts#Records
```
To instantiate a CoMap:
```ts index.ts#Instantiation
```
### Ownership
When creating CoMaps, you can specify ownership to control access:
```ts index.ts#Ownership
```
See [Groups as permission scopes](/docs/permissions-and-sharing/overview) for more information on how to use groups to control access to CoMaps.
## Reading from CoMaps
CoMaps can be accessed using familiar JavaScript object notation:
```ts index.ts#Reading
```
### Handling Optional Fields
Optional fields require checks before access:
```ts index.ts#Optional
```
### Recursive references
You can wrap references in getters. This allows you to defer evaluation until the property is accessed. This technique is particularly useful for defining circular references, including recursive (self-referencing) schemas, or mutually recursive schemas.
```ts with-getters.ts#WithGetter
```
When the recursive references involve more complex types, it is sometimes required to specify the getter return type:
```ts with-getters.ts#WithTypedGetter
```
### Partial
For convenience Jazz provies a dedicated API for making all the properties of a CoMap optional:
```ts partial.ts
```
### Pick
You can also pick specific fields from a CoMap:
```ts pick.ts
```
### Working with Record CoMaps
For record-type CoMaps, you can access values using bracket notation:
```ts record.ts#BracketNotation
```
## Updating CoMaps
To update a CoMap's properties, use the `$jazz.set` method:
```ts index.ts#Update
```
The `$jazz` namespace is available on all CoValues, and provides access to methods to modify and load CoValues,
as well as access common properties like `id` and `owner`.
When updating references to other CoValues, you can provide both the new CoValue or a JSON object from which the new CoValue will be created.
```ts index.ts#ImplicitExplicit
```
When providing a JSON object, Jazz will automatically create the CoValues for you.
To learn more about how permissions work in this case, refer to
[Ownership on inline CoValue creation](/docs/permissions-and-sharing/cascading-permissions#ownership-on-inline-covalue-creation).
### Type Safety
CoMaps are fully typed in TypeScript, giving you autocomplete and error checking:
```ts index.ts#TypeSafety
```
### Soft Deletion
Implementing a soft deletion pattern by using a `deleted` flag allows you to maintain data for potential recovery and auditing.
```ts soft-deletions.ts
```
When an object needs to be "deleted", instead of removing it from the system, the deleted flag is set to true. This gives us a property to omit it in the future.
### Deleting Properties
You can delete properties from CoMaps:
```ts index.ts#DeleteProperties
```
## Running migrations on CoMaps
Migrations are functions that run when a CoMap is loaded, allowing you to update existing data to match new schema versions. Use them when you need to modify the structure of CoMaps that already exist in your app. Unlike [Account migrations](/docs/core-concepts/schemas/accounts-and-migrations#when-migrations-run), CoMap migrations are not run when a CoMap is created.
**Note:** Migrations are run synchronously and cannot be run asynchronously.
Here's an example of a migration that adds the `priority` field to the `Task` CoMap:
```ts index.ts#Migrations
```
### Migration best practices
Design your schema changes to be compatible with existing data:
- **Add, don't change:** Only add new fields; avoid renaming or changing types of existing fields
- **Make new fields optional:** This prevents errors when loading older data
- **Use version fields:** Track schema versions to run migrations only when needed
### Migration & reader permissions
Migrations need write access to modify CoMaps. If some users only have read permissions, they can't run migrations on those CoMaps.
**Forward-compatible schemas** (where new fields are optional) handle this gracefully - users can still use the app even if migrations haven't run.
**Non-compatible changes** require handling both schema versions in your app code using discriminated unions.
When you can't guarantee all users can run migrations, handle multiple schema versions explicitly:
```ts index.ts#ComplexMigrations
```
## Best Practices
### Structuring Data
- Use struct-like CoMaps for entities with fixed, known properties
- Use record-like CoMaps for dynamic key-value collections
- Group related properties into nested CoMaps for better organization
### Common Patterns
#### Helper methods
You should define helper methods of CoValue schemas separately, in standalone functions:
```ts helpers.ts
```
#### Uniqueness
CoMaps are typically created with a CoValue ID that acts as an opaque UUID, by which you can then load them. However, there are situations where it is preferable to load CoMaps using a custom identifier:
- The CoMaps have user-generated identifiers, such as a slug
- The CoMaps have identifiers referring to equivalent data in an external system
- The CoMaps have human-readable & application-specific identifiers
- If an application has CoValues used by every user, referring to it by a unique *well-known* name (eg, `"my-global-comap"`) can be more convenient than using a CoValue ID
Consider a scenario where one wants to identify a CoMap using some unique identifier that isn't the Jazz CoValue ID:
```ts uniqueness.ts#FailLoading
```
Jazz provides the `getOrCreateUnique` method on CoMaps to support human-readable or application-specific identifiers.
When you call `getOrCreateUnique`, the CoValue ID is deterministically derived from the provided `unique` identifier (together with the owner), ensuring that the same `unique` value always refers to the same CoMap within that scope.
This allows you to easily load or create a CoMap using a readable identifier. If no such value exists, it will be created using the provided data.
```ts uniqueness.ts#GetOrCreateUnique
```
**Caveats:**
- The `unique` parameter acts as an *immutable* identifier - i.e. the same `unique` parameter in the same `Group` will always refer to the same CoValue.
- To make dynamic renaming possible, you can create an indirection where a stable CoMap identified by a specific value of `unique` is simply a pointer to another CoMap with a normal, dynamic CoValue ID. This pointer can then be updated as desired by users with the corresponding permissions.
- This way of introducing identifiers allows for very fast lookup of individual CoMaps by identifier, but it doesn't let you enumerate all the CoMaps identified this way within a `Group`. If you also need enumeration, consider using a global `co.record()` that maps from identifier to a CoMap, which you then do lookups in (this requires at least a shallow load of the entire `co.record()`, but this should be fast for up to 10s of 1000s of entries)
#### Creating Set-like Collections
You can use CoRecords as a way to create set-like collections, by keying the CoRecord on the item's CoValue ID. You can then use static `Object` methods to iterate over the CoRecord, effectively allowing you to treat it as a set.
```ts record.ts#RecordAsSet
```
You can choose a loading strategy for the CoRecord. Use $each when you need all item properties to be immediately available. In general, it is enough to shallowly load a CoRecord to access its keys, and then load the values of those keys as needed (for example, by passing the keys as strings to a child component).
```ts record.ts#DeeplyLoadingKeys
```
===PAGE:core-concepts/covalues/cotexts===
TITLE:core-concepts/covalues/cotexts
DESCRIPTION:description: "co. plainText() and co. richText() enable real-time collaborative text editing.
description: "co.plainText() and co.richText() enable real-time collaborative text editing. They provide fine-grained control over text edits, and efficient merging of concurrent changes."
};
# CoTexts
Jazz provides two CoValue types for collaborative text editing, collectively referred to as "CoText" values:
- **`co.plainText()`** for simple text editing without formatting
- **`co.richText()`** for rich text with HTML-based formatting (extends `co.plainText()`)
Both types enable real-time collaborative editing of text content while maintaining consistency across multiple users.
**Note:** If you're looking for a quick way to add rich text editing to your app, check out [our prosemirror plugin](#using-rich-text-with-prosemirror).
```ts index.ts#Basic
```
For a full example of CoTexts in action, see [our Richtext example app](https://github.com/garden-co/jazz/tree/main/examples/richtext-prosemirror), which shows plain text and rich text editing.
## `co.plainText()` vs `z.string()`
While `z.string()` is perfect for simple text fields, `co.plainText()` is the right choice when you need:
- Frequent text edits that aren't just replacing the whole field
- Fine-grained control over text edits (inserting, deleting at specific positions)
- Multiple users editing the same text simultaneously
- Character-by-character collaboration
- Efficient merging of concurrent changes
Both support real-time updates, but `co.plainText()` provides specialized tools for collaborative editing scenarios.
## Creating CoText Values
CoText values are typically used as fields in your schemas:
```ts index.ts#Comparison
```
Create a CoText value with a simple string:
```ts index.ts#StringInitial
```
### Ownership
Like other CoValues, you can specify ownership when creating CoTexts.
```ts index.ts#Ownership
```
See [Groups as permission scopes](/docs/permissions-and-sharing/overview) for more information on how to use groups to control access to CoText values.
## Reading Text
CoText values work similarly to JavaScript strings:
```ts index.ts#Reading
```
When using CoTexts in JSX, you can read the text directly:
```tsx react-snippet.tsx#JSX
```
`CoTexts` extend the native `String` class, so `typeof description` will return `object`. If you need the primitive string value (e.g. for strict equality checks or 3rd party libraries), use `description.toString()`.
## Making Edits
Insert and delete text with intuitive methods:
```ts index.ts#Editing
```
### Applying Diffs
Use `applyDiff` to efficiently update text with minimal changes:
```ts index.ts#ApplyDiff
```
Perfect for handling user input in form controls:
```ts react-snippet.tsx#TextEditor
```
```ts vanilla.ts#PlainText
```
```svelte svelte.svelte
```
## Using Rich Text with ProseMirror
Jazz provides a dedicated plugin for integrating `co.richText()` with the popular ProseMirror editor that enables bidirectional synchronization between your co.richText() instances and ProseMirror editors.
### ProseMirror Plugin Features
- **Bidirectional Sync**: Changes in the editor automatically update the `co.richText()` and vice versa
- **Real-time Collaboration**: Multiple users can edit the same document simultaneously
- **HTML Conversion**: Automatically converts between HTML (used by `co.richText()`) and ProseMirror's document model
### Installation
```bash
pnpm add prosemirror-view \
prosemirror-state \
prosemirror-schema-basic
```
### Integration
We don't currently have a React Native-specific example, but you need help you can [request one](https://github.com/garden-co/jazz/issues/new), or ask on [Discord](https://discord.gg/utDMjHYg42).
We don't currently have a React Native Expo-specific example, but you need help please [request one](https://github.com/garden-co/jazz/issues/new), or ask on [Discord](https://discord.gg/utDMjHYg42).
For use with React:
```tsx react-snippet.tsx#RichTextEditor
```
We don't currently have a Svelte-specific example, but you need help you can [request one](https://github.com/garden-co/jazz/issues/new), or ask on [Discord](https://discord.gg/utDMjHYg42).
For use without a framework:
```ts vanilla.ts#RichText
```
===PAGE:core-concepts/covalues/covectors===
TITLE:core-concepts/covalues/covectors
DESCRIPTION:description: "CoVectors store high-dimensional vectors on-device and enable local-first semantic search and similarity operations. " }; CoVectors let you store and query high‑dimensional vectors directly in Jazz apps. They are ideal for semantic search, or personalization features that work offline, sync across devices, and remain end‑to‑end encrypted.
description: "CoVectors store high-dimensional vectors on-device and enable local-first semantic search and similarity operations."
};
# CoVectors
CoVectors let you store and query high‑dimensional vectors directly in Jazz apps. They are ideal for semantic search, or personalization features that work offline, sync across devices, and remain end‑to‑end encrypted.
The [Journal example](https://github.com/garden-co/jazz/tree/main/examples/vector-search) demonstrates semantic search using of CoVector.
CoVectors are defined using `co.vector()`, and are often used as fields in a CoMap within a CoList (making it easy to perform vector search across list items).
```ts index.ts#Basic
```
The number of dimensions matches the embedding model used in your app. Many small sentence transformers produce 384‑dim vectors; others use 512, 768, 1024 or more.
## Creating CoVectors
You can create vectors in your Jazz application from an array of numbers, or Float32Array instance.
```ts index.ts#Create
```
### Ownership
Like other CoValues, you can specify ownership when creating CoVectors.
```ts index.ts#Ownership
```
See [Groups as permission scopes](/docs/permissions-and-sharing/overview) for more information on how to use groups to control access to CoVectors.
### Immutability
CoVectors cannot be changed after creation. Instead, create a new CoVector with the updated values and replace the previous one.
## Semantic Search
Semantic search lets you find data based on meaning, not just keywords. In Jazz, you can easily sort results by how similar they are to your search query.
Use the `useCoState` hook to load your data and sort it by similarity to your query embedding:
You can load your data using the `.load` method, then compute and sort the results by similarity to your query embedding:
} preferWrap>
```ts index.ts#SemanticSearch
```
} preferWrap>
```tsx react-snippet.tsx#SemanticSearch
```
} preferWrap>
```ts svelte.svelte
```
Wrapping each item with its similarity score makes it easy to sort, filter, and display the most relevant results. This approach is widely used in vector search and recommendation systems, since it keeps both the data and its relevance together for further processing or display.
### Cosine Similarity
To compare how similar two vectors are, we use their [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity). This returns a value between `-1` and `1`, describing how similar the vectors are:
- `1` means the vectors are identical
- `0` means the vectors are orthogonal (i.e. no similarity)
- `-1` means the vectors are opposite direction (perfectly dissimilar).
If you sort items by their cosine similarity, the ones which are most similar will appear at the top of the list.
Jazz provides a built-in `$jazz.cosineSimilarity` method to calculate this for you.
## Embedding Models
CoVectors handles storage and search, you provide the vectors. Generate embeddings with any model you prefer (Hugging Face, OpenAI, custom, etc).
**Recommended:** Run models locally for privacy and offline support using [Transformers.js](https://huggingface.co/docs/transformers.js). Check our [Journal app example](https://github.com/garden-co/jazz/tree/main/examples/vector-search) to see how to do this.
The following models offer a good balance between accuracy and performance:
- [Xenova/all-MiniLM-L6-v2](https://huggingface.co/Xenova/all-MiniLM-L6-v2) — 384 dimensions, ~23 MB
- [Xenova/paraphrase-multilingual-mpnet-base-v2](https://huggingface.co/Xenova/paraphrase-multilingual-mpnet-base-v2) — 768 dimensions, ~279 MB
- [mixedbread-ai/mxbai-embed-large-v1](https://huggingface.co/mixedbread-ai/mxbai-embed-large-v1) — 1024 dimensions, ~337 MB
- [Browse more models →](https://huggingface.co/models?pipeline_tag=feature-extraction&library=transformers.js)
Alternatively, you can generate embeddings using server-side or commercial APIs (such as OpenAI or Anthropic).
## Best Practices
### Changing embedding models
**Always use the same embedding model for all vectors you intend to compare.**
Mixing vectors from different models (or even different versions of the same model) will result in meaningless similarity scores, as the vector spaces are not compatible.
If you need to switch models, consider storing the model identifier alongside each vector, and re-embedding your data as needed.
===PAGE:core-concepts/covalues/filestreams===
TITLE:core-concepts/covalues/filestreams
DESCRIPTION:description: "FileStreams handle binary data in Jazz applications - think documents, audio files, and other non-text content. " }; FileStreams handle binary data in Jazz applications - think documents, audio files, and other non-text content. They're essentially collaborative versions of `Blob`s that sync automatically across devices.
description: "FileStreams handle binary data in Jazz applications - think documents, audio files, and other non-text content."
};
# FileStreams
FileStreams handle binary data in Jazz applications - think documents, audio files, and other non-text content. They're essentially collaborative versions of `Blob`s that sync automatically across devices.
Use FileStreams when you need to:
- Distribute documents across devices
- Store audio or video files
- Sync any binary data between users
**Note:** For images specifically, Jazz provides the higher-level `ImageDefinition` abstraction which manages multiple image resolutions - see the [ImageDefinition documentation](/docs/core-concepts/covalues/imagedef) for details.
FileStreams provide automatic chunking when using the `createFromBlob` method, track upload progress, and handle MIME types and metadata.
In your schema, reference FileStreams like any other CoValue:
schema.ts
```ts schema.ts
```
## Creating FileStreams
There are two main ways to create FileStreams: creating empty ones for manual data population or creating directly from existing files or blobs.
### Creating from Blobs and Files
For files from input elements or drag-and-drop interfaces, use `createFromBlob`:
```ts index.ts#FromBlob
```
### Creating Empty FileStreams
Create an empty FileStream when you want to manually [add binary data in chunks](#writing-to-filestreams):
```ts index.ts#Empty
```
### Ownership
Like other CoValues, you can specify ownership when creating FileStreams.
```ts index.ts#Ownership
```
See [Groups as permission scopes](/docs/permissions-and-sharing/overview) for more information on how to use groups to control access to FileStreams.
## Reading from FileStreams
`FileStream`s provide several ways to access their binary content, from raw chunks to convenient Blob objects.
### Getting Raw Data Chunks
To access the raw binary data and metadata:
```ts index.ts#GetRaw
```
By default, `getChunks()` only returns data for completely synced `FileStream`s. To start using chunks from a `FileStream` that's currently still being synced use the `allowUnfinished` option:
```ts index.ts#GetIncomplete
```
### Converting to Blobs
For easier integration with web APIs, convert to a `Blob`:
```ts index.ts#GetAsBlob
```
### Loading FileStreams as Blobs
You can directly load a `FileStream` as a `Blob` when you only have its ID:
```ts index.ts#LoadAsBlob
```
### Checking Completion Status
Check if a `FileStream` is fully synced:
```ts index.ts#CheckSync
```
## Writing to FileStreams
When creating a `FileStream` manually (not using `createFromBlob`), you need to manage the upload process yourself. This gives you more control over chunking and progress tracking.
### The Upload Lifecycle
`FileStream` uploads follow a three-stage process:
1. **Start** - Initialize with metadata
2. **Push** - Send one or more chunks of data
3. **End** - Mark the stream as complete
### Starting a `FileStream`
Begin by providing metadata about the file:
```ts index.ts#ManualStart
```
### Pushing Data
Add binary data in chunks - this helps with large files and progress tracking:
```ts index.ts#AddChunks
```
### Completing the Upload
Once all chunks are pushed, mark the `FileStream` as complete:
```ts index.ts#Finalise
```
## Subscribing to `FileStream`s
Like other CoValues, you can subscribe to `FileStream`s to get notified of changes as they happen. This is especially useful for tracking upload progress when someone else is uploading a file.
### Loading by ID
Load a `FileStream` when you have its ID:
```ts index.ts#LoadById
```
### Subscribing to Changes
Subscribe to a `FileStream` to be notified when chunks are added or when the upload is complete:
```ts index.ts#SubscribeById
```
### Waiting for Upload Completion
If you need to wait for a `FileStream` to be fully synchronized across devices:
```ts index.ts#WaitForSync
```
This is useful when you need to ensure that a file is available to other users before proceeding with an operation.
===PAGE:core-concepts/covalues/imagedef===
TITLE:core-concepts/covalues/imagedef
DESCRIPTION:description: "ImageDefinition is a CoValue for storing images with built-in UX features. " }; `ImageDefinition` is a specialized CoValue designed specifically for managing images in Jazz applications. It extends beyond basic file storage by supporting a blurry placeholder, built-in resizing, and progressive loading patterns.
description: "ImageDefinition is a CoValue for storing images with built-in UX features."
};
# ImageDefinition
`ImageDefinition` is a specialized CoValue designed specifically for managing images in Jazz applications. It extends beyond basic file storage by supporting a blurry placeholder, built-in resizing, and progressive loading patterns.
Beyond `ImageDefinition`, Jazz offers higher-level functions and components that make it easier to use images:
- [`createImage()`](#creating-images) - function to create an `ImageDefinition` from a file
- [`loadImage`, `loadImageBySize`, `highestResAvailable`](#displaying-images) - functions to load and display images
- [`Image`](#displaying-images) - Component to display an image
The [Image Upload example](https://github.com/gardencmp/jazz/tree/main/examples/image-upload) demonstrates use of images in Jazz.
The [Chat example](https://github.com/gardencmp/jazz/tree/main/examples/chat-svelte) includes an implementation of ImageDefinitions in Svelte.
## Installation [!framework=react-native]
Jazz's images implementation is based on `@bam.tech/react-native-image-resizer`. Check the [installation guide](/docs/react-native/project-setup#install-dependencies) for more details.
## Installation [!framework=react-native-exp]
Jazz's images implementation is based on `expo-image-manipulator`. Check the [installation guide](/docs/react-native-expo/project-setup#install-dependencies) for more details.
## Creating Images
The easiest way to create and use images in your Jazz application is with the `createImage()` function:
} value="vanilla" preferWrap>
```ts index.ts#Basic
```
} value="react" preferWrap>
```ts index.ts#Basic
```
} value="svelte" preferWrap>
```ts index.ts#Basic
```
} value="react-native" preferWrap>
```ts rn.tsx#Basic
```
} value="react-native-expo" preferWrap>
```ts rn.tsx#Basic
```
The `createImage()` function:
- Creates an `ImageDefinition` with the right properties
- Optionally generates a small placeholder for immediate display
- Creates multiple resolution variants of your image
- Returns the created `ImageDefinition`
### Configuration Options
```ts declarations.ts#CreateImageDeclaration
```
#### `image`
The image to create an `ImageDefinition` from.
This can be a `Blob` or a `File`.
This must be a `string` with the file path.
#### `owner`
The owner of the `ImageDefinition`. This is used to control access to the image. See [Groups as permission scopes](/docs/permissions-and-sharing/overview) for more information on how to use groups to control access to images.
#### `placeholder`
Disabled by default. This option allows you to automatically generate a low resolution preview for use while the image is loading. Currently, only `"blur"` is a supported.
#### `maxSize`
The image generation process includes a maximum size setting that controls the longest side of the image. A built-in resizing feature is applied based on this setting.
#### `progressive`
The progressive loading pattern is a technique that allows images to load incrementally, starting with a small version and gradually replacing it with a larger version as it becomes available. This is useful for improving the user experience by showing a placeholder while the image is loading.
Passing `progressive: true` to `createImage()` will create internal smaller versions of the image for future uses.
### Create multiple resized copies
To create multiple resized copies of an original image for better layout control, you can use the `createImage` function multiple times with different parameters for each desired size. Here’s an example of how you might implement this:
```ts index.ts#CreateResized
```
### Creating images on the server
We provide a `createImage` function to create images from server side using the same options as the browser version, using the package `jazz-tools/media/server`. Check the [server worker](/docs/server-side/setup) documentation to learn more.
The resize features are based on the `sharp` library, then it is requested as peer dependency in order to use it.
```sh
npm install sharp
```
```sh
pnpm install sharp
```
```ts server.ts
```
## Displaying Images
To use the stored ImageDefinition, there are two ways: declaratively, using the `Image` component, and imperatively, using the static methods.
The Image component is the best way to let Jazz handle the image loading.
### `` component [!framework=react,svelte,react-native,react-native-expo]
} value="react" preferWrap>
```tsx react-snippet.tsx#GalleryView
```
} value="svelte" preferWrap>
```svelte GalleryView.svelte
```
} value="react-native" preferWrap>
```tsx GalleryViewRN.tsx#GalleryView
```
} value="react-native-expo" preferWrap>
```tsx GalleryViewExpo.tsx#GalleryView
```
The `Image` component handles:
- Showing a placeholder while loading, if generated or specified
- Automatically selecting the appropriate resolution, if generated with progressive loading
- Progressive enhancement as higher resolutions become available, if generated with progressive loading
- Determining the correct width/height attributes to avoid layout shifting
- Cleaning up resources when unmounted
The component's props are:
```ts declarations.ts#BrowserImageProps
```
```ts declarations.ts#RNImageProps
```
#### Width and Height props [!framework=react,svelte,react-native,react-native-expo]
The `width` and `height` props are used to control the best resolution to use but also the width and height attributes of the image tag.
Let's say we have an image with a width of 1920px and a height of 1080px.
```tsx react-snippet.tsx#MultiImages
```
If the image was generated with progressive loading, the `width` and `height` props will determine the best resolution to use.
#### Lazy loading [!framework=react,svelte]
The `Image` component supports lazy loading with the [same options as the native browser `loading` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#loading). It will generate the blob url for the image when the browser's viewport reaches the image.
```tsx react-snippet.tsx#LazyLoad
```
#### Placeholder
You can choose to specify a custom placeholder to display as a fallback while an image is loading in case your image does not have a placeholder generated. A data URL or a URL for a static asset works well here.
### Imperative Usage [!framework=react,svelte,react-native,react-native-expo]
Like other CoValues, `ImageDefinition` can be used to load the object.
```tsx index.ts#Imperative
```
`image.original` is a `FileStream` and its content can be read as described in the [FileStream](/docs/core-concepts/covalues/filestreams#reading-from-filestreams) documentation.
Since FileStream objects are also CoValues, they must be loaded before use. To simplify loading, if you want to load the binary data saved as Original, you can use the `loadImage` function.
```tsx index.ts#LoadImageHelper
```
If the image was generated with progressive loading, and you want to access the best-fit resolution, use `loadImageBySize`. It will load the image of the best resolution that fits the wanted width and height.
```tsx index.ts#LoadImageBySize
```
If want to dynamically listen to the _loaded_ resolution that best fits the wanted width and height, you can use the `subscribe` and the `highestResAvailable` function.
```tsx index.ts#HighestResAvailable
```
## Custom image manipulation implementations
To manipulate images (like placeholders, resizing, etc.), `createImage()` uses different implementations depending on the environment.
On the browser, image manipulation is done using the `canvas` API.
On React Native, image manipulation is done using the `@bam.tech/react-native-image-resizer` library.
On Expo, image manipulation is done using the `expo-image-manipulator` library.
If you want to use a custom implementation, you can use the `createImageFactory` function in order create your own `createImage` function and use your preferred image manipulation library.
```tsx index.ts#CreateImageFactory
```
## Best Practices
- **Set image sizes** when possible to avoid layout shifts
- **Use placeholders** (like LQIP - Low Quality Image Placeholders) for instant rendering
- **Prioritize loading** the resolution appropriate for the current viewport
- **Consider device pixel ratio** (window.devicePixelRatio) for high-DPI displays
- **Always call URL.revokeObjectURL** after the image loads to prevent memory leaks
===PAGE:core-concepts/covalues/overview===
TITLE:core-concepts/covalues/overview
DESCRIPTION:description: "CoValues are the core abstraction of Jazz. They're your bread-and-butter datastructures that you use to represent everything in your app. ", }; **CoValues ("Collaborative Values") are the core abstraction of Jazz.
description: "CoValues are the core abstraction of Jazz. They're your bread-and-butter datastructures that you use to represent everything in your app.",
};
# Defining schemas: CoValues
**CoValues ("Collaborative Values") are the core abstraction of Jazz.** They're your bread-and-butter datastructures that you use to represent everything in your app.
As their name suggests, CoValues are inherently collaborative, meaning **multiple users and devices can edit them at the same time.**
**Think of CoValues as "super-fast Git for lots of tiny data."**
- CoValues keep their full edit histories, from which they derive their "current state".
- The fact that this happens in an eventually-consistent way makes them [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type).
- Having the full history also means that you often don't need explicit timestamps and author info - you get this for free as part of a CoValue's [edit metadata](/docs/key-features/history).
CoValues model JSON with CoMaps and CoLists, but also offer CoFeeds for simple per-user value feeds, and let you represent binary data with FileStreams.
## Start your app with a schema
Fundamentally, CoValues are as dynamic and flexible as JSON, but in Jazz you use them by defining fixed schemas to describe the shape of data in your app.
This helps correctness and development speed, but is particularly important...
- when you evolve your app and need migrations
- when different clients and server workers collaborate on CoValues and need to make compatible changes
Thinking about the shape of your data is also a great first step to model your app.
Even before you know the details of how your app will work, you'll probably know which kinds of objects it will deal with, and how they relate to each other.
In Jazz, you define schemas using `co` for CoValues and `z` (from [Zod](https://zod.dev/)) for their primitive fields.
schema.ts
```ts schema.ts#Basic
```
This gives us schema info that is available for type inference *and* at runtime.
Check out the inferred type of `project` in the example below, as well as the input `.create()` expects.
```ts app.ts#Basic
```
When creating CoValues that contain other CoValues, you can pass in a plain JSON object.
Jazz will automatically create the CoValues for you.
```ts app.ts#ImplicitPublic
```
Starting from Jazz 0.20.10, it is possible to opt in to run-time schema validation on writes. This will ensure that only data which conforms to the defined schema can be inserted into the CoValue. This helps to enforce data integrity and improve the type-safety of your application. The validation strategy is currently warn by default: updates and inserts of invalid data will still be allowed, but will give a console warning. This can be changed using the setDefaultValidationMode(), which accepts `strict` (throw if invalid), `warn` (warn if invalid) and `loose` (allow invalid), as options.
To learn more about how permissions work when creating nested CoValues with plain JSON objects,
refer to [Ownership on inline CoValue creation](/docs/permissions-and-sharing/cascading-permissions#ownership-on-inline-covalue-creation).
## Types of CoValues
### `CoMap` (declaration)
CoMaps are the most commonly used type of CoValue. They are the equivalent of JSON objects (Collaborative editing follows a last-write-wins strategy per-key).
You can either declare struct-like CoMaps:
```ts schema.ts#Task
```
Or record-like CoMaps (key-value pairs, where keys are always `string`):
```ts schema.ts#Records
```
See the corresponding sections for [creating](/docs/core-concepts/covalues/comaps#creating-comaps),
[subscribing/loading](/docs/core-concepts/subscription-and-loading),
[reading from](/docs/core-concepts/covalues/comaps#reading-from-comaps) and
[updating](/docs/core-concepts/covalues/comaps#updating-comaps) CoMaps.
### `CoList` (declaration)
CoLists are ordered lists and are the equivalent of JSON arrays. (They support concurrent insertions and deletions, maintaining a consistent order.)
You define them by specifying the type of the items they contain:
```ts schema.ts#Lists
```
See the corresponding sections for [creating](/docs/core-concepts/covalues/colists#creating-colists),
[subscribing/loading](/docs/core-concepts/subscription-and-loading),
[reading from](/docs/core-concepts/covalues/colists#reading-from-colists) and
[updating](/docs/core-concepts/covalues/colists#updating-colists) CoLists.
### `CoFeed` (declaration)
CoFeeds are a special CoValue type that represent a feed of values for a set of users/sessions (Each session of a user gets its own append-only feed).
They allow easy access of the latest or all items belonging to a user or their sessions. This makes them particularly useful for user presence, reactions, notifications, etc.
You define them by specifying the type of feed item:
```ts schema.ts#Feed
```
See the corresponding sections for [creating](/docs/core-concepts/covalues/cofeeds#creating-cofeeds),
[subscribing/loading](/docs/core-concepts/subscription-and-loading),
[reading from](/docs/core-concepts/covalues/cofeeds#reading-from-cofeeds) and
[writing to](/docs/core-concepts/covalues/cofeeds#writing-to-cofeeds) CoFeeds.
### `FileStream` (declaration)
FileStreams are a special type of CoValue that represent binary data. (They are created by a single user and offer no internal collaboration.)
They allow you to upload and reference files.
You typically don't need to declare or extend them yourself, you simply refer to the built-in `co.fileStream()` from another CoValue:
```ts schema.ts#FileStream
```
See the corresponding sections for [creating](/docs/core-concepts/covalues/filestreams#creating-filestreams),
[subscribing/loading](/docs/core-concepts/subscription-and-loading),
[reading from](/docs/core-concepts/covalues/filestreams#reading-from-filestreams) and
[writing to](/docs/core-concepts/covalues/filestreams#writing-to-filestreams) FileStreams.
**Note: For images, we have a special, higher-level `co.image()` helper, see [ImageDefinition](/docs/core-concepts/covalues/imagedef).**
### Unions of CoMaps (declaration)
You can declare unions of CoMaps that have discriminating fields, using `co.discriminatedUnion()`.
```ts core-concepts/schemas/schemaunions/schema.ts#SchemaUnion
```
See the corresponding sections for [creating](/docs/core-concepts/schemas/schemaunions#creating-schema-unions),
[subscribing/loading](/docs/core-concepts/subscription-and-loading) and
[narrowing](/docs/core-concepts/schemas/schemaunions#narrowing-unions) schema unions.
## CoValue field/item types
Now that we've seen the different types of CoValues, let's see more precisely how we declare the fields or items they contain.
### Primitive fields
You can declare primitive field types using `z` (re-exported in `jazz-tools` from [Zod](https://zod.dev/)).
Here's a quick overview of the primitive types you can use:
```ts schema.ts#PrimitiveTypes
```
Finally, for more complex JSON data, that you *don't want to be collaborative internally* (but only ever update as a whole), you can use more complex Zod types.
For example, you can use `z.object()` to represent an internally immutable position:
```ts schema.ts#ZObject
```
Or you could use a `z.tuple()`:
```ts schema.ts#ZTuple
```
### References to other CoValues
To represent complex structured data with Jazz, you form trees or graphs of CoValues that reference each other.
Internally, this is represented by storing the IDs of the referenced CoValues in the corresponding fields, but Jazz abstracts this away, making it look like nested CoValues you can get or assign/insert.
The important caveat here is that **a referenced CoValue might or might not be loaded yet,** but we'll see what exactly that means in [Subscribing and Deep Loading](/docs/core-concepts/subscription-and-loading).
In Schemas, you declare references by just using the schema of the referenced CoValue:
```ts schema.ts#References
```
#### Optional References
You can make schema fields optional using either `z.optional()` or `co.optional()`, depending on the type of value:
- Use `z.optional()` for primitive Zod values like `z.string()`, `z.number()`, or `z.boolean()`
- Use `co.optional()` for CoValues like `co.map()`, `co.list()`, or `co.record()`
You can make references optional with `co.optional()`:
```ts schema.ts#OptionalProperties
```
#### Recursive References
You can wrap references in getters. This allows you to defer evaluation until the property is accessed. This technique is particularly useful for defining circular references, including recursive (self-referencing) schemas, or mutually recursive schemas.
```ts schema.ts#SelfReferencing
```
You can use the same technique for mutually recursive references:
```ts schema.ts#MutuallyRecursive
```
If you try to reference `ListOfFriends` in `MutuallyRecursivePerson` without using a getter, you'll run into a `ReferenceError` because of the [temporal dead zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz).
### Helper methods
If you find yourself repeating the same logic to access computed CoValues properties,
you can define helper functions to encapsulate it for better reusability:
```ts helpers.ts#Basic
```
Similarly, you can encapsulate logic needed to update CoValues:
```ts helpers.ts#UpdateHelper
```
===PAGE:core-concepts/deleting===
TITLE:core-concepts/deleting
DESCRIPTION:description: "Learn how to permanently delete CoValues and their nested references using the deleteCoValues function" }; The `deleteCoValues` function allows you to permanently delete CoValues from Jazz. When a CoValue is deleted, it becomes inaccessible to all users. Deletion is permanent and cannot be undone.
description: "Learn how to permanently delete CoValues and their nested references using the deleteCoValues function"
};
# Deleting CoValues
The `deleteCoValues` function allows you to permanently delete CoValues from Jazz. When a CoValue is deleted, it becomes inaccessible to all users.
Deletion is permanent and cannot be undone. Once a CoValue is deleted, it cannot be recovered. Make sure you have proper confirmation flows in your application before calling `deleteCoValues`.
Only users with admin permissions on a CoValue's group can delete it. Attempting to delete a CoValue without admin access will throw an error.
Deleted values are not deleted from storage immediately, but are marked with a tombstone.
To balance performance considerations, the actual physical deletion of the data from storage is done asynchronously in the background, and is dependent on the sync server's configuration.
Jazz Cloud and `jazz-run sync` have delete enabled by default on a one minute schedule. If you're running a custom self-hosted sync server, you need to enable this feature.
Deleted CoValues stored in Jazz Cloud may persist in back-ups until they are overwritten. These back-ups undergo no additional processing, and will only be restored for disaster recovery purposes.
## Basic Usage
To delete a CoValue, pass the schema and the CoValue's ID to `deleteCoValues`:
```ts index.ts#Basic
```
After deletion, any attempt to load the CoValue will return a not-loaded state with `loadingState: "deleted"`.
## Deleting Nested CoValues
You can delete a CoValue along with its nested references by providing a `resolve` query. This works the same way as [resolve queries for loading](/docs/core-concepts/subscription-and-loading#using-resolve-queries).
```ts index.ts#WithResolve
```
All CoValues specified in the resolve query will be deleted together. This is useful for cleaning up related data, such as deleting a document along with all its attachments and files.
## Handling Inaccessible Data
Jazz validates permissions on all CoValues in the resolve query **before** deleting anything. If any CoValue in the tree cannot be deleted (due to missing admin permissions or inaccessibility), the entire operation fails and no data is deleted.
```ts index.ts#CollectionWithInaccessible
```
When working with collections that might contain values you don't have admin access to, iterate over the items and check permissions before attempting deletion. This allows you to handle mixed-permission scenarios gracefully rather than having the entire delete operation fail.
## Groups and Accounts
The `deleteCoValues` function skips Account and Group CoValues. Calling `deleteCoValues` on them has no effect:
```ts index.ts#Limitations
```
===PAGE:core-concepts/schemas/accounts-and-migrations===
TITLE:core-concepts/schemas/accounts-and-migrations
DESCRIPTION:description: "Define the structure of every account in your app and handle data migrations. " }; Compared to traditional relational databases with tables and foreign keys, Jazz is more like a graph database, or GraphQL APIs — where CoValues can arbitrarily refer to each other and you can resolve references without having to do a join. (See [Subscribing & deep loading](/docs/core-concepts/subscription-and-loading)).
description: "Define the structure of every account in your app and handle data migrations."
};
# Accounts & Migrations
## CoValues as a graph of data rooted in accounts
Compared to traditional relational databases with tables and foreign keys,
Jazz is more like a graph database, or GraphQL APIs —
where CoValues can arbitrarily refer to each other and you can resolve references without having to do a join.
(See [Subscribing & deep loading](/docs/core-concepts/subscription-and-loading)).
To find all data related to a user, the account acts as a root node from where you can resolve all the data they have access to.
These root references are modeled explicitly in your schema, distinguishing between data that is typically public
(like a user's profile) and data that is private (like their messages).
### `Account.root` - private data a user cares about
Every Jazz app that wants to refer to per-user data needs to define a custom root `CoMap` schema and declare it in a custom `Account` schema as the `root` field:
```ts schema.ts#Basic
```
### `Account.profile` - public data associated with a user
The built-in `Account` schema class comes with a default `profile` field, which is a CoMap (in a Group with `"everyone": "reader"` - so publicly readable permissions)
that is set up for you based on the username the `AuthMethod` provides on account creation.
Their pre-defined schemas roughly look like this:
```ts core-concepts/schemas/accounts-and-migrations/schema.ts#Account
```
If you want to keep the default `co.profile()` schema, but customise your account's private `root`, you can use `co.profile()` without options.
If you want to extend the `profile` to contain additional fields (such as an avatar `co.image()`), you can declare your own profile schema class using `co.profile({...})`. A `co.profile({...})` is a [type of CoMap](/docs/core-concepts/covalues/comaps), so you can add fields in the same way:
```ts core-concepts/schemas/accounts-and-migrations/schema.ts#Profile
```
When using custom profile schemas, you need to take care of initializing the `profile` field in a migration,
and set up the correct permissions for it. See [Adding/changing fields to root and profile](#addingchanging-fields-to-root-and-profile).
## Resolving CoValues starting at `profile` or `root`
To use per-user data in your app, you typically use `useAccount` with your custom Account schema and specify which references to resolve using a resolve query (see [Subscribing & deep loading](/docs/core-concepts/subscription-and-loading)).
Jazz will deduplicate loads, so you can safely use `useAccount` multiple times throughout your app without any performance overhead to ensure each component has exactly the data it needs.
To use per-user data in your app, you typically use `AccountCoState` with your custom Account schema and specify which references to resolve using a resolve query (see [Subscribing & deep loading](/docs/core-concepts/subscription-and-loading)).
Jazz will deduplicate loads, so you can safely use `AccountCoState` multiple times throughout your app without any performance overhead to ensure each component has exactly the data it needs.
To use per-user data in your app, you typically use your custom Account schema with a `.subscribe()` call, and specify which references to resolve using a resolve query (see [Subscribing & deep loading](/docs/core-concepts/subscription-and-loading)).
Jazz will deduplicate loads, so you can safely use this pattern multiple times throughout your app without any performance overhead to ensure each part of your app has exactly the data it needs.
} preferWrap>
```ts dashboard.ts
```
} preferWrap>
```tsx DashboardPageComponent.tsx#Main
```
} preferWrap>
```svelte DashboardPageComponent.svelte
```
} preferWrap>
```tsx DashboardPageComponent.tsx#ReactNative
```
} preferWrap>
```tsx DashboardPageComponent.tsx#ReactNative
```
## Populating and evolving `root` and `profile` schemas with migrations
As you develop your app, you'll likely want to
- initialise data in a user's `root` and `profile`
- add more data to your `root` and `profile` schemas
You can achieve both by overriding the `migrate()` method on your `Account` schema class.
### When migrations run
Migrations are run after account creation and every time a user logs in.
Jazz waits for the migration to finish before passing the account to your app's context.
### Initialising user data after account creation
```ts core-concepts/schemas/accounts-and-migrations/schema.ts#WithMigration
```
### Adding/changing fields to `root` and `profile`
To add new fields to your `root` or `profile` schemas, amend their corresponding schema classes with new fields,
and then implement a migration that will populate the new fields for existing users (by using initial data, or by using existing data from old fields).
To do deeply nested migrations, you might need to use the asynchronous `$jazz.ensureLoaded()` method before determining whether the field already exists, or is simply not loaded yet.
Now let's say we want to add a `myBookmarks` field to the `root` schema:
```ts core-concepts/schemas/accounts-and-migrations/schema-add-fields.ts#ExtendedMigration
```
### Guidance on building robust schemas
Once you've published a schema, you should only ever add fields to it. This is because you have no way of ensuring that a new schema is distributed to all clients, especially if you're building a local-first app.
You should plan to be able to handle data from users using any former schema version that you have published for your app.
===PAGE:core-concepts/schemas/codecs===
TITLE:core-concepts/schemas/codecs
DESCRIPTION:description: "Use z. codec to store arbitrary data types or classes in your schemas. ", }; You can use Zod `z.
description:
"Use z.codec to store arbitrary data types or classes in your schemas.",
};
# Codecs
You can use Zod `z.codec()` schemas to store arbitrary data types such as class instances within CoValues by defining custom encoders.
This allows you to directly use these data types within CoValues without having to do an extra manual conversion step.
## Using Zod codecs
To use a Zod `z.codec()` with Jazz, your encoder must encode the data into a JSON-compatible format.
This is means that the `Input` type shall map to the JSON-compatible type, and `Output` will map to your custom type.
```ts core-concepts/schemas/codecs/greeter.ts
```
Schemas that are not directly supported by Jazz such as `z.instanceof` are not re-exported by Jazz under the `z` object.
The full Zod API is exported under `z.z` if you need to use any of these schemas as part of a codec.
===PAGE:core-concepts/schemas/connecting-covalues===
TITLE:core-concepts/schemas/connecting-covalues
DESCRIPTION:description: "Learn how different CoValue types can reference the same data. " }; CoValues can form relationships with each other by **linking directly to other CoValues**. This creates a powerful connection where one CoValue can point to the unique identity of another.
description: "Learn how different CoValue types can reference the same data."
};
# Connecting CoValues with direct linking
CoValues can form relationships with each other by **linking directly to other CoValues**. This creates a powerful connection where one CoValue can point to the unique identity of another.
Instead of embedding all the details of one CoValue directly within another, you use its Jazz-Tools schema as the field type. This allows multiple CoValues to point to the same piece of data effortlessly.
```ts schema.ts
```
### Understanding CoList and CoFeed
- CoList is a collaborative list where each item is a reference to a CoValue
- CoFeed contains an append-only list of references to CoValues.
This direct linking approach offers a single source of truth. When you update a referenced CoValue, all other CoValues that point to it are automatically updated, ensuring data consistency across your application.
By connecting CoValues through these direct references, you can build robust and collaborative applications where data is consistent, efficient to manage, and relationships are clearly defined. The ability to link different CoValue types to the same underlying data is fundamental to building complex applications with Jazz.
===PAGE:core-concepts/schemas/schemaunions===
TITLE:core-concepts/schemas/schemaunions
DESCRIPTION:description: "Schema unions allow you to create types that can be one of several different schemas, similar to TypeScript union types. " }; Schema unions allow you to create types that can be one of several different schemas, similar to TypeScript union types. They use a discriminator field to determine which specific schema an instance represents at runtime, enabling type-safe polymorphism in your Jazz applications.
description: "Schema unions allow you to create types that can be one of several different schemas, similar to TypeScript union types."
};
# Schema Unions
Schema unions allow you to create types that can be one of several different schemas, similar to TypeScript union types.
They use a discriminator field to determine which specific schema an instance represents at runtime, enabling type-safe polymorphism in your Jazz applications.
The following operations are not available in schema unions:
* `$jazz.ensureLoaded` — use the union schema's `load` method, or narrow the type first
* `$jazz.subscribe` — use the union schema's `subscribe` method
* `$jazz.set` — use `$jazz.applyDiff`
## Creating schema unions
Schema unions are defined with `co.discriminatedUnion()` by providing an array of schemas and a discriminator field.
The discriminator field must be a `z.literal()`.
```ts schema.ts#SchemaUnion
```
To instantiate a schema union, just use the `create` method of one of the member schemas:
```ts index.ts#Dashboard
```
You can also use plain JSON objects, and let Jazz infer the concrete type from the discriminator field:
```ts index.ts#DashboardFromJSON
```
## Narrowing unions
When working with schema unions, you can access any property that is common to all members of the union.
To access properties specific to a particular union member, you need to narrow the type.
You can do this using a [TypeScript type guard](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) on the discriminator field:
```ts index.ts#NarrowingUnions
```
## Loading schema unions
You can load an instance of a schema union using its ID, without having to know its concrete type:
```ts index.ts#LoadWidget
```
## Nested schema unions
You can create complex hierarchies by nesting discriminated unions within other unions:
```ts schema.ts#NestedUnions
```
## Limitations with schema unions
Schema unions have some limitations that you should be aware of. They are due to TypeScript behaviour with type unions: when the type members of the union have methods with generic parameters, TypeScript will not allow calling those methods on the union type. This affects some of the methods on the `$jazz` namespace.
Note that these methods may still work at runtime, but their use is not recommended as you will lose type safety.
### `$jazz.ensureLoaded` and `$jazz.subscribe` require type narrowing
The `$jazz.ensureLoaded` and `$jazz.subscribe` methods are not supported directly on a schema union unless you first narrow the type using the discriminator.
### Updating union fields
You can't use `$jazz.set` to modify a schema union's fields (even if the field is present in all the union members).
Use `$jazz.applyDiff` instead.
===PAGE:core-concepts/subscription-and-loading===
TITLE:core-concepts/subscription-and-loading
DESCRIPTION:description: "Learn how to subscribe to CoValues, specify loading depths, and handle loading states and inaccessible data. " }; Jazz's Collaborative Values (such as [CoMaps](/docs/core-concepts/covalues/comaps) or [CoLists](/docs/core-concepts/covalues/colists)) are reactive. You can subscribe to them to automatically receive updates whenever they change, either locally or remotely.
description: "Learn how to subscribe to CoValues, specify loading depths, and handle loading states and inaccessible data."
};
# Subscriptions & Deep Loading
Jazz's Collaborative Values (such as [CoMaps](/docs/core-concepts/covalues/comaps) or [CoLists](/docs/core-concepts/covalues/colists)) are reactive. You can subscribe to them to automatically receive updates whenever they change, either locally or remotely.
You can also use subscriptions to load CoValues *deeply* by resolving nested values. You can specify exactly how much data you want to resolve and handle loading states and errors.
You can load and subscribe to CoValues in one of two ways:
- **shallowly** — all of the primitive fields are available (such as strings, numbers, dates), but the references to other CoValues are not loaded
- **deeply** — some or all of the referenced CoValues have been loaded
Jazz automatically deduplicates loading. If you subscribe to the same CoValue multiple times in your app, Jazz will only fetch it once. That means you don’t need to deeply load a CoValue *just in case* a child component might need its data, and you don’t have to worry about tracking every possible field your app needs in a top-level query. Instead, pass the CoValue ID to the child component and subscribe there — Jazz will only load what that component actually needs.
## Subscription Hooks
On your front-end, using a subscription hook is the easiest way to manage your subscriptions. The subscription and related clean-up is handled automatically, and you can use your data like any other piece of state in your app.
### Subscribe to CoValues
The `useCoState` hook allows you to reactively subscribe to CoValues in your React components. It will subscribe to updates when the component mounts and unsubscribe when it unmounts, ensuring your UI stays in sync and avoiding memory leaks.
The `CoState` class allows you to reactively subscribe to CoValues in your Svelte components. It will subscribe to updates when the component mounts and unsubscribe when it unmounts, ensuring your UI stays in sync and avoiding memory leaks.
} preferWrap>
```tsx ProjectView.tsx#Basic
```
} preferWrap>
```svelte ProjectView.svelte
```
**Note:** If you don't need to load a CoValue's references, you can choose to load it *shallowly* by omitting the resolve query.
### Subscribe to the current user's account
`useAccount` is similar to `useCoState`, but it returns the current user's account. You can use this at the top level of your app to subscribe to the current user's [account profile and root](/docs/core-concepts/schemas/accounts-and-migrations#covalues-as-a-graph-of-data-rooted-in-accounts).
`AccountCoState` is similar to `CoState`, but it returns the current user's account. You can use this at the top level of your app to subscribe to the current user's [account profile and root](/docs/core-concepts/schemas/accounts-and-migrations#covalues-as-a-graph-of-data-rooted-in-accounts).
} preferWrap>
```tsx ProjectList.tsx
```
} preferWrap>
```svelte ProjectList.svelte
```
### Loading States
When you load or subscribe to a CoValue through a hook (or directly), it can be either:
- **Loaded** → The CoValue has been successfully loaded and all its data is available
- **Not Loaded** → The CoValue is not yet available
You can use the `$isLoaded` field to check whether a CoValue is loaded. For more detailed information about why a CoValue is not loaded, you can check `$jazz.loadingState`:
- `"loading"` → The CoValue is still being fetched
- `"unauthorized"` → The current user doesn't have permission to access this CoValue
- `"unavailable"` → The CoValue couldn't be found or an error (e.g. a network timeout) occurred while loading
See the examples above for practical demonstrations of how to handle these three states in your application.
### Suspense Hooks [!framework=react,react-native,react-native-expo]
`useSuspenseCoState` and `useSuspenseAccount` are the suspense-enabled counterparts for `useCoState` and `useAccount`. They integrate with React's Suspense API by suspending component rendering while the requested value loads.
Once the component renders successfully, all of the data requested is guaranteed to be loaded and accessible. In case the requested data **can't** be loaded, the hook will throw an error during rendering.
To handle the various loading states, instead of checking `$isLoaded` or `$jazz.loadingState`, these hooks should be combined with `` and error boundaries.
```tsx ProjectView.tsx#Suspense
```
#### Use Suspense with Error Boundaries [!framework=react,react-native,react-native-expo]
An error boundary serves as a fallback for unrecoverable errors you cannot handle gracefully during rendering. If errors [can be handled locally](#loading-errors) in the component — for example, with `$onError: 'catch'` in a resolve query — it's generally better to do so.
As a rule of thumb, it’s best to wrap only your routes or major components in error boundaries.
When using an error boundary, you can handle errors thrown by the hooks by displaying a custom error message or UI. Jazz provides you with a `getJazzErrorType` helper which you can use in `getDerivedStateFromError` to allow you to determine what caused the error (for example, the CoValue may be unavailable, or the user may not have the appropriate permissions to access it).
[Check out an example error boundary here](https://github.com/garden-co/jazz/blob/main/examples/music-player/src/components/ErrorBoundary.tsx), or read more about them in the [React documentation](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
## Deep Loading
When you're working with related CoValues (like tasks in a project), you often need to load nested references as well as the top-level CoValue.
This is particularly the case when working with [CoMaps](/docs/core-concepts/covalues/comaps) that refer to other CoValues or [CoLists](/docs/core-concepts/covalues/colists) of CoValues. You can use `resolve` queries to tell Jazz what data you need to use.
### Using Resolve Queries
A `resolve` query tells Jazz how deeply to load data for your app to use. We can use `true` to tell Jazz to shallowly load the tasks list here. Note that this does *not* cause the tasks themselves to load, just the CoList that holds the tasks.
```ts ResolveQueries.ts#Basic
```
We can use an `$each` expression to tell Jazz to load the items in a list.
```ts ResolveQueries.ts#Each
```
We can also build a query that *deeply resolves* to multiple levels:
```ts ResolveQueries.ts#Deep
```
If you access a reference that wasn't included in your `resolve` query, you may find that it is already loaded, potentially because some other part of your app has already loaded it. **You should not rely on this**.
Expecting data to be there which is not explicitly included in your `resolve` query can lead to subtle, hard-to-diagnose bugs. Always include every nested CoValue you need to access in your `resolve` query.
### Where To Use Resolve Queries
The syntax for resolve queries is shared throughout Jazz. As well as using them in `load` and `subscribe` method calls, you can pass a resolve query to a front-end hook.
} preferWrap>
```tsx ProjectView.tsx#ShallowLoad
```
} preferWrap>
```svelte ShallowLoad.svelte
```
You can also specify resolve queries at the schema level, using the `.resolved()` method. These queries will be used when loading CoValues from that schema (if no resolve query is provided by the user) and in types defined with [co.loaded](/docs/core-concepts/subscription-and-loading#type-safety-with-coloaded).
```ts index.ts#ResolvedHelper
```
It is possible that in more complex cases, TypeScript cannot accurately infer types with circular references. For example, below, attempting to resolve `Project` on `Task` will in turn force `Project` to resolve `tasks`, which refers back to `Task`. This will result in an error.
```ts broken-schema.ts#BrokenSchema
```
In cases where you come across this, you can create a shallowly-loaded version of the schema which avoids loading the circular references.
```ts fixed-schema.ts#FixedSchema
```
## Loading Errors
A load operation will be successful **only** if all references requested (both optional and required) could be successfully loaded. If any reference cannot be loaded, the entire load operation will return a not-loaded CoValue to avoid potential inconsistencies.
```ts ResolveQueries.ts#Unauthorized
```
This is also true if **any** element of a list is inaccessible, even if all the others can be loaded.
```ts ResolveQueries.ts#NoCatch
```
Loading will be successful if all requested references are loaded. Non-requested references may or may not be available.
```ts ResolveQueries.ts#ShallowNoCatch
```
### Catching loading errors
We can use `$onError` to handle cases where some data you have requested is inaccessible, similar to a `try...catch` block in your query.
For example, in case of a `project` (which the user can access) with three `task` items:
Task
User can access task?
User can access task.description?
0
✅
✅
1
✅
❌
2
❌
❌
#### Scenario 1: Skip Inaccessible List Items
If some of your list items may not be accessible, you can skip loading them by specifying `$onError: 'catch'`. Inaccessible items will be not-loaded CoValues, while accessible items load properly.
```ts ResolveQueries.ts#SkipInaccessible
```
#### Scenario 2: Handling Inaccessible Nested References
An `$onError` applies only in the block where it's defined. If you need to handle multiple potential levels of error, you can nest `$onError` handlers.
This load will fail, because the `$onError` is defined only for the `task.description`, not for failures in loading the `task` itself.
```ts ResolveQueries.ts#NestedInaccessible
```
We can fix this by adding handlers at both levels
```ts ResolveQueries.ts#MultipleCatch
```
## Type safety with co.loaded
You can tell your application how deeply your data is loaded by using the `co.loaded` type.
The `co.loaded` type is especially useful when passing data between components, because it allows TypeScript to check at compile time whether data your application depends is properly loaded. The second argument lets you pass a `resolve` query to specify how deeply your data is loaded.
} preferWrap>
```ts core-concepts/subscription-and-loading/vanilla.ts
```
} preferWrap>
```tsx TaskList.tsx
```
} preferWrap>
```svelte TaskList.svelte
```
You can pass a `resolve` query of any complexity to `co.loaded`.
## Manual subscriptions
If you have a CoValue's ID, you can subscribe to it anywhere in your code using `CoValue.subscribe()`.
**Note:** Manual subscriptions are best suited for vanilla JavaScript — for example in server-side code or tests. Inside front-end components, we recommend using a subscription hook.
```ts index.ts#ManualSubscription
```
You can also subscribe to an existing CoValue instance using the `$jazz.subscribe` method.
```ts index.ts#SubscriptionInstanceMethod
```
### Error handling for manual subscriptions
You can pass an `onError` handler as an option to your subscription. This handler will run reactively whenever the CoValue you're subscribing to is inaccessible — for example, when the CoValue is unavailable (not found or network timeout) or when you do not have the appropriate permissions to read it. You can check `$jazz.loadingState` to determine the specific reason.
```ts error-handling-in-manual.ts#ErrorHandling
```
## Selectors [!framework=react,react-native,react-native-expo]
Sometimes, you only need to react to changes in specific parts of a CoValue. In those cases, you can provide a `select` function to specify what data you are interested in,
and an optional `equalityFn` option to control re-renders.
- `select`: extract the fields you care about
- `equalityFn`: (optional) control when data should be considered equal
```tsx ProjectView.tsx#Selector
```
By default, the return values of the select function will be compared using `Object.is`, but you can use the `equalityFn` to add your own logic.
You can also use `useAccount` in the same way, to subscribe to only the changes in a user's account you are interested in.
```tsx ProjectView.tsx#UseAccountWithSelector
```
### Avoiding Expensive Selectors
Selector functions optimise re-renders by only updating React state if the underlying CoValue has changed in a way that you care about.
However, the selector function itself still runs on every CoValue update, even if your `equalityFn` returns `true` and your component does not re-render. Because of this, you should **avoid doing expensive computation inside a selector**.
For expensive operations, use a lightweight selector which only tracks the minimum necessary to identify when the dependencies change, and run the expensive operations separately, wrapped in a `useMemo` hook.
This way, React can [batch state updates efficiently](https://react.dev/learn/queueing-a-series-of-state-updates) and only recompute the expensive memoised operation if the dependencies have changed.
```tsx ProjectView.tsx#ExpensiveSelector
```
## Ensuring data is loaded
In most cases, you'll have specified the depth of data you need in a `resolve` query when you first load or subscribe to a CoValue. However, sometimes you might have a CoValue instance which is not loaded deeply enough, or you're not sure how deeply loaded it is. In this case, you need to make sure data is loaded before proceeding with an operation. The `$jazz.ensureLoaded` method lets you guarantee that a CoValue and its referenced data are loaded to a specific depth (i.e. with nested references resolved):
```ts index.ts#EnsureLoaded
```
This can be useful if you have a shallowly loaded CoValue instance, and would like to load its references deeply.
## Loading multiple CoValues at the same time [!framework=react,react-native,react-native-expo]
When you need to load multiple CoValues dynamically (for example, when implementing pagination or loading a variable number of CoValues), React's Rules of Hooks can be challenging.
Since hooks can't be called conditionally or inside loops, you're forced to create a separate component for each CoValue you want to load.
The `useCoStates` and `useSuspenseCoStates` hooks solve this by accepting an array of CoValue IDs and returning the corresponding CoValues.
This allows you to load multiple CoValues in a single component.
- **`useCoStates`** — Returns `MaybeLoaded` CoValues, similar to `useCoState`. You should check `$isLoaded` before accessing the data.
- **`useSuspenseCoStates`** — Returns `Loaded` CoValues, similar to `useSuspenseCoState`. Use this with React Suspense boundaries.
Both hooks support the same options as their single-value counterparts, including `resolve` queries, `select` functions, and `equalityFn`.
```tsx ProjectView.tsx#ProjectViewWithPagination
```
## Best practices
- Load exactly what you need. Start shallow and add your nested references with care.
- Always check `$isLoaded` before accessing CoValue data. Use `$jazz.loadingState` for more detailed information.
- Use `$onError: 'catch'` at each level of your query that can fail to handle inaccessible data gracefully.
- Use selectors and an `equalityFn` to prevent unnecessary re-renders.
- Never rely on data being present unless it is requested in your `resolve` query.
===PAGE:core-concepts/subscription-and-loading/vanilla===
TITLE:core-concepts/subscription-and-loading/vanilla
DESCRIPTION:description: "Learn how to subscribe to CoValues, specify loading depths, and handle loading states and inaccessible data. " }; Jazz's Collaborative Values (such as [CoMaps](/docs/core-concepts/covalues/comaps) or [CoLists](/docs/core-concepts/covalues/colists)) are reactive. You can subscribe to them to automatically receive updates whenever they change, either locally or remotely.
description: "Learn how to subscribe to CoValues, specify loading depths, and handle loading states and inaccessible data."
};
# Subscriptions & Deep Loading
Jazz's Collaborative Values (such as [CoMaps](/docs/core-concepts/covalues/comaps) or [CoLists](/docs/core-concepts/covalues/colists)) are reactive. You can subscribe to them to automatically receive updates whenever they change, either locally or remotely.
You can also use subscriptions to load CoValues *deeply* by resolving nested values. You can specify exactly how much data you want to resolve and handle loading states and errors.
You can load and subscribe to CoValues in one of two ways:
- **shallowly** — all of the primitive fields are available (such as strings, numbers, dates), but the references to other CoValues are not loaded
- **deeply** — some or all of the referenced CoValues have been loaded
Jazz automatically deduplicates loading. If you subscribe to the same CoValue multiple times in your app, Jazz will only fetch it once. That means you don't need to deeply load a CoValue *just in case* you might need the data somewhere else in your app, and you don't have to worry about tracking every possible field your app needs in a top-level query. Instead, pass the CoValue ID to the child component and subscribe there — Jazz will only load what that component actually needs.
## Subscribing to changes
If you have a CoValue's ID, you can subscribe to it anywhere in your code using `CoValue.subscribe()`.
```ts core-concepts/subscription-and-loading/index.ts#ManualSubscription
```
You can also subscribe to an existing CoValue instance using the `$jazz.subscribe` method.
```ts core-concepts/subscription-and-loading/index.ts#SubscriptionInstanceMethod
```
### Loading States
When you load or subscribe to a CoValue through a hook (or directly), it can be either:
- **Loaded** → The CoValue has been successfully loaded and all its data is available
- **Not Loaded** → The CoValue is not yet available
You can use the `$isLoaded` field to check whether a CoValue is loaded. For more detailed information about why a CoValue is not loaded, you can check `$jazz.loadingState`:
- `"loading"` → The CoValue is still being fetched
- `"unauthorized"` → The current user doesn't have permission to access this CoValue
- `"unavailable"` → The CoValue couldn't be found or an error (e.g. a network timeout) occurred while loading
See the examples above for practical demonstrations of how to handle these three states in your application.
## Deep Loading
When you're working with related CoValues (like tasks in a project), you often need to load nested references as well as the top-level CoValue.
This is particularly the case when working with [CoMaps](/docs/core-concepts/covalues/comaps) that refer to other CoValues or [CoLists](/docs/core-concepts/covalues/colists) of CoValues. You can use `resolve` queries to tell Jazz what data you need to use.
### Using Resolve Queries
A `resolve` query tells Jazz how deeply to load data for your app to use. We can use `true` to tell Jazz to shallowly load the tasks list here. Note that this does *not* cause the tasks themselves to load, just the CoList that holds the tasks.
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#Basic
```
We can use an `$each` expression to tell Jazz to load the items in a list.
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#Each
```
We can also build a query that *deeply resolves* to multiple levels:
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#Deep
```
If you access a reference that wasn't included in your `resolve` query, you may find that it is already loaded, potentially because some other part of your app has already loaded it. **You should not rely on this**.
Expecting data to be there which is not explicitly included in your `resolve` query can lead to subtle, hard-to-diagnose bugs. Always include every nested CoValue you need to access in your `resolve` query.
### Where To Use Resolve Queries
The syntax for resolve queries is shared throughout Jazz. As well as using them in `load` and `subscribe` method calls, you can also specify resolve queries at the schema level, using the `.resolved()` method. These queries will be used when loading CoValues from that schema (if no resolve query is provided by the user) and in types defined with [co.loaded](/docs/core-concepts/subscription-and-loading#type-safety-with-coloaded).
```ts
const TaskWithDescription = Task.resolved({
description: true,
});
const ProjectWithTasks = Project.resolved({
tasks: {
// Use `.resolveQuery` to get the resolve query from a schema and compose it in other queries
$each: TaskWithDescription.resolveQuery,
}
});
// .load() will use the resolve query from the schema
const project = await ProjectWithTasks.load(projectId);
if (!project.$isLoaded) throw new Error("Project not found or not accessible");
// Both the tasks and the descriptions are loaded
project.tasks[0].description; // CoPlainText
```
## Loading Errors
A load operation will be successful **only** if all references requested (both optional and required) could be successfully loaded. If any reference cannot be loaded, the entire load operation will return a not-loaded CoValue to avoid potential inconsistencies.
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#Unauthorized
```
This is also true if **any** element of a list is inaccessible, even if all the others can be loaded.
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#NoCatch
```
Loading will be successful if all requested references are loaded. Non-requested references may or may not be available.
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#ShallowNoCatch
```
### Catching loading errors
We can use `$onError` to handle cases where some data you have requested is inaccessible, similar to a `try...catch` block in your query.
For example, in case of a `project` (which the user can access) with three `task` items:
Task
User can access task?
User can access task.description?
0
✅
✅
1
✅
❌
2
❌
❌
#### Scenario 1: Skip Inaccessible List Items
If some of your list items may not be accessible, you can skip loading them by specifying `$onError: 'catch'`. Inaccessible items will be not-loaded CoValues, while accessible items load properly.
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#SkipInaccessible
```
#### Scenario 2: Handling Inaccessible Nested References
An `$onError` applies only in the block where it's defined. If you need to handle multiple potential levels of error, you can nest `$onError` handlers.
This load will fail, because the `$onError` is defined only for the `task.description`, not for failures in loading the `task` itself.
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#NestedInaccessible
```
We can fix this by adding handlers at both levels
```ts core-concepts/subscription-and-loading/ResolveQueries.ts#MultipleCatch
```
## Type safety with co.loaded
You can tell your application how deeply your data is loaded by using the `co.loaded` type.
The `co.loaded` type is especially useful when passing data between components, because it allows TypeScript to check at compile time whether data your application depends is properly loaded. The second argument lets you pass a `resolve` query to specify how deeply your data is loaded.
} preferWrap>
```ts core-concepts/subscription-and-loading/vanilla.ts
```
} preferWrap>
```tsx core-concepts/subscription-and-loading/TaskList.tsx
```
} preferWrap>
```svelte core-concepts/subscription-and-loading/TaskList.svelte
```
You can pass a `resolve` query of any complexity to `co.loaded`.
## Ensuring data is loaded
In most cases, you'll have specified the depth of data you need in a `resolve` query when you first load or subscribe to a CoValue. However, sometimes you might have a CoValue instance which is not loaded deeply enough, or you're not sure how deeply loaded it is. In this case, you need to make sure data is loaded before proceeding with an operation. The `$jazz.ensureLoaded` method lets you guarantee that a CoValue and its referenced data are loaded to a specific depth (i.e. with nested references resolved):
```ts core-concepts/subscription-and-loading/index.ts#EnsureLoaded
```
This can be useful if you have a shallowly loaded CoValue instance, and would like to load its references deeply.
## Best practices
- Load exactly what you need. Start shallow and add your nested references with care.
- Always check `$isLoaded` before accessing CoValue data. Use `$jazz.loadingState` for more detailed information.
- Use `$onError: 'catch'` at each level of your query that can fail to handle inaccessible data gracefully.
- Use selectors and an `equalityFn` to prevent unnecessary re-renders.
- Never rely on data being present unless it is requested in your `resolve` query.
===PAGE:core-concepts/sync-and-storage===
TITLE:core-concepts/sync-and-storage
DESCRIPTION:description: "Learn how to sync and persist your data using Jazz Cloud, or run your own sync server. " }; For sync and storage, you can either use Jazz Cloud for zero-config magic, or run your own sync server. Replace the API key in the sync server URL with your API key.
description: "Learn how to sync and persist your data using Jazz Cloud, or run your own sync server."
};
# Sync and storage: Jazz Cloud or self-hosted
For sync and storage, you can either use Jazz Cloud for zero-config magic, or run your own sync server.
## Using Jazz Cloud
Replace the API key in the sync server URL with your API key.
```ts
"peer": `wss://cloud.jazz.tools/?key=${YOUR_API_KEY}`
```
Replace the API key in the Jazz provider sync server URL with your API key:
} preferWrap>
```tsx vanilla.ts
```
} preferWrap>
```tsx react-snippet.tsx#Basic
```
} preferWrap>
```tsx rn.tsx#Basic
```
} preferWrap>
```tsx rn.tsx#Expo
```
} preferWrap>
```svelte svelte.svelte
```
Jazz Cloud will
- sync CoValues in real-time between users and devices
- safely persist CoValues on redundant storage nodes with additional backups
- make use of geographically distributed cache nodes for low latency
### Free public alpha
- Jazz Cloud is free during the public alpha, with no strict usage limits
- We plan to keep a free tier, so you'll always be able to get started with zero setup
- See [Jazz Cloud pricing](/pricing) for more details
## Self-hosting your sync server
You can run your own sync server using:
```sh
npx jazz-run sync
```
And then use `ws://localhost:4200` as the sync server URL.
You can also run this simple sync server behind a proxy that supports WebSockets, for example to provide TLS.
In this case, provide the WebSocket endpoint your proxy exposes as the sync server URL.
Requires at least Node.js v20.
See our [Troubleshooting Guide](/docs/troubleshooting) for quick fixes.
### Command line options:
- `--host` / `-h` - the host to run the sync server on. Defaults to 127.0.0.1.
- `--port` / `-p` - the port to run the sync server on. Defaults to 4200.
- `--in-memory` - keep CoValues in-memory only and do sync only, no persistence. Persistence is enabled by default.
- `--db` - the path to the file where to store the data (SQLite). Defaults to `sync-db/storage.db`.
### Source code
The implementation of this simple sync server is available open-source [on GitHub](https://github.com/garden-co/jazz/blob/main/packages/jazz-run/src/startSyncServer.ts).
===PAGE:index===
TITLE:index
DESCRIPTION:title: "Documentation", }; **Jazz is a new kind of database** that's **distributed** across your frontend, containers, serverless functions and its own storage cloud. It syncs structured data, files and LLM streams instantly, and looks like local reactive JSON state. It also provides auth, orgs & teams, real-time multiplayer, edit histories, permissions, E2E encryption and offline-support out of the box.
title: "Documentation",
};
# Learn some Jazz
**Jazz is a new kind of database** that's **distributed** across your frontend, containers, serverless functions and its own storage cloud.
It syncs structured data, files and LLM streams instantly, and looks like local reactive JSON state.
It also provides auth, orgs & teams, real-time multiplayer, edit histories, permissions, E2E encryption and offline-support out of the box.
---
## Quickstart
### Show me
[Check out our tiny To Do list example](/docs#a-minimal-jazz-app) to see what Jazz can do in a nutshell.
### Help me understand
Follow our [quickstart guide](/docs/quickstart) for a more detailed guide on building a simple app with Jazz.
### Just want to get started?
You can use [`create-jazz-app`](/docs/tooling-and-resources/create-jazz-app) to create a new Jazz project from one of our starter templates or example apps:
```sh
npx create-jazz-app@latest --api-key you@example.com
```
**Using an LLM?** Add our llms.txt to your context window!
**Using an LLM?** Add our llms.txt to your context window!
**Using an LLM?** Add our llms.txt to your context window!
**Using an LLM?** Add our llms.txt to your context window!
**Using an LLM?** Add our llms.txt to your context window!
Requires at least Node.js v20.
See our [Troubleshooting Guide](/docs/troubleshooting) for quick fixes.
## How it works
1. **Define your data** with CoValues schemas
2. **Connect to storage infrastructure** (Jazz Cloud or self-hosted)
3. **Create and edit CoValues** locally
4. **Get automatic sync and persistence** across all devices and users
Your UI updates instantly on every change, everywhere. It's like having reactive local state that happens to be shared with the world.
## A Minimal Jazz App
Here, we'll scratch the surface of what you can do with Jazz. We'll build a quick and easy To Do list app — easy to use, easy to build, and easy to make comparisons with!
This is the end result: we're showing it here running in two iframes, updating in real-time through the Jazz Cloud.
Try adding items on the left and watch them appear instantly on the right!
These two iframes are syncing through the Jazz Cloud. You can use the toggle in the top right to switch between 'online' and 'offline' on each client, and see how with Jazz, you can keep working even when you're offline.
### Imports
Start by importing Jazz into your app.
```ts index.ts#Imports
```
### Schema
Then, define what your data looks like using [Collaborative Values](/docs/core-concepts/covalues/overview) — the building blocks that make Jazz apps work.
```ts index.ts#InlineSchema
```
### Context
Next, [give your app some context](/docs/project-setup#give-your-app-context) and tell Jazz your sync strategy — use the Jazz Cloud to get started quickly. We'll also create our to do list and get its ID here to use later.
```ts routing.ts#Context
```
### Build your UI
Now, build a basic UI skeleton for your app.
```ts index.ts#BuildUI
```
### Display Items
Display your items and add logic to mark them as done...
```ts index.ts#toDoItemElement
```
### Add New Items
...and add new items to the list using an input and a button.
```ts index.ts#newToDoFormElement
```
### Subscribe to Changes
Now for the magic: listen to changes coming from [**anyone, anywhere**](/docs/permissions-and-sharing/overview), and update your UI in real time.
```ts index.ts#SubscribeToChanges
```
### Simple Routing
Lastly, we'll add a tiny bit of routing logic to be able to share the list by URL: if there's an `id` search parameter, that'll be the list we'll subscribe to later. If we don't have an `id`, we'll [create a new ToDo list](/docs/core-concepts/covalues/colists#creating-colists). We'll replace the section where we created the `ToDoList` above.
```ts routing.ts#SimpleRouting
```
### All Together
Put it all together for a simple Jazz app in less than 100 lines of code.
```ts index.ts
```
## Want to see more?
Have a look at our [example apps](/examples) for inspiration and to see what's possible with Jazz. From real-time chat and collaborative editors to file sharing and social features — these are just the beginning of what you can build.
If you have any questions or need assistance, please don't hesitate to reach out to us on [Discord](https://discord.gg/utDMjHYg42). We'd love to help you get started.
===PAGE:key-features/authentication/authentication-states===
TITLE:key-features/authentication/authentication-states
DESCRIPTION:description: "Learn about Jazz's authentication states: anonymous, guest, and fully authenticated. " }; Jazz provides three distinct authentication states that determine how users interact with your app: **Anonymous Authentication**, **Guest Mode**, and **Authenticated Account**. When a user loads a Jazz application for the first time, we create a new Account by generating keys and storing them locally: - Users have full accounts with unique IDs - Data persists between sessions on the same device - Can be upgraded to a full account (passkey, passphrase, etc.
description: "Learn about Jazz's authentication states: anonymous, guest, and fully authenticated."
};
# Authentication States
Jazz provides three distinct authentication states that determine how users interact with your app: **Anonymous Authentication**, **Guest Mode**, and **Authenticated Account**.
## Anonymous Authentication
When a user loads a Jazz application for the first time, we create a new Account by generating keys and storing them locally:
- Users have full accounts with unique IDs
- Data persists between sessions on the same device
- Can be upgraded to a full account (passkey, passphrase, etc.)
- Data syncs across the network (if enabled)
## Authenticated Account
**Authenticated Account** provides full multi-device functionality:
- Persistent identity across multiple devices
- Full access to all application features
- Data can sync across all user devices
- Multiple authentication methods available
## Guest Mode
**Guest Mode** provides a completely accountless context:
- No persistent identity or account
- Only provides access to publicly readable content
- Cannot save or sync user-specific data
- Suitable for read-only access to public resources
## Detecting Authentication State
You can detect the current authentication state using `useAgent` and `useIsAuthenticated`.
} label="Vanilla" value="vanilla" preferWrap>
```tsx vanilla.ts#Basic
```
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#Basic
```
} label="Svelte" value="svelte" preferWrap>
```svelte svelte.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx rn.tsx#Basic
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx expo.tsx#Basic
```
## Migrating data from anonymous to authenticated account
When a user signs up, their anonymous account is transparently upgraded to an authenticated account, preserving all their data.
However, if a user has been using your app anonymously and later logs in with an existing account, their anonymous account data would normally be discarded. To prevent data loss, you can use the `onAnonymousAccountDiscarded` handler.
This example from our [music player example app](https://github.com/garden-co/jazz/tree/main/examples/music-player) shows how to migrate data:
```ts schema.ts#OnAnonymousAccountDiscarded
```
To see how this works, try uploading a song in the [music player demo](https://music.demo.jazz.tools/) and then log in with an existing account.
## Provider Configuration for Authentication
You can configure how authentication states work in your app with the [JazzReactProvider](/docs/project-setup/providers/). The provider offers several options that impact authentication behavior:
- `guestMode`: Enable/disable Guest Mode
- `onAnonymousAccountDiscarded`: Handle data migration when switching accounts
- `sync.when`: Control when data synchronization happens
- `defaultProfileName`: Set default name for new user profiles
For detailed information on all provider options, see [Provider Configuration options](/docs/project-setup/providers/#additional-options).
## Controlling sync for different authentication states
You can control network sync with [Providers](/docs/project-setup/providers/) based on authentication state:
- `when: "always"`: Sync is enabled for both Anonymous Authentication and Authenticated Account
- `when: "signedUp"`: Sync is enabled when the user is authenticated
- `when: "never"`: Sync is disabled, content stays local
} label="Vanilla" value="vanilla" preferWrap>
```tsx vanilla.ts#SyncSettings
```
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#SyncSettings
```
} label="Svelte" value="svelte" preferWrap>
```svelte provider-sync.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx rn.tsx#SyncSettings
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx expo.tsx#SyncSettings
```
### Disable sync for Anonymous Authentication
You can disable network sync to make your app local-only under specific circumstances.
For example, you may want to give users with Anonymous Authentication the opportunity to try your app locally-only (incurring no sync traffic), then enable network sync only when the user is fully authenticated.
} label="Vanilla" value="vanilla" preferWrap>
```tsx vanilla.ts#DisableAnonSync
```
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#DisableAnonSync
```
} label="Svelte" value="svelte" preferWrap>
```svelte disable-anon-sync.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx rn.tsx#DisableAnonSync
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx expo.tsx#DisableAnonSync
```
On iOS Jazz persists login credentials using Secure Store, which saves them to the Keychain. While this is secure, iOS does not clear the credentials along with other data if a user uninstalls your app.
User accounts *are* deleted, so if you are using `sync: 'never'` or `sync: 'signedUp'`, accounts for users who are not 'signed up' will be lost.
If the user later re-installs your app, the credentials for the deleted account remain in the Keychain. Jazz will attempt to use these to sign in and fail, as the account no longer exists.
To avoid this, consider using `sync: 'always'` for your iOS users.
If you want to offer a fully local mode, you can work around this by using the `Settings` API. Settings stored using the `Settings` API *are* cleared on an application uninstallation, which allows us to scope the credentials for the current installation. If the user uninstalls and reinstalls your app, the credentials will be regenerated correctly.
```tsx expo.tsx#IsolateStorage
```
### Configuring Guest Mode Access [!framework=react,react-native,react-native-expo,svelte]
You can configure Guest Mode access with the `guestMode` prop for [Providers](/docs/project-setup/providers/).
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#GuestModeAccess
```
} label="Svelte" value="svelte" preferWrap>
```svelte guest-mode.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx rn.tsx#GuestModeAccess
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx expo.tsx#GuestModeAccess
```
For more complex behaviours, you can manually control sync by statefully switching when between `"always"` and `"never"`.
===PAGE:key-features/authentication/better-auth===
TITLE:key-features/authentication/better-auth
DESCRIPTION:description: "Integrate Better Auth with your Jazz app to authenticate users. " }; [Better Auth](https://better-auth. com/) is a self-hosted, framework-agnostic authentication and authorisation framework for TypeScript.
description: "Integrate Better Auth with your Jazz app to authenticate users."
};
# Better Auth authentication
[Better Auth](https://better-auth.com/) is a self-hosted, framework-agnostic authentication and authorisation framework for TypeScript.
You can integrate Better Auth with your Jazz app, allowing your Jazz user's account keys to be saved with the corresponding Better Auth user.
## How it works
When using Better Auth authentication:
1. Users sign up or sign in through Better Auth's authentication system
2. Jazz securely stores the user's account keys with Better Auth
3. When logging in, Jazz retrieves these keys from Better Auth
4. Once authenticated, users can work offline with full Jazz functionality
This authentication method is not fully local-first, as login and signup need to be done online, but once authenticated, users can use all of Jazz's features without needing to be online.
## Authentication methods and plugins
Better Auth supports several authentication methods and plugins. The Jazz plugin has not been tested with all of them yet. Here is the compatibility matrix:
Better Auth method/plugin
Jazz plugin
Email/Password
✅
Social Providers
✅
Username
❓
Anonymous
❓
Phone Number
❓
Magic Link
❓
Email OTP
✅
Passkey
❓
One Tap
❓
✅: tested and working
❓: not tested yet
❌: not supported
## Getting started
First of all, follow the [Better Auth documentation](https://www.better-auth.com/docs/installation) to install Better Auth:
- Install the dependency and set env variables
- Create the betterAuth instance in the common `auth.ts` file, using the database adapter you want. {/* here the assist for next Jazz database adapter */}
- Set up the authentication methods you want to use
- Mount the handler in the API route
- Create the client instance in the common `auth-client.ts` file
The `jazz-tools/better-auth/auth` plugin provides both server-side and client-side integration for Better Auth with Jazz. Here's how to set it up:
### Server Setup
Add the `jazzPlugin` to the Better Auth instance:
src/lib/auth.ts
```ts auth.ts
```
Now run [migrations](https://www.better-auth.com/docs/concepts/database#running-migrations) to add the new fields to the users table.
The server-side plugin intercepts the custom header `x-jazz-auth` sent by client-side plugin. If server is behind a proxy, the header must be forwarded. If the server runs on a different origin than the client, the header must be allowed for cross-origin requests.
### Client Setup
Create the Better Auth client with the Jazz plugin:
src/lib/auth-client.ts
{/* TODO: 'use client' only for React */}
```ts auth-client.ts
```
Register your Jazz context with the Better Auth plugin.
Wrap your app with the `AuthProvider`, passing the `betterAuthClient` instance:
} label="Vanilla" value="vanilla" preferWrap>
```ts index.ts#Basic
```
} label="React" value="react" preferWrap>
```ts app.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte app.svelte
```
The AuthProvider component uses the `better-auth/client` package.
To verify the authentication state in your app, see Authentication states.
## Authentication methods
The Jazz plugin intercepts the Better Auth client's calls, so you can use the Better Auth [methods](https://www.better-auth.com/docs/basic-usage) as usual.
Here is how to sign up with email and password, and transform an anonymous Jazz account into a logged in user authenticated by Better Auth:
```ts index.ts#SignUp
```
You can then use the `signIn` and `signOut` methods on the `betterAuthClient`:
```ts index.ts#SignInOut
```
## Authentication states
Although Better Auth is not fully local-first, the Jazz client plugin tries to keep Jazz's authentication state in sync with Better Auth's. The best practice to check if the user is authenticated is using Jazz's methods [as described here](/docs/key-features/authentication/authentication-states#detecting-authentication-state).
You can use Better Auth's [native methods](https://www.better-auth.com/docs/basic-usage#session) if you need to check the Better Auth state directly.
## Server-side hooks
Better Auth provides [database hooks](https://www.better-auth.com/docs/reference/options#databasehooks) to run code when things happen. When using the Jazz, the user's Jazz account ID is always available in the `user` object. This means you can access it anywhere in Better Auth hooks.
```ts auth.ts#WithHooks
```
===PAGE:key-features/authentication/better-auth-database-adapter===
TITLE:key-features/authentication/better-auth-database-adapter
DESCRIPTION:The package `jazz-tools/better-auth/database-adapter` is a database adapter for Better Auth based on Jazz. Better Auth's data will be stored in CoValues encrypted by [Server Worker](/docs/server-side/setup), synced on our distributed [cloud infrastructure](https://dashboard. jazz.
# Jazz database adapter for Better Auth
The package `jazz-tools/better-auth/database-adapter` is a database adapter for Better Auth based on Jazz. Better Auth's data will be stored in CoValues encrypted by [Server Worker](/docs/server-side/setup), synced on our distributed [cloud infrastructure](https://dashboard.jazz.tools).
## Getting started
1. Install and configure [Better Auth](https://www.better-auth.com/docs/installation)
2. Install Jazz package `pnpm jazz-tools`
3. Generate a [worker's credentials](/docs/server-side/setup#generating-credentials)
```bash
npx jazz-run account create --name "Better Auth Server Worker"
```
Although all workers have the same capabilities, we recommend to use different workers for different purposes. As it will store user's credentials, the best practice is to keep it isolated from other workers.
4. Setup the database adapter on Better Auth server instance.
```ts auth.ts
```
5. You're ready to use Better Auth features without managing any database by yourself!
## How it works
The adapter automatically creates Jazz schemas from Better Auth's database schema, even if not all the SQL-like features are supported yet. The database is defined as a CoMap with two properties: `group` and `tables`. The first one contains the master Group that will own all the tables; the second one is a CoMap with table names as keys and data as values.
Internally it uses specialized repository for known models like `User`, `Session` and `Verification`, to add indexes and boost performances on common operations.
## How to access the database
The easiest way to access the database is using the same Server Worker's credentials and access the table we're looking for.
```ts index.ts
```
## Rotating the worker's credentials
If you need to change the worker, you can create a new one and add it to the master Group.
```ts rotate-worker.ts
```
Rotating keys means that data stored from that point forward will be encrypted with the new key, but the old worker's secret can still read data written up until the rotation. Read more about encryption in [Server Worker](/docs/reference/encryption).
## Compatibility
The adapter generates Jazz schemas reading from Better Auth's database schema, so it should be compatible with any plugin / user's code that introduces new tables or extends the existing ones.
So far, the adapter has been tested with **Better Auth v1.3.7** with the following plugins:
More features and plugins will be tested in the future.
===PAGE:key-features/authentication/clerk===
TITLE:key-features/authentication/clerk
DESCRIPTION:description: "Integrate Clerk with your Jazz app to authenticate users. This method combines Clerk's comprehensive authentication services with Jazz's local-first capabilities. " }; Jazz can be integrated with [Clerk](https://clerk.
description: "Integrate Clerk with your Jazz app to authenticate users. This method combines Clerk's comprehensive authentication services with Jazz's local-first capabilities."
};
# Clerk Authentication
Jazz can be integrated with [Clerk](https://clerk.com/) to authenticate users. This method combines Clerk's comprehensive authentication services with Jazz's local-first capabilities.
## How it works
When using Clerk authentication:
1. Users sign up or sign in through Clerk's authentication system
2. Jazz securely stores the user's account keys with Clerk
3. When logging in, Jazz retrieves these keys from Clerk
4. Once authenticated, users can work offline with full Jazz functionality
This authentication method is not fully local-first, as login and signup need to be done online, but once authenticated, users can use all of Jazz's features without needing to be online.
## Key benefits
- **Rich auth options**: Email/password, social logins, multi-factor authentication
- **User management**: Complete user administration dashboard
- **Familiar sign-in**: Standard auth flows users already know
- **OAuth providers**: Google, GitHub, and other popular providers
- **Enterprise features**: SSO, SAML, and other advanced options
## Implementation
Use `` to wrap your app:
Use `` to wrap your app.
The Clerk provider for Svelte depends on the community package `svelte-clerk`. Install it first:
```sh
npm install svelte-clerk
```
```sh
pnpm add svelte-clerk
```
Then wrap your app with `ClerkProvider` and a wrapper component that uses ``:
App.tsx
App.tsx
src/routes/+layout.svelte
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#Basic
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx expo.tsx#Basic
```
} label="Svelte" value="svelte" preferWrap>
```svelte svelte.svelte
```
src/lib/components/JazzClerkWrapper.svelte
```svelte JazzClerkWrapper.svelte
```
AuthButton.tsx
AuthButton.tsx
src/lib/components/AuthButton.svelte
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#AuthButton
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx expo.tsx#AuthButton
```
} label="Svelte" value="svelte" preferWrap>
```svelte AuthButton.svelte
```
## Examples
You can explore Jazz with Clerk integration in our [example projects](/examples). For more Clerk-specific demos, visit [Clerk's documentation](https://clerk.com/docs).
## When to use Clerk
Clerk authentication is ideal when:
- You need an existing user management system
- You want to integrate with other Clerk features (roles, permissions)
- You require email/password authentication with verification
- You need OAuth providers (Google, GitHub, etc.)
- You want to avoid users having to manage passphrases
## Limitations and considerations
- **Online requirement**: Initial signup/login requires internet connectivity
- **Third-party dependency**: Relies on Clerk's services for authentication
- **Not fully local-first**: Initial authentication requires a server
- **Platform support**: Not available on all platforms
## Additional resources
- [Clerk Documentation](https://clerk.com/docs)
===PAGE:key-features/authentication/clerk/react-native===
TITLE:key-features/authentication/clerk/react-native
DESCRIPTION:description: "We do not currently support Clerk in React Native, but we do have support for React Native Expo. " }; We do not currently support Clerk in React Native, but we do have support for [React Native Expo](/docs/react-native-expo/key-features/authentication/clerk).
description: "We do not currently support Clerk in React Native, but we do have support for React Native Expo."
};
# Clerk Authentication
We do not currently support Clerk in React Native, but we do have support for [React Native Expo](/docs/react-native-expo/key-features/authentication/clerk).
===PAGE:key-features/authentication/overview===
TITLE:key-features/authentication/overview
DESCRIPTION:description: "Learn about the different authentication methods that you can use with your Jazz app. " }; Jazz authentication is based on cryptographic keys ("Account keys"). Their public part represents a user's identity, their secret part lets you act as that user.
description: "Learn about the different authentication methods that you can use with your Jazz app."
};
# Authentication in Jazz
Jazz authentication is based on cryptographic keys ("Account keys"). Their public part represents a user's identity, their secret part lets you act as that user.
## Authentication Flow
When a user first opens your app, they'll be in one of these states:
- **Anonymous Authentication**: Default starting point where Jazz automatically creates a local account on first visit. Data persists on one device and can be upgraded to a full account.
- **Authenticated Account**: Full account accessible across multiple devices using [passkeys](/docs/key-features/authentication/passkey), [passphrases](/docs/key-features/authentication/passphrase), or third-party authentications, such as [Clerk](/docs/key-features/authentication/clerk).
- **Guest Mode**: No account, read-only access to public content. Users can browse but can't save data or sync.
Learn more about these states in the [Authentication States](/docs/key-features/authentication/authentication-states) documentation.
Without authentication, users are limited to using the application on only one device.
When a user logs out of an Authenticated Account, they return to the Anonymous Authentication state with a new local account.
Here's what happens during registration and login:
- **Register**: When a user registers with an authentication provider, their Anonymous account credentials are stored in the auth provider, and the account is marked as Authenticated. The user keeps all their existing data.
- **Login**: When a user logs in with an authentication provider, their Anonymous account is discarded and the credentials are loaded from the auth provider. Data from the Anonymous account can be transferred using the [onAnonymousAccountDiscarded handler](/docs/key-features/authentication/authentication-states#migrating-data-from-anonymous-to-authenticated-account).
## Available Authentication Methods
Jazz provides several ways to authenticate users:
- [**Passkeys**](/docs/key-features/authentication/passkey): Secure, biometric authentication using WebAuthn
- [**Passphrases**](/docs/key-features/authentication/passphrase): Bitcoin-style word phrases that users store
- [**Clerk Integration**](/docs/key-features/authentication/clerk): Third-party authentication service with OAuth support
- [**Better Auth**](/docs/key-features/authentication/better-auth): Self-hosted authentication service
**Note**:
For serverless authentication methods (passkey, passphrase), Jazz stores your account's credentials in your browser's local storage. This avoids needing to reauthenticate on every page load, but means you must take extra care to avoid [XSS attacks](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS). In particular, you should take care to [sanitise user input](https://github.com/cure53/DOMPurify), set [appropriate CSP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP), and avoid third-party JavaScript wherever possible.
===PAGE:key-features/authentication/passkey===
TITLE:key-features/authentication/passkey
DESCRIPTION:description: "Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides because keys are managed by the device/operating system itself. " }; Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides because keys are managed by the device/operating system itself. Passkey authentication is based on the [Web Authentication API](https://developer.
description: "Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides because keys are managed by the device/operating system itself."
};
# Passkey Authentication
Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides because keys are managed by the device/operating system itself.
## How it works
Passkey authentication is based on the [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) and uses familiar FaceID/TouchID flows that users already know how to use.
## Key benefits
- **Most secure**: Keys are managed by the device/OS
- **User-friendly**: Uses familiar biometric verification (FaceID/TouchID)
- **Cross-device**: Works across devices with the same biometric authentication
- **No password management**: Users don't need to remember or store anything
- **Wide support**: Available in most modern browsers and mobile platforms
## Implementation
Using passkeys in Jazz is as easy as this:
} label="Vanilla" value="vanilla" preferWrap>
```ts AuthModal.ts
```
} label="React" value="react" preferWrap>
```tsx AuthModal.tsx#Basic
```
} label="Svelte" value="svelte" preferWrap>
```svelte AuthModal.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx AuthModalRN.tsx#Basic
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx AuthModalRN.tsx#Basic
```
### React Native Setup
Passkey authentication on React Native requires the `react-native-passkey` library and domain configuration:
1. Install the peer dependency:
```bash
npm install react-native-passkey
```
2. Configure your app's associated domains:
- **iOS**: Add an Associated Domains entitlement with `webcredentials:yourdomain.com` and host an Apple App Site Association (AASA) file at `https://yourdomain.com/.well-known/apple-app-site-association`
- **Android**: Host a Digital Asset Links file at `https://yourdomain.com/.well-known/assetlinks.json`
3. **For React Native 0.76+ with New Architecture**: The `react-native-passkey` library uses the legacy `NativeModules` bridge pattern. You need to disable bridgeless mode while keeping New Architecture enabled.
Add this override to your `AppDelegate.swift`:
```
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
// ... existing methods ...
override func bridgelessEnabled() -> Bool {
return false
}
}
```
See the [react-native-passkey documentation](https://github.com/f-23/react-native-passkey) for detailed setup instructions.
## Examples
You can try passkey authentication using our [passkey example](https://passkey.demo.jazz.tools/) or the [music player demo](https://music.demo.jazz.tools/).
## When to use Passkeys
Passkeys are ideal when:
- Security is a top priority
- You want the most user-friendly authentication experience
- You're targeting modern browsers and devices
- You want to eliminate the risk of password-based attacks
## Limitations and considerations
- Requires hardware/OS support for biometric authentication
- Not supported in older browsers (see browser support below)
- Requires a fallback method for unsupported environments
### React Native Limitations
- Requires `react-native-passkey` peer dependency
- Passkeys require domain verification (AASA for iOS, assetlinks.json for Android)
- Not available in Expo Go (requires a development build)
- React Native 0.76+ with New Architecture requires disabling bridgeless mode (see setup instructions above)
- Android builds may hang at `configureCMakeDebug` with NDK 27.x - use NDK 28.2+ to avoid this
### Browser Support
[Passkeys are supported in most modern browsers](https://caniuse.com/passkeys).
For older browsers, we recommend using [passphrase authentication](/docs/key-features/authentication/passphrase) as a fallback.
## Additional resources
For more information about the Web Authentication API and passkeys:
- [WebAuthn.io](https://webauthn.io/)
- [MDN Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)
===PAGE:key-features/authentication/passphrase===
TITLE:key-features/authentication/passphrase
DESCRIPTION:description: "Passphrase authentication lets users log into any device using a recovery phrase consisting of multiple words (similar to cryptocurrency wallets). " }; Passphrase authentication lets users log into any device using a recovery phrase consisting of multiple words (similar to cryptocurrency wallets). Users are responsible for storing this passphrase safely.
description: "Passphrase authentication lets users log into any device using a recovery phrase consisting of multiple words (similar to cryptocurrency wallets). "
};
# Passphrase Authentication
Passphrase authentication lets users log into any device using a recovery phrase consisting of multiple words (similar to cryptocurrency wallets). Users are responsible for storing this passphrase safely.
## How it works
When a user creates an account with passphrase authentication:
1. Jazz generates a unique recovery phrase derived from the user's cryptographic keys
2. This phrase consists of words from a wordlist
3. Users save this phrase and enter it when logging in on new devices
You can use one of the ready-to-use wordlists from the [BIP39 repository](https://github.com/bitcoinjs/bip39/tree/a7ecbfe2e60d0214ce17163d610cad9f7b23140c/src/wordlists) or create your own. If you do decide to create your own wordlist, it's recommended to use at least 2048 unique words (or some higher power of two).
## Key benefits
- **Portable**: Works across any device, even without browser or OS support
- **User-controlled**: User manages their authentication phrase
- **Flexible**: Works with any wordlist you choose
- **Offline capable**: No external dependencies
## Implementation
You can implement passphrase authentication in your application quickly and easily:
} label="Vanilla" value="vanilla" preferWrap>
```ts AuthModal.ts#PassphraseAuth
```
} label="React" value="react" preferWrap>
```tsx AuthModal.tsx#Basic
```
} label="Svelte" value="svelte" preferWrap>
```svelte AuthModal.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx AuthModalRN.tsx#Basic
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx AuthModalRN.tsx#Basic
```
## Examples
You can see passphrase authentication in our [passphrase example](https://passphrase.demo.jazz.tools/) or the [todo list demo](https://todo.demo.jazz.tools/).
## When to use Passphrases
Passphrase authentication is ideal when:
- You need to support older browsers without WebAuthn capabilities
- Your users need to access the app on many different devices
- You want a fallback authentication method alongside passkeys
## Limitations and considerations
- **User responsibility**: Users must securely store their passphrase
- **Recovery concerns**: If a user loses their passphrase, they cannot recover their account
- **Security risk**: Anyone with the passphrase can access the account
- **User experience**: Requires users to enter a potentially long phrase
Make sure to emphasize to your users:
1. Store the passphrase in a secure location (password manager, written down in a safe place)
2. The passphrase is the only way to recover their account
3. Anyone with the passphrase can access the account
===PAGE:key-features/authentication/quickstart===
TITLE:key-features/authentication/quickstart
DESCRIPTION:description: "Add authentication to your Jazz app. " }; This guide will show you how you can access your data on multiple devices by signing in to your app. If you haven't gone through the [front-end Quickstart](/docs/quickstart), you might find this guide a bit confusing, as it continues from there.
description: "Add authentication to your Jazz app."
};
# Add Authentication to your App
This guide will show you how you can access your data on multiple devices by signing in to your app.
If you haven't gone through the [front-end Quickstart](/docs/quickstart), you might find this guide a bit confusing, as it continues from there. If you're looking for a quick reference, you might find [this page](/docs/key-features/authentication/overview) or our [Passkey Auth example app](https://github.com/gardencmp/jazz/tree/main/starters/react-passkey-auth) more helpful!
## Add passkey authentication
Jazz ships with everything you need to get started with authenticating users using passkeys. We'll add a simple sign-up form at the top of our app, allowing users to register a new passkey or log in with an existing one.
Jazz has a built-in passkey authentication component that you can use to add authentication to your app. This is the easiest way to get started with securely authenticating users into your application. By adding this component, when users access your app, they'll be greeted with an input where they can enter their name, and create a passkey.
First, we need to setup an auth instance, and provide it with some details from our current context, as well as a name for our app.
src/main.ts
```ts main.ts#AuthInstance
```
Then, we'll create the sign-up form itself:
app/components/JazzWrapper.tsx
src/routes/+layout.svelte
} preferWrap>
```ts main.ts#SignUpForm
```
} preferWrap>
```tsx JazzWrapper.tsx
```
} preferWrap>
```svelte +layout.svelte
```
Already completed the server-side rendering guide?
You'll need to make a couple of small changes to your structure in order for this to work on the server. In particular, we only want to display the passkey auth UI on the client, otherwise, we should just render on the child.
app/components/JazzWrapper.tsx
```tsx JazzWrapperSSR.tsx
```
You'll also need to be aware that the server agent can only render public CoValues.
## Give it a go!
... what, already?! Yes! Run your app and try creating a passkey and logging in!
### Not working?
- Did you get everything you need from the current context and pass it to the auth instance?
- Did you make sure your context is properly set up?
- Did you add `` *inside* your provider?
- Does it wrap all the children?
- Are you running your app in a secure context (either HTTPS or localhost)?
## Add a recovery method
Passkeys are very convenient for your users because they offer a secure alternative to traditional authentication methods and they're normally synchronised across devices automatically by the user's browser or operating system.
However, they're not available everywhere, and in case the user loses or deletes their passkey by mistake, they won't be able to access their account.
So, let's add a secondary login method using a passphrase. You can integrate [as many different authentication methods as you like](https://github.com/garden-co/jazz/tree/main/examples/multiauth) in your app.
### Create an `Auth` component
The `PasskeyAuthBasicUI` component is not customisable, so we'll implement our own Auth component so that we can extend it.
First, we'll extract our auth out into a separate component to make it easier to reason about.
src/AuthComponent.ts
app/components/Auth.tsx
src/lib/components/Auth.svelte
} preferWrap>
```ts AuthComponent.ts
```
} label="React" value="react" preferWrap>
```tsx Auth.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte Auth.svelte
```
### Use your new component
src/main.ts
app/components/JazzWrapper.tsx
src/routes/+layout.svelte
} preferWrap>
```ts main.ts#AuthComponent
```
} preferWrap>
```tsx JazzWrapperWithAuth.tsx
```
} preferWrap>
```svelte +layout-with-auth.svelte
```
### Show recovery key
Jazz allows you to generate a passphrase from a wordlist which can be used to log in to an account. This passphrase will work regardless of how the account was originally created (passkey, Clerk, BetterAuth, etc.). Each account will always have the same recovery key.
You can get started with a wordlist [from here](https://github.com/bitcoinjs/bip39/tree/master/src/wordlists). For example, you could save the `english.json` file in your project and format it as a JavaScript export.
wordlist.ts
```ts
"abandon",
// ... many more words
"zoo"
];
```
We'll import this, and add a textarea into our auth component which will show the recovery key for the current user's account.
} preferWrap>
```ts AuthWithPassphrase.ts
```
} label="React" value="react" preferWrap>
```tsx AuthWithPassphrase.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte AuthWithPassphrase.svelte
```
This 'recovery key' is a method of authenticating into an account, and if compromised, it *cannot* be changed! You should impress on your users the importance of keeping this key secret.
### Allow users to log in with the recovery key
Now you're displaying a recovery key to users, so we'll allow users to login using a saved recovery key by extending the Auth component a little further.
} preferWrap>
```ts AuthWithPassphraseLogin.ts
```
} label="React" value="react" preferWrap>
```tsx AuthWithPassphraseLogin.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte AuthWithPassphraseLogin.svelte
```
Although we're presenting this as a 'recovery key' here, this key could also be used as the primary method of authenticating users into your app. You could even completely remove passkey support if you wanted.
**Congratulations! 🎉** You've added authentication to your app, allowing your users to log in from multiple devices, and you've added a recovery method, allowing users to make sure they never lose access to their account.
## Next steps
- Check out how to [use other types of authentication](/docs/key-features/authentication/overview#available-authentication-methods)
- Learn more about [sharing and collaboration](/docs/permissions-and-sharing/quickstart)
- Find out how to [use server workers](/docs/server-side/quickstart) to build more complex applications
===PAGE:key-features/history===
TITLE:key-features/history
DESCRIPTION:Jazz tracks every change to your data automatically. See who changed what, when they did it, and even look at your data from any point in the past. See the [version history example](https://github.
# History
Jazz tracks every change to your data automatically. See who changed what, when they did it, and even look at your data from any point in the past.
See the [version history example](https://github.com/garden-co/jazz/tree/main/examples/version-history) for reference.
Let's use the following schema to see how we can use the edit history.
```ts schema.ts#Basic
```
## The $jazz.getEdits() method
Every CoValue has a `$jazz.getEdits()` method that contains the complete history for each field. Here's
how to get the edit history for `task.status`:
```ts index.ts#GetEdits
```
## Edit Structure
Each edit contains:
```ts index.ts#EditDetails
```
## Accessing History
### Latest Edit
Get the most recent change to a field:
```ts index.ts#Latest
```
### All Edits
Get the complete history for a field:
```ts index.ts#All
```
### Initial Values
The first edit contains the initial value:
```ts index.ts#Initial
```
### Created Date and Last Updated Date
To show created date and last updated date, use the `$jazz.createdAt` and `$jazz.lastUpdatedAt` getters.
```tsx index.ts#CreatedAtLastUpdatedAt
```
### Created By
To show who created the value, use the `$jazz.createdBy` getter. This will return the ID of the user who created the value as a string.
```tsx index.ts#CreatedBy
```
## Requirements
- CoValues must be loaded to access history (see [Subscription & Loading](/docs/core-concepts/subscription-and-loading))
- History is only available for fields defined in your schema
- Edit arrays are ordered chronologically (oldest to newest)
## Common Patterns
For practical implementations using history, see [History Patterns](/docs/reference/design-patterns/history-patterns):
- Building audit logs
- Creating activity feeds
- Implementing undo/redo
- Showing change indicators
- Querying historical data
===PAGE:key-features/version-control===
TITLE:key-features/version-control
DESCRIPTION:description: "Learn how to use branching and merging in Jazz" }; Jazz provides built-in version control through branching and merging, allowing multiple users to work on the same resource in isolation and merge their changes when they are ready. You can use this to design new editing workflows where users (or agents! ) can create branches, make changes, and merge them back to the main version.
description: "Learn how to use branching and merging in Jazz"
};
# Version Control
Jazz provides built-in version control through branching and merging, allowing multiple users to work on the same resource in isolation and merge their changes when they are ready.
You can use this to design new editing workflows where users (or agents!) can create branches, make changes, and merge them back to the main version.
Version control is currently unstable and we may ship breaking changes in patch releases.
## Working with branches
### Creating Branches
To create a branch, use the `unstable_branch` option when loading a CoValue:
You can also create a branch via `CoState`:
You can also create a branch via the `useCoState` hook:
} label="Vanilla" value="vanilla" preferWrap>
```ts index.ts#Branch
```
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#UseCoState
```
} label="Svelte" value="svelte" preferWrap>
```ts svelte.svelte#CoState
```
} label="React Native" value="react-native" preferWrap>
```tsx react-snippet.tsx#UseCoState
```
} label="React Native (Expo)" value="react-native-expo" preferWrap>
```tsx react-snippet.tsx#UseCoState
```
You can also include nested CoValues in your branch by using a [`resolve` query](/docs/core-concepts/subscription-and-loading#using-resolve-queries).
You are in control of how nested CoValues are included in your branch. When you specify the CoValue to branch, any nested CoValues specified in a `resolve` query will also be branched. Nested CoValues *not* specified in your resolve query will not be branched.
In order to access branched nested CoValues, you should access them in the same way you would normally access a deeply loaded property, and all operations will work within the branch context.
In case you create a separate reference to a nested CoValue (for example by loading it by its ID), or you use `.$jazz.ensureLoaded()` or `.$jazz.subscribe()`, you will need to specify the branch you wish to load.
### Making Changes
Once you have a branch, you can make changes just as you would with the original CoValue:
{/* TODO: this is not suitable for vanilla */}
} label="Vanilla" value="vanilla" preferWrap>
```ts index.ts#EditOnBranch
```
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#EditOnBranch
```
} label="Svelte" value="svelte" preferWrap>
```ts EditOnBranch.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx react-snippet.tsx#EditOnBranchRN
```
} label="React Native (Expo)" value="react-native-expo" preferWrap>
```tsx react-snippet.tsx#EditOnBranchRN
```
### Account & Group
Branching does not bring isolation on Account and Group CoValues.
This means that, adding a member on a branched Group will also add the member to the main Group.
```ts index.ts#Permissions
```
If you are modifying an account, be aware that replacing the root or profile will also modify the main account (although updating the properties will happen on the branch).
{/* TODO: Not framework friendly */}
} label="Vanilla" value="vanilla" preferWrap>
```ts index.ts#AccountModifications
```
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#AccountModifications
```
} label="Svelte" value="svelte" preferWrap>
```svelte AccountModifications.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx react-snippet.tsx#AccountModifications
```
} label="React Native (Expo)" value="react-native-expo" preferWrap>
```tsx react-snippet.tsx#AccountModifications
```
### Merging Branches
There are two ways to merge a branch in Jazz, each with different characteristics:
#### 1. Merge loaded values
This method merges all the values that are currently loaded inside the branch. It happens synchronously and there is no possibility of errors because the values are already loaded.
```ts index.ts#Merge
```
This approach is recommended when you can co-locate the merge operation with the branch load, keeping at a glance what the merge operation will affect.
Important: The merge operation will only affect values loaded in the current subscription scope. Values loaded via `ensureLoaded` or `subscribe` will not be affected.
#### 2. Merge with resolve query
This is a shortcut for loading a value and calling `branch.$jazz.unstable_merge()` on it and will fail if the load isn't possible due to permission errors or network issues.
```ts index.ts#MergeWithResolve
```
This approach is recommended for more complex merge operations where it's not possible to co-locate the merge with the branch load.
#### Best Practices
When using version control with Jazz, always be exhaustive when defining the resolve query to keep the depth of the branch under control and ensure that the merge covers all the branched values.
The mechanism that Jazz uses to automatically load accessed values should be avoided with branching, as it might lead to cases where merge won't reach all the branch changes.
All the changes made to the branch will be merged into the main CoValue, preserving both author and timestamp.
The merge is idempotent, so you can merge the same branch multiple times, the result will always depend on the branch changes and loading state.
The merge operation cascades down to the CoValue's children, but not to its parents. So if you call `unstable_merge()` on a task, only the changes to the task and their children will be merged:
```tsx index.ts#CascadingMerge
```
## Conflict Resolution
When conflicts occur (the same field is modified in both the branch and main), Jazz uses a "last writer wins" strategy:
```ts index.ts#ConflictResolution
```
## Private branches
When the owner is not specified, the branch has the same permissions as the main values.
You can also create a private branch by providing a group owner.
```ts index.ts#PrivateBranches
```
You can use private branches both to make the changes to the branches "private" until merged, or to give controlled write access to a group of users.
Only users with both write access to the main branch and read access to the private branch have the rights to merge the branch.
Important: Branch names are scoped to their owner. The same branch name with different owners creates completely separate branches. For example, a branch named "feature-branch" owned by User A is completely different from a branch named "feature-branch" owned by User B.
## Branch Identification
You can get the current branch information from the `$jazz` field.
```ts index.ts#BranchIdentification
```
===PAGE:permissions-and-sharing/cascading-permissions===
TITLE:permissions-and-sharing/cascading-permissions
DESCRIPTION:description: "Add groups as members of other groups. " }; Groups can be added to other groups using the `addMember` method. When a group is added as a member of another group, members of the added group will become part of the containing group.
description: "Add groups as members of other groups."
};
# Groups as members
Groups can be added to other groups using the `addMember` method.
When a group is added as a member of another group, members of the added group will become part of the containing group.
## Basic usage
Here's how to add a group as a member of another group:
```ts index.ts#Basic
```
When you add groups as members:
- Members of the added group become members of the container group
- Their roles are inherited (with some exceptions, see [below](#the-rules-of-role-inheritance))
- Revoking access from the member group also removes its access to the container group
## Levels of inheritance
Adding a group as a member of another is not limited in depth:
```ts index.ts#Inheritance
```
Members of the grandparent group will get access to all descendant groups based on their roles.
## Roles
### The rules of role inheritance
If the account is already a member of the container group, it will get the more permissive role:
```ts index.ts#RuleOfMostPermissive
```
When adding a group to another group, only admin, manager, writer and reader roles are inherited:
```ts index.ts#WriteOnlyOmitted
```
### Overriding the added group's roles
In some cases you might want to inherit all members from an added group but override their roles to the same specific role in the containing group. You can do so by passing an "override role" as a second argument to `addMember`:
```ts index.ts#Overrides
The "override role" works in both directions:
```ts index.ts#OverrideContainers
```
### Permission changes
When you remove a member from an added group, they automatically lose access to all containing groups. We handle key rotation automatically to ensure security.
```ts index.ts#RemoveMembers
```
## Removing groups from other groups
You can remove a group from another group by using the `removeMember` method:
```ts index.ts#RevokeExtension
```
## Getting all added groups
You can get all of the groups added to a group by calling the `getParentGroups` method:
```ts index.ts#GetParentGroups
```
## Ownership on inline CoValue creation
When creating CoValues that contain other CoValues (or updating references to CoValues) using plain JSON objects, Jazz not only creates
the necessary CoValues automatically but it will also manage their group ownership.
```ts index.ts#InlineCoValueCreation
```
For each created column and task CoValue, Jazz also creates a new group as its owner and
adds the referencing CoValue's owner as a member of that group. This means permissions for nested CoValues
are inherited from the CoValue that references them, but can also be modified independently for each CoValue
if needed.
```ts index.ts#ManageImplicitPermissions
```
If you prefer to manage permissions differently, you can always create CoValues explicitly:
```ts index.ts#ExplicitPermissions
```
## Example: Team Hierarchy
Here's a practical example of using group inheritance for team permissions:
```ts index.ts#TeamHierarchy
```
This creates a hierarchy where:
- The CEO has admin access to everything
- Team members get writer access to team and project content
- Team leads get admin access to team and project content
- The client can only read project content
===PAGE:permissions-and-sharing/overview===
TITLE:permissions-and-sharing/overview
DESCRIPTION:description: "Manage permissions of CoValues using Groups. Learn how to add members to a Group, check permissions, and more. " }; Every CoValue has an owner, which can be a `Group` or an `Account`.
description: "Manage permissions of CoValues using Groups. Learn how to add members to a Group, check permissions, and more."
};
# Groups as permission scopes
Every CoValue has an owner, which can be a `Group` or an `Account`.
You can use a `Group` to grant access to a CoValue to **multiple users**. These users can
have different roles, such as "writer", "reader" or "admin".
CoValues owned by an Account can only be accessed by that Account. Additional collaborators cannot be added,
and the ownership cannot be transferred to another Account. This makes account ownership very rigid.
Creating a Group for every new CoValue is a best practice, even if the Group only has a single user in it
(this is the default behavior when creating a CoValue with no explicit owner).
While creating CoValues with Accounts as owners is still technically possible for backwards compatibility,
it will be removed in a future release.
## Role Matrix
Role
admin
manager
writer
writeOnly
reader
Summary
Full control
Delegated management
Standard writer
Blind submissions
Viewer
Can add admins*
✅
❌
❌
❌
❌
Can add/remove managers
✅
❌
❌
❌
❌
Can add/remove readers and writers
✅
✅
❌
❌
❌
Can write
✅
✅
✅
✅**
❌
Can read
✅
✅
✅
❌***
✅
* `admin` users cannot be removed by anyone else, they must leave the group themselves.
** `writeOnly` users can only create and edit their own updates/submissions.
*** `writeOnly` cannot read updates from other users.
## Creating a Group
Here's how you can create a `Group`.
```ts index.ts#Basic
```
The `Group` itself is a CoValue, and whoever owns it is the initial admin.
You typically add members using [public sharing](/docs/permissions-and-sharing/sharing#public-sharing) or [invites](/docs/permissions-and-sharing/sharing#invites).
But if you already know their ID, you can add them directly (see below).
## Adding group members by ID
You can add group members by ID by using `co.account().load` and `Group.addMember`.
```tsx index.ts#AddMember
```
## Changing a member's role
To change a member's role, use the `addMember` method.
```ts index.ts#ChangeRole
```
Bob just went from a writer to a reader.
**Note:** only admins and managers can change a member's role.
## Removing a member
To remove a member, use the `removeMember` method.
```ts index.ts#RemoveMember
```
Rules:
- All roles can remove themselves
- Admins can remove all roles (except other admins)
- Managers can remove users with less privileged roles (writer, writeOnly, reader)
## Getting the Group of an existing CoValue
You can get the group of an existing CoValue by using `coValue.$jazz.owner`.
```ts index.ts#GetGroup
```
## Checking the permissions
You can check the permissions of an account on a CoValue by using the `canRead`, `canWrite`, `canManage` and `canAdmin` methods.
```ts index.ts#CheckPermissions
```
To check the permissions of another account, you need to load it first:
```ts index.ts#CheckPermissionsAlice
```
## Defining permissions at the schema level
You can define permissions at the schema level by using the `withPermissions` method on any CoValue schema. Whenever you create a new CoValue using the schema (i.e., with the `.create()` method), the permissions will be applied automatically.
```ts index.ts#DefinePermissionsAtSchemaLevel
```
`withPermissions` supports several options that let you customize how permissions are applied when creating or composing CoValues.
#### `default`
`default` defines a group to be used when calling `.create()` without an explicit owner.
#### `onInlineCreate`
`onInlineCreate` allows you to choose the behaviour when a CoValue is [created inline](/docs/permissions-and-sharing/cascading-permissions#ownership-on-inline-covalue-creation)
This configuration **is not applied** when using `.create()` for nested CoValues. In that case, `default` is used.
`onInlineCreate` supports the following options:
- `"extendsContainer"` - create a new group that includes the container CoValue's owner as a member, inheriting all permissions from the container. This is the default if no `onInlineCreate` option is provided.
- `"sameAsContainer"` - reuse the same owner as the container CoValue
- `"newGroup"` - create a new group for inline CoValues, with the active account as admin
- `{ extendsContainer: "reader" }` - similar to `“extendsContainer”`, but allows overriding the role of the container’s owner in the new group
- `groupConfigurationCallback` - create a new group and configure it as needed
#### `onCreate`
`onCreate` is a callback that runs every time a CoValue is created. It can be used to configure the CoValue's owner.
It runs both when creating CoValues with `.create()` and when creating inline CoValues.
### Configuring permissions globally
You can configure the default permissions for all CoValue schemas by using `setDefaultSchemaPermissions`.
This is useful if you want to modify inline CoValue creation to [always re-use the container CoValue's owner](/docs/reference/performance#minimise-group-extensions).
```ts index.ts#SetDefaultSchemaPermissions
```
===PAGE:permissions-and-sharing/quickstart===
TITLE:permissions-and-sharing/quickstart
DESCRIPTION:description: "Add collaboration features to your Jazz app. " }; This guide will take your festival app to the next level by showing you how to use invite links to collaborate with others. If you haven't gone through the [front-end Quickstart](/docs/quickstart), you might find this guide a bit confusing.
description: "Add collaboration features to your Jazz app."
};
# Add Collaboration to your App
This guide will take your festival app to the next level by showing you how to use invite links to collaborate with others.
If you haven't gone through the [front-end Quickstart](/docs/quickstart), you might find this guide a bit confusing.
## Understanding Groups
Jazz uses Groups to manage how users are able to access data. Each group member normally has one of three primary 'roles': `reader`, `writer`, or `admin`.
You can add users to groups manually, or you can use invite links to allow people to join groups themselves. Invite links work even for unauthenticated users!
## Create an invite link
Let's create an invite link that others can use to access our data. We'll create an invite link that allows others to make updates to our festival.
When we create a link, we can choose what level of permission to grant. Here, we want others to be able to collaborate, so we'll grant `writer` permissions.
src/main.ts
app/components/Festival.tsx
src/lib/components/Festival.svelte
} label="Vanilla" value="vanilla" preferWrap>
```ts FestivalWithoutProp.ts#CreateInvite
```
} label="React" value="react" preferWrap>
```tsx FestivalWithoutProp.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte FestivalWithoutProp.svelte
```
## Accept an invite
Now we need to set up a way for Jazz to handle the links for the users who are following them.
Jazz provides a handler which we can add to our `Festival` component to accept the invite. This will automatically fire when there's an invite link in the URL, and grant the user the right accesses.
src/main.ts
app/components/Festival.tsx
src/lib/components/Festival.svelte
} label="Vanilla" value="vanilla" preferWrap>
```ts FestivalWithoutProp.ts#AcceptInvite
```
} label="React" value="react" preferWrap>
```tsx FestivalWithAccept.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte FestivalWithAccept.svelte
```
Already completed the server-side rendering guide?
You'll need to make a small change to your structure because the invite handler can only run on the client.
app/components/Festival.tsx
```tsx FestivalWithSSR.tsx
```
You'll also need to be aware that the server agent can only render public CoValues, and the schema above does not publicly share any data (neither bands nor festivals).
## Create the festival page
Now we need to create the festival page, so that we can view other people's festivals and collaborate with them.
We will use `URLPattern` to determine what to render. Vite already supports this in dev mode, but you'll need to make sure your server is properly configured or replace this with another routing strategy.
In order to do this, we'll extract the form and the band list from the `main.ts` file, and export them from their own modules.
### Update our Festival component
We're going to continue updating our existing `Festival` component so that it can optionally take a prop for the festival ID.
We're going to encapsulate our logic for rendering a festival and extract it to its own file. We can import it, and pass in a festival ID to tell it what to render, similar to a front-end framework component.
src/FestivalComponent.ts
app/components/Festival.tsx
src/lib/components/Festival.svelte
} label="Vanilla" value="vanilla" preferWrap>
```ts FestivalComponent.ts
```
} label="React" value="react" preferWrap>
```tsx Festival.tsx
```
} preferWrap>
```svelte Festival.svelte
```
### Update our New Band component
We're going to do the same thing for our form that adds a new band to our festival. We'll pass in the festival so we can be sure we're adding the band in the right place.
src/NewBandComponent.ts
app/components/NewBand.tsx
src/lib/components/NewBand.svelte
} label="Vanilla" value="vanilla" preferWrap>
```ts NewBandComponent.ts
```
} preferWrap>
```tsx NewBand.tsx
```
} preferWrap>
```svelte NewBand.svelte
```
### Create a route
Last, we need to set up our `main.ts` so that it can react to different routes.
src/main.ts
app/festival/[festivalId]/page.tsx
src/routes/festival/[festivalId]/+page.svelte
} label="Vanilla" value="vanilla" preferWrap>
```ts mainWithRouter.ts
```
} label="React" value="react" preferWrap>
```tsx page.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte +page.svelte
```
## Put it all together
Now we can test it out by inviting someone to collaborate on our festival.
1. Open your app and sign in.
2. Open a new incognito window and sign up with a new passkey.
3. From your first browser tab, create an invite link for the festival.
4. You should be able to invite someone to collaborate on the festival.
5. Paste the invite link into the incognito window. You should be able to add bands to the festival!
**Congratulations! 🎉** You've added public sharing to your app! You've learned what groups are, and how Jazz manages permissions, as well as how to invite others to collaborate on data in your app with you.
## Next steps
- Learn how to [authenticate users](/docs/key-features/authentication/quickstart) so you can access data wherever you are.
- Discover how you can use [groups as members of other groups](/docs/permissions-and-sharing/cascading-permissions) to build advanced permissions structures.
- Find out how to [use server workers](/docs/server-side/quickstart) to build more complex applications
===PAGE:permissions-and-sharing/sharing===
TITLE:permissions-and-sharing/sharing
DESCRIPTION:description: "Share CoValues in Jazz through public sharing and invite links. Enable collaboration by granting access to everyone or specific users with different permission levels. " }; You can share CoValues publicly by setting the `owner` to a `Group`, and granting access to "everyone".
description: "Share CoValues in Jazz through public sharing and invite links. Enable collaboration by granting access to everyone or specific users with different permission levels."
};
# Public sharing and invites
## Public sharing
You can share CoValues publicly by setting the `owner` to a `Group`, and granting access to "everyone".
```ts index.ts#Basic
```
You can also use `makePublic(role)` alias to grant access to everyone with a specific role (defaults to `reader`).
```ts index.ts#MakePublic
```
This is done in the [chat example](https://github.com/garden-co/jazz/tree/main/examples/chat) where anyone can join the chat, and send messages.
You can also [add members by Account ID](/docs/permissions-and-sharing/overview#adding-group-members-by-id).
## Invites
You can grant users access to a CoValue by sending them an invite link.
This is used in the [todo example](https://github.com/garden-co/jazz/tree/main/examples/todo).
} label="Vanilla" value="vanilla" preferWrap>
```tsx create-invite-link.ts#CreateLink
```
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#CreateInviteLink
```
} label="Svelte" value="svelte" preferWrap>
```ts create-invite-link.svelte.ts#Basic
```
} label="React Native" value="react-native" preferWrap>
```tsx rn.tsx#CreateInviteLink
```
} label="Expo" value="react-native-expo" preferWrap>
```tsx expo.tsx#CreateInviteLink
```
It generates a URL that looks like `.../#/invite/[CoValue ID]/[inviteSecret]`
In your app, you need to handle this route, and let the user accept the invitation,
as done [here](https://github.com/garden-co/jazz/tree/main/examples/todo/src/2_main.tsx).
} label="Vanilla" value="vanilla" preferWrap>
```ts create-invite-link.ts#ConsumeLink
```
} label="React" value="react" preferWrap>
```tsx react-snippet.tsx#AcceptInvite
```
} label="Svelte" value="svelte" preferWrap>
```svelte svelte.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx rn.tsx#AcceptInvite
```
} label="Expo" value="react-native-expo" preferWrap>
```ts expo.tsx#AcceptInvite
```
You can accept an invitation programmatically by using the `acceptInvite` method on an account.
Pass the ID of the CoValue you're being invited to, the secret from the invite link, and the schema of the CoValue.
```ts index.ts#AcceptInviteProgrammatically
```
### Invite Secrets
The invite links generated by Jazz are convenient ways of handling invites.
In case you would prefer more direct control over the invite, you can create an invite to a `Group` using `Group.createInvite(id, role)` or `group.$jazz.createInvite(role)`.
This will generate a string starting with `inviteSecret_`. You can then accept this invite using `acceptInvite`, with the group ID as the first argument, and the invite secret as the second.
```ts index.ts#InviteToGroup
```
**Invites do not expire and cannot be revoked.** If you choose to generate your own secrets in this way, take care that they are not shared in plain text over an insecure channel.
One particularly tempting mistake is passing the secret as a route parameter or a query. However, this will cause your secret to appear in server logs. You should only ever use fragment identifiers (i.e. parts after the hash in the URL) to share secrets, as these are not sent to the server (see the `createInviteLink` implementation).
### Requesting Invites
To allow a non-group member to request an invitation to a group you can use the `writeOnly` role.
This means that users only have write access to a specific requests list (they can't read other requests).
However, Administrators can review and approve these requests.
Create the data models.
```ts schema.ts#JoinRequest
```
Set up the request system with appropriate access controls.
```ts index.ts#CreateRequestsToJoin
```
Using the write-only access users can submit requests that only administrators can review and approve.
```ts index.ts#ApproveJoinRequest
```
===PAGE:project-setup===
TITLE:project-setup
DESCRIPTION:description: "Learn how to set up Jazz in your application. " }; Add Jazz to your application in minutes. This setup covers standard front-end set up, and gives an overview of experimental SSR approaches.
description: "Learn how to set up Jazz in your application."
};
# Installation and Setup
Add Jazz to your application in minutes. This setup covers standard front-end set up, and gives an overview of experimental SSR approaches.
Integrating Jazz with your existing app is straightforward. You'll define data schemas that describe your application's structure, then wrap your app with a provider that handles sync and storage. The whole process takes just three steps:
1. [Install dependencies](#install-dependencies)
2. [Write your schema](#write-your-schema)
3. [Give your app context](#give-your-app-context)
Looking for complete examples? Check out our [example applications](/examples) for chat apps, collaborative editors, and more.
## Install dependencies
First, install the required packages:
## Write your schema
Define your data schema using [CoValues](/docs/core-concepts/covalues/overview) from `jazz-tools`.
app/schema.ts
src/lib/schema.ts
```ts project-setup/schema.ts
```
See [CoValues](/docs/core-concepts/covalues/overview) for more information on how to define your schema.
## Give your app context
Jazz depends on having context to know who's reading and writing data.
You can make sure Jazz always has the right context by wrapping your application with a provider to connect to the Jazz network and define your data schema:
If you're using vanilla JS, you'll need to manually create a Jazz context and return the API methods necessary for getting the current user, logging the current user out, and accessing the authentication storage.
app.tsx
src/routes/+layout.svelte
} preferWrap>
```ts jazz.ts
```
} preferWrap>
```tsx project-setup/app.tsx
```
} preferWrap>
```svelte project-setup/+layout.svelte
```
## Using your App [!framework=vanilla]
You can start using your app by passing a `sync` parameter and a custom account schema to the `createVanillaJazzApp` factory.
```ts vanilla.ts
```
See [Authentication States](/docs/key-features/authentication/authentication-states#controlling-sync-for-different-authentication-states) for more details on how the `when` property affects synchronisation based on authentication state.
You can find out more about how to [load and subscribe to data here](/docs/vanilla/core-concepts/subscription-and-loading#manual-subscriptions).
This setup handles:
- Connection to the Jazz sync server
- Schema registration for type-safe data handling
- Local storage configuration
With this in place, you're ready to start using Jazz hooks in your components. [Learn how to access and update your data](/docs/core-concepts/subscription-and-loading#subscription-hooks).
## SSR Integration [!framework=react,svelte]
Jazz's default behaviour is to wait until the user's account is loaded before rendering the provider's children. This can be problematic on the server, where no account is available.
We can solve this by asking Jazz to render the children using an a read-only account that can be used without credentials, known as an 'agent'.
Agents can access any data which is available to the public.
### Enabling Provider rendering on the server [!framework=react,svelte]
In order to tell Jazz to allow children to be loaded even in an account-less context, we can set the `enableSSR` setting on the provider.
app/components/JazzWrapper.tsx
src/routes/+layout.svelte
} preferWrap>
```ts project-setup/JazzWrapper.tsx
```
} preferWrap>
```svelte project-setup/+layout-with-ssr.svelte
```
Since the provider is running without an authenticated user, all the `useCoState` and `useAccount` hooks will return null.
Since the provider is running without an authenticated user, all the `CoState` and `AccountCoState` instances will return null.
### Load data with an agent [!framework=react,svelte]
In order to actually load and render data, you need to tell Jazz how to do this without an account. We can use the `loadAs` option in our `.load` call, combined with an agent, generated using `createSSRJazzAgent`.
app/todo/[item]/page.tsx
src/routes/todo/[item]/+page.svelte
} preferWrap>
```tsx project-setup/page.tsx
```
} preferWrap>
```svelte project-setup/+page.svelte
```
Because subscription hooks can't run on the server, you need to use the `.load()` static method to load data on the server.
Take a look at our [Next.js example](https://github.com/garden-co/jazz/tree/main/examples/jazz-nextjs) to see a complete example of how to use SSR with Jazz.
Take a look at our [SvelteKit example](https://github.com/garden-co/jazz/tree/main/examples/jazz-sveltekit) to see a complete example of how to use SSR with Jazz.
For a complete example of Jazz with Svelte, check out our [file sharing example](https://github.com/gardencmp/jazz/tree/main/examples/file-share-svelte) which demonstrates Passkey authentication, file uploads and access control.
## Further Reading
- [Schemas](/docs/core-concepts/covalues/overview) - Learn about defining your data model
- [Provider Configuration](/docs/project-setup/providers) - Learn about other configuration options for Providers
- [Authentication](/docs/key-features/authentication/overview) - Set up user authentication
- [Sync and Storage](/docs/core-concepts/sync-and-storage) - Learn about data persistence
===PAGE:project-setup/providers===
TITLE:project-setup/providers
DESCRIPTION:description: "Configure your JazzReactProvider - the core component that connects your app to Jazz, handling sync, storage, account schema, and auth. ", }; `` is the core component that connects your React application to Jazz. It handles: `` is the core component that connects your Svelte application to Jazz.
description: "Configure your JazzReactProvider - the core component that connects your app to Jazz, handling sync, storage, account schema, and auth.",
};
# Providers
`` is the core component that connects your React application to Jazz. It handles:
`` is the core component that connects your Svelte application to Jazz. It handles:
`` is the core component that connects your React Native application to Jazz. It handles:
`` is the core component that connects your Expo application to Jazz. It handles:
- **Data Synchronization**: Manages connections to peers and the Jazz cloud
- **Local Storage**: Persists data locally between app sessions
- **Schema Types**: Provides APIs for the [AccountSchema](/docs/core-concepts/schemas/accounts-and-migrations)
- **Authentication**: Connects your authentication system to Jazz
Our [Chat example app](https://jazz.tools/examples#chat) provides a complete implementation of JazzReactProvider with authentication and real-time data sync.
Our [File Share example app](https://github.com/garden-co/jazz/blob/main/examples/file-share-svelte/src/routes/%2Blayout.svelte) provides an implementation of JazzSvelteProvider with authentication and real-time data sync.
## Setting up the Provider
The provider accepts several configuration options:
} preferWrap>
```tsx app.tsx#Basic
```
} preferWrap>
```svelte svelte.svelte
```
} preferWrap>
```tsx app-rn.tsx#Basic
```
} preferWrap>
```tsx app-expo.tsx#Basic
```
## Provider Options
### Sync Options
The `sync` property configures how your application connects to the Jazz network:
```ts sync-config.ts
```
On iOS Jazz persists login credentials using Secure Store, which saves them to the Keychain. While this is secure, iOS does not clear the credentials along with other data if a user uninstalls your app.
User accounts *are* deleted, so if you are using `sync: 'never'` or `sync: 'signedUp'`, accounts for users who are not 'signed up' will be lost.
If the user later re-installs your app, the credentials for the deleted account remain in the Keychain. Jazz will attempt to use these to sign in and fail, as the account no longer exists.
To avoid this, consider using `sync: 'always'` for your iOS users, or [check the work around here](/docs/key-features/authentication/authentication-states#disable-sync-for-anonymous-authentication).
See [Authentication States](/docs/key-features/authentication/authentication-states#controlling-sync-for-different-authentication-states) for more details on how the `when` property affects synchronization based on authentication state.
### Account Schema
The `AccountSchema` property defines your application's account structure:
} preferWrap>
```tsx app.tsx#Basic
```
} preferWrap>
```svelte svelte.svelte
```
} preferWrap>
```tsx app-rn.tsx#Basic
```
} preferWrap>
```tsx app-expo.tsx#Basic
```
### Additional Options
The provider accepts these additional options:
- `kvStore`
- `MMKVStoreAdapter` (default)
- `AccountSchema`
- `Account` (default)
- `kvStore`
- `ExpoSecureStoreAdapter` (default)
- `AccountSchema`
- `Account` (default)
} preferWrap>
```tsx app.tsx#AllOptions
```
} preferWrap>
```svelte all-options.svelte
```
See [Authentication States](/docs/key-features/authentication/authentication-states) for more information on authentication states, guest mode, and handling anonymous accounts.
## Authentication
The Jazz Provider works with various authentication methods to enable users to access their data across multiple devices. For a complete guide to authentication, see our [Authentication Overview](/docs/key-features/authentication/overview).
The Provider works with various authentication methods, with PassphraseAuth being the easiest way to get started for development and testing. For authentication details, refer to our [Authentication Overview](/docs/key-features/authentication/overview) guide.
The authentication hooks must always be used inside the Provider component.
Implementing PassphraseAuth is straightforward:
1. Import the [wordlist](https://github.com/bitcoinjs/bip39/tree/a7ecbfe2e60d0214ce17163d610cad9f7b23140c/src/wordlists) for generating recovery phrases
2. Use the `usePassphraseAuth` hook to handle authentication
3. Create simple registration and sign-in screens
} preferWrap>
```tsx passphrase-rn.tsx#Passphrase
```
} preferWrap>
```tsx passphrase-expo.tsx#Passphrase
```
## Local Persistence [!framework=react-native,react-native-expo]
Jazz for React Native includes built-in local persistence using SQLite. This implementation uses:
- **Database Storage**: `@op-engineering/op-sqlite` - A high-performance SQLite implementation
- **Key-Value Storage**: `react-native-mmkv` - A fast key-value storage system
Jazz for Expo includes built-in local persistence using SQLite. Following Expo's best practices, the Expo implementation uses:
- **Database Storage**: `expo-sqlite` - Expo's official SQLite module
- **Key-Value Storage**: `expo-secure-store` - Expo's secure storage system
Local persistence is enabled by default with no additional configuration required. Your data will automatically persist across app restarts.
## RNCrypto [!framework=react-native,react-native-expo]
**Starting from Expo SDK 54, you do not need to follow these steps, `RNCrypto` will be enabled for you by default. These steps below should be followed only if you are using an older Expo version.**
For accelerated crypto operations, you can use the `RNCrypto` crypto provider. It is the most performant crypto provider available for React Native.
To use it, install the following package:
```bash
pnpm add cojson-core-rn
```
You must keep the versions of `cojson-core-rn` and `jazz-tools` the same.
```json
"dependencies": {
"cojson-core-rn": "x.x.x", # same version as jazz-tools
"jazz-tools": "x.x.x" # same version as cojson-core-rn
}
```
While you can distribute your own JS code changes OTA, if you update your Jazz version, this will result in changes in the native dependencies, which *cannot* be distributed over the air and requires a new store submission.
## Need Help?
If you have questions about configuring the Jazz Provider for your specific use case, [join our Discord community](https://discord.gg/utDMjHYg42) for help.
===PAGE:project-setup/providers/vanilla===
TITLE:project-setup/providers/vanilla
DESCRIPTION:If you're using vanilla JS, you won't be able to use an off the shelf provider. Instead, you need to provide context to your app yourself. [Find out more here](/docs/vanilla/project-setup).
# Providers
If you're using vanilla JS, you won't be able to use an off the shelf provider. Instead, you need to provide context to your app yourself.
[Find out more here](/docs/vanilla/project-setup).
Alternatively, if you're looking to implement a provider yourself in a framework we don't support, you can check our reference implementations to see how it could be done:
* [React](https://github.com/garden-co/jazz/blob/main/packages/jazz-tools/src/react/provider.tsx)
* [Svelte](https://github.com/garden-co/jazz/blob/main/packages/jazz-tools/src/svelte/Provider.svelte)
* [Vue](https://github.com/garden-co/jazz/blob/main/packages/community-jazz-vue/src/provider.ts) — Community maintained
===PAGE:project-setup/react-native===
TITLE:project-setup/react-native
DESCRIPTION:description: "Learn how to set up Jazz in your React Native application. " }; This guide covers setting up Jazz for React Native applications from scratch. If you're using Expo, please refer to the [React Native - Expo](/docs/react-native-expo/project-setup) guide instead.
description: "Learn how to set up Jazz in your React Native application."
};
# React Native Installation and Setup
This guide covers setting up Jazz for React Native applications from scratch. If you're using Expo, please refer to the [React Native - Expo](/docs/react-native-expo/project-setup) guide instead. If you just want to get started quickly, you can use our [React Native Chat Demo](https://github.com/garden-co/jazz/tree/main/examples/chat-rn) as a starting point.
Jazz supports the [New Architecture](https://reactnative.dev/architecture/landing-page) for React Native.
Tested with:
```json
"react-native": "0.79.2",
"react": "18.3.1"
```
## Installation
### Create a new project
(Skip this step if you already have one)
```bash
npx @react-native-community/cli init myjazzapp
cd myjazzapp
```
If you intend to build for iOS, you can accept the invitation to install CocoaPods. If you decline, or you get an error, [you can install it with `pod-install`](#install-cocoapods).
### Install dependencies
```bash
# React Native dependencies
npm install @react-native-community/netinfo @bam.tech/react-native-image-resizer
# React Native polyfills
npm install @azure/core-asynciterator-polyfill react-native-url-polyfill readable-stream react-native-get-random-values @op-engineering/op-sqlite react-native-mmkv react-native-fast-encoder
# Jazz dependencies
npm install jazz-tools cojson-core-rn
```
`cojson-core-rn` is our high-performance crypto provider for React Native. And it is **required** for React Native applications.
In the React Native ecosystem, the Autolinking process (which links native code from libraries to your iOS and Android projects)
relies on scanning the dependencies listed in your application's root package.json. As a result, `cojson-core-rn` must be a direct dependency of the app, and not a dependency of a dependency.
You must keep the versions of the two packages (`jazz-tools` and `cojson-core-rn`) the same.
```json
"dependencies": {
"cojson-core-rn": "x.x.x", # same version as jazz-tools
"jazz-tools": "x.x.x" # same version as cojson-core-rn
}
```
While you can distribute your own JS code changes OTA, if you update your Jazz version, this will result in changes in the native dependencies, which *cannot* be distributed over the air and requires a new store submission.
### Add polyfills
Jazz provides a quick way for you to apply the polyfills in your project. Import them in your root `index.js` file:
```ts project-setup/index.js
```
## Authentication
Jazz provides authentication to help users access their data across multiple devices. For details on implementing authentication, check our [Authentication Overview](/docs/key-features/authentication/overview) guide and see the [React Native Chat Demo](https://github.com/garden-co/jazz/tree/main/examples/chat-rn) for a complete example.
## Next Steps
Now that you've set up your React Native project for Jazz, you'll need to:
1. [Set up the Jazz Provider](/docs/project-setup/providers) - Configure how your app connects to Jazz
2. [Add authentication](/docs/key-features/authentication/overview) (optional) - Enable users to access data across devices
3. Define your schema - See the [schema docs](/docs/core-concepts/covalues/overview) for more information
4. Run your app:
```sh
# Start Metro
npm run start
# In a new terminal tab:
npm run ios
# or
npm run android
```
If all goes well, your app should start up without any angry red error screens. Take a quick look at the Metro console too - no Jazz-related errors there means you're all set! If you see your app's UI come up smoothly, you've nailed the installation.
If you run into any issues that aren't covered in the Common Issues section, [drop by our Discord for help](https://discord.gg/utDMjHYg42).
## Common Issues
- **Metro bundler errors**: If you see errors about missing polyfills, ensure you installed them all and are importing them correctly
- **iOS build failures**: Make sure you've run `pod install` after adding the dependencies.
- **Android build failures**: Ensure your Android SDK and NDK versions are compatible with the native modules.
### Install CocoaPods
If you're compiling for iOS, you'll need to install CocoaPods for your project. If you need to install it, we recommend using [`pod-install`](https://www.npmjs.com/package/pod-install):
```bash
npx pod-install
```
===PAGE:project-setup/react-native-expo===
TITLE:project-setup/react-native-expo
DESCRIPTION:description: "Learn how to set up Jazz in your React Native Expo application. " }; Jazz supports Expo through the dedicated `jazz-tools/expo` entry, which is specifically designed for Expo applications. If you're building for React Native without Expo, please refer to the [React Native](/docs/react-native/project-setup) guide instead.
description: "Learn how to set up Jazz in your React Native Expo application."
};
# React Native (Expo) Installation and Setup
Jazz supports Expo through the dedicated `jazz-tools/expo` entry, which is specifically designed for Expo applications. If you're building for React Native without Expo, please refer to the [React Native](/docs/react-native/project-setup) guide instead.
Jazz requires an [Expo development build](https://docs.expo.dev/develop/development-builds/introduction/) using [Expo Prebuild](https://docs.expo.dev/workflow/prebuild/) for native code. It is **not compatible** with Expo Go. Jazz also supports the [New Architecture](https://docs.expo.dev/guides/new-architecture/).
Tested with:
```json
"expo": "~53.0.0",
"react-native": "0.79.2",
"react": "18.3.1"
```
## Installation
### Create a new project
(Skip this step if you already have one)
```bash
npx create-expo-app my-jazz-app
cd my-jazz-app
npx expo prebuild
```
### Install dependencies
```bash
# Expo dependencies
npx expo install expo-linking expo-secure-store expo-sqlite expo-file-system @react-native-community/netinfo expo-image-manipulator
# React Native polyfills
npm install @azure/core-asynciterator-polyfill react-native-url-polyfill readable-stream react-native-get-random-values react-native-fast-encoder
```
While you can distribute your own JS code changes OTA, if you update your Jazz version, this will result in changes in the native dependencies, which *cannot* be distributed over the air and requires a new store submission.
Using Expo SDK 53 or lower?
Expo SDK 54 will automatically link the native dependency for you, but older versions of Expo do not. As a result, either upgrade to Expo 54, or install `cojson-core-rn` as an explicit dependency manually.
**Pay Attention:** The version of `cojson-core-rn` must be the same as the version of `jazz-tools`.
```json
"dependencies": {
"cojson-core-rn": "x.x.x", # same version as jazz-tools
"jazz-tools": "x.x.x" # same version as cojson-core-rn
}
```
#### Fix incompatible dependencies
If you encounter incompatible dependencies, you can try to fix them with the following command:
```bash
npx expo install --fix
```
### Add polyfills
Jazz provides a quick way for you to apply the polyfills in your project. Import them in your root `_layout.tsx` component:
app/_layout.tsx
```tsx project-setup/_layout.tsx
```
## Authentication
Jazz provides authentication to help users access their data across multiple devices. For details on implementing authentication with Expo, check our [Authentication Overview](/docs/key-features/authentication/overview) guide and see the [Expo Clerk Demo](https://github.com/garden-co/jazz/tree/main/examples/clerk-expo) for a complete example.
## Next Steps
Now that you've set up your Expo project for Jazz, you'll need to:
1. [Set up the Jazz Provider](/docs/project-setup/providers) - Configure how your app connects to Jazz
2. [Add authentication](/docs/key-features/authentication/overview) (optional) - Enable users to access data across devices
3. Define your schema - See the [schema docs](/docs/core-concepts/covalues/overview) for more information
4. Run your app:
```sh
npx expo run:ios
# or
npx expo run:android
```
If all goes well, your app should start up without any angry red error screens. Take a quick look at the Metro console too - no Jazz-related errors there means you're all set! If you see your app's UI come up smoothly, you've nailed the installation.
If you run into any issues that aren't covered in the Common Issues section, [drop by our Discord for help](https://discord.gg/utDMjHYg42).
## Common Issues
- **Metro bundler errors**: If you see errors about missing polyfills, ensure you installed them all and are importing them correctly
- **iOS build failures**: Make sure you've run `pod install` after adding the dependencies.
- **Android build failures**: Ensure you've run `npx expo prebuild` to generate native code.
- **Expo Go incompatibility**: Remember that Jazz requires a development build and won't work with Expo Go.
### Install CocoaPods
If you're compiling for iOS, you'll need to install CocoaPods for your project. If you need to install it, we recommend using [`pod-install`](https://www.npmjs.com/package/pod-install):
```bash
npx pod-install
```
===PAGE:quickstart===
TITLE:quickstart
DESCRIPTION:description: "Get started building a simple front-end app with Jazz in 10 minutes. " }; This quickstart guide will take you from an empty project to a working app with a simple data model and components to create and display your data. We're going to use a bare-bones Vite + TS app to get started.
description: "Get started building a simple front-end app with Jazz in 10 minutes."
};
# Get started with Jazz in 10 minutes
This quickstart guide will take you from an empty project to a working app with a simple data model and components to create and display your data.
## Create your App
We're going to use a bare-bones Vite + TS app to get started. This allows us to import modules easily and allows us to use TypeScript from the start. It's not strictly necessary to use Vite and TypeScript, but this gives the minimal set-up for a modern web development workflow, and we *strongly* recommend it.
```sh
npm create vite@latest jazzfest -- --template vanilla-ts
cd jazzfest
```
```sh
pnpm create vite@latest jazzfest -- --template vanilla-ts
cd jazzfest
```
We'll be using Next.js for this guide per the [React team's recommendation](https://react.dev/learn/creating-a-react-app), but Jazz works great with vanilla React and other full-stack frameworks too.
You can accept the defaults for all the questions, or customise the project as you like.
```sh
npx create-next-app@latest --typescript jazzfest
cd jazzfest
```
```sh
pnpx create-next-app@latest --typescript jazzfest
cd jazzfest
```
We'll be using SvelteKit for this guide, per the [Svelte team's recommendation](https://svelte.dev/docs/svelte/getting-started), but Jazz works great with vanilla Svelte too.
You can accept the defaults for all the questions, or customise the project as you like.
```sh
npx sv create --types ts --template minimal jazzfest
cd jazzfest
```
```sh
pnpx sv create --types ts --template minimal jazzfest
cd jazzfest
```
**Note: Requires Node.js 20+**
## Install Jazz
The `jazz-tools` package includes everything you're going to need to build your first Jazz app.
## Get your free API key
## Define your schema
Jazz uses Zod for more simple data types (like strings, numbers, booleans), and its own schemas to create collaborative data structures known as CoValues. CoValues are automatically persisted across your devices and the cloud and synced in real-time. Here we're defining a schema made up of both Zod types and CoValues.
Adding a `root` to the user's account gives us a container that can be used to keep a track of all the data a user might need to use the app.
The migration runs when the user logs in, and ensures the account is properly set up before we try to use it.
src/schema.ts
app/schema.ts
src/lib/schema.ts
```ts quickstart/schema.ts
```
## Create a Jazz context [!framework=vanilla]
Jazz needs to have a 'context' in order to run. Once created, you can use Jazz to create, read, and update data. You can delete all the boilerplate in the `src/main.ts` file and replace it with the code below:
## Add the Jazz Provider [!framework=react,svelte]
Wrap your app with a provider so components can use Jazz.
app/components/JazzWrapper.tsx
```tsx quickstart/JazzWrapper.tsx
```
src/main.ts
app/layout.tsx
src/routes/+layout.svelte
} preferWrap>
```tsx quickstart/main.ts#Context
```
} preferWrap>
```tsx quickstart/layout.tsx
```
} preferWrap>
```svelte quickstart/+layout.svelte
```
## Start your app
Moment of truth — time to start your app and see if it works.
If everything's going according to plan, you should see a blank page!
If everything's going according to plan, you should see the default Next.js welcome page!
If everything's going according to plan, you should see the default SvelteKit welcome page!
### Not loading?
If you're not seeing the welcome page:
- Check you wrapped your app with the Jazz Provider in `app/layout.tsx`
- Check your schema is properly defined in `app/schema.ts`
- Check you wrapped your app with the Jazz Provider in `src/routes/+layout.svelte`
- Check your schema is properly defined in `src/lib/schema.ts`
## Create data
Let's create a simple form to add a new band to the festival. We'll use the `getCurrentAccount` function we defined above to get the current account. Then, we'll load it using our custom schema and trigger the migration manually.
Let's create a simple form to add a new band to the festival. We'll use the `useAccount` hook to get the current account and tell Jazz to load the `myFestival` CoValue by passing a `resolve` query.
src/main.ts
app/components/NewBand.tsx
src/lib/components/NewBand.svelte
} preferWrap>
```tsx quickstart/main.ts#AddBand
```
} preferWrap>
```tsx quickstart/NewBand.tsx
```
} preferWrap>
```svelte quickstart/NewBand.svelte
```
## Display your data
Now we've got a way to create data, so let's add a component to display it.
src/main.ts
app/components/Festival.tsx
src/lib/components/Festival.svelte
} preferWrap>
```tsx quickstart/main.ts#Display
```
} label="React" value="react" preferWrap>
```tsx quickstart/Festival.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte quickstart/Festival.svelte
```
## Put it all together
You've built all your components, time to put them together.
src/main.ts
app/page.tsx
src/routes/+page.svelte
} preferWrap>
```tsx quickstart/main.ts#Page
```
} preferWrap>
```tsx quickstart/page.tsx
```
} label="Svelte" value="svelte" preferWrap>
```svelte quickstart/+page.svelte
```
You should now be able to add a band to your festival, and see it appear in the list!
**Congratulations! 🎉** You've built your first Jazz app!
You've begun to scratch the surface of what's possible with Jazz. Behind the scenes, your local-first JazzFest app is **already** securely syncing your data to the cloud in real-time, ready for you to build more and more powerful features.
Psst! Got a few more minutes and want to add Server Side Rendering to your app? [We've got you covered!](/docs/server-side/ssr)
## Next steps
- [Add authentication](/docs/key-features/authentication/quickstart) to your app so that you can log in and view your data wherever you are!
- Dive deeper into the collaborative data structures we call [CoValues](/docs/core-concepts/covalues/overview)
- Learn how to share and [collaborate on data](/docs/permissions-and-sharing/overview) using groups and permissions
- Complete the [server-side quickstart](/docs/server-side/quickstart) to learn more about Jazz on the server
===PAGE:reference/data-modelling===
TITLE:reference/data-modelling
DESCRIPTION:description: "Explore data modelling and schema design in Jazz. " }; To understand how best to model data for your Jazz application, it's helpful to first think about how your data is related. In a traditional database, you might model different data types as 'tables' or 'collections'.
description: "Explore data modelling and schema design in Jazz."
};
# Data Modelling and Schema Design in Jazz
To understand how best to model data for your Jazz application, it's helpful to first think about how your data is related.
## Jazz as a Collaborative Graph
In a traditional database, you might model different data types as 'tables' or 'collections'. With Jazz, you model data types as schemas, and data as an explicitly linked graph.
For example, consider the following SQL data model for a simple blog:
```sql
-- Users table
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
-- Posts table
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
);
```
This data model for a Jazz app is defined in a schema which might look like this:
```ts basic-schemas.ts#BasicSchemas
```
What are `z.` and `co.`?
Jazz uses Zod schemas to define primitive data types. `z.string()` indicates that the stored value should be a string.
To define collaborative data types, Jazz provides utilities under the `co` namespace. For example, here, we use `co.richText()`, to indicate a collaborative value.
## Permissions are part of the data model
In traditional databases, permissions are often left to the application layer, or are a more advanced feature of the database that requires additional configuration.
With Jazz, permissions are an integral part of the data model — you need to consider permissions when structuring your data, not just when you're accessing it. Each CoValue has an ownership group, and permissions are defined based on the ownership hierarchy. This is very powerful, as it allows you to easily build cascading permissions hierarchies, but it does mean that you need to ensure each piece of data which needs different permissions to be applied lives in a different container (e.g. a separate `CoMap` or `CoList`). It is not possible to change the group which owns a CoValue. In order to change the permissions applying to a particular CoValue, an admin (or manager) can [make changes to the group membership](/docs/permissions-and-sharing/overview).
You should consider the default permissions you would like a CoValue to have when you are designing your data model. You can [specify these in your schema](/docs/permissions-and-sharing/overview#defining-permissions-at-the-schema-level).
### Permissions Levels
The following main permissions levels exist (with each level including all the permissions from the previous level):
* **none**: can't read the content of CoValue
* **reader**: can read the content of the CoValue
* **writer**: can update the content of the CoValue (overwrite values, add/remove items from lists, etc.)
* **admin**: can grant or revoke permissions for others, as well as writing.
These permissions can be granted to individual users or to groups.
By default, **only** the user creating a CoValue is the admin. In contrast to traditional databases, Jazz does not have a concept of an 'application admin', or a 'root' or 'superuser'.
Unless explicitly defined otherwise in your data model, only the creator of the CoValue will be added as an admin. Once a CoValue is created, its permissions can **only** be changed by an admin (or a manager).
If [creating a nested CoValue inline](/docs/permissions-and-sharing/cascading-permissions#ownership-on-inline-covalue-creation), then by default, permissions are inherited from the containing CoValue.
## Choosing your building blocks
Jazz helps you build your app by providing concrete building blocks. Basic types which do not need collaborative editing, such as simple strings, numbers, and other scalar types are defined using Zod.
Most apps will have more complex needs, however, and for this, Jazz provides you with **CoValues**, which are composite data structures which hold references to either these scalar types or other CoValues. Each CoValue is suited to a particular use case.
TypeScript Type
Corresponding CoValue
Usage
object
CoMap
Key-value stores with pre-defined keys (struct-like)
Record<string, T>
CoRecord
Key-value stores with arbitrary string keys (dict-like)
T[]
CoList
Lists
T[] (append-only)
CoFeed
Session-based append-only lists
string
CoPlainText/CoRichText
Collaborative text
Blob | File
FileStream
Files
Blob | File (image)
ImageDefinition
Images
number[] | Float32Array
CoVector
Embeddings
T | U (discriminated)
DiscriminatedUnion
Lists of different types of items
In some cases, there are both scalar and collaborative options for the same data type. For example, you can use either `z.string()` or `co.richText()` for a string field. The key difference is that `z.string()` does not support collaborative editing. If you would like to update the string field, you can only do so by replacing the entire field with a new value. `co.richText()` supports collaborative editing, allows multiple users to edit the same string simultaneously.
In our blog example, we used `co.richText()` for the content, to allow multiple users to edit the same post simultaneously, but we preferred `z.string()` for the title, as the title doesn't need to be edited collaboratively.
This same principle applies with other data types too. For example, you can use `z.object()` or `co.map()` for an object field, and `z.tuple()` or `co.list()` for an array field.
As a general rule of thumb: if you expect the whole object to be replaced when you update it, you should use a scalar type. If you expect to make small, surgical edits, or collaborate with others, a CoValue may be a better choice.
Can't I just use CoValues for everything?
Short answer is yes, you can. But you should be aware of the trade-offs. CoValues track their full edit history, and although they are very performant, they cannot achieve the same raw speed as their scalar counterparts for single-writer, full-value replacement updates.
In most real-world applications, the benefits of collaborative editing outweigh the (slight) performance costs.
## Linking them together
In almost every case, you'll want to link your CoValues together somehow. This is because CoValues are only addressable by their unique ID. Discovering CoValues without knowing their ID is only possible by traversing references. In a normal Jazz app, we attach a 'root' to a user's account which serves as the entry point into the graph. In case there is a need to create a 'global root', then a CoValue ID can be hard-coded, or added as an environment variable.
### One directional relationships
Let's extend our example above to attach an `Author` to each `Post`.
```ts one-directional-relationship.ts#OneDirectionalRelationship
```
Here, `Post.author` is a one-way reference. Jazz stores the ID of the referenced `Author`, and when loading a `Post` you can choose whether to resolve that reference (and if so, how deeply).
The above models a one-to-one relationship: both the `Post` and the `Author` have a single reference you can fill.
If you would like to model a one-to-many relationship, use a `CoList`:
```ts one-to-many-relationship.ts#OneToManyRelationship
```
Mental model
This is similar to a foreign key in a relational database: the reference lives on one side, and Jazz doesn't infer reverse links for you. You explicitly control how references are followed using resolve queries.
### Modelling inverse relationships
Jazz relationships are one-way by default: a reference is stored and you can follow the breadcrumbs to resolve the references. If you need to be able to traverse the relationship from both sides, you solve this by adding the reference to both sides of the relationship.
You are in full control of the relationships in your app, Jazz will not create inferred inverse relationships for you. It is particularly important to bear this in mind because it is not possible to query CoValues based on references from other CoValues.
### Recursive references
As soon as you start building schemas with inverse (or recursive) relationships, you'll need to defer schema evaluation to avoid TypeScript errors. If we want `Author` to reference `Post` and `Post` to reference `Author`, whichever order we put them in will cause an error.
You can address this by using getters to refer to schemas which are not yet defined:
```ts recursive-references.ts#RecursiveReferences
```
To model a many-to-many relationship, use a `CoList` at both ends of the relationship — be aware that Jazz does not maintain consistency for you, this should be managed in your application code.
```ts many-to-many-relationship.ts#ManyToManyRelationship
```
Can I add a unique constraint?
A `CoList` can contain the same item multiple times. There's no built in way to enforce that items in the list are unique, but you can adjust your data model to use a `CoRecord` keyed on the referenced CoValue ID to create a [set-like collection](/docs/core-concepts/covalues/colists#set-like-collections)
Note that CoRecords are *always* keyed on strings. Jazz does not enforce referential integrity here, so validating that these are valid `Post` IDs is an application-level responsibility.
## Changing Your Data Model
Over time, your data model may change. In a traditional app with a single source of truth, you could simply update the data directly in the database. With Jazz, each individual copy of a CoValue is its own authoritative state, and the schema simply tells Jazz how to interpret it.
As a result, it is possible — indeed likely — that your users will end up on different versions of your schema at the same time. As a result, we recommend the following:
* Add a version field to your schema
* Only add fields, never remove them
* Do not change the data type for existing fields
* When adding fields, make them optional (so that you can load older data without these fields set).
You can also use the [`withMigration()` method](/docs/core-concepts/covalues/comaps#running-migrations-on-comaps) which runs every time a CoValue is loaded (available on `CoMaps` and `Accounts`).
Migrations run *every* time a CoValue is loaded. A poorly-written migration could cause a lot of unnecessary work being done, and potentially slow your app down. Exit as early as possible from the migration, and check the update is necessary before updating.
## Further Reading
Looking for a deeper walk through?
* Check out our [Overview of CoValues](/docs/core-concepts/covalues/overview) to learn more about the different building blocks
* Read about [permissions in detail](/docs/permissions-and-sharing/overview) to understand how permissions govern which users can see what data
* Learn about [how to connect CoValues](/docs/core-concepts/schemas/connecting-covalues)
===PAGE:reference/design-patterns/form===
TITLE:reference/design-patterns/form
DESCRIPTION:description: "Learn how to handle forms in Jazz for creating and updating CoValues. " }; This guide shows you a simple and powerful way to implement forms for creating and updating CoValues. [See the full example here.
description: "Learn how to handle forms in Jazz for creating and updating CoValues."
};
# How to write forms with Jazz
This guide shows you a simple and powerful way to implement forms for creating and updating CoValues.
[See the full example here.](https://github.com/garden-co/jazz/tree/main/examples/form)
## Updating a CoValue
To update a CoValue, we simply assign the new value directly as changes happen. These changes are synced to the server.
```tsx index.tsx#Basic
order.$jazz.set("name", e.target.value)}
/>
```
It's that simple!
## Creating a CoValue
When creating a CoValue, we can use a partial version that allows us to build up the data before submitting.
### Using a Partial CoValue
Let's say we have a CoValue called `BubbleTeaOrder`. We can create a partial version,
`PartialBubbleTeaOrder`, which has some fields made optional so we can build up the data incrementally.
schema.ts
```ts schema.ts
```
{/* TODO: add other frameworks */}
## Writing the components in React
Let's write the form component that will be used for both create and update.
```tsx OrderForm.tsx#OrderForm
```
### Writing the edit form
To make the edit form, simply pass the `BubbleTeaOrder`. Changes are automatically saved as you type.
```tsx OrderForm.tsx#EditForm
```
### Writing the create form
For the create form, we need to:
1. Create a partial order.
2. Edit the partial order.
3. Convert the partial order to a "real" order on submit.
Here's how that looks like:
```tsx OrderForm.tsx#CreateOrder
```
## Editing with a save button
If you need a save button for editing (rather than automatic saving), you can use Jazz's branching feature. The example app shows how to create a private branch for editing that can be merged back when the user saves:
```tsx OrderForm.tsx#EditOrderWithSave
```
This approach creates a private branch using `unstable_branch` with a unique owner group. The user can edit the branch without affecting the original data, and changes are only persisted when they click save via `unstable_merge()`.
Important: Version control is currently unstable and we may ship breaking changes in patch releases.
## Handling different types of data
Forms can be more complex than just a single string field, so we've put together an example app that shows you
how to handle single-select, multi-select, date, boolean inputs, and rich text.
[See the full example here.](https://github.com/garden-co/jazz/tree/main/examples/form)
===PAGE:reference/design-patterns/history-patterns===
TITLE:reference/design-patterns/history-patterns
DESCRIPTION:Jazz's automatic history tracking enables powerful patterns for building collaborative features. Here's how to implement common history-based functionality.
# History Patterns
Jazz's automatic history tracking enables powerful patterns for building collaborative features. Here's how to implement common history-based functionality.
## Audit Logs
Build a complete audit trail showing all changes to your data:
```ts index.ts#AuditLogs
```
## Activity Feeds
Show recent activity across your application:
```ts index.ts#GetRecentActivity
```
## Change Indicators
Show when something was last updated:
```ts index.ts#GetLastUpdated
```
## Finding Specific Changes
Query history for specific events:
```ts index.ts#QueryHistory
```
## Further Reading
- [History](/docs/key-features/history) - Complete reference for the history API
- [Subscription & Loading](/docs/core-concepts/subscription-and-loading) - Ensure CoValues are loaded before accessing history
===PAGE:reference/design-patterns/organization===
TITLE:reference/design-patterns/organization
DESCRIPTION:description: "Learn how to share a set of data between users through organizations. " }; This guide shows you how to share a set of CoValues between users. Different apps have different names for this concept, such as "teams" or "workspaces".
description: "Learn how to share a set of data between users through organizations."
};
# How to share data between users through Organizations
This guide shows you how to share a set of CoValues between users. Different apps have different names for this concept, such as "teams" or "workspaces".
We'll use the term Organization.
[See the full example here.](https://github.com/garden-co/jazz/tree/main/examples/organization)
## Defining the schema for an Organization
Create a CoMap shared by the users of the same organization to act as a root (or "main database") for the shared data within an organization.
For this example, users within an `Organization` will be sharing `Project`s.
schema.ts
```ts permissions-and-sharing/sharing/schema.ts#Basic
```
Learn more about [defining schemas](/docs/core-concepts/covalues/overview).
## Adding a list of Organizations to the user's Account
Let's add the list of `Organization`s to the user's Account `root` so they can access them.
```tsx permissions-and-sharing/sharing/schema.ts#AddToAccount
```
This schema now allows users to create `Organization`s and add `Project`s to them.
[See the schema for the example app here.](https://github.com/garden-co/jazz/blob/main/examples/organization/src/schema.ts)
## Adding members to an Organization
Here are different ways to add members to an `Organization`.
- Send users an invite link.
- [The user requests to join.](/docs/permissions-and-sharing/sharing#requesting-invites)
This guide and the example app show you the first method.
### Adding members through invite links
Here's how you can generate an [invite link](/docs/permissions-and-sharing/sharing#invites).
When the user accepts the invite, add the `Organization` to the user's `organizations` list.
} label="Vanilla" value="vanilla" preferWrap>
```ts permissions-and-sharing/sharing/create-invite-link.ts#ConsumeLink
```
} label="React" value="react" preferWrap>
```tsx permissions-and-sharing/sharing/react-snippet.tsx#AcceptInvite
```
} label="Svelte" value="svelte" preferWrap>
```tsx permissions-and-sharing/sharing/svelte.svelte
```
} label="React Native" value="react-native" preferWrap>
```tsx permissions-and-sharing/sharing/rn.tsx#AcceptInvite
```
} label="Expo" value="react-native-expo" preferWrap>
```ts permissions-and-sharing/sharing/expo.tsx#AcceptInvite
```
## Further reading
- [Allowing users to request an invite to join a Group](/docs/permissions-and-sharing/sharing#requesting-invites)
- [Groups as permission scopes](/docs/permissions-and-sharing/overview#adding-group-members-by-id)
===PAGE:reference/encryption===
TITLE:reference/encryption
DESCRIPTION:description: "How Jazz encrypts your data to ensure privacy and security. " }; Jazz uses proven cryptographic primitives in a novel, but simple protocol to implement auditable permissions while allowing real-time collaboration and offline editing. Jazz uses proven cryptographic primitives in a novel, but simple protocol to implement auditable permissions while allowing real-time collaboration and offline editing.
description: "How Jazz encrypts your data to ensure privacy and security."
};
# Encryption
Jazz uses proven cryptographic primitives in a novel, but simple protocol to implement auditable permissions while allowing real-time collaboration and offline editing.
## How encryption works
Jazz uses proven cryptographic primitives in a novel, but simple protocol to implement auditable permissions while allowing real-time collaboration and offline editing.
### Write permissions: Signing with your keys
When you create or modify CoValues, Jazz cryptographically signs every transaction:
- All transactions are signed with your account's signing keypair
- This proves the transaction came from you
- Whether transactions are valid depends on your permissions in the Group that owns the CoValue
- Groups have internal logic ensuring only admins can change roles or create invites
- You can add yourself to a Group only with a specific role via invites
### Read permissions: Symmetric encryption
Groups use a shared "read key" for encrypting data:
- Admins reveal this symmetric encryption key to accounts with "reader" role or higher
- All transactions in CoValues owned by that Group are encrypted with the current read key
- When someone is removed from a Group, the read key rotates and gets revealed to all remaining members
- CoValues start using the new read key for future transactions
This means removed members can't read new data, but existing data they already had access to remains readable to them.
## Key rotation and security
Jazz automatically handles key management:
- **Member removal triggers rotation**: When you remove someone from a Group, Jazz generates a new read key
- **Seamless transition**: New transactions use the new key immediately
- **No data loss**: Existing members get the new key automatically
## Streaming encryption
Jazz encrypts data efficiently for real-time collaboration:
- **Incremental hashing**: CoValue sessions use [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) for append-only hashing
- **Session signatures**: Each session is signed with [Ed25519](https://ed25519.cr.yp.to/) after each transaction
- **Stream ciphers**: Data is encrypted using [XSalsa20](https://cr.yp.to/salsa20.html) stream cipher
- **Integrity protection**: Hashing and signing ensure data hasn't been tampered with
Although we're not lawyers, and so can't give legal advice, the encryption algorithms used in Jazz are widely published. As a result, we believe that Jazz does not use 'Non-standard cryptography' per the [BIS requirements](https://www.ecfr.gov/current/title-15/subtitle-B/chapter-VII/subchapter-C/part-772#p-772.1(Non-standard%20cryptography)) (and therefore the requirements for publishing Jazz apps in the Apple App Store).
## Content addressing
CoValue IDs are the [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) hash of their immutable "header" (containing CoValue type and owning group). This allows CoValues to be "content addressed" while remaining dynamic and changeable.
## What this means for you
**Privacy by default**: Your data is always encrypted, even on Jazz Cloud servers. Only people you explicitly give access to can read your data.
**Flexible permissions**: Use Groups to control exactly who can read, write, or admin your CoValues.
**Automatic security**: Key rotation and encryption happen behind the scenes - you don't need to think about it.
**Verifiable authenticity**: Every change is cryptographically signed, so you always know who made what changes.
## Further reading
- [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) - append-only hashing
- [Ed25519](https://ed25519.cr.yp.to/) - signature scheme
- [XSalsa20](https://cr.yp.to/salsa20.html) - stream cipher for data encryption
### Implementation details
The cryptographic primitives are implemented in the [`cojson/src/crypto`](https://github.com/garden-co/jazz/tree/main/packages/cojson/src/crypto) package.
Key files to explore:
- [`permissions.ts`](https://github.com/garden-co/jazz/blob/main/packages/cojson/src/permissions.ts) - Permission logic
- [`permissions.test.ts`](https://github.com/garden-co/jazz/blob/main/packages/cojson/src/tests/permissions.test.ts) - Permission tests
- [`verifiedState.ts`](https://github.com/garden-co/jazz/blob/main/packages/cojson/src/coValueCore/verifiedState.ts) - State verification
- [`coValueCore.test.ts`](https://github.com/garden-co/jazz/blob/main/packages/cojson/src/tests/coValueCore.test.ts) - Core functionality tests
===PAGE:reference/faq===
TITLE:reference/faq
DESCRIPTION:description: "Get answers to common questions about Jazz and our commitment to open source. " }; Jazz is backed by fantastic angel and institutional investors with experience and know-how in devtools and has been in development since 2020. We're committed to Jazz being around for a long time!
description: "Get answers to common questions about Jazz and our commitment to open source."
};
# Frequently Asked Questions
## How established is Jazz?
Jazz is backed by fantastic angel and institutional investors with experience and know-how in devtools and has been in development since 2020.
## Will Jazz be around long-term?
We're committed to Jazz being around for a long time! We understand that when you choose Jazz for your projects, you're investing time and making a significant architectural choice, and we take that responsibility seriously.
That's why we've designed Jazz with longevity in mind from the start:
- The open source nature of our sync server means you'll always be able to run your own infrastructure
- Your data remains accessible even if our cloud services change
- We're designing the protocol as an open specification
This approach creates a foundation that can continue regardless of any single company's involvement. The local-first architecture means your apps will always work, even offline, and your data remains yours.
## How secure is my data?
Jazz encrypts all your data by default using modern cryptographic standards. Every transaction is cryptographically signed, and data is encrypted using industry-standard algorithms including BLAKE3 hashing, Ed25519 signatures, and XSalsa20 stream ciphers.
Key features of Jazz's security:
- **Privacy by default**: Your data is encrypted even on Jazz Cloud servers
- **Automatic key rotation**: When members are removed from Groups, encryption keys rotate automatically
- **Verifiable authenticity**: Every change is cryptographically signed
- **Zero-trust architecture**: Only people you explicitly grant access can read your data
For technical details, see our [encryption documentation](/docs/reference/encryption).
## Does Jazz use Non-standard cryptography?
Jazz uses BLAKE3, XSalsa20, and Ed25519, which are all widely published and publicly reviewed standard cryptographic algorithms.
Although we're not lawyers and so can't give legal advice, we believe that Jazz does not use 'Non-standard cryptography' as defined in the [BIS requirements](https://www.ecfr.gov/current/title-15/subtitle-B/chapter-VII/subchapter-C/part-772#p-772.1(Non-standard%20cryptography)) and therefore meets the requirements for publishing Jazz apps in the Apple App Store.
===PAGE:reference/performance===
TITLE:reference/performance
DESCRIPTION:description: "Advanced performance tips for using Jazz. " }; The fastest implementations are (in order): 1. [Node-API crypto](/docs/server-side/setup#node-api) (only available in some Node/Deno environments) and RNCrypto on React Native (Default) 2.
description: "Advanced performance tips for using Jazz."
};
# Tips for maximising Jazz performance
## Use the best crypto implementation for your platform
The fastest implementations are (in order):
1. [Node-API crypto](/docs/server-side/setup#node-api) (only available in some Node/Deno environments) and RNCrypto on React Native (Default)
2. [WASM crypto](/docs/server-side/setup#wasm-on-edge-runtimes)
Check whether your environment supports Node-API.
Some edge runtimes may not enable WASM by default.
## Initialize WASM asynchronously
If you want to initialize the WASM asynchronously (**Suggested**), you can use the `initWasm` function.
Otherwise, the WASM will be initialized synchronously and will block the main thread (**Not Recommended**).
```ts index.ts#InitWasmAsync
```
## Minimise group extensions
Group extensions make it easy to cascade permissions and they’re fast enough for most cases.
However, performance can slow down when many parent groups need to load in the dependency chain.
To avoid this, create and reuse groups manually when their permissions stay the same for both CoValues over time.
**Note**: Implicit CoValue creation extends groups automatically. Be careful about how you create nested CoValues if you are likely to build long dependency chains.
```ts index.ts#ImplicitVsExplicit
```
You can also configure Jazz to reuse the container CoValue's owner when creating nested CoValues:
```ts permissions-and-sharing/overview/index.ts#SetDefaultSchemaPermissions
```
## Choose simple datatypes where possible
CoValues will always be slightly slower to load than their primitive counterparts. For most cases, this is negligible.
In data-heavy apps where lots of data has to be loaded at the same time, you can choose to trade off some of the flexibility of CoValues for speed by opting for primitive data types.
### `z.string()` vs CoTexts
In case you use a CoText, Jazz will enable character-by-character collaboration possibilities for you. However, in many cases, users do not expect to be able to collaborate on the text itself, and are happy with replacing the whole string at once, especially shorter strings. In this case, you could use a `z.string()` for better performance.
Examples:
- names
- URLs
- phone numbers
### `z.object()/z.tuple()` vs CoMaps
CoMaps allow granular updates to objects based on individual keys. If you expect your whole object to be updated at once, you could consider using the `z.object()` or `z.tuple()` type. Note that if you use these methods, you must replace the whole value if you choose to update it.
Examples:
- locations/co-ordinates
- data coming from external sources
- data which is rarely changed after it is created
```ts index.ts#ZObjectsZTuples
```
### Avoid expensive selectors [!framework=react]
Using selectors is a great way to avoid unnecessary re-renders in your app. However, an expensive selector will cause your app to run slowly as the selector will re-run every time the CoValue updates.
In case you need to run expensive computations on your CoValues, [extract this into a separate `useMemo` call](/docs/react/core-concepts/subscription-and-loading#avoiding-expensive-selectors).
===PAGE:reference/testing===
TITLE:reference/testing
DESCRIPTION:description: "Guidance on testing Jazz apps. " }; As you develop your Jazz app, you might find yourself needing to test functionality relating to sync, identities, and offline behaviour. The `jazz-tools/testing` utilities provide helpers to enable you to do so.
description: "Guidance on testing Jazz apps."
};
# Testing Jazz Apps
As you develop your Jazz app, you might find yourself needing to test functionality relating to sync, identities, and offline behaviour. The `jazz-tools/testing` utilities provide helpers to enable you to do so.
## Core test helpers
Jazz provides some key helpers that you can use to simplify writing complex tests for your app's functionality.
### `setupJazzTestSync`
This should normally be the first thing you call in your test setup, for example in a `beforeEach` or `beforeAll` block. This function sets up an in-memory sync node for the test session, which is needed in case you want to test data synchronisation functionality. Test data is not persisted, and no clean-up is needed between test runs.
```ts index.ts#SetupJazzTestSync
```
### `createJazzTestAccount`
After you've created the initial account using `setupJazzTestSync`, you'll typically want to create user accounts for running your tests.
You can use `createJazzTestAccount()` to create an account and link it to the sync node. By default, this account will become the currently active account (effectively the 'logged in' account).
You can use it like this:
```ts index.ts#CreateTestAccount
```
#### `AccountSchema`
This option allows you to provide a custom account schema to the utility to be used when creating the account. The account will be created based on the schema, and all attached migrations will run.
#### `isCurrentActiveAccount`
This option (disabled by default) allows you to quickly switch to the newly created account when it is created.
```ts index.ts#CurrentlyActive
```
#### `creationProps`
This option allows you to specify `creationProps` for the account which are used during the account creation (and passed to the migration function on creation).
## Managing active Accounts
During your tests, you may need to manage the currently active account after account creation, or you may want to simulate behaviour where there is no currently active account.
### `setActiveAccount`
Use `setActiveAccount()` to switch between active accounts during a test run.
You can use this to test your app with multiple accounts.
```ts index.ts#SetActiveAccount
```
### `runWithoutActiveAccount`
If you need to test how a particular piece of code behaves when run without an active account.
```ts index.ts#RunWithoutAccount
```
## Managing Context
To test UI components, you may need to create a mock Jazz context.
In most cases, you'd use this for initialising a provider. You can see how we [initialise a test provider for React tests here](https://github.com/garden-co/jazz/blob/main/packages/jazz-tools/src/react-core/testing.tsx), or see how you could [integrate with `@testing-library/react` here](https://github.com/garden-co/jazz/blob/main/packages/jazz-tools/src/react-core/tests/testUtils.tsx).
You can render your components for testing by passing a mocked Jazz context to the `@testing-library/svelte` `render` helper.
[You can see an example of how we do that here](https://github.com/garden-co/jazz/blob/main/packages/jazz-tools/src/svelte/tests/testUtils.ts).
The `TestJazzContextManager` mocks the `JazzContextManager` to allow you to instantiate a Jazz context as a user or a guest, allowing you to run tests which depend on an authenticated or a guest session.
You'll normally use either:
* `TestJazzContextManager.fromAccount(account, props?)` to simulate a logged-in context. You can pass `isAuthenticated: false` as an option to simulate an [anonymous user](docs/key-features/authentication/authentication-states#anonymous-authentication).
* `TestJazzContextManager.fromGuest({ guest }, props?)` to simulate a [guest context](/docs/key-features/authentication/authentication-states#guest-mode).
You can also use `TestJazzContextManager.fromAccountOrGuest()` to allow you to pass either.
### Simulating connection state changes
You can use `MockConnectionStatus.setIsConnected(isConnected: boolean)` to simulate disconnected and connected states (depending on whether `isConnected` is set to `true` or `false`).
## Next Steps
You're ready to start writing your own tests for your Jazz apps now. For further details and reference, you can check how we do our testing below.
* [Unit test examples](https://github.com/garden-co/jazz/tree/main/packages/jazz-tools/src/tools/tests)
* [End-to-end examples](https://github.com/garden-co/jazz/tree/main/tests/e2e/tests)
* [React-specific tests](https://github.com/garden-co/jazz/tree/main/packages/jazz-tools/src/react-core/tests)
* [Svelte-specific tests](https://github.com/garden-co/jazz/tree/main/packages/jazz-tools/src/svelte/tests)
===PAGE:reference/workflow-world===
TITLE:reference/workflow-world
DESCRIPTION:description: "Learn how to use Jazz as a World to support Vercel Workflows" }; Vercel Workflows allow developers to create durable, reliable, observable functions using TypeScript. You can use a simple, declarative API to define and use your workflows, replacing hand-rolled queues and custom retries. [Learn more about using Workflow](https://useworkflow.
description: "Learn how to use Jazz as a World to support Vercel Workflows"
};
# Jazz Workflow World
Vercel Workflows allow developers to create durable, reliable, observable functions using TypeScript.
You can use a simple, declarative API to define and use your workflows, replacing hand-rolled queues and custom retries.
[Learn more about using Workflow](https://useworkflow.dev/), or
## Getting started
We recommend using Jazz Cloud as a backend for your workflows. Your workflow state will be persisted using Jazz, allowing you to run and deploy your apps without any additional infrastructure.
## Get your free API key
Set your API key in your .env.local or production environment variables.
.env.local
*(or add to your production environment variables)*
```bash
JAZZ_API_KEY=
WORKFLOW_TARGET_WORLD=workflow-world-jazz
```
## Install the Jazz Workflow World
Install the Jazz Workflow World, and add `WORKFLOW_TARGET_WORLD=workflow-world-jazz` to your `.env.local` file (or your production environment variables) to take use the Jazz Workflow World for running your workflows.
```sh
npm add workflow-world-jazz
```
```sh
pnpm add workflow-world-jazz
```
## Create a worker
Head over to https://dashboard.jazz.tools/ and create a new app. Once your app is ready, you'll see the option to *Create Worker Group*. This will do two things:
1. Create a group, with a group ID, a master account ID, and a master account secret. This account is used for revocation and rotation of workers, and should *not* be used as a normal worker.
2. Create a normal worker with an ID and a secret, which can be used to run your workflows.
Secrets displayed in the Jazz dashboard will only appear once. Make sure to save them somewhere secure. If you lose them, you will need to create a new worker group.
Click to create a worker group. Save **all** these details securely!
## Add your worker ID and secret to your environment variables
.env.local
*(or add to your production environment variables)*
```bash
JAZZ_API_KEY=
WORKFLOW_TARGET_WORLD=workflow-world-jazz
JAZZ_WORKER_ACCOUNT=
JAZZ_WORKER_SECRET=
```
## Set up webhooks
### For local development
Run the following command to configure a local webhook registry (which is needed to progress the workflow from your development environment). The webhook registry details will be added automatically to your environment variables.
```sh
npx env-cmd -f .env.local -x npx jazz-run webhook create-registry --grant \$JAZZ_WORKER_ACCOUNT >> .env.local
```
```sh
pnpx env-cmd -f .env.local -x -- pnpx jazz-run webhook create-registry --grant \$JAZZ_WORKER_ACCOUNT >> .env.local
```
What's this?
A webhook registry is a service that allows you to respond to state changes in your workflow. Let's break this down.
* `env-cmd -f .env.local -x` —  allows the `jazz-run` tool to read the environment variables you've already set in your `.env.local` file
* `jazz-run webhook create-registry` — creates a registry
* `--grant \$JAZZ_WORKER_ACCOUNT` — grants access to the worker you just created (where the `\` is escaping the dollar sign in the terminal).
Once your webhook registry is configured, you can start it up using the following command:
```sh
npx env-cmd -f .env.local npx jazz-run webhook run
```
```sh
pnpx env-cmd -f .env.local -- pnpx jazz-run webhook run
```
```bash
```
What's this?
Same as above, but this time, we're actually running the webhook registry server
* `env-cmd -f .env.local -x` — allows the `jazz-run` tool to read the environment variables you've already set in your `.env.local` file
* `jazz-run webhook run` — runs a server hosting the webhooks.
### For production
On the https://dashboard.jazz.tools/ dashboard, you can turn on the *Enable Webhooks* toggle. You'll see a new environment variable added to your *Worker Environment Variables*: `JAZZ_WEBHOOK_REGISTRY_ID`. Add this to your production environment variables, along with `JAZZ_WEBHOOK_ENDPOINT`, which should be the URL of your production application's public URL.
## Develop your workflows
You can now run your app, and start using workflows!
## Self-hosting
You can also host the sync server and webhook registry yourself using our open-source sync server.
### Run your sync server
You can run a sync server from the command line using the following command:
```sh
npx jazz-run sync
```
```sh
pnpm --allow-build=better-sqlite3 dlx jazz-run sync
```
Your sync server will start by default at `http://localhost:4200`. In case you are running the sync server in production, we recommend using a reverse proxy. In this case, replace `localhost:4200` from this point on with the public URL for your sync server.
### Create a worker account
You can create a worker account locally from the terminal:
```sh
npx jazz-run account create --peer http://localhost:4200 --name "Workflow Worker" >> .env.local
```
```sh
pnpx jazz-run account create --peer http://localhost:4200 --name "Workflow Worker" >> .env.local
```
### Create a webhook registry
As you're running the sync server outside the Jazz cloud, you'll need to host your own webhook registry. You can create a webhook registry locally from the terminal:
```sh
npx env-cmd -f .env.local -x npx jazz-run webhook create-registry --peer http://localhost:4200 --grant \$JAZZ_WORKER_ACCOUNT >> .env.local
```
```sh
pnpx env-cmd -f .env.local -x -- pnpx jazz-run webhook create-registry --peer http://localhost:4200 --grant \$JAZZ_WORKER_ACCOUNT >> .env.local
```
### Tell Jazz about your sync server
Your `.env.local` file should now contain all the environment variables you need to run your app, except for the URL of the sync server.
Add `JAZZ_SYNC_SERVER=http://localhost:4200` to your `.env.local` file.
Your `.env.local` file should look like this:
.env.local
```bash
WORKFLOW_TARGET_WORLD=workflow-world-jazz
JAZZ_WORKER_ACCOUNT=
JAZZ_WORKER_SECRET=
JAZZ_WEBHOOK_REGISTRY_ID=
JAZZ_WEBHOOK_REGISTRY_SECRET=
JAZZ_SYNC_SERVER=http://localhost:4200
```
### Develop your workflows
You can now run your app, and start using workflows!
### Deploying to production
In order to deploy your app to production, you will need to have both the sync server and the webhook registry available as long-running processes, with the correct environment variables set.
## Reference guide for environment variables
Variable
Description
Default
`WORKFLOW_TARGET_WORLD`
Set to `'workflow-world-jazz'` to use this world
-
`JAZZ_API_KEY`
Jazz API key (if using Jazz Cloud)
-
`JAZZ_WORKER_ACCOUNT`
Jazz account that will own all of the workflow data
-
`JAZZ_WORKER_SECRET`
Secret for the worker account
-
`JAZZ_WEBHOOK_REGISTRY_ID`
CoValue ID for the webhook registry
-
`JAZZ_WEBHOOK_REGISTRY_SECRET`
Secret for the webhook registry (only if self-hosting)
-
`JAZZ_SYNC_SERVER`
Sync server URL (only if self-hosting)
Jazz Cloud (`wss://cloud.jazz.tools/?key=${JAZZ_API_KEY}`)
`JAZZ_WEBHOOK_ENDPOINT`
The endpoint where webhooks should get delivered
`http://localhost:${PORT}` or `http://localhost:3000` if `PORT` is not set
===PAGE:server-side/communicating-with-workers/http-requests===
TITLE:server-side/communicating-with-workers/http-requests
DESCRIPTION:description: "How to use HTTP requests to communicate with Server Workers using Web API" }; HTTP requests are the simplest way to communicate with Server Workers. While they don't provide all the features of [JazzRPC](/docs/server-side/jazz-rpc), they are a good solution when all you need is basic authentication. They work by generating a short-lived token with `generateAuthToken` and attaching it to the request headers as `Authorization: Jazz `.
description: "How to use HTTP requests to communicate with Server Workers using Web API"
};
# HTTP Requests with Server Workers
HTTP requests are the simplest way to communicate with Server Workers. While they don't provide all the features of [JazzRPC](/docs/server-side/jazz-rpc), they are a good solution when all you need is basic authentication.
They work by generating a short-lived token with `generateAuthToken` and attaching it to the request headers as `Authorization: Jazz `.
The server can then verify the token with `authenticateRequest` and get the account that the request was made by.
While the token is cryptographically secure, using non secure connections still makes you vulnerable to MITM attacks as - unlike JazzRPC - the request is not signed.
Replay attacks are mitigated by token expiration (default to 1 minute), but it's up to you to ensure that the token is not reused.
It is recommended to use HTTPS whenever possible.
## Creating a Request
You can use any method to create a request; the most common is the `fetch` API.
By default, the token is expected to be in the `Authorization` header in the form of `Jazz `.
```ts index.ts#Basic
```
## Authenticating requests
You can use the `authenticateRequest` function to authenticate requests.
Attempting to authenticate a request without a token doesn't fail; it returns `account` as `undefined`. For endpoints that **require** authentication, ensure `account` is defined in addition to any permission checks you may need.
```ts index.ts#GetRequest
```
## Multi-account environments
If you are using multiple accounts in your environment - for instance if your server starts multiple workers - or in general if you need to send and authenticate requests as a specific account, you can specify which one to use when generating the token or when authenticating the request.
### Making a request as a specific account
`generateAuthToken` accepts an optional account parameter, so you can generate a token for a specific account.
```ts index.ts#GenerateAsAccount
```
### Authenticating a request as a specific account
Similarly, specify the account used to verify the token via the `loadAs` option:
```ts index.ts#VerifyAsAccount
```
## Custom token expiration
You can specify the expiration time of the token using the `expiration` option. The default expiration time is 1 minute.
```ts index.ts#CustomExpiration
```
## Custom token location
While using the `Authorization` header using the `Jazz ` format is the most common way to send the token, you can provide the token in any other way you want.
For example, you can send the token in the `x-jazz-auth-token` header:
```ts index.ts#CustomLocation
```
Then you can specify the location of the token using the `getToken` option:
```ts index.ts#GetToken
```
## Manual token parsing
If you need to manually parse a token from a string, you can use the `parseAuthToken` function.
```ts index.ts#ManualTokenParsing
```
===PAGE:server-side/communicating-with-workers/inbox===
TITLE:server-side/communicating-with-workers/inbox
DESCRIPTION:description: "How to use the Inbox API to communicate with Server Workers using experimental_useInboxSender and inbox. subscribe. " }; The Inbox API provides a message-based communication system for Server Workers in Jazz.
description: "How to use the Inbox API to communicate with Server Workers using experimental_useInboxSender and inbox.subscribe."
};
# Inbox API with Server Workers
The Inbox API provides a message-based communication system for Server Workers in Jazz.
It works on top of the Jazz APIs and uses sync to transfer messages between the client and the server.
## Setting up the Inbox API
### Define the inbox message schema
Define the inbox message schema in your schema file:
```ts schema.ts#BookTicketMessageSchema
```
Any kind of CoMap is valid as an inbox message.
### Setting up the Server Worker
Run a server worker and subscribe to the `inbox`:
```ts index.ts#Basic
```
### Handling multiple message types
`inbox.subscribe` should be called once per worker instance.
If you need to handle multiple message types, you can use the `co.discriminatedUnion` function to create a union of the message types.
```ts schema.ts#UnionSchema
```
And check the message type in the handler:
```ts index.ts#WithUnionSchema
```
## Sending messages from the client
### Using the Inbox Sender hook
Use `experimental_useInboxSender` to send messages from React components:
{/* TODO: other frameworks ? Inbox API is not really supported for anything other than React */}
```ts react-snippet.tsx
```
The `sendInboxMessage` API returns a Promise that waits for the message to be handled by a Worker.
A message is considered to be handled when the Promise returned by `inbox.subscribe` resolves.
The value returned will be the id of the CoValue returned in the `inbox.subscribe` resolved promise.
## Deployment considerations
Multi-region deployments are not supported when using the Inbox API.
If you need to split the workload across multiple regions, you can use the [HTTP API](/docs/server-side/communicating-with-workers/http-requests) instead.
===PAGE:server-side/communicating-with-workers/overview===
TITLE:server-side/communicating-with-workers/overview
DESCRIPTION:description: "How to send data to Server Workers, set permissions and subscriptions. " }; Server Workers in Jazz can receive data from clients through two different APIs, each with their own characteristics and use cases. This guide covers the key properties of each approach to help you choose the right one for your application.
description: "How to send data to Server Workers, set permissions and subscriptions."
};
# Communicating with Server Workers
Server Workers in Jazz can receive data from clients through two different APIs, each with their own characteristics and use cases.
This guide covers the key properties of each approach to help you choose the right one for your application.
## Overview
Jazz provides three ways to communicate with Server Workers:
1. **JazzRPC** - A simple, yet powerful RPC system that allows you to call functions on Server Workers from the client side.
1. **HTTP Requests** - The easiest to work with and deploy, ideal for simple communication with workers.
1. **Inbox** - Fully built using the Jazz data model with offline support
## JazzRPC (Recommended)
JazzRPC is the most straightforward way to communicate with Server Workers. It works well with any framework or runtime that supports standard Request and Response objects, can be scaled horizontally, and put clients and workers in direct communication.
### When to use JazzRPC
Use JazzRPC when you need immediate responses, are deploying to serverless environments, need horizontal scaling, or are working with standard web frameworks.
It's also a good solution when using full-stack frameworks like Next.js, where you can use the API routes to handle the server-side logic.
[Learn more about JazzRPC →](/docs/server-side/jazz-rpc)
## HTTP Requests
If all you need is basic authentication when communicating with a worker, you can use Regular HTTP requests. They are the easiest to work with and deploy, ideal for simple communication with workers.
HTTP requests are the easiest way to communicate with Server Workers. They don't come with any of the benefits of JazzRPC, but are a good solution for simple communication with workers.
### When to use HTTP Requests
Use HTTP requests when you don't need the advanced features of JazzRPC, but you need to communicate with a worker from a serverless environment or a standard web framework and need basic authentication.
[Learn more about HTTP Requests →](/docs/server-side/communicating-with-workers/http-requests)
## Inbox
The Inbox API is fully built using the Jazz data model and provides offline support. Requests and responses are synced as soon as the device becomes online, but require the Worker to always be online to work properly.
### When to use Inbox
Use Inbox when you need offline support, want to leverage the Jazz data model, can ensure the worker stays online, need persistent message storage, or want to review message history.
It works great when you don't want to expose your server with a public address, because it uses Jazz's sync to make the communication happen.
Since Jazz handles all the network communication, the entire class of network errors that usually come with traditional HTTP requests are not a problem when
using the Inbox API.
[Learn more about Inbox →](/docs/server-side/communicating-with-workers/inbox)
===PAGE:server-side/jazz-rpc===
TITLE:server-side/jazz-rpc
DESCRIPTION:description: "How to use JazzRPC to communicate with Server Workers using experimental_defineRequest. " }; JazzRPC is the most straightforward and complete way to securely communicate with Server Workers. It works well with any framework or runtime that supports standard Request and Response objects, can be scaled horizontally, and puts clients and workers in direct communication.
description: "How to use JazzRPC to communicate with Server Workers using experimental_defineRequest."
};
# JazzRPC
JazzRPC is the most straightforward and complete way to securely communicate with Server Workers. It works well with any framework or runtime that supports standard Request and Response objects, can be scaled horizontally, and puts clients and workers in direct communication.
## Setting up JazzRPC
### Defining request schemas
Use `experimental_defineRequest` to define your API schema:
```ts bookEventTicket.ts#Basic
```
### Setting up the Server Worker
We need to start a Server Worker instance that will be able to sync data with the sync server, and handle the requests.
```ts jazzServer.ts#Basic
```
## Handling JazzRPC requests on the server
### Creating API routes
Create API routes to handle the defined RPC requests. Here's an example using Next.js API routes:
```ts server.ts#Basic
```
## Making requests from the client
### Using the defined API
Make requests from the client using the defined API:
```ts client.ts
```
## Error handling
### Server-side error handling
Use `JazzRequestError` to return proper HTTP error responses:
```ts server.ts#ErrorHandling
```
To ensure that the limit is correctly enforced, the handler should be deployed in a single worker instance (e.g. a single Cloudflare DurableObject).
Details on how to deploy a single instance Worker are available in the [Deployments & Transactionality](#deployments--transactionality) section.
### Client-side error handling
Handle errors on the client side:
```ts client.ts#ErrorHandling
```
The `experimental_defineRequest` API is still experimental and may change in future versions. For production applications, consider the stability implications.
## Security safeguards provided by JazzRPC
JazzRPC includes several built-in security measures to protect against common attacks:
### Cryptographic Authentication
- **Digital Signatures**: Each RPC is cryptographically signed using the sender's private key
- **Signature Verification**: The server verifies the signature using the sender's public key to ensure message authenticity and to identify the sender account
- **Tamper Protection**: Any modification to the request payload will invalidate the signature
### Replay Attack Prevention
- **Unique Message IDs**: Each RPC has a unique identifier (`co_z${string}`)
- **Duplicate Detection**: incoming messages ids are tracked to prevent replay attacks
- **Message Expiration**: RPCs expire after 60 seconds to provide additional protection
These safeguards ensure that JazzRPC requests are secure, authenticated, and protected against common attack vectors while maintaining the simplicity of standard HTTP communication.
## Deployments & Transactionality
### Single Instance Requirements
Some operations need to happen one at a time and in the same place, otherwise the data can get out of sync.
For example, if you are checking capacity for an event and creating tickets, you must ensure only one server is doing it.
If multiple servers check at the same time, they might all think there is space and allow too many tickets.
Jazz uses eventual consistency (data takes a moment to sync between regions), so this problem is worse if you run multiple server copies in different locations.
Until Jazz supports transactions across regions, the solution is to deploy a single server instance for these sensitive operations.
Examples of when you must deploy on a single instance are:
1. Distribute a limited number of tickets
* Limiting ticket sales so that only 100 tickets are sold for an event.
* The check (“is there space left?”) and ticket creation must happen together, or you risk overselling.
2. Inventory stock deduction
* Managing a product stock count (e.g., 5 items left in store).
* Multiple instances could let multiple buyers purchase the last item at the same time.
3. Sequential ID or token generation
* Generating unique incremental order numbers (e.g., #1001, #1002).
* Multiple instances could produce duplicates if not coordinated.
Single servers are necessary to enforce invariants or provide a consistent view of the data.
As a rule of thumb, when the output of the request depends on the state of the database, you should probably deploy on a single instance.
### Multi-Region Deployment
If your code doesn’t need strict rules to keep data in sync (no counters, no limits, no “check‑then‑update” logic), you can run your workers in many regions at the same time.
This way:
* Users connect to the closest server (faster).
* If one region goes down, others keep running (more reliable).
Examples of when it's acceptable to deploy across multiple regions are:
1. Sending confirmation emails
* After an action is complete, sending an email to the user does not depend on current database state.
2. Pushing notifications
* Broadcasting “event booked” notifications to multiple users can be done from any region.
3. Logging or analytics events
* Recording “user clicked this button” or “page viewed” events, since these are additive and don’t require strict ordering.
4. Calling external APIs (e.g., LLMs, payment confirmations)
* If the response does not modify shared counters or limits, it can be done from any region.
5. Pre-computing cached data or summaries
* Generating read-only previews or cached summaries where stale data is acceptable and does not affect core logic.
Generally speaking, if the output of the request does not depend on the state of the database, you can deploy across multiple regions.
===PAGE:server-side/quickstart===
TITLE:server-side/quickstart
DESCRIPTION:description: "Get started building a simple server worker with Jazz in 10 minutes. " }; This quickstart guide will take you from an empty project to a server worker which can interact with your Jazz application. - You'll get the most out of this guide if you complete [the frontend quickstart guide](/docs/quickstart) first.
description: "Get started building a simple server worker with Jazz in 10 minutes."
};
# Get started with Server Workers in 10 minutes
This quickstart guide will take you from an empty project to a server worker which can interact with your Jazz application.
- You'll get the most out of this guide if you complete [the frontend quickstart guide](/docs/quickstart) first.
- If you've already completed the frontend quickstart, you can skip straight to [extending your schema](#define-your-schema).
## Create Your App
We'll be using a basic Vite + TypeScript template for simplicity, but you can use any framework you like.
```sh
npm create vite@latest jazzfest -- --template vanilla-ts
cd jazzfest
```
```sh
pnpm create vite@latest jazzfest -- --template vanilla-ts
cd jazzfest
```
We'll be using Next.js for simplicity, but you can use any framework you like.
You can accept the defaults for all the questions, or customise the project as you like.
```sh
npm create-next-app@latest --typescript jazzfest
cd jazzfest
```
```sh
pnpm create-next-app@latest --typescript jazzfest
cd jazzfest
```
We'll be using SvelteKit for simplicity, but you can use any framework you like.
You can accept the defaults for all the questions, or customise the project as you like.
```sh
npx sv create --types ts --template minimal jazzfest
cd jazzfest
```
```sh
pnpx sv create --types ts --template minimal jazzfest
cd jazzfest
```
Requires Node.js 20+
## Install Jazz
The `jazz-tools` package includes everything you're going to need to build your first Jazz server worker.
## Set your API key
## Define your schema
We're going to define a simple schema for our server worker. We'll use the `root` on the worker to store a list of bands. We're also going to add a migration to initialise the `root` if it doesn't exist.
schema.ts
app/schema.ts
src/lib/schema.ts
```ts schema.ts
```
If you're continuing from the [front-end Quickstart](/docs/quickstart), you can extend your existing schema.
## Create a Server Worker
Jazz provides a CLI to create server workers. You can create a server worker using the following command:
```sh
npx jazz-run account create --name "JazzFest Server Worker"
```
```sh
pnpx jazz-run account create --name "JazzFest Server Worker"
```
You can copy the output of this command and paste it directly into your `.env` file:
.env
} value="vanilla" preferWrap>
```bash
# Note that you will likely need to set up your env vars so they are readable by both your front AND backend. Check your server's docs.
VITE_JAZZ_API_KEY=you@example.com # or your API key
#[!code ++:2]
VITE_JAZZ_WORKER_ACCOUNT=co_z...
JAZZ_WORKER_SECRET=sealerSecret_z.../signerSecret_z...
```
} value="react" preferWrap>
```bash
NEXT_PUBLIC_JAZZ_API_KEY=you@example.com # or your API key
#[!code ++:2]
NEXT_PUBLIC_JAZZ_WORKER_ACCOUNT=co_z...
JAZZ_WORKER_SECRET=sealerSecret_z.../signerSecret_z...
```
} value="svelte" preferWrap>
```bash
PUBLIC_JAZZ_API_KEY=you@example.com # or your API key
#[!code ++:2]
PUBLIC_JAZZ_WORKER_ACCOUNT=co_z...
JAZZ_WORKER_SECRET=sealerSecret_z.../signerSecret_z...
```
Your `JAZZ_WORKER_SECRET` should **never** be exposed to the client.
## Defining your HTTP request schema
Next, we're going to set up an HTTP request schema to define our request and response. Here, we tell Jazz that we will send a `Band` under the key `band` and expect a `bandList` in response, which is a list of `Band`s.
We also need to tell Jazz which keys should be treated as loaded in the request and response using the `resolve` query.
announceBandSchema.ts
app/announceBandSchema.ts
src/lib/announceBandSchema.ts
} value="vanilla" preferWrap>
```ts announceBandSchemaVanilla.ts
```
} value="react" preferWrap>
```ts announceBandSchema.ts
```
} value="svelte" preferWrap>
```ts announceBandSchema.svelte.ts
```
## Configure your Server Worker
We're going to use the `startWorker` function to start our server worker, and register a `POST` handler, which will listen for the requests being sent to our server worker.
We'll also use a `resolve` query here to make sure that the `bandList` is loaded on the worker's root.
worker.ts
app/api/announce-band/route.ts
src/routes/api/announce-band/+server.ts
} value="vanilla" preferWrap>
```ts vanilla.ts
```
} value="react" preferWrap>
```ts route.ts
```
} value="svelte" preferWrap>
```ts +server.ts
```
## Start your server worker
We can now start our development server to make sure everything is working.
*You'll probably need to check your server's docs, otherwise the command below will start the Vite frontend dev server, not your backend HTTP server.*
If you open your browser, you should see the default Next.js welcome page.
If you open your browser, you should see the default SvelteKit welcome page.
### Not working?
- You should double check in your server's documentation how to launch the server.
- Check you set up your `.env` file correctly with `NEXT_PUBLIC_` where necessary
- Check you set up your `.env` file correctly with `PUBLIC_` where necessary
- Check you're importing `startWorker` from `jazz-tools/worker`
## Send requests to your server worker
### Creating a Jazz Client
*If you already have a working context from the frontend quickstart, you can skip this step.*
*If you already have a working provider from the frontend quickstart, you can skip this step.*
We're going to initialise a context so we can use Jazz on our client.
We're going to wrap our Next.js app in a `JazzReactProvider` so that we can use Jazz on our client.
We're going to wrap our SvelteKit app in a `JazzSvelteProvider` so that we can use Jazz on our client.
src/main.ts
app/layout.tsx
src/routes/+layout.svelte
} preferWrap>
```tsx main.ts#Context
```
} preferWrap>
```tsx layout.tsx
```
} preferWrap>
```svelte +layout.svelte
```
### Creating your page component
We're going to send a request to our server worker to announce a new band. Our worker will respond with a list of bands that we can display on our page.
src/main.ts
app/page.tsx
src/routes/+page.svelte
} preferWrap>
```tsx main.ts#Page
```
} preferWrap>
```tsx page.tsx
```
} preferWrap>
```svelte +page.svelte
```
## Try it out!
If you followed the instructions above, your backend server is already running. You will also need to start the Vite development server for your frontend application.
Your browser should now be showing you a page with an input field and a button. If you enter a band name and click the button, your server worker will receive the request and add the band to the list.
**Congratulations! 🎉** You've just built your first Jazz server worker!
This simple pattern is the foundation for building powerful, real-time applications.
Here are some ideas about what you could use your server worker for:
- integrating with payment providers
- sending emails/SMSes
- gathering data from external APIs
- managing authoritative state
Looking forward to seeing what you build!
## Next steps
- Complete the [front-end quickstart](/docs/quickstart) to learn more about how to build real-time UIs using Jazz
- Find out how to [handle errors](/docs/server-side/jazz-rpc#error-handling) gracefully in your server worker
- Learn how to share and [collaborate on data](/docs/permissions-and-sharing/overview) in groups with complex permissions
===PAGE:server-side/setup===
TITLE:server-side/setup
DESCRIPTION:description: "Use Jazz server-side through Server Workers which act like Jazz accounts. " }; Jazz is a distributed database that can be used on both clients or servers without any distinction. You can use servers to: - perform operations that can't be done on the client (e.
description: "Use Jazz server-side through Server Workers which act like Jazz accounts."
};
# Running Jazz on the server
Jazz is a distributed database that can be used on both clients or servers without any distinction.
You can use servers to:
- perform operations that can't be done on the client (e.g. sending emails, making HTTP requests, etc.)
- validate actions that require a central authority (e.g. a payment gateway, booking a hotel, etc.)
We call the code that runs on the server a "Server Worker".
The main difference to keep in mind when working with Jazz compared to traditional systems is that server code doesn't have any
special or privileged access to the user data. You need to be explicit about what you want to share with the server.
This means that your server workers will have their own accounts, and they need to be explicitly given access to the CoValues they need to work on.
## Generating credentials
Server Workers typically have static credentials, consisting of a public Account ID and a private Account Secret.
To generate new credentials for a Server Worker, you can run:
```sh
npx jazz-run account create --name "My Server Worker"
```
The name will be put in the public profile of the Server Worker's `Account`, which can be helpful when inspecting metadata of CoValue edits that the Server Worker has done.
By default the account will be stored in Jazz Cloud. You can use the `--peer` flag to store the account on a different sync server.
## Running a server worker
You can use `startWorker` to run a Server Worker. Similarly to setting up a client-side Jazz context, it:
- takes a custom `AccountSchema` if you have one (for example, because the worker needs to store information in its private account root)
- takes a URL for a sync & storage server
The migration defined in the `AccountSchema` will be executed every time the worker starts, the same way as it would be for a client-side Jazz context.
```ts index.ts#Basic
```
`worker` is an instance of the `Account` schema provided, and acts like `me` (as returned by `useAccount` on the client).
It will implicitly become the current account, and you can avoid mentioning it in most cases.
For this reason we also recommend running a single worker instance per server, because it makes your code much more predictable.
In case you want to avoid setting the current account, you can pass `asActiveAccount: false` to `startWorker`.
## Storing & providing credentials
Server Worker credentials are typically stored and provided as environment variables.
**Take extra care with the Account Secret — handle it like any other secret environment variable such as a DB password.**
## Wasm on Edge runtimes
On some edge platforms, such as Cloudflare Workers or Vercel Edge Functions, environment security restrictions may trigger WASM crypto to fail.
To avoid this failure, you can ensure that Jazz uses the WASM implementation by importing the WASM loader before using Jazz. For example:
```ts loadJazz.ts
```
Currently, the Jazz Loader is tested on the following edge environments:
- Cloudflare Workers
- Vercel Functions
### Requirements
- Edge runtime environment that supports WebAssembly
- `jazz-tools/load-edge-wasm` must be imported before any Jazz import
## Node-API
Jazz uses a WASM-based crypto implementation that provides near-native performance while ensuring full compatibility across a wide variety of environments.
For even higher performance on Node.js or Deno, you can enable the native crypto (Node-API) implementation. Node-API is Node.js's native API for building modules in Native Code (Rust/C++)
that interact directly with the underlying system, allowing for true native execution speed.
You can use it as follows:
```ts index.ts#NapiCrypto
```
The Node-API implementation is not available on all platforms. It is only available on Node.js 20.x and higher.
The supported platforms are:
- macOS (x64, ARM64)
- Linux (x64, ARM64, ARM, musl)
It does not work in edge runtimes.
### On Next.js
In order to use Node-API with Next.js, you need to tell Next.js to bundle the native modules in your build.
You can do this by adding the required packages to the [`serverExternalPackages`](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) array in your `next.config.js`.
**Note**: if you're deploying to Vercel, be sure to use the `nodejs` runtime!
next.config.js
```ts next.config.js
```
===PAGE:server-side/ssr===
TITLE:server-side/ssr
DESCRIPTION:description: "Add server-side rendering to your Jazz app. " }; This guide will take your simple client-side app to the next level by showing you how to create a server-rendered page to publish your data to the world. If you haven't gone through the [front-end Quickstart](/docs/quickstart), you might find this guide a bit confusing.
description: "Add server-side rendering to your Jazz app."
};
# Add Server-Side Rendering to your App
This guide will take your simple client-side app to the next level by showing you how to create a server-rendered page to publish your data to the world.
If you haven't gone through the [front-end Quickstart](/docs/quickstart), you might find this guide a bit confusing. If you're looking for a quick reference, you might find [this page](/docs/project-setup#ssr-integration) more helpful!
## Creating an agent
For Jazz to access data on the server, we need to create an SSR agent, which is effectively a read-only user which can access public data stored in Jazz.
We can create this user using the `createSSRJazzAgent` function. In this example, we'll create a new file and export the agent, which allows us to import and use the same agent in multiple pages.
app/jazzSSR.ts
src/lib/jazzSSR.ts
```ts jazzSSR.ts
```
If you want to initialize the WASM asynchronously (**Suggested**), you can use the `initWasm` function.
Otherwise, the WASM will be initialized synchronously and will block the main thread (**Not Recommended**).
app/jazzSSR.ts
src/lib/jazzSSR.ts
```ts jazzSSRInitWasm.ts
```
## Telling Jazz to use the SSR agent
Normally, Jazz expects a logged in user (or an anonymous user) to be accessing data. We can use the `enableSSR` setting to tell Jazz that this may not be the case, and the data on the page may be being accessed by an agent.
app/components/JazzWrapper.tsx
src/routes/+layout.svelte
} preferWrap>
```tsx JazzWrapper.tsx
```
} preferWrap>
```svelte +layout.svelte
```
## Making your data public
By default, when you create data in Jazz, it's private and only accessible to the account that created it.
However, the SSR agent is credential-less and unauthenticated, so it can only read data which has been made public. Although Jazz allows you to define [complex, role-based permissions](/docs/permissions-and-sharing/overview), here, we'll focus on making the CoValues public.
app/schema.ts
```ts schema.ts
```
## Creating a server-rendered page
Now let's set up a page which will be read by the agent we created earlier, and rendered fully on the server.
app/festival/[festivalId]/page.tsx
src/routes/festival/[festivalId]/+page.svelte
} preferWrap>
```tsx page.tsx
```
} preferWrap>
```svelte +page.svelte
```
TypeScript might not recognise that `params` is a promise. This is a new feature in Next.js 15, which you can [read more about here](https://nextjs.org/docs/messages/sync-dynamic-apis).
## Linking to your server-rendered page
The last step is to link to your server-rendered page from your `Festival` component so that you can find it easily!
app/components/Festival.tsx
lib/components/Festival.svelte
} preferWrap>
```tsx Festival.tsx
```
} preferWrap>
```svelte Festival.svelte
```
## Syncing data before navigation [!framework=svelte]
In some cases, especially on slow networks, and when navigating immediately after updating data, it is possible that updates to data may still be syncing to the sync server before the SSR agent renders the page. This results in old data appearing in the server-rendered page.
To prevent this, you can pass SvelteKit's `beforeNavigate` and `goto` functions to the `JazzSvelteProvider` using the `navigation` prop. This automatically waits for any pending syncs to complete before allowing navigation.
src/routes/+layout.svelte
```svelte +layout-with-navigation.svelte
```
Since `$app/navigation` is a SvelteKit virtual module, you need to pass these to the Provider as props.
The `navigation` prop can only be used in SvelteKit apps that use SSR.
## Start your app
Let's fire up your app and see if it works!
If everything's going according to plan, your app will load with the home page. You can click the link to your server-rendered page to see your data - fully rendered on the server!
**Congratulations! 🎉** You've now set up server-side rendering in your React app. You can use this same pattern to render any page on the server.
### Not working?
- Did you add `enableSSR` to the provider?
- Did you add `loadAs: jazzSSR` to `Festival.load`?
- Did you add the migrations to make the data public?
## Bonus: making the server-rendered page dynamic
Just like client-side pages, Jazz can update server-rendered pages in real-time.
For that we can use `export` to serialize values from Jazz and pass them to a client component:
app/festival/[festivalId]/page.tsx
```tsx dynamicPage.tsx
```
Then we can pass the exported value to the preloaded option of the `useCoState` hook.
This way Jazz can synchronously hydrate the CoValue data directly from the component props, avoiding the need to load the data:
app/festival/[festivalId]/FestivalComponent.tsx
```tsx FestivalComponent.tsx
```
Now your festival page will update in real-time, without needing to reload the page.
## Next steps
- Learn more about how to [manage complex permissions](/docs/permissions-and-sharing/overview) using groups and roles
- Dive deeper into the collaborative data structures we call [CoValues](/docs/core-concepts/covalues/overview)
- Learn more about migrations in the [accounts and migrations docs](/docs/core-concepts/schemas/accounts-and-migrations)
===PAGE:tooling-and-resources/ai-tools===
TITLE:tooling-and-resources/ai-tools
DESCRIPTION:description: "Learn how to leverage AI tools when building Jazz apps. Use llms. txt to help LLMs understand Jazz.
description: "Learn how to leverage AI tools when building Jazz apps. Use llms.txt to help LLMs understand Jazz."
};
# Using AI to build Jazz apps
AI tools, particularly large language models (LLMs), can accelerate your development with Jazz. Searching docs, responding to questions and even helping you write code are all things that LLMs are starting to get good at.
However, Jazz is a rapidly evolving framework, so sometimes AI might get things a little wrong.
To help the LLMs, we provide the Jazz documentation in a txt file that is optimized for use with AI tools, like Cursor.
llms-full.txt
llms-full.txt
llms-full.txt
llms-full.txt
llms-full.txt
## Setting up AI tools
Every tool is different, but generally, you'll need to either paste the contents of the [llms-full.txt](https://jazz.tools/llms-full.txt) file directly in your prompt, or attach the file to the tool.
### ChatGPT and v0
Upload the txt file in your prompt.

### Cursor
1. Go to Settings > Cursor Settings > Features > Docs
2. Click "Add new doc"
3. Enter the following URL:
```
https://jazz.tools/llms-full.txt
```
## llms.txt convention
We follow the llms.txt [proposed standard](https://llmstxt.org/) for providing documentation to AI tools at inference time that helps them understand the context of the code you're writing.
## Limitations and considerations
AI is amazing, but it's not perfect. What works well this week could break next week (or be twice as good).
We're keen to keep up with changes in tooling to help support you building the best apps, but if you need help from humans (or you have issues getting set up), please let us know on [Discord](https://discord.gg/utDMjHYg42).
===PAGE:tooling-and-resources/create-jazz-app===
TITLE:tooling-and-resources/create-jazz-app
DESCRIPTION:description: "create-jazz-app is a CLI tool that helps you quickly scaffold new Jazz applications. " }; Jazz comes with a CLI tool that helps you quickly scaffold new Jazz applications. There are two main ways to get started: 1.
description: "create-jazz-app is a CLI tool that helps you quickly scaffold new Jazz applications."
};
# create-jazz-app
Jazz comes with a CLI tool that helps you quickly scaffold new Jazz applications. There are two main ways to get started:
1. **Starter templates** - Pre-configured setups to start you off with your preferred framework
2. **Example apps** - Extend one of our [example applications](https://jazz.tools/examples) to build your project
## Quick Start with Starter Templates
Create a new Jazz app from a starter template in seconds:
```bash
npx create-jazz-app@latest --api-key YOUR_API_KEY
```
This launches an interactive CLI that guides you through selecting:
- Pre-configured frameworks and authentication methods (See [Available Starters](#available-starters))
- Package manager
- Project name
- Jazz Cloud API key (optional) - Provides seamless sync and storage for your app
## Command Line Options
If you know what you want, you can specify options directly from the command line:
```bash
# Basic usage with project name
npx create-jazz-app@latest my-app --framework react --api-key YOUR_API_KEY
# Specify a starter template
npx create-jazz-app@latest my-app --starter react-passkey-auth --api-key YOUR_API_KEY
# Specify example app
npx create-jazz-app@latest my-app --example chat --api-key YOUR_API_KEY
```
### Available Options
- `directory` - Directory to create the project in (defaults to project name)
- `-f, --framework` - Framework to use (React, React Native, Svelte)
- `-s, --starter` - Starter template to use
- `-e, --example` - Example project to use
- `-p, --package-manager` - Package manager to use (npm, yarn, pnpm, bun, deno)
- `-k, --api-key` - Jazz Cloud API key (during our [free public alpha](/docs/core-concepts/sync-and-storage#free-public-alpha), you can use your email as the API key)
- `-h, --help` - Display help information
## Start From an Example App
Want to start from one of [our example apps](https://jazz.tools/examples)? Our example apps include specific examples of features and use cases. They demonstrate real-world patterns for building with Jazz. Use one as your starting point:
```bash
npx create-jazz-app@latest --example chat
```
## Available Starters
Starter templates are minimal setups that include the basic configuration needed to get started with Jazz. They're perfect when you want a clean slate to build on.
Choose from these ready-to-use starter templates:
- `react-passkey-auth` - React with Passkey authentication (easiest to start with)
- `react-clerk-auth` - React with Clerk authentication
- `svelte-passkey-auth` - Svelte with Passkey authentication
- `rn-clerk-auth` - React Native with Clerk authentication
Run `npx create-jazz-app --help` to see the latest list of available starters.
## What Happens Behind the Scenes
When you run `create-jazz-app`, we'll:
1. Ask for your preferences (or use your command line arguments)
2. Clone the appropriate starter template
3. Update dependencies to their latest versions
4. Install all required packages
5. Set up your project and show next steps
## Requirements
- Node.js 20.0.0 or later
- Your preferred package manager (npm, yarn, pnpm, bun, or deno)
===PAGE:tooling-and-resources/inspector===
TITLE:tooling-and-resources/inspector
DESCRIPTION:description: "Visually inspect a Jazz account and other CoValues using their ID. " }; [Jazz Inspector](https://inspector. jazz.
description: "Visually inspect a Jazz account and other CoValues using their ID."
};
# Jazz Inspector
[Jazz Inspector](https://inspector.jazz.tools) is a tool to visually inspect a Jazz account or other CoValues.
To pass your account credentials, go to your Jazz app, copy the full JSON from the `jazz-logged-in-secret` local storage key,
and paste it into the Inspector's Account ID field.
Alternatively, you can pass the Account ID and Account Secret separately.
[https://inspector.jazz.tools](https://inspector.jazz.tools)
## Exporting current account to Inspector from your app
In development mode, you can launch the Inspector from your Jazz app to inspect your account by pressing `Cmd+J`.
## Embedding the Inspector widget into your app [!framework=react,svelte,vue,vanilla]
You can also embed the Inspector directly into your app, so you don't need to open a separate window.
```tsx react-snippet.tsx#Install
```
Install the custom element and render it.
```ts index.ts
```
Or
```svelte svelte.svelte
```
This will show the Inspector launch button on the right of your page.
### Positioning the Inspector button [!framework=react]
You can also customize the button position with the following options:
- right (default)
- left
- bottom right
- bottom left
- top right
- top left
For example:
```tsx react-snippet.tsx#Position
```
Your app
Check out the [music player app](https://github.com/garden-co/jazz/blob/main/examples/music-player/src/2_main.tsx) for a full example.
Check out the [file share app](https://github.com/garden-co/jazz/blob/main/examples/file-share-svelte/src/routes/%2Blayout.svelte) for a full example.
===PAGE:troubleshooting===
TITLE:troubleshooting
DESCRIPTION:title: "Setup troubleshooting", description: "A few reported setup hiccups and how to fix them. " }; A few reported setup hiccups and how to fix them. --- Jazz requires **Node.
title: "Setup troubleshooting",
description: "A few reported setup hiccups and how to fix them."
};
# Setup troubleshooting
A few reported setup hiccups and how to fix them.
---
## Node.js version requirements
Jazz requires **Node.js v20 or later** due to native module dependencies.
Check your version:
```sh
node -v
```
If you’re on Node 18 or earlier, upgrade via nvm:
```sh
nvm install 20
nvm use 20
```
---
### Required TypeScript Configuration
In order to build successfully with TypeScript, you must ensure that you have the following options configured (either in your `tsconfig.json` or using the command line):
* `skipLibCheck` must be `true`
* `exactOptionalPropertyTypes` must be `false`
---
## npx jazz-run: command not found
If, when running:
```sh
npx jazz-run sync
```
you encounter:
```sh
sh: jazz-run: command not found
```
This is often due to an npx cache quirk. (For most apps using Jazz)
1. Clear your npx cache:
```sh
npx clear-npx-cache
```
2. Rerun the command:
```sh
npx jazz-run sync
```
---
### Node 18 workaround (rebuilding the native module)
If you can’t upgrade to Node 20+, you can rebuild the native `better-sqlite3` module for your architecture.
1. Install `jazz-run` locally in your project:
```sh
pnpm add -D jazz-run
```
2. Find the installed version of better-sqlite3 inside node_modules.
It should look like this:
```sh
./node_modules/.pnpm/better-sqlite3{version}/node_modules/better-sqlite3
```
Replace `{version}` with your installed version and run:
```sh
# Navigate to the installed module and rebuild
pushd ./node_modules/.pnpm/better-sqlite3{version}/node_modules/better-sqlite3
&& pnpm install
&& popd
```
If you get ModuleNotFoundError: No module named 'distutils':
Linux:
```sh
pip install --upgrade setuptools
```
macOS:
```sh
brew install python-setuptools
```
Workaround originally shared by @aheissenberger on Jun 24, 2025.
---
### Still having trouble?
If none of the above fixes work:
Make sure dependencies installed without errors (`pnpm install`).
Double-check your `node -v` output matches the required version.
Open an issue on GitHub with:
- Your OS and version
- Node.js version
- Steps you ran and full error output
We're always happy to help! If you're stuck, reachout via [Discord](https://discord.gg/utDMjHYg42)
===PAGE:upgrade/0-10-0===
TITLE:upgrade/0-10-0
DESCRIPTION:For Jazz 0. 10. 0 we have been focusing on enhancing authentication to make it optional, more flexible and easier to use.
# Jazz 0.10.0 is out!
For Jazz 0.10.0 we have been focusing on enhancing authentication to make it optional, more flexible and easier to use.
The default is now anonymous auth, which means that you can build the functionality of your app first and figure out auth later. For users this means that they can start using your app right away on one device -- and once you integrate an auth method, users can sign up and their anonymous accounts are transparently upgraded to authenticated accounts that work across devices.
There are also some other minor improvements that will make your Jazz experience even better!
## What's new?
Here is what's changed in this release:
- [New authentication flow](#new-authentication-flow): Now with anonymous auth, redesigned to make Jazz easier to start with and be more flexible.
- [Local-only mode](#local-only-mode): Users can now explore your app in local-only mode before signing up.
- [Improvements on the loading APIs](#improvements-on-the-loading-apis); `ensureLoaded` now always returns a value and `useCoState` now returns `null` if the value is not found.
- [Jazz Workers on native WebSockets](#jazz-workers-on-native-websockets): Improves compatibility with a wider set of Javascript runtimes.
- [Group inheritance with role mapping](#group-inheritance-with-role-mapping): Groups can now inherit members from other groups with a fixed role.
- Support for Node 14 dropped on cojson.
- Bugfix: `Group.removeMember` now returns a promise.
- Now `cojson` and `jazz-tools` don't export directly the crypto providers anymore. Replace the import with `cojson/crypto/WasmCrypto` or `cojson/crypto/PureJSCrypto` depending on your use case.
## New authentication flow
Up until now authentication has been the first part to figure out when building a Jazz app, and this was a stumbling block for many.
Now it is no longer required and setting up a Jazz app is as easy as writing this:
{/* prettier-ignore */}
```tsx
```
New users are initially authenticated as "anonymous" and you can sign them up later with the hooks/providers of your chosen auth method.
Your auth code can now blend into your component tree:
{/* prettier-ignore */}
```tsx
const [username, setUsername] = useState("");
const [isSignUp, setIsSignUp] = useState(true);
const auth = usePasskeyAuth({ // Must be inside the JazzProvider!
appName: "My super-cool web app",
});
const handleViewChange = () => {
setIsSignUp(!isSignUp);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (isSignUp) {
await auth.signUp(username);
} else {
await auth.logIn();
}
onOpenChange(false);
};
```
{/* prettier-ignore */}
```tsx
const [username, setUsername] = useState("");
const [isSignUp, setIsSignUp] = useState(true);
const [loginPassphrase, setLoginPassphrase] = useState("");
const auth = usePassphraseAuth({ // Must be inside the JazzProvider!
wordlist,
});
const handleViewChange = () => {
setIsSignUp(!isSignUp);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (isSignUp) {
await auth.signUp();
} else {
await auth.logIn(loginPassphrase);
}
onOpenChange(false);
};
```
{/* prettier-ignore */}
```tsx
const auth = usePasskeyAuth({ appName: "My super-cool web app" });
let error = $state(undefined);
function signUp(e: Event) {
const formData = new FormData(e.currentTarget as HTMLFormElement);
const name = formData.get('name') as string;
if (!name) {
error = 'Name is required';
return;
}
e.preventDefault();
error = undefined;
auth.current.signUp(name).catch((e) => {
error = e.message;
});
}
function logIn() {
error = undefined;
auth.current.logIn().catch((e) => {
error = e.message;
});
}
```
{/* prettier-ignore */}
```tsx
const auth = usePasskeyAuth({ appName: "My super-cool web app" });
const error = ref(undefined);
function signUp(e: Event) {
const formData = new FormData(e.currentTarget as HTMLFormElement);
const name = formData.get('name') as string;
if (!name) {
error.value = 'Name is required';
return;
}
e.preventDefault();
error.value = undefined;
auth.value.signUp(name).catch((e) => {
error.value = e.message;
});
}
function logIn(e: Event) {
error.value = undefined;
e.preventDefault();
auth.value.logIn().catch((e) => {
error.value = e.message;
});
}
```
When a user signs up with one of the auth methods we upgrade the current anonymous account to an authenticated account by storing the account's secret keys in the auth method.
To check if the user has registered you can use the new `useIsAuthenticated` hook:
{/* prettier-ignore */}
```tsx
const isAuthenticated = useIsAuthenticated();
const { logOut } = useAccount();
const [open, setOpen] = useState(false);
if (isAuthenticated) {
return (
Sign out
);
}
return (
<>
setOpen(true)}>
Sign up
>
);
}
```
To check if the user has registered you can use the new `useIsAuthenticated` hook:
{/* prettier-ignore */}
```tsx
const isAuthenticated = useIsAuthenticated();
const { logOut } = useAccount();
const [open, setOpen] = useState(false);
if (isAuthenticated) {
return (
Sign out
);
}
return (
<>
setOpen(true)}>
Sign up
>
);
}
```
If you do want to require your users to register before using your app, you can do it by moving the auth code outside of your app and conditionally rendering your app as a child -- or apply this pattern for specific parts of the app you'd like to gate behind authentication.
Our provided "Basic UIs" for the different auth methods render their children conditionally by default, so you can either nest them in your app without children, or nest your app or gated parts of your app inside *them*, depending on the desired behavior.
{/* prettier-ignore */}
```tsx
ReactDOM.createRoot(document.getElementById("root")!).render(
);
```
For the changes related to the specific auth providers see the updated [authentication docs](/docs/key-features/authentication/overview).
## Local-only mode
If you are ok with data not being persisted on the sync server for anonymous users, you can now set your app to local-only depending on the user's authentication state.
With `sync.when` set to `"signedUp"` the app will work in local-only mode when the user is anonymous and unlock the multiplayer/multi-device features and cloud persistence when they sign up:
```tsx
```
You can control when Jazz will sync by switching the `when` config to `"always"` or `"never"`.
## Improvements on the loading APIs
Before 0.10.0 `ensureLoaded` was returning a nullable value forcing the Typescript code to always include null checks:
```ts
const me = await MusicaAccount.getMe().ensureLoaded({
root: {},
});
if (!me) { // Technically can never happen
throw new Error("User not found");
}
me.root.activeTrack = track;
}
```
Now `ensureLoaded` always returns a value, making it's usage leaner:
```ts
const { root } = await MusicaAccount.getMe().ensureLoaded({
root: {},
});
// Null checks are no more required
root.activeTrack = track;
}
```
It will throw if the value is not found, which can happen if the user tries to resolve a value that is not available.
`useCoState` now returns `null` if the value is not found, making it now possible to check against not-found values:
```ts
const value = useCoState(MusicTrack, id, {});
if (value === null) {
return
Track not found
;
}
```
## Jazz Workers on native WebSockets
We have removed the dependency on `ws` and switched to the native WebSocket API for Jazz Workers.
This improves the compatibility with a wider set of Javascript runtimes adding drop-in support for Deno, Bun, Browsers and Cloudflare Durable Objects.
If you are using a Node.js version lower than 22 you will need to install the `ws` package and provide the WebSocket constructor:
```ts
const { worker } = await startWorker({
WebSocket,
});
```
## Group inheritance with role mapping
You can override the inherited role by passing a second argument to `extend`.
This can be used to give users limited access to a child group:
```ts
const organization = Group.create();
const billing = Group.create();
billing.extend(organization, "reader");
```
This way the members of the organization can only read the billing data, even if they are admins in the organization group.
More about the group inheritance can be found in the [dedicated docs page](/docs/permissions-and-sharing/cascading-permissions).
===PAGE:upgrade/0-11-0===
TITLE:upgrade/0-11-0
DESCRIPTION:Jazz 0. 11. 0 brings several improvements to member handling, roles, and permissions management.
# Jazz 0.11.0 is out!
Jazz 0.11.0 brings several improvements to member handling, roles, and permissions management. This guide will help you upgrade your application to the latest version.
## What's new?
Here is what's changed in this release:
- [New permissions check APIs](#new-permissions-check-apis): New methods like `canRead`, `canWrite`, `canAdmin`, and `getRoleOf` to simplify permission checks.
- [Group.revokeExtend](#grouprevokeextend): New method to revoke group extension permissions.
- [Group.getParentGroups](#groupgetparentgroups): New method to get all the parent groups of an account.
- [Account Profile & Migrations](#account-profile--migrations): Fixed issues with custom account profile migrations for a more consistent experience
- [Dropped support for Accounts owning Profiles](#dropped-support-for-accounts-owning-profiles): Profiles can now only be owned by Groups.
- [Group.members now includes inherited members](#member-inheritance-changes): Updated behavior for the `members` getter method to include inherited members and have a more intuitive type definition.
## New Features
### New permissions check APIs
New methods have been added to both `Account` and `Group` classes to improve permission handling:
#### Permission checks
The new `canRead`, `canWrite` and `canAdmin` methods on `Account` allow you to easily check if the account has specific permissions on a CoValue:
{/* prettier-ignore */}
```typescript
const me = Account.getMe();
if (me.canAdmin(value)) {
console.log("I can share value with others");
} else if (me.canWrite(value)) {
console.log("I can edit value");
} else if (me.canRead(value)) {
console.log("I can view value");
} else {
console.log("I cannot access value");
}
```
#### Getting the role of an account
The `getRoleOf` method has been added to query the role of specific entities:
```typescript
const group = Group.create();
group.getRoleOf(me); // admin
group.getRoleOf(Eve); // undefined
group.addMember(Eve, "writer");
group.getRoleOf(Eve); // writer
```
#### Group.revokeExtend
We added a new method to revoke the extend of a Group:
```typescript
function addTrackToPlaylist(playlist: Playlist, track: MusicTrack) {
const trackGroup = track._owner.castAs(Group);
trackGroup.extend(playlist._owner, "reader"); // Grant read access to the track to the playlist accounts
playlist.tracks.push(track);
}
function removeTrackFromPlaylist(playlist: Playlist, track: MusicTrack) {
const trackGroup = track._owner.castAs(Group);
trackGroup.revokeExtend(playlist._owner); // Revoke access to the track to the playlist accounts
const index = playlist.tracks.findIndex(t => t.id === track.id);
if (index !== -1) {
playlist.tracks.splice(index, 1);
}
}
```
### Group.getParentGroups
The `getParentGroups` method has been added to `Group` to get all the parent groups of a group.
```ts
const childGroup = Group.create();
const parentGroup = Group.create();
childGroup.extend(parentGroup);
console.log(childGroup.getParentGroups()); // [parentGroup]
```
## Breaking Changes
### Account Profile & Migrations
The previous way of making the `Profile` migration work was to assume that the profile was always already there:
```ts
profile = coField.ref(MyAppProfile);
async migrate(this: MyAppAccount, creationProps: { name: string, lastName: string }) {
if (creationProps) {
const { profile } = await this.ensureLoaded({ profile: {} });
profile.name = creationProps.name;
profile.bookmarks = ListOfBookmarks.create([], profileGroup);
}
}
}
```
This was kind-of tricky to picture, and having different migration strategies for different CoValues was confusing.
We changed the logic so the default profile is created only if you didn't provide one in your migration.
This way you can use the same pattern for both `root` and `profile` migrations:
```ts
profile = coField.ref(MyAppProfile);
async migrate(this: MyAppAccount, creationProps?: { name: string }) {
if (this.profile === undefined) {
const profileGroup = Group.create();
profileGroup.addMember("everyone", "reader");
this.profile = MyAppProfile.create({
name: creationProps?.name,
bookmarks: ListOfBookmarks.create([], profileGroup),
}, profileGroup);
}
}
}
```
If you provide a custom `Profile` in your `Account` schema and migration for a Worker account,
make sure to also add `everyone` as member with `reader` role to the owning group.
Failing to do so will prevent any account from sending messages to the Worker's Inbox.
### Dropped support for Accounts owning Profiles
Starting from `0.11.0` `Profile`s can only be owned by `Group`s.
Existing profiles owned by `Account`s will still work, but you will get incorrect types when accessing a `Profile`'s `_owner`.
### Member Inheritance Changes
The behavior of groups' `members` getter method has been updated to return both direct members and inherited ones from ancestor groups.
This might affect your application if you were relying on only direct members being returned.
```ts
/**
* The following pseudocode only illustrates the inheritance logic,
* the actual implementation is different.
*/
const parentGroup = Group.create();
parentGroup.addMember(John, "admin");
const childGroup = Group.create();
childGroup.addMember(Eve, "admin");
childGroup.extend(parentGroup);
console.log(childGroup.members);
// Before 0.11.0
// [Eve]
// After 0.11.0
// [Eve, John]
```
Additionally:
- now `Group.members` doesn't include the `everyone` member anymore
- the account type in `Group.members` is now the globally registered Account schema and we have removed the `co.members` way to define an AccountSchema for members
If you need to explicitly check if "everyone" is a member of a group, you can use the `getRoleOf` method instead:
```ts
if (group.getRoleOf("everyone")) {
console.log("Everyone has access to the group");
}
```
#### Migration Steps
1. Review your member querying logic to account for inherited members.
2. Update your permission checking code to utilize the new `hasPermissions` and `getRoleOf` methods.
3. Consider implementing `"everyone"` role checks where appropriate.
### Removed auto-update of `profile.name` in `usePasskeyAuth`
The `usePasskeyAuth` hook now doesn't update the `profile.name` if the provided username is empty.
## Troubleshooting
> I'm getting the following error: `Error: Profile must be owned by a Group`
If you previously forced a migration of your `Account` schema to include a custom `Profile`,
and assigned its ownership to an `Account`, you need to recreate your profile code and assign it to a `Group` instead.
```ts
profile = coField.ref(MyAppProfile);
override async migrate() {
// ...
const me = await this.ensureLoaded({
profile: {},
});
if ((me.profile._owner as Group | Account)._type === "Account") {
const profileGroup = Group.create();
profileGroup.addMember("everyone", "reader");
// recreate your profile here...
me.profile = Profile.create(
{
name: me.profile.name,
},
profileGroup,
);
}
}
}
```
===PAGE:upgrade/0-12-0===
TITLE:upgrade/0-12-0
DESCRIPTION:Jazz 0. 12. 0 makes it easier and safer to load nested data.
# Jazz 0.12.0 - Deeply resolved data
Jazz 0.12.0 makes it easier and safer to load nested data. You can now specify exactly which nested data you want to load, and Jazz will check permissions and handle missing data gracefully. This helps catch errors earlier during development and makes your code more reliable.
## What's new?
- New resolve API for a more type-safe deep loading
- A single, consistent load option for all loading methods
- Improved permission checks on deep loading
- Easier type safety with the `Resolved` type helper
## Breaking changes
### New Resolve API
We're introducing a new resolve API for deep loading, more friendly to TypeScript, IDE autocompletion and LLMs.
**Major changes:**
1. Functions and hooks for loading now take the resolve query as an explicit nested `resolve` prop
2. Shallowly loading a collection is now done with `true` instead of `[]` or `{}`
```tsx
const { me } = useAccount({ root: { friends: [] } }); // [!code --]
// After
const { me } = useAccount({ // [!code ++]
resolve: { root: { friends: true } } // [!code ++]
}); // [!code ++]
```
3. For collections, resolving items deeply is now done with a special `$each` key.
For a `CoList`:
```tsx
class Task extends CoMap { }
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
const id = "co_123" as ID;
// Before
// @ts-expect-error
const tasks = useCoState(ListOfTasks, id, [{}]); // [!code --]
// After
const tasks = useCoState(ListOfTasks, id, { resolve: { $each: true } }); // [!code ++]
```
For a `CoMap.Record`:
```tsx
class UsersByUsername extends CoMap.Record(coField.ref(MyAppAccount)) {}
// Before
// @ts-expect-error
const usersByUsername = useCoState(UsersByUsername, id, [{}]); // [!code --]
// After
const usersByUsername = useCoState(UsersByUsername, id, { // [!code ++]
resolve: { $each: true } // [!code ++]
}); // [!code ++]
```
Nested loading — note how it's now less terse, but more readable:
```tsx
class Org extends CoMap {
name = coField.string;
}
class Assignee extends CoMap {
name = coField.string;
org = coField.ref(Org);
}
class ListOfAssignees extends CoList.Of(coField.ref(Assignee)) {}
class Task extends CoMap {
content = coField.string;
assignees = coField.ref(ListOfAssignees);
}
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
// Before
// @ts-expect-error
const tasksWithAssigneesAndTheirOrgs = useCoState(ListOfTasks, id, [{ // [!code --]
assignees: [{ org: {}}]} // [!code --]
]); // [!code --]
// After
const tasksWithAssigneesAndTheirOrgs = useCoState(ListOfTasks, id, { // [!code ++]
resolve: { // [!code ++]
$each: { // [!code ++]
assignees: { // [!code ++]
$each: { org: true } // [!code ++]
} // [!code ++]
} // [!code ++]
} // [!code ++]
}); // [!code ++]
```
It's also a lot more auto-complete friendly:
```tsx
const tasksWithAssigneesAndTheirOrgs = useCoState(ListOfTasks, id, {
resolve: {
$each: {
assignees: {
$
// ^|
}
}
}
});
```
### A single, consistent load option
The new API works across all loading methods, and separating out the resolve query means
other options with default values are easier to manage, for example: loading a value as a specific account instead of using the implicit current account:
```ts
// Before
// @ts-expect-error
Playlist.load(id, otherAccount, { // [!code --]
tracks: [], // [!code --]
}); // [!code --]
// After
Playlist.load(id, { // [!code ++]
loadAs: otherAccount, // [!code ++]
resolve: { tracks: true } // [!code ++]
}); // [!code ++]
```
### Improved permission checks on deep loading
Now `useCoState` will return `null` when the current user lacks permissions to load requested data.
Previously, `useCoState` would return `undefined` if the current user lacked permissions, making it hard to tell if the value is loading or if it's missing.
Now `undefined` means that the value is definitely loading, and `null` means that the value is temporarily missing.
We also have implemented a more granular permission checking, where if an *optional* CoValue cannot be accessed, `useCoState` will return the data stripped of that CoValue.
**Note:** The state handling around loading and error states will become more detailed and easy-to-handle in future releases, so this is just a small step towards consistency.
{/* prettier-ignore */}
```tsx
class ListOfTracks extends CoList.Of(coField.optional.ref(Track)) {}
function TrackListComponent({ id }: { id: ID }) {
// Before (ambiguous states)
// @ts-expect-error
const tracks = useCoState(ListOfTracks, id, [{}]); // [!code --]
if (tracks === undefined) return
; // [!code ++]
// This will only show tracks that we have access to and that are loaded.
return tracks.map(track => track && );
}
```
The same change is applied to the load function, so now it returns `null` instead of `undefined` when the value is missing.
```tsx
// Before
// @ts-expect-error
const map = await MyCoMap.load(id);
if (map === undefined) {
throw new Error("Map not found");
}
// After
const map = await MyCoMap.load(id);
if (map === null) {
throw new Error("Map not found or access denied");
}
```
## New Features
### The `Resolved` type helper
The new `Resolved` type can be used to define what kind of deeply loaded data you expect in your parameters, using the same resolve query syntax as the new loading APIs:
```tsx
type PlaylistResolved = Resolved;
function TrackListComponent({ playlist }: { playlist: PlaylistResolved }) {
// Safe access to resolved tracks
return playlist.tracks.map(track => );
}
```
===PAGE:upgrade/0-13-0===
TITLE:upgrade/0-13-0
DESCRIPTION:Version 0. 13. 0 introduces a significant architectural change in how Jazz supports React Native applications.
# Jazz 0.13.0 - React Native Split
Version 0.13.0 introduces a significant architectural change in how Jazz supports React Native applications. We've separated the React Native implementation into two distinct packages to better serve different React Native development approaches:
1. **[jazz-react-native](https://www.npmjs.com/package/jazz-react-native)**: Focused package for framework-less React Native applications
2. **[jazz-expo](https://www.npmjs.com/package/jazz-expo)**: Dedicated package for Expo applications
3. **[jazz-react-native-core](https://www.npmjs.com/package/jazz-react-native-core)**: Shared core functionality used by both implementations
If you're using React Native without Expo, see the [React Native Upgrade Guide](/docs/react-native/upgrade/0-13-0). If you're using Expo, please see the [Expo upgrade guide](/docs/react-native-expo/upgrade/0-13-0).
## Summary of Changes
- **Split Implementation**: React Native support is now divided into separate packages for Expo and framework-less React Native
- **Clerk Authentication**: Clerk auth is now included directly in the jazz-expo package
- **Storage Adapters**: Each implementation uses platform-optimized storage solutions
For detailed migration instructions, please see the appropriate guide:
- [React Native Upgrade Guide](/docs/react-native/upgrade/0-13-0)
- [Expo Upgrade Guide](/docs/react-native-expo/upgrade/0-13-0)
===PAGE:upgrade/0-13-0/react-native===
TITLE:upgrade/0-13-0/react-native
DESCRIPTION:Version 0. 13. 0 introduces a significant architectural change in how Jazz supports React Native applications.
# Jazz 0.13.0 - React Native Split
Version 0.13.0 introduces a significant architectural change in how Jazz supports React Native applications. We've separated the React Native implementation into two distinct packages to better serve different React Native development approaches:
1. **[jazz-react-native](https://www.npmjs.com/package/jazz-react-native)**: Focused package for framework-less React Native applications
2. **[jazz-expo](https://www.npmjs.com/package/jazz-expo)**: Dedicated package for Expo applications
3. **[jazz-react-native-core](https://www.npmjs.com/package/jazz-react-native-core)**: Shared core functionality used by both implementations
This guide focuses on upgrading **React Native without Expo** applications. If you're using Expo, please see the [Expo upgrade guide](/docs/react-native-expo/upgrade/0-13-0).
## Migration Steps for React Native
1. **Update Dependencies**
```bash
# Ensure you have the required dependencies
npm install @op-engineering/op-sqlite react-native-mmkv @react-native-community/netinfo
# Remove the old packages
npm install jazz-react-native-expo # [!code --]
# Install the new packages
npm install jazz-react-native jazz-react-native-media-images # [!code ++]
# Run pod install for iOS
npx pod-install
```
2. **No Import Changes Required**
Your existing imports from `jazz-react-native` should continue to work, but the implementation now uses a different storage solution (op-sqlite and MMKV).
## Storage Adapter Changes
The `jazz-react-native` package now uses:
- `OpSQLiteAdapter` for database storage (using `@op-engineering/op-sqlite`)
- `MMKVStoreAdapter` for key-value storage (using `react-native-mmkv`)
These are now the default storage adapters in the `JazzProvider` for framework-less React Native applications.
## Example Provider Setup
```tsx
// @noErrors: 2307 2686 2664
// App.tsx
return (
{children}
);
}
// Register the Account schema
declare module "jazz-react-native" {
interface Register {
Account: MyAppAccount;
}
}
```
## New Architecture Support
The `jazz-react-native` implementation fully supports [the React Native New Architecture](https://reactnative.dev/docs/the-new-architecture/landing-page). This includes compatibility with:
- JavaScript Interface (JSI) for more efficient JavaScript-to-native communication
- Fabric rendering system for improved UI performance
- TurboModules for better native module management
- Codegen for type-safe interfaces
No additional configuration is needed to use Jazz with the New Architecture.
## Potential Podfile Issues
If you encounter pod installation issues in a pnpm workspace environment (such as `undefined method '[]' for nil` in the Podfile at the line `config = use_native_modules!`), replace the problematic line with a manual path reference:
```ts
react_native_path = "../node_modules/react-native"
config = { :reactNativePath => react_native_path }
```
This approach bypasses issues with dependency resolution in workspace setups where packages may be hoisted to the root `node_modules`.
## For More Information
For detailed setup instructions, refer to the [React Native Setup Guide](/docs/react-native/project-setup)
===PAGE:upgrade/0-13-0/react-native-expo===
TITLE:upgrade/0-13-0/react-native-expo
DESCRIPTION:Version 0. 13. 0 introduces a significant architectural change in how Jazz supports React Native applications.
# Upgrade to Jazz 0.13.0 for React Native Expo
Version 0.13.0 introduces a significant architectural change in how Jazz supports React Native applications. We've separated the React Native implementation into two distinct packages to better serve different React Native development approaches:
1. **jazz-expo**: Dedicated package for Expo applications
2. **jazz-react-native**: Focused package for framework-less React Native applications
3. **jazz-react-native-core**: Shared core functionality used by both implementations (probably not imported directly)
This guide focuses on upgrading **Expo applications**. If you're using framework-less React Native, please see the [React Native upgrade guide](/docs/react-native/upgrade/0-13-0).
## Migration Steps for Expo
1. **Update Dependencies**
Remove the old packages and install the new `jazz-expo` package.
```bash
# Remove the old package
npm uninstall jazz-react-native
# Install the new Expo-specific packages
npx expo install expo-sqlite expo-secure-store expo-file-system @react-native-community/netinfo
# Install the new packages
npm install jazz-expo jazz-react-native-media-images
```
2. **Update Imports**
Update your imports to use the new `jazz-expo` package.
```tsx
// @noErrors: 2300 2307
// Before
// After
```
3. **Update Type Declarations**
Update your type declarations to use the new `jazz-expo` package.
```tsx
// @noErrors: 2664 2304
// Before
declare module "jazz-react-native" { // [!code --:5]
interface Register {
Account: MyAppAccount;
}
}
// After
declare module "jazz-expo" { // [!code ++:5]
interface Register {
Account: MyAppAccount;
}
}
```
## Clerk Authentication
Clerk authentication has been moved inside the `jazz-expo` package. This is a breaking change that requires updating both your imports and providers.
If you're using Clerk auth in your Expo application, you'll need to:
```tsx
// @noErrors: 2300 2307
// Before
// After
```
Since Clerk only supports Expo applications, this consolidation makes sense and simplifies your dependency structure. You'll need to completely remove the `jazz-react-native-clerk` package from your dependencies and use the Clerk functionality that's now built into `jazz-expo`.
## Storage Adapter Changes
The `jazz-expo` package now uses:
- `ExpoSQLiteAdapter` for database storage (using `expo-sqlite`)
- `ExpoSecureStoreAdapter` for key-value storage (using `expo-secure-store`)
These are now the default storage adapters in the `JazzProvider` for Expo applications, so you don't need to specify them explicitly.
## Example Provider Setup
```tsx
// @noErrors: 2300 2307 2686 2664 2435 1005
return (
{children}
);
}
// Register the Account schema
declare module "jazz-react-native" { // [!code --:5]
interface Register {
Account: MyAppAccount;
}
}
declare module "jazz-expo" { // [!code ++:5]
interface Register {
Account: MyAppAccount;
}
}
```
## New Architecture Support
The `jazz-expo` implementation supports the Expo New Architecture.
## For More Information
For detailed setup instructions, refer to the [React Native Expo Setup Guide](/docs/react-native-expo/project-setup)
===PAGE:upgrade/0-14-0===
TITLE:upgrade/0-14-0
DESCRIPTION:We're excited to move from our own schema syntax to using Zod v4. This is the first step in a series of releases to make Jazz more familiar and to make CoValues look more like regular data structures. So far, Jazz has relied on our own idiosyncratic schema definition syntax where you had to extend classes and be careful to use `co.
# Jazz 0.14.0 Introducing Zod-based schemas
We're excited to move from our own schema syntax to using Zod v4.
This is the first step in a series of releases to make Jazz more familiar and to make CoValues look more like regular data structures.
## Overview:
So far, Jazz has relied on our own idiosyncratic schema definition syntax where you had to extend classes and be careful to use `co.ref` for references.
```ts
// BEFORE
text = co.ref(CoPlainText);
image = co.optional.ref(ImageDefinition);
important = co.boolean;
}
```
While this had certain ergonomic benefits it relied on unclean hacks to work.
In addition, many of our adopters expressed a preference for avoiding class syntax, and LLMs consistently expected to be able to use Zod.
For this reason, we completely overhauled how you define and use CoValue schemas:
```ts
// AFTER
text: co.plainText(),
image: z.optional(co.image()),
important: z.boolean(),
});
```
## Major breaking changes
### Schema definitions
You now define CoValue schemas using two new exports from `jazz-tools`:
- a new `co` definer that mirrors Zod's object/record/array syntax to define CoValue types
- `co.map()`, `co.record()`, `co.list()`, `co.feed()`
- `co.account()`, `co.profile()`
- `co.plainText()`, `co.richText()`,
- `co.fileStream()`, `co.image()`
- see the updated [Defining CoValue Schemas](/docs/core-concepts/covalues/overview)
- `z` re-exported from Zod v4
- primitives like `z.string()`, `z.number()`, `z.literal()`
- **note**: additional constraints like `z.min()` and `z.max()` are not yet enforced, we'll add validation in future releases
- complex types like `z.object()` and `z.array()` to define JSON-like fields without internal collaboration
- combinators like `z.optional()` and `z.discriminatedUnion()`
- these also work on CoValue types!
- see the updated [Docs on Primitive Fields](/docs/core-concepts/covalues/overview#primitive-fields),
[Docs on Optional References](/docs/core-concepts/covalues/overview#optional-references)
and [Docs on Unions of CoMaps](/docs/core-concepts/covalues/overview#unions-of-comaps-declaration)
Similar to Zod v4's new object syntax, recursive and mutually recursive types are now [much easier to express](/docs/react/core-concepts/covalues/overview#recursive-references).
### How to pass loaded CoValues
Calls to `useCoState()` work just the same, but they return a slightly different type than before.
And while you can still read from the type just as before...
```tsx
const Pet = co.map({
name: z.string(),
age: z.number(),
});
type Pet = co.loaded;
const Person = co.map({
name: z.string(),
age: z.number(),
pets: co.list(Pet),
});
type Person = co.loaded;
function MyComponent({ id }: { id: string }) {
const person = useCoState(Person, id);
return person && ;
}
function PersonName({ person }: {
person: Person
}) {
return
{person.name}
;
}
```
`co.loaded` can also take a second argument to specify the loading depth of the expected CoValue, mirroring the `resolve` options for `useCoState`, `load`, `subscribe`, etc.
```tsx
const Pet = co.map({
name: z.string(),
age: z.number(),
});
type Pet = co.loaded;
const Person = co.map({
name: z.string(),
age: z.number(),
pets: co.list(Pet),
});
type Person = co.loaded;
function MyComponent({ id }: { id: string }) {
const personWithPets = useCoState(Person, id, {
resolve: { pets: { $each: true } } // [!code ++]
});
return personWithPets && ;
}
function PersonAndFirstPetName({ person }: {
person: co.loaded // [!code ++]
}) {
return
{person.name} & {person.pets[0].name}
;
}
```
We've removed the `useCoState`, `useAccount` and `useAccountOrGuest` hooks.
You should now use the `CoState` and `AccountCoState` reactive classes instead. These provide greater stability and are significantly easier to work with.
Calls to `new CoState()` work just the same, but they return a slightly different type than before.
And while you can still read from the type just as before...
```ts
// @filename: schema.ts
const Pet = co.map({
name: z.string(),
age: z.number(),
});
type Pet = co.loaded;
const Person = co.map({
name: z.string(),
age: z.number(),
pets: co.list(Pet),
});
type Person = co.loaded;
```
```svelte
// @filename: app.svelte
{person.current?.name}
```
`co.loaded` can also take a second argument to specify the loading depth of the expected CoValue, mirroring the `resolve` options for `CoState`, `load`, `subscribe`, etc.
```svelte
{props.person.name}
{#each props.person.pets as pet}
{pet.name}
{/each}
```
### Removing AccountSchema registration
We have removed the Typescript AccountSchema registration.
It was causing some deal of confusion to new adopters so we have decided to replace the magic inference with a more explicit approach.
You still need to pass your custom AccountSchema to your provider!
```tsx
declare module "jazz-react" { // [!code --]
interface Register { // [!code --]
Account: MyAccount; // [!code --]
} // [!code --]
} // [!code --]
return (
{children}
);
}
```
When using `useAccount` you should now pass the `Account` schema directly:
```tsx
function MyComponent() {
const { me } = useAccount(MyAccount, {
resolve: {
profile: true,
},
});
return
{me?.profile.name}
;
}
```
```svelte
// [!code --]
{@render children()}
```
When using `AccountCoState` you should now pass the `Account` schema directly:
```svelte
{account.current?.profile.name}
```
### Defining migrations
Now account schemas need to be defined with `co.account()` and migrations can be declared using `withMigration()`:
```ts
const Pet = co.map({
name: z.string(),
age: z.number(),
});
const MyAppRoot = co.map({
pets: co.list(Pet),
});
const MyAppProfile = co.profile({
name: z.string(),
age: z.number().optional(),
});
root: MyAppRoot,
profile: MyAppProfile,
}).withMigration((account, creationProps?: { name: string }) => {
if (account.root === undefined) {
account.root = MyAppRoot.create({
pets: co.list(Pet).create([]),
});
}
if (account.profile === undefined) {
const profileGroup = Group.create();
profileGroup.addMember("everyone", "reader");
account.profile = MyAppProfile.create({
name: creationProps?.name ?? "New user",
}, profileGroup);
}
});
```
### Defining Schema helper methods
You can no longer define helper methods directly within your schema, create standalone functions instead. See
[Docs on Helper methods](/docs/core-concepts/covalues/overview#helper-methods) for an example.
## Minor breaking changes
### `_refs` and `_edits` are now potentially null
The type of `_refs` and `_edits` is now nullable.
```ts
const Person = co.map({
name: z.string(),
age: z.number(),
});
const person = Person.create({ name: "John", age: 30 });
person._refs; // now nullable
person._edits; // now nullable
```
### `members` and `by` now return basic `Account`
We have removed the Account schema registration, so now `members` and `by` methods now always return basic `Account`.
This means that you now need to rely on `useCoState` on them to load their using your account schema.
```tsx
function GroupMembers({ group }: { group: Group }) {
const members = group.members;
return (
);
}
```
===PAGE:upgrade/0-15-0===
TITLE:upgrade/0-15-0
DESCRIPTION:One of the pain points that our adopters have been facing while maintaining Jazz apps is keeping the different package versions aligned. This becomes especially hard when using a monorepo setup for multi-platform apps. To address this problem, we have decided to move all the bindings into a single package and export the different bindings using export paths.
# Jazz 0.15.0 - Moving everything inside `jazz-tools`
One of the pain points that our adopters have been facing while maintaining Jazz apps is keeping the different package versions aligned.
This becomes especially hard when using a monorepo setup for multi-platform apps.
To address this problem, we have decided to move all the bindings into a single package and export the different bindings using export paths.
## Overview:
So far, when building a Jazz app you would need to use multiple packages.
Now everything is inside `jazz-tools`:
```tsx
name: z.string(),
});
todoId
}: {
todoId: string;
}) {
const todo = useCoState(TodoItem, todoId);
return (
(todo.name = e.target.value)} />
);
}
```
## Major breaking changes
- A single package for everything: `jazz-tools` now exports all the bindings using export paths
- Support for Vue is discontinued: maintaining multiple framework bindings is hard and we have decided to focus our efforts on the most used frameworks in our community
- A single package for everything: `jazz-tools` now exports all the bindings using export paths
- Support for Vue is discontinued: maintaining multiple framework bindings is hard and we have decided to focus our efforts on the most used frameworks in our community
- SSR is now stable: We have removed the experimental flag from `enableSSR` and unified `useAccount` and `useAccountOrGuest` hooks
### A single package for all
All our stable packages that were depending on `jazz-tools` are now part of the `jazz-tools` package:
- `jazz-react` -> `jazz-tools/react`
- `jazz-react-native` -> `jazz-tools/react-native`
- `jazz-expo` -> `jazz-tools/expo`
- `jazz-svelte` -> `jazz-tools/svelte`
- `jazz-nodejs` -> `jazz-tools/worker`
- `jazz-react-auth-clerk` -> `jazz-tools/react`
- `jazz-expo/auth/clerk` -> `jazz-tools/expo`
- `jazz-browser` -> `jazz-tools/browser`
- `jazz-browser-media-images` -> `jazz-tools/browser-media-images`
- `jazz-react-native-media-images` -> `jazz-tools/react-native-media-images`
- `jazz-inspector` -> `jazz-tools/inspector`
- `jazz-inspector-element` -> `jazz-tools/inspector/register-custom-element`
- `jazz-prosemirror` -> `jazz-tools/prosemirror`
- `jazz-tiptap` -> `jazz-tools/tiptap`
This means that now you can remove all these packages and keep only `jazz-tools`.
To reduce the probability of importing an API from the wrong entry, we have also done some renaming:
- `JazzProvider` becomes:
- `JazzReactProvider` in `jazz-tools/react`
- `JazzSvelteProvider` in `jazz-tools/svelte`
- `JazzReactNativeProvider` in `jazz-tools/react-native`
- `JazzExpoProvider` in `jazz-tools/expo`
- Clerk bindings are now exported directly from the framework entries and renamed as:
- `import { JazzReactProviderWithClerk } from "jazz-tools/react"`
- `import { JazzExpoProviderWithClerk } from "jazz-tools/expo"`
- We have added the `Native` suffix to the platform-specific hooks when imported from the `react-native` or `expo` entries:
- `useAcceptInvite` -> `useAcceptInviteNative`
- `useProgressiveImg` -> `useProgressiveImgNative`
- `ProgressiveImg` -> `ProgressiveImgNative`
- `useAcceptInvite` becomes `new InviteListener` in Svelte
We have not renamed APIs like `useCoState` or `useAccount` because their implementation is cross-platform.
When experimenting with VSCode, we've noticed that usually the import path suggested is `jazz-tools/react-core`, which is the same as importing from `jazz-tools/react` or the specific framework entry.
To make this upgrade possible we have fully transtioned to [Package Exports](https://nodejs.org/docs/latest-v18.x/api/packages.html#package-entry-points) in `jazz-tools`.
This means that if you are using an old version of React Native you will need to enable the [enablePackageExports](https://metrobundler.dev/docs/configuration/#unstable_enablepackageexports-experimental) flag in your metro config.
### SSR is now stable
The `enableSSR` was marked as experimental because the `useAccount` return type was not matching the returned values in SSR mode.
In this release, we have refactored `useAccount` to make "me" always nullable, matching what happens when rendering on the server.
We have also removed `useAccountOrGuest` and made `useAccount` return an `agent` value that can be used to load data in all rendering modes.
For more details, check the [SSR docs](/docs/react/server-side/ssr).
===PAGE:upgrade/0-16-0===
TITLE:upgrade/0-16-0
DESCRIPTION:This release introduces a cleaner separation between Zod and CoValue schemas, improves type inference with circular references, and simplifies how you access internal schemas. While most applications won't require extensive refactors, some breaking changes will require action. Before 0.
# Jazz 0.16.0 - Cleaner separation between Zod and CoValue schemas
This release introduces a cleaner separation between Zod and CoValue schemas, improves type inference with circular references, and simplifies how you access internal schemas.
While most applications won't require extensive refactors, some breaking changes will require action.
## Motivation
Before 0.16.0, CoValue schemas were a thin wrapper around Zod schemas. This made it easy to use Zod methods on CoValue schemas,
but it also prevented the type checker from detecting issues when combining Zod and CoValue schemas.
For example, the following code would previously compile without errors, but would have severe limitations:
```tsx
const Dog = co.map({
breed: z.string(),
});
const Person = co.map({
pets: z.array(Dog),
});
// You can create a CoMap with a z.array field that contains another CoMap
const map = Person.create({
pets: [Dog.create({ breed: "Labrador" })],
});
// But then you cannot eagerly load the nested CoMap, because
// there's a plain JS object in between. So this would fail:
Person.load(map.id, { resolve: { pets: { $each: true } } });
```
Schema composition rules are now stricter: Zod schemas can only be composed with other Zod schemas.
CoValue schemas can be composed with either Zod or other CoValue schemas. These rules are enforced at the type level, to make it easier
to spot errors in schema definitions and avoid possible footguns when mixing Zod and CoValue schemas.
Having a stricter separation between Zod and CoValue schemas also allowed us to improve type inference with circular references.
Previously, the type checker would not be able to infer types for even simple circular references, but now it can!
```tsx
const Person = co.map({
name: z.string(),
get friends(): CoListSchema { // [!code --]
get friends() { // [!code ++]
return co.list(Person);
},
});
```
There are some scenarios where recursive type inference can still fail due to TypeScript limitations, but these should be rare.
## Breaking changes
### The Account root id is now discoverable
In prior Jazz releases, the Account root id was stored encrypted and accessible only by the account owner.
This made it impossible to load the account root this way:
```tsx
const bob = MyAppAccount.load(bobId, { resolve: { root: true }, loadAs: me });
```
So we changed Account root id to be discoverable by everyone.
**This doesn't affect the visibility of the account root**, which still follows the permissions defined in its group.
For existing accounts, the change is applied the next time the user loads their account.
No action is required on your side, but we preferred to mark this as a breaking change because it
minimally affects access to the account root. (e.g., if in your app the root is public, now users can access other users' root by knowing their account ID)
### `z.optional()` and `z.discriminatedUnion()` no longer work with CoValue schemas
You'll now need to use the `co.optional()` and `co.discriminatedUnion()` equivalents.
This change may require you to update any explicitly typed cyclic references.
```tsx
const Person = co.map({
name: z.string(),
get bestFriend(): z.ZodOptional { // [!code --]
return z.optional(Person); // [!code --]
get bestFriend(): co.Optional { // [!code ++]
return co.optional(Person); // [!code ++]
}
});
```
### CoValue schema types are now under the `co.` namespace
All CoValue schema types are now accessed via the `co.` namespace. If you're using explicit types (especially in recursive schemas), you'll need to update them accordingly.
```tsx
const Person = co.map({
name: z.string(),
get friends(): CoListSchema { // [!code --]
get friends(): co.List { // [!code ++]
return co.list(Person);
}
});
```
### Unsupported Zod methods have been removed from CoMap schemas
CoMap schemas no longer incorrectly inherit Zod methods like `.extend()` and `.partial()`. These methods previously appeared to work but could behave unpredictably. They have now been disabled.
We're keeping `.optional()` and plan to introduce more Zod-like methods in future releases.
### Internal schema access is now simpler
You no longer need to use Zod's `.def` to access schema internals. Instead, you can directly use methods like `CoMapSchema.shape`, `CoListSchema.element`, and `CoOptionalSchema.innerType`.
```tsx
const Message = co.map({
content: co.richText(),
});
const Thread = co.map({
messages: co.list(Message),
});
const thread = Thread.create({
messages: Thread.def.shape.messages.create([ // [!code --]
messages: Thread.shape.messages.create([ // [!code ++]
Message.create({
content: co.richText().create("Hi!"),
}),
Message.create({
content: co.richText().create("What's up?"),
}),
]),
});
```
### Removed the deprecated `withHelpers` method from CoValue schemas
The deprecated `withHelpers()` method has been removed from CoValue schemas. You can define helper functions manually to encapsulate CoValue-related logic.
[Learn how to define helper methods](/docs/vanilla/core-concepts/covalues/overview#helper-methods).
===PAGE:upgrade/0-17-0===
TITLE:upgrade/0-17-0
DESCRIPTION:This release introduces a comprehensive refactoring of the image API, from creation to consumption. The result is a more flexible set of components and lower-level primitives that provide better developer experience and performance. Before 0.
# Jazz 0.17.0 - New Image APIs
This release introduces a comprehensive refactoring of the image API, from creation to consumption. The result is a more flexible set of components and lower-level primitives that provide better developer experience and performance.
## Motivation
Before 0.17.0, the image APIs had several limitations:
- Progressive loading created confusion in usage patterns, and the API lacked flexibility to support all use cases
- The resize methods were overly opinionated, and the chosen library had compatibility issues in incognito mode
- The imperative functions for loading images were unnecessarily complex for simple use cases
## Breaking Changes
- The `createImage` options have been restructured, and the function has been moved to the `jazz-tools/media` namespace for both React and React Native
- The `` component has been replaced with `` from `jazz-tools/react`
- The `` component has been replaced with `` from `jazz-tools/react-native`
- The `highestResAvailable` function has been moved from `ImageDefinition.highestResAvailable` to `import { highestResAvailable } from "jazz-tools/media"`
- Existing image data remains compatible and accessible
- Progressive images created with previous versions will continue to work
## Changes
### `createImage` Function
The `createImage` function has been refactored to allow opt-in specific features and moved to the `jazz-tools/media` namespace.
```tsx
owner?: Group | Account;
placeholder?: "blur" | false;
maxSize?: number;
progressive?: boolean;
};
```
- By default, images are now created with only the original size saved (no progressive loading or placeholder)
- The `maxSize` property is no longer restricted and affects the original size saved
- Placeholder generation is now a configurable property, disabled by default. Currently, only `"blur"` is supported, with more built-in options planned for future releases
- The `progressive` property creates internal resizes used exclusively via public APIs. Direct manipulation of internal resize state is no longer recommended
The `pica` library used internally for browser image resizing has been replaced with a simpler canvas-based implementation. Since every image manipulation library has trade-offs, we've chosen the simplest solution while providing flexibility through `createImageFactory`. This new factory function allows you to create custom `createImage` instances with your preferred libraries for resizing, placeholder generation, and source reading. It's used internally to create default instances for browsers, React Native, and Node.js environments.
### Replaced `` Component with ``
The `` component has been replaced with `` component for both React and React Native.
```tsx
// Before
{({ src }) => }
// After
```
The `width` and `height` props are now used internally to load the optimal image size, but only if progressive loading was enabled during image creation.
For detailed usage examples and API reference, see the [Image component documentation](/docs/react/core-concepts/covalues/imagedef#displaying-images).
### New `Image` Component for Svelte
A new `Image` component has been added for Svelte, featuring the same API as the React and React Native components.
```svelte
```
For detailed usage examples and API reference, see the [Image component documentation](/docs/svelte/core-concepts/covalues/imagedef#displaying-images).
### New Image Loading Utilities
Two new utility functions are now available from the `jazz-tools/media` package:
- `loadImage` - Fetches the original image file by ID
- `loadImageBySize` - Fetches the best stored size for a given width and height
For detailed usage examples and API reference, see the [Image component documentation](/docs/vanilla/core-concepts/covalues/imagedef#displaying-images).
===PAGE:upgrade/0-18-0===
TITLE:upgrade/0-18-0
DESCRIPTION:This release introduces a new `$jazz` field to CoValues. This field serves as a namespace for Jazz methods and internal properties. Jazz aims to make CoValues as similar as possible to JSON objects, but collaborative objects have unique requirements that create challenges: 1.
# Jazz 0.18.0 - New `$jazz` field in CoValues
This release introduces a new `$jazz` field to CoValues. This field serves as a namespace for Jazz methods and internal properties.
## Motivation
Jazz aims to make CoValues as similar as possible to JSON objects, but collaborative objects have unique requirements that create challenges:
1. **Property conflicts**: Developers need to define collaborative objects with custom fields and Jazz-specific properties (`id`, `owner`),
without naming conflicts
2. **Direct property assignments**: Direct assignments clash with modern front-end frameworks that discourage mutations, like Vue, Svelte and the
upcoming React Compiler.
3. **Tracking changes to CoValues**: Proxies are currently used for tracking changes to CoValues, but they introduce several usability problems.
They make debugging and serialization unnecessarily complex, introduce issues with object identity, and behave inconsistently in different environments.
The new `$jazz` namespace solves these issues by making CoValues behave like readonly JSON objects while providing Jazz-specific methods
in a dedicated namespace. This approach enables more ergonomic APIs (like `$jazz.set` accepting plain JSON) and sets us up to remove
proxies entirely in the future.
## Changes
### The new `$jazz` field
The new `$jazz` field is a namespace for Jazz methods and internal properties. It is available on all CoValues, and it
provides access to Jazz internal properties and utility methods:
- `id`: The ID of the CoValue
- `owner`: The owner of the CoValue
- `refs`: A map of references to other CoValues
- `getEdits`: Returns a list of edits made to the CoValue
- `ensureLoaded` & `subscribe`: Perform sync operations
### Streamlined CoValue updates
The `$jazz.set` method allows you to update CoValues using either CoValues or plain JSON objects. This makes it easier to work with
nested data.
```ts
const Dog = co.map({
name: co.plainText(),
});
const Person = co.map({
name: co.plainText(),
dog: Dog,
})
const person = Person.create({
name: "John",
dog: { name: "Rex" },
});
// Instead of
person.dog = Dog.create({ name: co.plainText().create("Fido") });
// You can now use plain JSON objects
person.$jazz.set("dog", { name: "Fido" });
```
CoValues are created automatically, and permissions are handled in the same way than when creating CoValues from JSON
(see [docs](/docs/vanilla/permissions-and-sharing/cascading-permissions#ownership-on-inline-covalue-creation)).
This feature is now available on all methods that mutate CoValues (like `CoList.$jazz.push`, `CoList.$jazz.splice` and `CoFeed.$jazz.push`).
#### New CoList Utility Methods
Jazz 0.18.0 introduces two new utility methods for easier CoList editing:
```ts
// Remove items
list.$jazz.remove(1); // By index
list.$jazz.remove(item => item.completed); // By predicate
// Keep only items matching the predicate
list.$jazz.retain(item => !item.completed);
```
They allow editing a CoList in-place with a simpler API than `$jazz.splice`.
## Breaking Changes
### Jazz Properties Migration
All Jazz properties are now available in the `$jazz` namespace.
```ts
// Before
coValue.id; // [!code --:4]
coValue._owner;
coValue._refs.someProperty;
coValue._edits.someProperty;
// After
coValue.$jazz.id; // [!code ++:4]
coValue.$jazz.owner;
coValue.$jazz.refs.someProperty;
coValue.$jazz.getEdits().someProperty;
```
The only exception is the `_type` field, which has been renamed to `$type$` and is still available on CoValues.
This allows Typescript to perform [type narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) on CoValues.
### CoMap Changes
All CoMap methods have been moved into `$jazz`, to allow defining any arbitrary key in the CoMap (except for `$jazz` and `$type$`) without conflicts.
```ts
// Direct assignment
person.name = "Alice"; // [!code --]
person.$jazz.set("name", "Alice"); // [!code ++]
// Deleting properties
delete person.age; // [!code --]
person.$jazz.delete("age"); // [!code ++]
// Update multiple properties
person.applyDiff({ // [!code --:4]
name: "Alice",
age: 42,
});
person.$jazz.applyDiff({ // [!code ++:4]
name: "Alice",
age: 42,
});
```
For CoMaps created with `co.map()`, fields are now `readonly` and direct assignment will cause type errors. CoMaps created with class schemas will throw
runtime errors prompting you to use `$jazz.set`.
### CoList Changes
CoLists are now readonly arrays. All mutation methods have been moved to `$jazz` and using array mutation methods directly will cause type errors.
This means CoLists are no longer a subtype of Array. If you were previously treating CoLists as Arrays, you'll need to migrate to using `ReadonlyArray`.
```ts
// Before
list.push("item"); // [!code --:4]
list.unshift("first");
list.splice(1, 1, "replacement");
list[0] = "new value";
// After
list.$jazz.push("item"); // [!code ++:4]
list.$jazz.unshift("first");
list.$jazz.splice(1, 1, "replacement");
list.$jazz.set(0, "new value");
```
The following methods have been deprecated and should not be used:
- `sort()`
- `reverse()`
- `fill()`
- `copyWithin()`
These methods could behave inconsistently with CoLists. `$jazz` replacements may be introduced in future releases.
### Ownership Changes
The `$jazz.owner` field now always returns a Group (instead of a Group or Account). Having Account owners is discouraged, because it
restricts the CoValue to a single user, who cannot be changed. As a best practice, it's a good idea to create a Group for every new CoValue,
even if the Group only has a single user in it (this is the default behavior when creating a CoValue with no explicit owner).
CoValues owned by Accounts will now return a Group that represents that Account. It will continue to behave the same way it previously did:
trying to add a new member to the Group will fail.
Accounts as CoValue owners will be phased out in a future release.
### Removed Features
#### castAs() Method
The `castAs()` method has been completely removed as it was an unsafe operation that bypassed type checking and enabled using CoValues in unsupported ways.
#### toJSON() Changes
The `_type` fields has been removed from `toJSON()` output for Account, CoMap, CoFeed, and FileStream classes, and the `id` field has been moved to the `$jazz` namespace.
Note: before `0.18.4`, the `id` field was also removed from the `toJSON()` output. This has since been reverted.
#### Group Property Removal
The `root` and `profile` fields have been removed from the Group class. These fields were not documented and were not intended to be used by users.
### Codemod
We provide a codemod to make the upgrade to the new `$jazz` field quicker.
The codemod is type-aware, and it is required to have `jazz-tools` already upgraded to 0.18.
```bash
npx jazz-tools-codemod-0-18@latest
```
Or if you want to run it on a specific path or file:
```bash
npx jazz-tools-codemod-0-18@latest ./path/to/your/src
```
The goal of this codemod is to get you through the 90% of the work quickly, running a TypeScript check after the codemod will help you to find the remaining issues.
Expect situations where code is migrated the wrong way or legacy code that isn't detected by the codemod.
===PAGE:upgrade/0-19-0===
TITLE:upgrade/0-19-0
DESCRIPTION:This release introduces explicit loading states when loading CoValues and a new way to define how CoValues are loaded. Previously, APIs that loaded CoValues returned `undefined` to represent values that were still loading, and `null` for values that couldn't be loaded due to authorization or network errors. This approach had several problems: - **Lack of diagnostic information**: It's difficult to distinguish between different failure modes (authorization error vs.
# Jazz 0.19.0 - Explicit CoValue loading states
This release introduces explicit loading states when loading CoValues and a new way to define how CoValues are loaded.
## Motivation
Previously, APIs that loaded CoValues returned `undefined` to represent values that were still loading, and `null` for values that couldn't be loaded due to authorization or network errors.
This approach had several problems:
- **Lack of diagnostic information**: It's difficult to distinguish between different failure modes (authorization error vs. network error).
- **Ambiguous nullable values**: It's easy to confuse unset CoValue properties with not-loaded CoValues, since both are represented as nullable values.
- **Implicit error handling**: Unloaded values are typically handled with null-checks or optional chaining, which conflate multiple distinct states into a single code path. This often causes unexpected behavior when apps are deployed and used collaboratively.
In this release, we're introducing a new way to represent loading states:
```ts
// APIs that load a CoValue now return a "maybe loaded" CoValue:
type MaybeLoaded = CoValue | NotLoaded;
type NotLoaded = {
$isLoaded: false;
$jazz: {
id: ID;
loadingState: "loading" | "unauthorized" | "unavailable";
};
};
type CoValue = {
$isLoaded: true;
$jazz: {
id: ID;
loadingState: "loaded";
} & CoValueAPI; // the whole `$jazz` API
... // all other properties from that CoValue
};
```
The goal of explicit loading states is to make it easier for developers to build apps which handle all kinds of loading states properly.
## Changes
### The new `$isLoaded` field
Before consuming a CoValue, you must check whether it's loaded and handle the case where it's not. Use the `$isLoaded` field to perform this check.
```ts
const Person = co.map({
name: z.string(),
address: co.map({
street: z.string(),
}),
});
const person = await Person.load(id, {
resolve: { address: true },
});
if (!person.$isLoaded) {
// Handle the case where the CoValue is not loaded
throw new Error("Person not found or not accessible");
}
// Thanks to the resolve query, we know that if person is loaded, so is address
console.log(person.address.street); // "123 Fake Street"
```
### `$jazz.loadingState` provides additional information
To handle different loading states more granularly, use the `$jazz.loadingState` field.
```tsx
const person = useCoState(Person, id, {
resolve: { address: true },
});
if (!person.$isLoaded) {
switch (person.$jazz.loadingState) {
case "loading":
return "Loading...";
case "unauthorized":
return "Person not accessible";
case "unavailable":
return "Person not found";
}
}
```
### Schema-level resolve queries
When working with deeply-nested CoValue schemas, you need to handle complex resolve queries, and also make sure that they are in sync with the types of loaded CoValues to avoid type errors or unnecessary loading state checks:
```tsx
const account = useAccount(MusicAccount, {
resolve: { root: { playlists: { $each: { tracks: { $each: true } } } } },
});
type AccountWithPlaylists = co.loaded<
typeof MusicAccount,
{ root: { playlists: { $each: { tracks: { $each: true } } } } }
>;
function allTracks(account: AccountWithPlaylists): MusicTrack[] {
return account.root.playlists.flatMap(playlist => playlist.tracks);
}
allTracks(account);
```
This has been a common source of confusion for users, so we've added a simpler way to define resolve queries.
Schema-level resolve queries allow you specify the resolve query once at the schema level, and that query will be used both when loading CoValues from that schema (when no resolve query is provided by the user) and in `co.loaded` types:
```tsx
// `.resolved()` adds a resolve query to a schema
const PlaylistWithTracks = Playlist.resolved({ tracks: { $each: true } });
const AccountWithPlaylists = MusicAccount.resolved(
// Use `.resolveQuery` to get the resolve query from a schema and compose it in other queries
{ root: { playlists: { $each: PlaylistWithTracks.resolveQuery } } },
});
// The schema's resolve query will be used if no other resolve query is provided
const account = useAccount(AccountWithPlaylists);
// `co.loaded` will infer the type of the loaded CoValue using the schema's resolve query.
type AccountWithPlaylists = co.loaded;
function allTracks(account: AccountWithPlaylists): MusicTrack[] {
return account.root.playlists.flatMap(playlist => playlist.tracks);
}
allTracks(account);
```
### All React hooks now accept selector functions
The `useAccountWithSelector` and `useCoStateWithSelector` React hooks have seen widespread adoption, allowing you to select a subset of a CoValue's properties to return.
This prevents unnecessary re-renders, as components only re-render when the data you're interested in changes.
In this release, we've added the `select` functionality directly to the `useAccount` and `useCoState` hooks.
```tsx
const profileName = useAccount(Account, {
resolve: { profile: true },
select: (account) =>
account.$isLoaded
? account.profile.name
: "Unavailable",
});
```
## Breaking Changes
### Return types when loading CoValues
All methods and functions that load CoValues now return a `MaybeLoaded` instead of `CoValue | null | undefined`.
You'll need to update your code to check the `$isLoaded` field instead of checking for `null` or `undefined`.
```ts
const person = await Person.load(id, {
resolve: { address: true },
});
if (!person) { // [!code --]
if (!person.$isLoaded) { // [!code ++]
return;
}
```
We've added a `getLoadedOrUndefined` utility function so you can keep using your existing error handling logic and migrate to the new loading states gradually.
### Renamed `$onError: null` to `$onError: "catch"`
Since `null` is no longer used to represent a not-loaded CoValue, we've renamed the `$onError: null` option in resolve queries to `$onError: "catch"`.
For more information about `$onError`, see the [Catching loading errors](/docs/core-concepts/subscription-and-loading#catching-loading-errors) documentation.
### Split the `useAccount` hook into three separate hooks
Previously, `useAccount` returned an object containing the current account, the current agent, and a logout function.
This caused confusion for users who only needed the current agent or the logout function.
To address this, we've split `useAccount` into three separate hooks:
- `useAccount`: now returns only the current account
- `useAgent`: returns the current agent
- `useLogOut`: returns a function for logging out of the current account
### Removed the `useAccountWithSelector` and `useCoStateWithSelector` hooks
Now that `useAccount` and `useCoState` accept selector functions, the `useAccountWithSelector` and `useCoStateWithSelector` hooks are no longer needed.
The APIs are equivalent, so you can use `useAccount` and `useCoState` in place of the `-WithSelector` variants.
## Codemod
We provide a codemod to make the upgrade to explicit loading states quicker.
The codemod is type-aware, so you must upgrade `jazz-tools` to 0.19 before running it.
```bash
npx jazz-tools-codemod-0-19@latest
```
Or if you want to run it on a specific path or file:
```bash
npx jazz-tools-codemod-0-19@latest ./path/to/your/src
```
The goal of this codemod is to get you through 80% of the work quickly. After running it, perform a TypeScript type check to identify the remaining issues.
Note that some edge cases may be migrated incorrectly, and some code patterns (e.g. optional chaining) may not be detected by the codemod.
We've found AI coding assistants like Cursor to be effective at fixing remaining issues.
## Known Issue: TypeScript Iterator Bug
While introducing explicit loading states, we discovered [a TypeScript bug](https://github.com/microsoft/TypeScript/issues/62462) that prevents iterating over CoLists using methods that require an iterator, when these CoLists are loaded inside other CoValues (CoMaps, other CoLists, or CoFeeds).
We've submitted [a fix](https://github.com/microsoft/TypeScript/pull/62661) for this bug. Until it's released, use one of the following workarounds if you encounter this issue:
```ts
const Pet = co.map({
name: z.string(),
});
const Person = co.map({
pets: co.list(Pet),
});
if (!person.$isLoaded) {
return;
}
// Use `.values()` to explicitly get the iterator
const [firstPet] = person.pets; // [!code --]
const [firstPet] = person.pets.values(); // [!code ++]
// Array iteration methods (like `forEach` and `map`) work as expected
for (const pet of person.pets) { // [!code --]
person.pets.forEach(pet => { // [!code ++]
console.log(pet.name);
} // [!code --]
}); // [!code ++]
```
Alternatively, you can suppress the TypeScript error by adding a `@ts-expect-error` comment, since this is purely a type-level issue.
===PAGE:upgrade/0-20-0===
TITLE:upgrade/0-20-0
DESCRIPTION:With this release, we complete our migration to a pure **native** Rust toolchain. We have removed the JavaScript compatibility layer, meaning our native Rust core now runs everywhere: React Native, Edge runtimes, all server-side environments, and the web. Before 0.
# Jazz 0.20.0 - Full native crypto
With this release, we complete our migration to a pure **native** Rust toolchain.
We have removed the JavaScript compatibility layer, meaning our native Rust core now runs everywhere: React Native, Edge runtimes, all server-side environments, and the web.
## Motivation
Before 0.20.0, Jazz relied on a JavaScript crypto implementation to work around compatibility issues in some runtimes-most notably React Native.
As a result, our native Rust core wasn’t running in React Native and, on some Edge runtimes,
only worked when WASM was explicitly initialized (see [WASM on Edge runtimes](/docs/react/server-side/setup#wasm-on-edge-runtimes)).
The JavaScript crypto implementation is much slower than native Rust crypto. Although workarounds like RNQuickCrypto for React Native improved performance, they still only wrapped certain native libraries, rather than running Jazz's full Rust crypto.
With native Rust crypto now running everywhere, Jazz comes with good performance in every platform.
This also helps us speed up the migration of Jazz Core to Rust which will help us improve the Jazz overall performance.
## What changed
- There is no more fallback to Javascript crypto. If the crypto fails to initialize, it will throw an error. See [Edge runtimes](#edge-runtimes) for upgrading.
- As Jazz crypto now runs natively in all environments, we no longer support `PureJSCrypto`. See [Expo](#expo) and [React Native](#react-native-non-expo) for upgrade instructions.
- RNCrypto is now the default crypto provider on React Native. As a result, we're also discontinuing support for `RNQuickCrypto`.
- Optimized the JS-to-Rust communication by implementing native data type exchange, eliminating serialization overhead.
- You can now only use strings or records of strings as [`unique` parameters](/docs/react/core-concepts/covalues/comaps#uniqueness). This ensures that serialisation is deterministic. See [a more strict `unique` param](#a-more-strict-unique-param).
- The `removeMember` method now throws an error if the caller is not authorized.
- We have introduced a way to permanently [delete CoValues](/docs/react/core-concepts/deleting). This brings a [new `deleted` loading state](#new-deleted-loading-state).
- The React context API in `jazz-tools/react-core` changed: `useJazzContextValue` replaces `useJazzContext` for value access, and nesting `JazzProvider` now throws. See [React context/provider changes](#react-contextprovider-changes).
## Breaking changes
### Edge runtimes
If you use WASM crypto on Edge runtimes, you need to ensure that WASM is available and initialized, there is no more fallback to Javascript crypto.
See more details at [WASM on Edge runtimes](/docs/react/server-side/setup#wasm-on-edge-runtimes).
```ts
// Other Jazz Imports
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Jazz application logic
return new Response("Hello from Jazz on Cloudflare!");
},
};
```
If you don't do this, you will get an error like this:
```
Critical Error: Failed to load WASM module
You need to add \`import "jazz-tools/load-edge-wasm";\` on top
of your entry module to make Jazz work with this runtime
A native crypto module is required for Jazz to work.
See https://jazz.tools/docs/react/reference/performance#use-the-best-crypto-implementation-for-your-platform
for possible alternatives.
```
Before 0.20.0, you would get a warning like this:
```
Warning: WASM crypto is not available. Falling back to JavaScript crypto.
```
### Expo
If you had previously installed QuickCrypto, then you should follow the steps below. Otherwise, no action is needed to take advantage of RNCrypto.
1) **Remove QuickCrypto dependencies/config** (only if you previously added them for `RNQuickCrypto`):
- Remove `react-native-quick-crypto` (and any associated config you added for it).
- Remove any `SODIUM_ENABLED` / `sodiumEnabled` settings you added specifically for QuickCrypto.
2) **Remove `CryptoProvider` entirely** (RNCrypto is now the default)
You have to remove the `CryptoProvider` option, it is no longer supported:
```tsx
const apiKey = "you@example.com";
return (
{children}
);
}
```
While you can distribute your own JS code changes OTA, if you update your Jazz version, this will result in changes in the native dependencies, which *cannot* be distributed over the air and requires a new store submission.
### React Native (non-Expo)
If you use React Native, you need to install `cojson-core-rn` and make sure the version matches `jazz-tools`. `cojson-core-rn` is our new high-performance crypto provider for React Native,
and it is **required** for React Native applications.
1) **Install `cojson-core-rn` (required)** and make sure the version matches `jazz-tools`:
```bash
npm install cojson-core-rn
```
```json
"dependencies": {
"cojson-core-rn": "x.x.x", # same version as jazz-tools
"jazz-tools": "x.x.x" # same version as cojson-core-rn
}
```
While you can distribute your own JS code changes OTA, if you update your Jazz version, this will result in changes in the native dependencies, which *cannot* be distributed over the air and requires a new store submission.
See also: [React Native setup](/docs/react-native/project-setup).
2) **Remove QuickCrypto dependencies/config** (only if you previously added them for `RNQuickCrypto`):
- Remove `react-native-quick-crypto` (and any associated config you added for it).
- Remove any `SODIUM_ENABLED` / `sodiumEnabled` settings you added specifically for QuickCrypto.
3) **Remove `CryptoProvider` entirely** (RNCrypto is now the default)
After installing `cojson-core-rn`, you have to remove the `CryptoProvider` option, it is no longer supported:
```tsx
const apiKey = "you@example.com";
return (
{children}
);
}
```
### A more strict `unique` param
Jazz uses [uniqueness](/docs/react/core-concepts/covalues/comaps#uniqueness) to help identify CoValues.
Occasionally, you may wish to produce a deterministic ID for the CoValue. To do so, you can manually define the uniqueness using the [`unique`](/docs/react/core-concepts/covalues/comaps#uniqueness) parameter.
Before 0.20.0, you could pass any `JsonValue` as unique param. This was not stable across languages and could lead to unexpected behavior.
The `unique` property now enforces strict type validation, limiting inputs to specific primitives rather than generic `JsonValue` types.
Accepted types are `string`, `boolean`, `null`, `undefined`, or objects containing `string` values.
Providing any other type will throw an error, with the following distinction for numbers:
- Floating-point numbers (e.g., `1.5`) will throw an error.
- Integers (e.g., `1`) are accepted and will not throw an error, though their usage is deprecated and not supported in the type system anymore.
We expect that this change **will not impact** any projects in our hosted Cloud based on our analysis.
However, since we do **not have visibility into self-hosted sync server deployments**, we cannot rule out the possibility that your code may rely on the previously allowed unique value types.
If you are migrating from an earlier version and your project passes type-checks, you are almost certainly unaffected.
If you do encounter issues (for example, errors about unsupported `unique` value types),
the recommended upgrade path is to convert your uniqueness constraint to a string (as shown below), or, if necessary, to an object containing only string properties.
**If you are affected by this change and cannot migrate to a supported unique type, please [reach us on Discord](https://discord.gg/ANm5jY6Y) for help or guidance.**
### New `deleted` loading state
With the introduction of [CoValue deletion](/docs/react/core-concepts/deleting), there's a new `deleted` loading state. If you're handling loading states explicitly, you should add a case for `deleted`:
```tsx
if (!project.$isLoaded) {
switch (project.$jazz.loadingState) {
case "loading":
return "Loading project...";
case "unavailable":
return "Project not found";
case "unauthorized":
return "Project not accessible";
case "deleted":
return "Project deleted";
}
}
```
### React context/provider changes
The React context API in `jazz-tools/react-core` changed to prevent nested providers and clarify context access:
- `JazzContextManagerContext` was removed.
- `useJazzContext` was renamed to `useJazzContextValue` for accessing the context value.
- `useJazzContext` now returns the context manager instead of the value.
- Nesting `JazzProvider` components now throws an error.
If you were using `useJazzContext` to get the context value, rename it to `useJazzContextValue`:
```ts
// [!code --:1]
- import { useJazzContext } from "jazz-tools/react-core";
// [!code ++:1]
+ import { useJazzContextValue } from "jazz-tools/react-core";
// [!code --:1]
- const context = useJazzContext();
// [!code ++:1]
+ const context = useJazzContextValue();
```
If you need to provide context to children without creating a new context (e.g., for components that don't propagate React context), use:
```tsx
{children}
```
===PAGE:upgrade/0-9-0/react===
TITLE:upgrade/0-9-0/react
DESCRIPTION:Version 0. 9. 0 simplifies the application setup and makes Jazz more intellisense friendly by replacing the `createJazzReactApp` API with top-level imports.
# Upgrade to Jazz 0.9.0
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
replacing the `createJazzReactApp` API with top-level imports.
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
## New provider setup
The `JazzProvider` is now imported from `jazz-react` instead of `createJazzReactApp`.
While `createJazzReactApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
we found that this approach made the Jazz setup awkward and confusing for some users.
So we decided to remove `createJazzReactApp` step and to provide the types through namespace declarations:
{/* prettier-ignore */}
```tsx
import { JazzProvider, usePasskeyAuth, PasskeyAuthBasicUI } from "jazz-react";
import { MyAppAccount } from "./schema";
// Remove these lines // [!code --]
const Jazz = createJazzReactApp({ AccountSchema: MyAppAccount }); // [!code --]
export const { useAccount, useCoState } = Jazz; // [!code --]
export function JazzAndAuth({ children }: { children: React.ReactNode }) { // old
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName }); // old
return (
<>
{/* Replace Jazz.Provider with provider from jazz-react */}
{children} // old
// old
> // old
);
}
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
declare module "jazz-react" { // [!code ++]
interface Register { // [!code ++]
Account: MyAppAccount; // [!code ++]
} // [!code ++]
} // [!code ++]
```
## Top level imports for hooks
All Jazz hooks are now available as top-level imports from the `jazz-react` package.
This change improves IDE intellisense support and simplifies imports:
{/* prettier-ignore */}
```tsx
// Replace local imports with "jazz-react" imports
import { useAccount } from "./main"; // [!code --]
import { useAccount } from "jazz-react"; // [!code ++]
export function Hello() {
const { me } = useAccount();
return (
<>
Hello {me.profile?.name}
>
);
}
```
## New testing utilities
Removing `createJazzReactApp` also makes testing way easier!
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
your components and hooks using `JazzTestProvider`:
{/* prettier-ignore */}
```tsx
import { createJazzTestAccount, JazzTestProvider } from "jazz-react/testing";
import { renderHook } from "@testing-library/react"; // old
import { usePlaylist } from "./usePlaylist"; // old
import { Playlist, MusicAccount } from "./schema"; // old
test("should load the playlist", async () => {
// ✅ Create a test account with your schema
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
// ✅ Set up test data
const playlist = Playlist.create({
name: "My playlist",
}, account);
// ✅ Use JazzTestProvider in your tests
const { result } = renderHook(() => usePlaylist(playlist.id), {
wrapper: ({ children }) => (
{children}
),
});
// The result is resolved synchronously, so you can assert the value immediately
expect(result.current?.name).toBe("My playlist");
});
```
===PAGE:upgrade/0-9-0/react-native===
TITLE:upgrade/0-9-0/react-native
DESCRIPTION:Version 0. 9. 0 simplifies the application setup and makes Jazz more intellisense friendly by replacing the `createJazzRNApp` API with top-level imports.
# Upgrade to Jazz 0.9.0
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
replacing the `createJazzRNApp` API with top-level imports.
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
## New provider setup
The `JazzProvider` is now imported from `jazz-react-native` instead of `createJazzRNApp`.
While `createJazzRNApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
we found that this approach made the Jazz setup awkward and confusing for some users.
So we decided to remove `createJazzRNApp` step and to provide the types through namespace declarations:
{/* prettier-ignore */}
```tsx
import { JazzProvider, useDemoAuth, DemoAuthBasicUI } from "jazz-react-native";
import { MyAppAccount } from "./schema";
// Remove these lines // [!code --]
const Jazz = createJazzRNApp({ AccountSchema: MyAppAccount }); // [!code --]
export const { useAccount, useCoState } = Jazz; // [!code --]
export function JazzAndAuth({ children }: { children: React.ReactNode }) { // old
const [auth, state] = useDemoAuth(); // old
return (
<>
{/* Replace Jazz.Provider with provider from jazz-react */}
{children} // old
// old
> // old
);
}
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
declare module "jazz-react-native" {
interface Register {
Account: MyAppAccount;
}
}
```
## Top level imports for hooks
All Jazz hooks are now available as top-level imports from the `jazz-react-native` package.
This change improves IDE intellisense support and simplifies imports:
{/* prettier-ignore */}
```tsx
// Replace local imports with "jazz-react-native" imports
import { useAccount } from "./main"; // [!code --]
import { useAccount } from "jazz-react-native"; // [!code ++]
export function Hello() {
const { me } = useAccount();
return (
<>
Hello {me.profile?.name}
>
);
}
```
## New testing utilities
Removing `createJazzRNApp` also makes testing way easier!
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
your components and hooks using `JazzTestProvider`:
{/* prettier-ignore */}
```tsx
import { createJazzTestAccount, JazzTestProvider } from "jazz-react-native/testing";
import { renderHook } from "@testing-library/react-native"; // old
import { usePlaylist } from "./usePlaylist"; // old
import { Playlist, MusicAccount } from "./schema"; // old
test("should load the playlist", async () => {
// ✅ Create a test account with your schema
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
// ✅ Set up test data
const playlist = Playlist.create({
name: "My playlist",
}, account);
// ✅ Use JazzTestProvider in your tests
const { result } = renderHook(() => usePlaylist(playlist.id), {
wrapper: ({ children }) => (
{children}
),
});
// The result is resolved synchronously, so you can assert the value immediately
expect(result.current?.name).toBe("My playlist");
});
```
===PAGE:upgrade/0-9-0/svelte===
TITLE:upgrade/0-9-0/svelte
DESCRIPTION:Version 0. 9. 0 simplifies the application setup and makes Jazz more intellisense friendly by replacing the `createJazzApp` API with top-level imports.
# Upgrade to Jazz 0.9.0
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
replacing the `createJazzApp` API with top-level imports.
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
## New provider setup
The `JazzProvider` is now imported from `jazz-svelte` instead of `createJazzApp`.
While `createJazzApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
we found that this approach made the Jazz setup awkward and confusing for some users.
So we decided to remove `createJazzApp` step and to provide the types through namespace declarations:
{/* prettier-ignore */}
```svelte
```
## Top level imports for hooks
All Jazz hooks are now available as top-level imports from the `jazz-svelte` package.
This change improves IDE intellisense support and simplifies imports:
{/* prettier-ignore */}
```svelte
Hello {me.profile?.name}
```
## New testing utilities
Removing `createJazzApp` also makes testing way easier!
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
your components and hooks using `JazzTestProvider`:
{/* prettier-ignore */}
```ts
import { useCoState } from "jazz-svelte";
import { createJazzTestAccount, JazzTestProvider } from "jazz-svelte/testing";
import { render } from "@testing-library/svelte"; // old
import { Playlist, MusicAccount } from "./schema"; // old
test("should load the playlist", async () => {
// ✅ Create a test account with your schema
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
// ✅ Set up test data
const playlist = Playlist.create({
name: "My playlist",
}, account);
// ✅ Use createJazzTestContext in your tests
render(PlaylistComponent, {
context: createJazzTestContext({ account: options.account }),
props: {
id: playlist.id,
},
});
expect(await screen.findByRole("heading", { name: "My playlist" })).toBeInTheDocument();
});
```
===PAGE:upgrade/0-9-0/vue===
TITLE:upgrade/0-9-0/vue
DESCRIPTION:Version 0. 9. 0 simplifies the application setup and makes Jazz more intellisense friendly by replacing the `createJazzVueApp` API with top-level imports.
# Upgrade to Jazz 0.9.0
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
replacing the `createJazzVueApp` API with top-level imports.
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
## New provider setup
The `JazzProvider` is now imported from `jazz-vue` instead of `createJazzVueApp`.
While `createJazzReactApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
we found that this approach made the Jazz setup awkward and confusing for some users.
So we decided to remove `createJazzReactApp` step and to provide the types through namespace declarations:
{/* prettier-ignore */}
```typescript
// Remove these lines // [!code --]
const Jazz = createJazzVueApp({ AccountSchema: ToDoAccount }); // [!code --]
const { JazzProvider } = Jazz; // [!code --]
const RootComponent = defineComponent({ // old
name: "RootComponent", // old
setup() { // old
const { authMethod, state } = useDemoAuth(); // old
return () => [ // old
h( // old
JazzProvider, // old
{ // old
AccountSchema: ToDoAccount, // The custom Account schema is passed here now
auth: authMethod.value, // old
peer: "wss://cloud.jazz.tools/?key=vue-todo-example-jazz@garden.co", // old
}, // old
{ // old
default: () => h(App), // old
}, // old
), // old
state.state !== "signedIn" && // old
h(DemoAuthBasicUI, { // old
appName: "Jazz Vue Todo", // old
state, // old
}), // old
]; // old
}, // old
}); // old
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
declare module "jazz-vue" {
interface Register {
Account: ToDoAccount;
}
}
const app = createApp(RootComponent); // old
app.use(router); // old
app.mount("#app"); // old
```
## Top level imports for hooks
All Jazz hooks are now available as top-level imports from the `jazz-vue` package.
This change improves IDE intellisense support and simplifies imports:
{/* prettier-ignore */}
```typescript
Hello {{ me.profile?.name }}
```
## New testing utilities
Removing `createJazzTestApp` also makes testing way easier!
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
your components and hooks using `JazzTestProvider`:
{/* prettier-ignore */}
```tsx
import { createJazzTestAccount, JazzTestProvider } from "jazz-vue/testing";
import { createApp, defineComponent, h } from "vue";
import { usePlaylist } from "./usePlaylist";
import { Playlist, MusicAccount } from "./schema"; // old
// This can be reused on other tests!
export const renderComposableWithJazz = any>(
composable: C,
{ account }: { account: Account | { guest: AnonymousJazzAgent } },
) => {
let result;
const wrapper = defineComponent({
setup() {
result = composable();
// suppress missing template warning
return () => {};
},
});
// ✅ Use JazzTestProvider in your tests
const app = createApp({
setup() {
return () =>
h(
JazzTestProvider,
{
account,
},
{
default: () => h(wrapper),
},
);
},
});
app.mount(document.createElement("div"));
return [result, app] as [ReturnType, ReturnType];
};
test("should load the playlist", async () => {
// ✅ Create a test account with your schema
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
// ✅ Set up test data
const playlist = Playlist.create({
name: "My playlist",
}, account);
// ✅ Set up test data
const { result } = renderComposableWithJazz(() => usePlaylist(playlist.id), {
account,
});
// The result is resolved synchronously, so you can assert the value immediately
expect(result?.name).toBe("My playlist");
});
```
===PAGE:upgrade/0-9-8===
TITLE:upgrade/0-9-8
DESCRIPTION:We have simplified the API to make the "me" value always optional! This removes the need of using `useAccount` like the 90% of the time! {/* prettier-ignore */} This also applies to the load API: {/* prettier-ignore */} And `Group.
# Jazz 0.9.8 - Without me!
We have simplified the API to make the "me" value always optional!
This removes the need of using `useAccount` like the 90% of the time!
{/* prettier-ignore */}
```ts
import { useState } from "react";
import { Issue } from "./schema";
import { IssueComponent } from "./components/Issue.tsx";
function App() {
const [issue, setIssue] = useState();
const createIssue = () => {
setIssue(Issue.create(
{
title: "Buy terrarium",
description: "Make sure it's big enough for 10 snails.",
estimate: 5,
status: "backlog",
}, // The owner defaults now to a group managed by the current user!
));
};
if (issue) {
return ;
} else {
return ;
}
}
```
This also applies to the load API:
{/* prettier-ignore */}
```ts
const issue = Issue.load(issueId, {})
```
And `Group.create`:
{/* prettier-ignore */}
```tsx
const group = Group.create()
const sharedIssue = Issue.create(payload, group)
group.addMember('everyone', 'reader')
```
Everything is backward compatible, so no upgrade steps are required.
With this Jazz API becomes way more lean and more is coming!
===PAGE:upgrade/react-native-local-persistence===
TITLE:upgrade/react-native-local-persistence
DESCRIPTION:Version 0. 9. 2 introduces local persistence for React Native apps using SQLite.
# Enable local persistence
Version 0.9.2 introduces local persistence for React Native apps using SQLite. In version 0.12, we've separated the implementations for Expo and framework-less React Native.
If you are upgrading from a version before 0.9.2, you need to enable local persistence by following the steps below.
Local persistence is enabled by default in version 0.12 and higher.
## Choose your implementation
Depending on whether you're using Expo or framework-less React Native, you'll need to follow different steps:
- For Expo applications, refer to the [React Native - Expo](/docs/react-native-expo/project-setup) guide
- For framework-less React Native applications, refer to the [React Native](/docs/react-native/project-setup) guide
## Add the required dependencies
### For Expo
As SQLite package, Expo uses `expo-sqlite`. Install it with:
```bash
npx expo install expo-sqlite
npx expo install expo-secure-store
```
### For framework-less React Native
As SQLite package, we use `@op-engineering/op-sqlite` and `react-native-mmkv` for key-value storage:
```bash
npm install @op-engineering/op-sqlite react-native-mmkv
npx pod-install
```
## Update your JazzProvider
In version 0.12 and higher, you need to use either the `jazz-expo` package (for Expo) or the `jazz-react-native` package (for framework-less React Native). Local persistence is enabled by default in both packages.
To enable it, you need to pass the `storage` option to the `JazzProvider` component:
```tsx
```