# Jazz (react-native-expo) ## 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 ``` **Using an LLM?** [Add our llms.txt](/react-native-expo/llms-full.txt) to your context window! **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 **Note: Requires Node.js 20+** ## Install Jazz The `jazz-tools` package includes everything you're going to need to build your first Jazz app. ```sh npm install 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. ```bash VITE_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. ```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: [], }); } }); ``` ```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; } ``` ## Start your app Moment of truth — time to start your app and see if it works. ```bash npm run dev ``` ### Not loading? If you're not seeing the welcome page: **Info: Still stuck?** Ask for help on [Discord](https://discord.gg/utDMjHYg42)! ## Create data ```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); ``` ## Display your data Now we've got a way to create data, so let's add a component to display it. ```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); }); ``` ## Put it all together You've built all your components, time to put them together. ```tsx const app = document.querySelector('#app')!; app.append(form, bandList); ``` 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. ## 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 * **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 ## Setting up the Provider The provider accepts several configuration options: ```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. ```bash VITE_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", }; ``` **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). 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: ```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: * `kvStore` * `ExpoSecureStoreAdapter` (default) * `AccountSchema` * `Account` (default) ## Authentication 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 } ``` **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. ## 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. --- # 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 ``` **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. 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: **File name: app/\_layout.tsx** ```tsx // [!code ++:1] import "jazz-tools/expo/polyfills"; import { DarkTheme, DefaultTheme, ThemeProvider, } from "@react-navigation/native"; // ... ``` ## 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 ``` ### 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