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!
| TypeScript Type | Corresponding CoValue | Usage |
|---|---|---|
object | CoMap | Key-value stores with pre-defined keys (struct-like) |
Record<string, T> | CoRecord | Key-value stores with arbitrary string keys (dict-like) |
T[] | CoList | Lists |
T[] (append-only) | CoFeed | Session-based append-only lists |
string | CoPlainText/CoRichText | Collaborative text |
Blob | File | FileStream | Files |
Blob | File (image) | ImageDefinition | Images |
number[] | Float32Array | CoVector | Embeddings |
T | U (discriminated) | DiscriminatedUnion | Lists of different types of items |
Defining Schemas
CoValues are defined using schemas which combine CoValue types with Zod schemas. You can find out more about schemas in the schemas section of the overview guide.
// 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 |
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 accountco.profile()— a user profileco.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 is the preferred way to create an ImageDefinition |
| CoVector | Embedding.create([0.1, 0.2, ...]) |
| DiscriminatedUnion | MyThis.create({ type: "this", ... })note that you can only instantiate one of the schemas in the union |
FileStream CoValues can be created using the .create() method. This will create an empty FileStream for you to push chunks into (useful for advanced streaming cases). However, in many cases, using .createFromBlob(blobOrFile) to create a FileStream directly from a File or Blob will be more convenient.
Inline Creation
Where a schema has references, you can create nested CoValues one by one and attach them, but Jazz also allows you to create them inline by specifying their initial values.
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.
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.
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 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. 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.
// 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); }
CoList
Behaves like a TypeScript array when reading.
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 }
CoPlainText/CoRichText
Behaves like a TypeScript string when reading.
// String operations const summary = description.substring(0, 100);
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.
// 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.
CoFeed Structure
┌────────┐ │ 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.
// 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 <a> tag or <iframe> const fileBlob = fileStream.toBlob(); const fileUrl = fileBlob && URL.createObjectURL(fileBlob);
ImageDefinition
Although you can read an ImageDefinition as a CoMap, Jazz provides an <Image /> component for front-end frameworks which vastly simplifies their use.
If you want to use the ImageDefinition type without a framework, or you need access to the image itself you can use the loadImageBySize helper.
// Component-based usage (recommended) <Image image={productImage} width = {300} />// Imperative usage: Access the highest available resolution import { loadImageBySize } from "jazz-tools/media"; // Not guaranteed to exist if no variant exists that fulfills your constraints const imageDef = await loadImageBySize(productImage, 300, 400); // Takes either an ImageDefinition or an ID, and returns a FileStream.
Once you have a loaded image, you can convert it to a blob with the .toBlob() method, as you would with any other FileStream. You can then use this to create a URL to use as an <img> source.
const blob = imageDef && imageDef.image.toBlob(); const url = blob && URL.createObjectURL(blob); // Don't forget to clean this up when you're done! myImg.src = url ?? "";
CoVector
You can read a CoVector in the same way as you'd read an array, but in most cases, you'll want to use the cosineSimilarity helper.
// Calculate similarity between two vectors const similarity = myEmbedding.$jazz.cosineSimilarity(targetVector);
DiscriminatedUnion
After loading a DiscriminatedUnion, you will be able to read any property that is common to all members, otherwise, you will need to narrow the type to be able to access properties which are only on some subset of members of the union.
// Use the discriminator to check the type if (item.type === "task") { console.log(item.title); } else if (item.type === "note") { console.log(item.content); }
Updating CoValues
Most CoValues are updated using mutation methods in the .$jazz namespace. Some CoValues, such as CoPlainText and CoRichText, have methods available directly on the instance itself.
Updating CoMaps (and the CoRecord sub-type)
CoRecords will allow you to arbitrarily set and delete keys, while CoMaps will only allow you to set and delete keys defined in the schema.
.$jazz.set(key, value): void.$jazz.delete(key): void.$jazz.applyDiff(diff): void
// Set or update a property todo.$jazz.set("task", "Try out Jazz"); // Delete an optional property todo.$jazz.delete("dueDate"); // Update multiple properties at once todo.$jazz.applyDiff({ task: "Apply a diff to update a task", completed: true, });
Updating CoLists
CoLists implement most of the usual array mutation methods under the .$jazz namespace (except sort and reverse).
.$jazz.push(...items: T[]): number.$jazz.unshift(...items: T[]): number.$jazz.pop(): T | undefined.$jazz.shift(): T | undefined.$jazz.remove(...indexes: number[]): T[].$jazz.remove(predicate: (item: T, index: number, coList: L) => boolean): T[]- Removes elements by index(es) or predicate. Returns the removed elements.
.$jazz.retain(predicate: (item: T, index: number, coList: L) => boolean): T[]- Retains only elements matching the predicate. Returns the removed elements.
.$jazz.splice(start: number, deleteCount: number, ...items: T[]): T[].$jazz.applyDiff(result: T[]): L- Updates the list to match another list, efficiently calculated
// Add items tasks.$jazz.push(newTask); tasks.$jazz.unshift(importantTask); // Remove items const removed = tasks.$jazz.remove(0); // Remove by index, returns removed items tasks.$jazz.remove((task) => task.completed); // Remove by predicate const lastTask = tasks.$jazz.pop(); // Remove and return last item const task1 = tasks.$jazz.shift(); // Remove and return first item // Retain only matching items tasks.$jazz.retain((task) => !task.completed); // Keep only incomplete tasks // Replace/Move tasks.$jazz.splice(1, 1, replacementTask); if (!task1?.$isLoaded) throw new Error(); // [!code hide] // Efficiently update to match another list tasks.$jazz.applyDiff([task1, task2, task3]); // Updates list to match exactly
Updating CoPlainTexts/CoRichTexts
CoPlainText and CoRichText methods are mostly available directly on the instance.
insertAfter(after: number, text: string): voidinsertBefore(before: number, text: string): voiddeleteRange(range: { from: number, to: number }): void.$jazz.applyDiff(newText: string): void
const message = co.plainText().create("Hello world!"); // Hello world message.insertAfter(4, ","); // Hello, world! message.insertBefore(7, "everybody in the "); // Hello, everybody in the world! message.deleteRange({ from: 16, to: 29 }); // Hello, everybody! // Update efficiently message.$jazz.applyDiff("Hello, my Jazzy friends!"); // 'Hello, ' has not changed and will not be updated// Use directly with templating languages (e.g. React, Svelte) <span>{message}</span>
Updating CoFeeds
CoFeeds are append-only. The only mutation you can perform is push() (in the .$jazz namespace), which adds a new entry to the feed.
feed.$jazz.push(newMessage);
Updating FileStreams
If you wish to push binary data into an existing FileStream, you can start it with metadata, then push chunks of data, and finally end it.
start(metadata: { mimeType: string; fileName?: string; totalSizeBytes?: number; }): voidpush(chunk: Uint8Array): voidend(): void
const myUploadedPDF = UploadedPDF.create(); // Advanced usage: manual chunk streaming myUploadedPDF.start({ mimeType: "application/pdf" }); myUploadedPDF.push(chunks); // Uint8Array myUploadedPDF.end();
Updating ImageDefinitions
If you would like to update an ImageDefinition, you will need to create a new FileStream and set it on the ImageDefinition. The ImageDefinition behaves like a CoRecord with keys for each image resolution as <width>x<height>, and the values are FileStream instances. You can replace resolutions or add new ones.
const w = 800; const h = 600; const imageFile = await co.fileStream().createFromBlob(some800x600Blob); myUploadedImage.$jazz.set(`${w}x${h}`, imageFile);
Updating CoVectors
CoVectors are immutable. You cannot update them. You should create a new CoVector to replace the old one.
Updating DiscriminatedUnions
When updating a discriminated union, you should first narrow the type using the discriminator. After that, you'll be able to update it using the $jazz.set, $jazz.applyDiff, or $jazz.delete methods.
const myLoadedThisOrThat = await MyThisOrThat.load("co_z..."); if (myLoadedThisOrThat.$isLoaded && myLoadedThisOrThat.type === "this") { myLoadedThisOrThat.$jazz.set("thisProperty", "Only available on 'this'!"); } else if (myLoadedThisOrThat.$isLoaded && myLoadedThisOrThat.type === "that") { myLoadedThisOrThat.$jazz.set("thatProperty", "Only available on 'that'!"); }
Shared Properties and Methods
Universal CoValue Interface
These properties and methods are available directly on every CoValue instance.
| Feature | Property/Method | Description |
|---|---|---|
| Loading | $isLoaded | Check if the CoValue is fully loaded and ready to read. |
| Inspection | .toJSON() | Get a plain JS representation of the CoValue. |
Common $jazz Interface
Every CoValue instance has a .$jazz namespace with the following common utilities (some are only available on specific CoValue types).
| Feature | Property/Method | Description |
|---|---|---|
| Basics | id, owner | Key metadata. |
| Life-cycle | createdAt, lastUpdatedAt, createdBy | Audit trails and creation timestamps. |
| Reactivity | subscribe(), waitForSync() | Listen for changes or wait for network persistence. |
| Loading | loadingState, ensureLoaded() | Check loading state or load a copy of the CoValue with a different resolution depth. |
| Inspection | refs, has | Available on CoMaps, check for the existence of references/specific keys. |
| Version Control | isBranched, branchName, unstable_merge() | Utilities for local branching and merging. |
Metadata
.$jazz.id:ID<CoValue>- The definitive, globally unique ID (e.g.,
co_zXy...).
- The definitive, globally unique ID (e.g.,
.$jazz.owner:Group- The
groupthat this CoValue is owned by.
- The
.$jazz.createdAt:number- The time that this CoValue was created (in milliseconds since the Unix epoch).
.$jazz.createdBy:ID<Account> | undefined- The ID of the account that originally created this CoValue (
undefinedfor accounts themselves).
- The ID of the account that originally created this CoValue (
.$jazz.lastUpdatedAt:number- The time that this CoValue was last updated (in milliseconds since the Unix epoch).
.$jazz.refs:Record<key, Ref> | undefined(CoMap/CoRecord only)- Access nested CoValues as references (with IDs) without loading them. Useful for checking if a reference exists or getting its ID without triggering a network fetch.
// Check if a reference exists without loading it if (person.$jazz.refs.pet) { console.log("Pet ID:", person.$jazz.refs.pet.id); }.$jazz.has(key): boolean(CoMap/CoRecord only)- Checks if a key exists in the CoValue.
Loading
$isLoaded:boolean- True if the CoValue is fully loaded and ready to read.
.$jazz.loadingState:"loading" | "loaded" | "unavailable" | "unauthorized"- Current state of the CoValue. See Loading States for details on handling each case.
.$jazz.ensureLoaded<T>(resolveQuery: { resolve: ResolveQuery<T> }): Promise<Resolved<T>>- Waits for nested references to load to the specified depth.
- Returns: A new typed instance
Resolved<T>where specified properties are guaranteed to be fully loaded.
Note: When callingconst profile = await shallowProfile.$jazz.ensureLoaded({ resolve: { avatar: true, }, }); console.log(profile.avatar); // Safe to access.$jazz.ensureLoaded()on a discriminated union, you must first narrow the type using the discriminator property. If any nested reference cannot be loaded, the entire operation may fail (see Loading Errors).Mental ModelensureLoadedreturns a copy of the CoValue where the specific fields you requested in the resolve query are guaranteed to be present and non-null.
Subscription & Sync
.$jazz.subscribe(listener: (updated: this) => void): () => void- Listen for any changes to this CoValue. Only needed if you are manually handling your subscriptions.
- Returns an
unsubscribefunction for manual teardown. - For error handling in manual subscriptions, see Manual Subscriptions.
const unsub = person.$jazz.subscribe((updatedPerson) => { console.log("Person updated:", updatedPerson); });.$jazz.waitForSync():Promise<void>- Waits until changes are synced to other peers (useful for tests or critical saves).
Version Control
.$jazz.branchName:string | undefined- If this CoValue is branched, the name of the branch, otherwise
undefined.
- If this CoValue is branched, the name of the branch, otherwise
.$jazz.isBranched:boolean- Whether this CoValue is branched.
.$jazz.unstable_merge():void- Merges this branched CoValue back into the main branch. Only works when the CoValue is branched.