# Jazz ## Getting started ### Overview # 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 ``` \--- Section applies only to react --- **Using an LLM?** [Add our llms.txt](/react/llms-full.txt) to your context window! \--- End of react specific section --- \--- Section applies only to svelte --- **Using an LLM?** [Add our llms.txt](/svelte/llms-full.txt) to your context window! \--- End of svelte specific section --- \--- Section applies only to react-native --- **Using an LLM?** [Add our llms.txt](/react-native/llms-full.txt) to your context window! \--- End of react-native specific section --- \--- Section applies only to react-native-expo --- **Using an LLM?** [Add our llms.txt](/react-native-expo/llms-full.txt) to your context window! \--- End of react-native-expo specific section --- \--- Section applies only to vanilla --- **Using an LLM?** [Add our llms.txt](/vanilla/llms-full.txt) to your context window! \--- End of vanilla specific section --- **Info:** 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! **Info: Using Jazz Cloud** 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 import { co, z } from 'jazz-tools'; import { JazzBrowserContextManager } from 'jazz-tools/browser'; ``` ### 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 const ToDo = co.map({ title: z.string(), completed: z.boolean() }); const ToDoList = co.list(ToDo); ``` ### 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 await new JazzBrowserContextManager().createContext({ sync: { peer: 'wss://cloud.jazz.tools?key=minimal-vanilla-example', when: 'always', }, }); const newList = ToDoList.create([{ title: 'Learn Jazz', completed: false }]); const listId = newList.$jazz.id; ``` ### Build your UI Now, build a basic UI skeleton for your app. ```ts const app = document.querySelector('#app')!; const id = Object.assign(document.createElement('small'), { innerText: `List ID: ${listId}`, }); const listContainer = document.createElement('div'); app.append(listContainer, id); ``` ### Display Items Display your items and add logic to mark them as done... ```ts function toDoItemElement(todo: co.loaded) { const label = document.createElement('label'); const checkbox = Object.assign(document.createElement('input'), { type: 'checkbox', checked: todo.completed, onclick: () => todo.$jazz.set('completed', checkbox.checked), }); label.append(checkbox, todo.title); return label; } ``` ### Add New Items ...and add new items to the list using an input and a button. ```ts function newToDoFormElement(list: co.loaded) { const form = Object.assign(document.createElement('form'), { onsubmit: (e: Event) => { e.preventDefault(); list.$jazz.push({ title: input.value, completed: false }); } }); const input = Object.assign(document.createElement('input'), { placeholder: 'New task', }); const btn = Object.assign(document.createElement('button'), { innerText: 'Add', }); form.append(input, btn); return form; } ``` ### 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 const unsubscribe = ToDoList.subscribe( listId, { resolve: { $each: true } }, (toDoList) => { const addForm = newToDoFormElement(toDoList); listContainer.replaceChildren( ...toDoList.map((todo) => { return toDoItemElement(todo); }), addForm ); } ); ``` ### 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 //[!code --:2] const newList = ToDoList.create([{ title: 'Learn Jazz', completed: false }]); const listId = newList.$jazz.id; // [!code ++:8] const listId = new URLSearchParams(window.location.search).get('id'); if (!listId) { const newList = ToDoList.create([{ title: 'Learn Jazz', completed: false }]); await newList.$jazz.waitForSync(); window.location.search = `?id=${newList.$jazz.id}`; throw new Error('Redirecting...'); } ``` ### All Together Put it all together for a simple Jazz app in less than 100 lines of code. ```ts import { co, z } from 'jazz-tools'; import { JazzBrowserContextManager } from 'jazz-tools/browser'; const ToDo = co.map({ title: z.string(), completed: z.boolean() }); const ToDoList = co.list(ToDo); await new JazzBrowserContextManager().createContext({ sync: { peer: 'wss://cloud.jazz.tools?key=minimal-vanilla-example', when: 'always', }, }); const listId = new URLSearchParams(window.location.search).get('id'); if (!listId) { const newList = ToDoList.create([{ title: 'Learn Jazz', completed: false }]); await newList.$jazz.waitForSync(); window.location.search = `?id=${newList.$jazz.id}`; throw new Error('Redirecting...'); } const app = document.querySelector('#app')!; const id = Object.assign(document.createElement('small'), { innerText: `List ID: ${listId}`, }); const listContainer = document.createElement('div'); app.append(listContainer, id); function toDoItemElement(todo: co.loaded) { const label = document.createElement('label'); const checkbox = Object.assign(document.createElement('input'), { type: 'checkbox', checked: todo.completed, onclick: () => todo.$jazz.set('completed', checkbox.checked), }); label.append(checkbox, todo.title); return label; } function newToDoFormElement(list: co.loaded) { const form = Object.assign(document.createElement('form'), { onsubmit: (e: Event) => { e.preventDefault(); list.$jazz.push({ title: input.value, completed: false }); } }); const input = Object.assign(document.createElement('input'), { placeholder: 'New task', }); const btn = Object.assign(document.createElement('button'), { innerText: 'Add', }); form.append(input, btn); return form; } const unsubscribe = ToDoList.subscribe( listId, { resolve: { $each: true } }, (toDoList) => { const addForm = newToDoFormElement(toDoList); listContainer.replaceChildren( ...toDoList.map((todo) => { return toDoItemElement(todo); }), addForm ); } ); ``` ## 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. ### Quickstart # 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 \--- Section applies only to vanilla --- 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. ##### npm: ```sh npm create vite@latest jazzfest -- --template vanilla-ts cd jazzfest ``` ##### pnpm: ```sh pnpm create vite@latest jazzfest -- --template vanilla-ts cd jazzfest ``` \--- End of vanilla specific section --- \--- Section applies only to react --- 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. ##### npm: ```sh npx create-next-app@latest --typescript jazzfest cd jazzfest ``` ##### pnpm: ```sh pnpx create-next-app@latest --typescript jazzfest cd jazzfest ``` \--- End of react specific section --- \--- Section applies only to svelte --- 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. ##### npm: ```sh npx sv create --types ts --template minimal jazzfest cd jazzfest ``` ##### pnpm: ```sh pnpx sv create --types ts --template minimal jazzfest cd jazzfest ``` \--- End of svelte specific section --- **Note: Requires Node.js 20+** ## Install Jazz The `jazz-tools` package includes everything you're going to need to build your first Jazz app. ##### npm: ```sh npm install jazz-tools ``` ##### pnpm: ```sh pnpm add jazz-tools ``` ## Get your free API key Sign up for a free API key at [dashboard.jazz.tools](https://dashboard.jazz.tools) for higher limits or production use, or use your email address as a temporary key to get started quickly. \--- Section applies only to vanilla --- **File name: .env** \--- End of vanilla specific section --- \--- Section applies only to react --- **File name: .env** \--- End of react specific section --- \--- Section applies only to svelte --- **File name: .env** \--- End of svelte specific section --- ##### Vanilla: ```bash VITE_JAZZ_API_KEY="you@example.com" # or your API key ``` ##### React: ```bash NEXT_PUBLIC_JAZZ_API_KEY="you@example.com" # or your API key ``` ##### Svelte: ```bash PUBLIC_JAZZ_API_KEY="you@example.com" # or your 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. \--- Section applies only to react,svelte --- The migration runs when the user logs in, and ensures the account is properly set up before we try to use it. \--- End of react,svelte specific section --- \--- Section applies only to vanilla --- **File name: src/schema.ts** \--- End of vanilla specific section --- \--- Section applies only to react --- **File name: app/schema.ts** \--- End of react specific section --- \--- Section applies only to svelte --- **File name: src/lib/schema.ts** \--- End of svelte specific section --- ```ts import { co, z } from "jazz-tools"; export const Band = co.map({ name: z.string(), // Zod primitive type }); export const Festival = co.list(Band); export const JazzFestAccountRoot = co.map({ myFestival: Festival, }); export const JazzFestAccount = co .account({ root: JazzFestAccountRoot, profile: co.profile(), }) .withMigration((account) => { if (!account.$jazz.has("root")) { account.$jazz.set("root", { myFestival: [], }); } }); ``` \--- Section applies only to vanilla --- ## 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: \--- End of vanilla specific section --- \--- Section applies only to react,svelte --- ## Add the Jazz Provider \[!framework=react,svelte\] Wrap your app with a provider so components can use Jazz. \--- End of react,svelte specific section --- \--- Section applies only to react --- **File name: app/components/JazzWrapper.tsx** ```tsx "use client"; // tells Next.js that this component can't be server-side rendered. If you're not using Next.js, you can remove it. import { JazzReactProvider } from "jazz-tools/react"; import { JazzFestAccount } from "@/app/schema"; const apiKey = process.env.NEXT_PUBLIC_JAZZ_API_KEY; export function JazzWrapper({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` \--- End of react specific section --- \--- Section applies only to vanilla --- **File name: src/main.ts** \--- End of vanilla specific section --- \--- Section applies only to react --- **File name: app/layout.tsx** \--- End of react specific section --- \--- Section applies only to svelte --- **File name: src/routes/+layout.svelte** \--- End of svelte specific section --- ##### Vanilla: ```tsx import { JazzBrowserContextManager } from 'jazz-tools/browser'; import { JazzFestAccount } from './schema'; const apiKey = import.meta.env.VITE_JAZZ_API_KEY; const contextManager = new JazzBrowserContextManager(); await contextManager.createContext({ sync: { peer: `wss://cloud.jazz.tools?key=${apiKey}` }, }); function getCurrentAccount() { const context = contextManager.getCurrentValue(); if (!context || !("me" in context)) { throw new Error(""); } return context.me; } ``` ##### React: ```tsx import { JazzWrapper } from "@/app/components/JazzWrapper"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ##### Svelte: ```svelte {@render children?.()} ``` ## Start your app Moment of truth — time to start your app and see if it works. ##### npm: ```bash npm run dev ``` ##### pnpm: ```bash pnpm run dev ``` \--- Section applies only to vanilla --- If everything's going according to plan, you should see a blank page! \--- End of vanilla specific section --- \--- Section applies only to react --- If everything's going according to plan, you should see the default Next.js welcome page! \--- End of react specific section --- \--- Section applies only to svelte --- If everything's going according to plan, you should see the default SvelteKit welcome page! \--- End of svelte specific section --- ### Not loading? If you're not seeing the welcome page: \--- Section applies only to react --- * Check you wrapped your app with the Jazz Provider in `app/layout.tsx` * Check your schema is properly defined in `app/schema.ts` \--- End of react specific section --- \--- Section applies only to svelte --- * 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` \--- End of svelte specific section --- **Info: Still stuck?** Ask for help on [Discord](https://discord.gg/utDMjHYg42)! ## Create data \--- Section applies only to vanilla --- 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. \--- End of vanilla specific section --- \--- Section applies only to react,svelte --- 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. \--- End of react,svelte specific section --- \--- Section applies only to vanilla --- **File name: src/main.ts** \--- End of vanilla specific section --- \--- Section applies only to react --- **File name: app/components/NewBand.tsx** \--- End of react specific section --- \--- Section applies only to svelte --- **File name: src/lib/components/NewBand.svelte** \--- End of svelte specific section --- ##### Vanilla: ```tsx const me = getCurrentAccount(); const account = await JazzFestAccount.load(me.$jazz.id); if (!account.$isLoaded) throw new Error("Account is not loaded"); account.migrate(); const myAccount = await account.$jazz.ensureLoaded({ resolve: { root: { myFestival: true } }, }); const form = document.createElement('form'); const input = Object.assign(document.createElement('input'), { type: 'text', name: 'band', placeholder: 'Band name' }); const button = Object.assign(document.createElement('button'), { name: 'band', innerText: 'Add', onclick: async (e: Event) => { e.preventDefault(); // Prevent navigation if (!myAccount.$isLoaded) return; myAccount.root.myFestival.$jazz.push({ name: input.value }); input.value = ''; } }); form.append(input, button); ``` ##### React: ```tsx "use client"; import { useAccount } from "jazz-tools/react"; import { JazzFestAccount } from "@/app/schema"; import { useState } from "react"; export function NewBand() { const me = useAccount(JazzFestAccount, { resolve: { root: { myFestival: true } }, }); const [name, setName] = useState(""); const handleSave = () => { if (!me.$isLoaded) return; me.root.myFestival.$jazz.push({ name }); setName(""); }; return (
setName(e.target.value)} />
); } ``` ##### Svelte: ```svelte
``` ## Display your data Now we've got a way to create data, so let's add a component to display it. \--- Section applies only to vanilla --- **File name: src/main.ts** \--- End of vanilla specific section --- \--- Section applies only to react --- **File name: app/components/Festival.tsx** \--- End of react specific section --- \--- Section applies only to svelte --- **File name: src/lib/components/Festival.svelte** \--- End of svelte specific section --- ##### Vanilla: ```tsx const bandList = document.createElement('ul'); const unsubscribe = myAccount.root.myFestival.$jazz.subscribe((festival) => { if (!festival.$isLoaded) throw new Error("Festival not loaded"); const bandElements = festival .map((band) => { if (!band.$isLoaded) return; const bandElement = document.createElement("li"); bandElement.innerText = band.name; return bandElement; }) .filter((band) => band !== undefined); bandList.replaceChildren(...bandElements); }); ``` ##### React: ```tsx "use client"; import { useAccount } from "jazz-tools/react"; import { JazzFestAccount } from "@/app/schema"; export function Festival() { const me = useAccount(JazzFestAccount, { resolve: { root: { myFestival: { $each: true } } }, }); if (!me.$isLoaded) return null; return (
    {me.root.myFestival.map( (band) => band &&
  • {band.name}
  • , )}
); } ``` ##### Svelte: ```svelte
    {#if me.current.$isLoaded} {#each me.current.root.myFestival as band}
  • {band.name}
  • {/each} {/if}
``` ## Put it all together You've built all your components, time to put them together. \--- Section applies only to vanilla --- **File name: src/main.ts** \--- End of vanilla specific section --- \--- Section applies only to react --- **File name: app/page.tsx** \--- End of react specific section --- \--- Section applies only to svelte --- **File name: src/routes/+page.svelte** \--- End of svelte specific section --- ##### Vanilla: ```tsx const app = document.querySelector('#app')!; app.append(form, bandList); ``` ##### React: ```tsx import { Festival } from "@/app/components/Festival"; import { NewBand } from "@/app/components/NewBand"; export default function Home() { return (

🎪 My Festival

); } ``` ##### Svelte: ```svelte

🎪 My Festival

``` 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. \--- Section applies only to react --- 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) \--- End of react specific section --- ## 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 ### Installation # Providers \--- Section applies only to react --- `` is the core component that connects your React application to Jazz. It handles: \--- End of react specific section --- \--- Section applies only to svelte --- `` is the core component that connects your Svelte application to Jazz. It handles: \--- End of svelte specific section --- \--- Section applies only to react-native --- `` is the core component that connects your React Native application to Jazz. It handles: \--- End of react-native specific section --- \--- Section applies only to expo --- `` is the core component that connects your Expo application to Jazz. It handles: \--- End of expo specific section --- * **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 \--- Section applies only to react --- Our [Chat example app](https://jazz.tools/examples#chat) provides a complete implementation of JazzReactProvider with authentication and real-time data sync. \--- End of react specific section --- \--- Section applies only to svelte --- 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. \--- End of svelte specific section --- ## Setting up the Provider The provider accepts several configuration options: ##### React: ```tsx import { JazzReactProvider } from "jazz-tools/react"; import { MyAppAccount } from "./schema"; export function MyApp({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ##### Svelte: ```svelte {@render children()} ``` ##### React Native: ```tsx import { JazzReactNativeProvider } from "jazz-tools/react-native"; import { MyAppAccount } from "./schema"; export function MyJazzProvider({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ##### Expo: ```tsx import { JazzExpoProvider } from "jazz-tools/expo"; import { MyAppAccount } from "./schema"; export function MyJazzProvider({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` **Info: Tip** Sign up for a free API key at [dashboard.jazz.tools](https://dashboard.jazz.tools) for higher limits or production use, or use your email address as a temporary key to get started quickly. \--- Section applies only to vanilla --- **File name: .env** \--- End of vanilla specific section --- \--- Section applies only to react --- **File name: .env** \--- End of react specific section --- \--- Section applies only to svelte --- **File name: .env** \--- End of svelte specific section --- ##### Vanilla: ```bash VITE_JAZZ_API_KEY="you@example.com" # or your API key ``` ##### React: ```bash NEXT_PUBLIC_JAZZ_API_KEY="you@example.com" # or your API key ``` ##### Svelte: ```bash PUBLIC_JAZZ_API_KEY="you@example.com" # or your API key ``` ## Provider Options ### Sync Options The `sync` property configures how your application connects to the Jazz network: ```ts import { type SyncConfig } from "jazz-tools"; export const syncConfig: SyncConfig = { // Connection to Jazz Cloud or your own sync server peer: `wss://cloud.jazz.tools/?key=${apiKey}`, // When to sync: "always" (default), "never", or "signedUp" when: "always", }; ``` \--- Section applies only to react-native-expo --- **Warning: iOS Credential Persistence** 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). \--- End of react-native-expo specific section --- 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: ##### React: ```tsx import { JazzReactProvider } from "jazz-tools/react"; import { MyAppAccount } from "./schema"; export function MyApp({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ##### Svelte: ```svelte {@render children()} ``` ##### React Native: ```tsx import { JazzReactNativeProvider } from "jazz-tools/react-native"; import { MyAppAccount } from "./schema"; export function MyJazzProvider({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ##### Expo: ```tsx import { JazzExpoProvider } from "jazz-tools/expo"; import { MyAppAccount } from "./schema"; export function MyJazzProvider({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Additional Options The provider accepts these additional options: \--- Section applies only to react-native --- * `kvStore` * `MMKVStoreAdapter` (default) * `AccountSchema` * `Account` (default) \--- End of react-native specific section --- \--- Section applies only to react-native-expo --- * `kvStore` * `ExpoSecureStoreAdapter` (default) * `AccountSchema` * `Account` (default) \--- End of react-native-expo specific section --- \--- Section applies only to react,svelte --- ##### React: ```tsx export function MyApp({ children }: { children: React.ReactNode }) { return ( { console.log("User logged out"); }} // Handle anonymous account data when user logs in to existing account onAnonymousAccountDiscarded={(account) => { console.log("Anonymous account discarded", account.$jazz.id); // Migrate data here return Promise.resolve(); }} > {children} ); } ``` ##### Svelte: ```svelte {@render children()} ``` See [Authentication States](/docs/key-features/authentication/authentication-states) for more information on authentication states, guest mode, and handling anonymous accounts. \--- End of react,svelte specific section --- ## Authentication \--- Section applies only to react,svelte --- 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). \--- End of react,svelte specific section --- \--- Section applies only to react-native --- 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 ##### React Native: ```tsx import { JazzReactNativeProvider, usePassphraseAuth, } from "jazz-tools/react-native"; import { englishWordlist } from "./wordlist"; function JazzAuthentication({ children }: { children: ReactNode }) { const auth = usePassphraseAuth({ wordlist: englishWordlist, }); // If the user is already signed in, render the App if (auth.state === "signedIn") { return children; } // Otherwise, show a sign-in screen return ; } function AuthenticatedProvider({ children }: { children: ReactNode }) { return ( {children} ); } ``` ##### Expo: ```tsx import { JazzExpoProvider, usePassphraseAuth } from "jazz-tools/expo"; import { englishWordlist } from "./wordlist"; function JazzAuthentication({ children }: { children: ReactNode }) { const auth = usePassphraseAuth({ wordlist: englishWordlist, }); // If the user is already signed in, render the App if (auth.state === "signedIn") { return children; } // Otherwise, show a sign-in screen return ; } function AuthenticatedProvider({ children }: { children: ReactNode }) { return ( {children} ); } ``` ## Local Persistence \[!framework=react-native,react-native-expo\] \--- End of react-native specific section --- \--- Section applies only to react-native --- 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 \--- End of react-native specific section --- \--- Section applies only to react-native-expo --- 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 \--- End of react-native-expo specific section --- \--- Section applies only to react-native,react-native-expo --- Local persistence is enabled by default with no additional configuration required. Your data will automatically persist across app restarts. \--- End of react-native,react-native-expo specific section --- \--- Section applies only to react-native,react-native-expo --- ## RNCrypto \[!framework=react-native,react-native-expo\] \--- End of react-native,react-native-expo specific section --- \--- Section applies only to 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.** \--- End of react-native-expo specific section --- \--- Section applies only to react-native,react-native-expo --- 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 } ``` **Warning: Versioning** 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. \--- End of react-native,react-native-expo specific section --- ## 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. ### API Reference # 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 | **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. **File name: schema.ts** ```ts // 1. CoMaps: Object-like with fixed keys const ToDo = co.map({ task: z.string(), completed: z.boolean(), dueDate: z.date().optional(), }); // 2. CoRecords: Object-like with arbitrary string keys const PhoneBook = co.record(z.string(), z.string()); // co.record(keyType, valueType) // 3. CoLists: Array-like ordered list const ToDoList = co.list(ToDo); // co.list(itemType) // 4. CoFeeds: Array-like append-only list const Message = co.map({ text: z.string() }); const ChatMessages = co.feed(Message); // co.feed(itemType) // 5. CoPlainTexts/CoRichTexts: String-like const Description = co.plainText(); // or co.richText(); // 6. FileStreams: Blob-like const UploadedPDF = co.fileStream(); // 7. ImageDefinitions: Blob-like const UploadedImage = co.image(); // 8. CoVectors: Array-like list of numbers/Float32Array const Embedding = co.vector(384); // co.vector(dimensions) // 9. DiscriminatedUnions: Union of different types of items const ThisSchema = co.map({ type: z.literal("this"), thisProperty: z.string(), }); const ThatSchema = co.map({ type: z.literal("that"), thatProperty: z.string(), }); const MyThisOrThat = co.discriminatedUnion("type", [ThisSchema, ThatSchema]); // co.discriminatedUnion(discriminatorKey, arrayOfSchemas) ``` 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 | **Info: Tip** 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 }) | | **CoRecord** | PhoneBook.create({ "Jenny": "867-5309" }) | | **CoList** | TaskList.create(\[task1, task2\]) | | **CoPlainText/CoRichText** | Description.create("Hello World") | | **CoFeed** | ChatMessages.create(\[{ message: "Hello world!" }\]) | | **FileStream** | UploadedPDF.createFromBlob(myFile) | | **ImageDefinition** | 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_ | **Info: create vs. createFromBlob** `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 const Task = co.map({ title: z.string(), completed: z.boolean(), }); const TaskList = co.list(Task); const taskList = TaskList.create([ { title: "Task 1", completed: false }, // These will create new Task CoValues { title: "Task 2", completed: false }, // both will be inserted into the TaskList ]); ``` ### 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 const group = co.group().create(); const task = Task.create( { title: "Buy milk", completed: false }, { owner: group }, ); ``` 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 // CoMap: Access fixed keys console.log(user.name); // "Alice" // CoRecord: Access arbitrary keys const phone = phoneBook["Jenny"]; // Iteration works as with a TypeScript object for (const [name, number] of Object.entries(phoneBook)) { console.log(name, number); } ``` [Read more →](/docs/core-concepts/covalues/comaps) ### CoList Behaves like a TypeScript array **when reading**. ```ts const firstTask = taskList[0]; const length = taskList.length; // Iteration works as with a TypeScript array taskList.map((task) => console.log(task.title)); for (const task of taskList) { // Do something } ``` [Read more →](/docs/core-concepts/covalues/colists) ### CoPlainText/CoRichText Behaves like a TypeScript string **when reading**. ```ts // String operations const summary = description.substring(0, 100); ``` [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 // Get the feed for a specific session (e.g. this browser tab) const thisSessionsFeed = chatMessages.perSession[thisSessionId]; // or .inCurrentSession as shorthand const latestMessageFromThisSession = thisSessionsFeed.value; const allMessagesFromThisSession = thisSessionsFeed.all; // Get the feed for a specific account const accountFeed = chatMessages.perAccount[accountId]; const latestMessageFromThisAccount = accountFeed.value; const allMessagesFromThisAccount = accountFeed.all; // Get the feed for my account const myFeed = chatMessages.byMe; // shorthand for chatMessages.perAccount[myAccountId] const latestMessageFromMyAccount = myFeed?.value; const allMessagesFromMyAccount = myFeed?.all; // Iterate over all entries in a CoFeed for (const userId of Object.keys(chatMessages.perAccount)) { const accountFeed = chatMessages.perAccount[userId]; for (const entry of accountFeed.all) { if (entry.value.$isLoaded) { console.log(entry.value); } } } ``` 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 // Get raw data chunks and metadata. // Optionally pass { allowUnfinished: true } to get chunks of a FileStream which is not yet fully synced. const fileData = fileStream.getChunks({ allowUnfinished: true }); // Convert to a Blob for use in a tag or