# Jazz (react-native) ## 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/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 `` is the core component that connects your React Native 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 ## Setting up the Provider The provider accepts several configuration options: ```tsx import { JazzReactNativeProvider } from "jazz-tools/react-native"; 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", }; ``` 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 { JazzReactNativeProvider } from "jazz-tools/react-native"; import { MyAppAccount } from "./schema"; export function MyJazzProvider({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Additional Options The provider accepts these additional options: * `kvStore` * `MMKVStoreAdapter` (default) * `AccountSchema` * `Account` (default) ## Authentication 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 ```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} ); } ``` ## 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 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\] 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 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. **Info: Note** 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 } ``` **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. ### 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 import { AppRegistry } from 'react-native'; import App from './App'; import { name as appName } from './app.json'; // [!code ++:1] import 'jazz-tools/react-native/polyfills'; AppRegistry.registerComponent(appName, () => App); ``` ## 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 ``` ### 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