Accounts & Migrations

CoValues as a graph of data rooted in accounts

Compared to traditional relational databases with tables and foreign keys, Jazz is more like a graph database, or GraphQL APIs — where CoValues can arbitrarily refer to each other and you can resolve references without having to do a join. (See Subscribing & deep loading).

To find all data related to a user, the account acts as a root node from where you can resolve all the data they have access to. These root references are modeled explicitly in your schema, distinguishing between data that is typically public (like a user's profile) and data that is private (like their messages).

Account.root - private data a user cares about

Every Jazz app that wants to refer to per-user data needs to define a custom root CoMap schema and declare it in a custom Account schema as the root field:

import { import coco, import zz } from "jazz-tools";

const 
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
MyAppRoot
= import coco.
map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}>(shape: {
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}): co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
export map
map
({
myChats: co.List<co.Map<{}, unknown, Account | Group>>myChats: import coco.
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>>
export list
list
(const Chat: co.Map<{}, unknown, Account | Group>Chat),
}); export const
const MyAppAccount: co.Account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<BaseProfileShape>;
}>
MyAppAccount
= import coco.
account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<BaseProfileShape>;
}>(shape?: {
    ...;
} | undefined): co.Account<...>
export account
Defines a collaborative account schema for Jazz applications. Creates an account schema that represents a user account with profile and root data. Accounts are the primary way to identify and manage users in Jazz applications.
@templateShape - The shape of the account schema extending BaseAccountShape@paramshape - The account schema shape. Defaults to a basic profile with name, inbox, and inboxInvite fields, plus an empty root object.@example```typescript // Basic account with default profile const BasicAccount = co.account(); // Custom account with specific profile and root structure const JazzAccount = co.account({ profile: co.profile({ name: z.string(), avatar: z.optional(z.string()), }), root: co.map({ organizations: co.list(Organization), draftOrganization: DraftOrganization, }), }).withMigration(async (account) => { // Migration logic for existing accounts if (!account.$jazz.has("profile")) { const group = Group.create(); account.$jazz.set("profile", co.profile().create( { name: getRandomUsername() }, group )); group.addMember("everyone", "reader"); } }); ```
account
({
root: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
root
:
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
MyAppRoot
,
profile: co.Profile<BaseProfileShape>profile: import coco.
profile<BaseProfileShape>(shape?: (BaseProfileShape & Partial<DefaultProfileShape>) | undefined): co.Profile<BaseProfileShape>
export profile
profile
(),
});

Account.profile - public data associated with a user

The built-in Account schema class comes with a default profile field, which is a CoMap (in a Group with "everyone": "reader" - so publicly readable permissions) that is set up for you based on the username the AuthMethod provides on account creation.

Their pre-defined schemas roughly look like this:

// ...somewhere in jazz-tools itself...
const 
const Account: co.Account<{
    root: co.Map<{}, unknown, Account | Group>;
    profile: co.Profile<BaseProfileShape>;
}>
Account
= import coco.
account<{
    root: co.Map<{}, unknown, Account | Group>;
    profile: co.Profile<BaseProfileShape>;
}>(shape?: {
    root: co.Map<{}, unknown, Account | Group>;
    profile: co.Profile<...>;
} | undefined): co.Account<...>
export account
Defines a collaborative account schema for Jazz applications. Creates an account schema that represents a user account with profile and root data. Accounts are the primary way to identify and manage users in Jazz applications.
@templateShape - The shape of the account schema extending BaseAccountShape@paramshape - The account schema shape. Defaults to a basic profile with name, inbox, and inboxInvite fields, plus an empty root object.@example```typescript // Basic account with default profile const BasicAccount = co.account(); // Custom account with specific profile and root structure const JazzAccount = co.account({ profile: co.profile({ name: z.string(), avatar: z.optional(z.string()), }), root: co.map({ organizations: co.list(Organization), draftOrganization: DraftOrganization, }), }).withMigration(async (account) => { // Migration logic for existing accounts if (!account.$jazz.has("profile")) { const group = Group.create(); account.$jazz.set("profile", co.profile().create( { name: getRandomUsername() }, group )); group.addMember("everyone", "reader"); } }); ```
account
({
root: co.Map<{}, unknown, Account | Group>root: import coco.
map<{}>(shape: {}): co.Map<{}, unknown, Account | Group>
export map
map
({}),
profile: co.Profile<BaseProfileShape>profile: import coco.
profile<BaseProfileShape>(shape?: (BaseProfileShape & Partial<DefaultProfileShape>) | undefined): co.Profile<BaseProfileShape>
export profile
profile
(),
});

If you want to keep the default co.profile() schema, but customise your account's private root, all you have to do is define a new root field in your account schema and use co.profile() without options:

const 
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
MyAppRoot
= import coco.
map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}>(shape: {
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}): co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
export map
map
({
myChats: co.List<co.Map<{}, unknown, Account | Group>>myChats: import coco.
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>>
export list
list
(const Chat: co.Map<{}, unknown, Account | Group>Chat),
}); export const
const MyAppAccount: co.Account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<BaseProfileShape>;
}>
MyAppAccount
= import coco.
account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<BaseProfileShape>;
}>(shape?: {
    ...;
} | undefined): co.Account<...>
export account
Defines a collaborative account schema for Jazz applications. Creates an account schema that represents a user account with profile and root data. Accounts are the primary way to identify and manage users in Jazz applications.
@templateShape - The shape of the account schema extending BaseAccountShape@paramshape - The account schema shape. Defaults to a basic profile with name, inbox, and inboxInvite fields, plus an empty root object.@example```typescript // Basic account with default profile const BasicAccount = co.account(); // Custom account with specific profile and root structure const JazzAccount = co.account({ profile: co.profile({ name: z.string(), avatar: z.optional(z.string()), }), root: co.map({ organizations: co.list(Organization), draftOrganization: DraftOrganization, }), }).withMigration(async (account) => { // Migration logic for existing accounts if (!account.$jazz.has("profile")) { const group = Group.create(); account.$jazz.set("profile", co.profile().create( { name: getRandomUsername() }, group )); group.addMember("everyone", "reader"); } }); ```
account
({
root: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
root
:
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
MyAppRoot
,
profile: co.Profile<BaseProfileShape>profile: import coco.
profile<BaseProfileShape>(shape?: (BaseProfileShape & Partial<DefaultProfileShape>) | undefined): co.Profile<BaseProfileShape>
export profile
profile
(),
});

If you want to extend the profile to contain additional fields (such as an avatar co.image()), you can declare your own profile schema class using co.profile({...}):

export const 
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
MyAppRoot
= import coco.
map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}>(shape: {
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}): co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
export map
map
({
myChats: co.List<co.Map<{}, unknown, Account | Group>>myChats: import coco.
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>>
export list
list
(const Chat: co.Map<{}, unknown, Account | Group>Chat),
}); export const
const MyAppProfile: co.Profile<{
    name: z.z.ZodString;
    avatar: co.Optional<co.Map<{
        original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema;
        originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>;
        placeholderDataURL: z.ZodOptional<z.z.ZodString>;
        progressive: z.z.ZodBoolean;
    }, co.FileStream, Account | Group>>;
}>
MyAppProfile
= import coco.
profile<{
    name: z.z.ZodString;
    avatar: co.Optional<co.Map<{
        original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema;
        originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>;
        placeholderDataURL: z.ZodOptional<z.z.ZodString>;
        progressive: z.z.ZodBoolean;
    }, co.FileStream, Account | Group>>;
}>(shape?: ({
    ...;
} & Partial<...>) | undefined): co.Profile<...>
export profile
profile
({
name: z.z.ZodString & z.z.core.$ZodString<string>name: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload)
export string
string
(), // compatible with default Profile schema
avatar: co.Optional<co.Map<{
    original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema;
    originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>;
    placeholderDataURL: z.ZodOptional<z.z.ZodString>;
    progressive: z.z.ZodBoolean;
}, co.FileStream, Account | Group>>
avatar
: import coco.
optional<co.Map<{
    original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema;
    originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>;
    placeholderDataURL: z.ZodOptional<z.z.ZodString>;
    progressive: z.z.ZodBoolean;
}, co.FileStream, Account | Group>>(schema: co.Map<...>): co.Optional<...>
export optional
optional
(import coco.
function image(): co.Image
export image
image
()),
}); export const
const MyAppAccount: co.Account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<{
        name: z.z.ZodString;
        avatar: co.Optional<...>;
    }>;
}>
MyAppAccount
= import coco.
account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<{
        name: z.z.ZodString;
        avatar: co.Optional<...>;
    }>;
}>(shape?: {
    ...;
} | undefined): co.Account<...>
export account
Defines a collaborative account schema for Jazz applications. Creates an account schema that represents a user account with profile and root data. Accounts are the primary way to identify and manage users in Jazz applications.
@templateShape - The shape of the account schema extending BaseAccountShape@paramshape - The account schema shape. Defaults to a basic profile with name, inbox, and inboxInvite fields, plus an empty root object.@example```typescript // Basic account with default profile const BasicAccount = co.account(); // Custom account with specific profile and root structure const JazzAccount = co.account({ profile: co.profile({ name: z.string(), avatar: z.optional(z.string()), }), root: co.map({ organizations: co.list(Organization), draftOrganization: DraftOrganization, }), }).withMigration(async (account) => { // Migration logic for existing accounts if (!account.$jazz.has("profile")) { const group = Group.create(); account.$jazz.set("profile", co.profile().create( { name: getRandomUsername() }, group )); group.addMember("everyone", "reader"); } }); ```
account
({
root: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
root
:
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
MyAppRoot
,
profile: co.Profile<{
    name: z.z.ZodString;
    avatar: co.Optional<co.Map<{
        original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema;
        originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>;
        placeholderDataURL: z.ZodOptional<z.z.ZodString>;
        progressive: z.z.ZodBoolean;
    }, co.FileStream, Account | Group>>;
}>
profile
:
const MyAppProfile: co.Profile<{
    name: z.z.ZodString;
    avatar: co.Optional<co.Map<{
        original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema;
        originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>;
        placeholderDataURL: z.ZodOptional<z.z.ZodString>;
        progressive: z.z.ZodBoolean;
    }, co.FileStream, Account | Group>>;
}>
MyAppProfile
,
});

When using custom profile schemas, you need to take care of initializing the profile field in a migration, and set up the correct permissions for it. See Adding/changing fields to root and profile.

Resolving CoValues starting at profile or root

To use per-user data in your app, you typically use useAccount somewhere in a high-level component, pass it your custom Account schema and specify which references to resolve using a resolve query (see Subscribing & deep loading).

import { 
function useAccount<A extends AccountClass<Account> | CoreAccountSchema, R extends ResolveQuery<A> = true>(AccountSchema?: A, options?: {
    resolve?: ResolveQueryStrict<A, R>;
}): {
    me: co.loaded<A, R> | undefined | null;
    agent: AnonymousJazzAgent | co.loaded<A, true>;
    logOut: () => void;
}
React hook for accessing the current user's account and authentication state. This hook provides access to the current user's account profile and root data, along with authentication utilities. It automatically handles subscription to the user's account data and provides a logout function.
@returnsAn object containing: - `me`: The loaded account data, or `undefined` if loading, or `null` if not authenticated - `agent`: The current agent (anonymous or authenticated user). Can be used as `loadAs` parameter for load and subscribe methods. - `logOut`: Function to log out the current user@example```tsx // Deep loading with resolve queries function ProjectListWithDetails() { const { me } = useAccount(MyAppAccount, { resolve: { profile: true, root: { myProjects: { $each: { tasks: true, }, }, }, }, }); if (!me) { return me === null ? <div>Failed to load your projects</div> : <div>Loading...</div>; } return ( <div> <h1>{me.profile.name}'s projects</h1> <ul> {me.root.myProjects.map((project) => ( <li key={project.id}> {project.name} ({project.tasks.length} tasks) </li> ))} </ul> </div> ); } ```
useAccount
} from "jazz-tools/react";
function function DashboardPageComponent(): React.JSX.ElementDashboardPageComponent() { const {
const me: CoMapLikeLoaded<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account, {
    ...;
}, 10, []> | null | undefined
me
} =
useAccount<co.Account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<{
        name: z.z.core.$ZodString<string>;
        inbox: z.z.core.$ZodOptional<z.z.core.$ZodString>;
        inboxInvite: z.z.core.$ZodOptional<z.z.core.$ZodString>;
    }>;
}>, {
    ...;
}>(AccountSchema?: co.Account<...> | undefined, options?: {
    ...;
} | undefined): {
    ...;
}
React hook for accessing the current user's account and authentication state. This hook provides access to the current user's account profile and root data, along with authentication utilities. It automatically handles subscription to the user's account data and provides a logout function.
@returnsAn object containing: - `me`: The loaded account data, or `undefined` if loading, or `null` if not authenticated - `agent`: The current agent (anonymous or authenticated user). Can be used as `loadAs` parameter for load and subscribe methods. - `logOut`: Function to log out the current user@example```tsx // Deep loading with resolve queries function ProjectListWithDetails() { const { me } = useAccount(MyAppAccount, { resolve: { profile: true, root: { myProjects: { $each: { tasks: true, }, }, }, }, }); if (!me) { return me === null ? <div>Failed to load your projects</div> : <div>Loading...</div>; } return ( <div> <h1>{me.profile.name}'s projects</h1> <ul> {me.root.myProjects.map((project) => ( <li key={project.id}> {project.name} ({project.tasks.length} tasks) </li> ))} </ul> </div> ); } ```
useAccount
(
const MyAppAccount: co.Account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<{
        name: z.z.core.$ZodString<string>;
        inbox: z.z.core.$ZodOptional<z.z.core.$ZodString>;
        inboxInvite: z.z.core.$ZodOptional<z.z.core.$ZodString>;
    }>;
}>
MyAppAccount
, {
resolve?: RefsToResolve<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account, 10, []> | undefined
Resolve query to specify which nested CoValues to load from the account
resolve
: {
profile?: RefsToResolve<{
    readonly name: string;
    readonly inbox: string | undefined;
    readonly inboxInvite: string | undefined;
} & CoMap & Profile, 10, [0]> | undefined
profile
: true,
root?: RefsToResolve<{
    readonly myChats: CoList<({} & CoMap) | null> | null;
} & CoMap, 10, [0]> | undefined
root
: {
myChats?: RefsToResolve<CoList<({} & CoMap) | null>, 10, [0, 0]> | undefinedmyChats: { $each?: RefsToResolve<{} & CoMap, 10, [0, 0, 0]> | undefined$each: true }, } }}); return ( <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> <React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>Dashboard</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1> {
const me: CoMapLikeLoaded<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account, {
    ...;
}, 10, []> | null | undefined
me
? (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Logged in as {
const me: CoMapLikeLoaded<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account, {
    ...;
}, 10, []>
me
.
Account.profile: {
    readonly name: string;
    readonly inbox: string | undefined;
    readonly inboxInvite: string | undefined;
} & CoMap & Profile
profile
.Profile.name: stringname}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>My chats</React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2> {
const me: CoMapLikeLoaded<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account, {
    ...;
}, 10, []>
me
.
Account.root: {
    readonly myChats: readonly ({} & CoMap)[] & CoList<({} & CoMap) | null>;
} & {
    readonly myChats: CoList<({} & CoMap) | null> | null;
} & CoMap
root
.myChats: readonly ({} & CoMap)[] & CoList<({} & CoMap) | null>myChats.map<React.JSX.Element>(callbackfn: (value: {} & CoMap, index: number, array: readonly ({} & CoMap)[]) => React.JSX.Element, thisArg?: any): React.JSX.Element[] (+1 overload)
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((chat: {} & CoMapchat) => (
<class ChatPreviewChatPreview React.Attributes.key?: React.Key | null | undefinedkey={chat: {} & CoMapchat.CoMap.$jazz: CoMapJazzApi<{} & CoMap>
Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.
$jazz
.CoMapJazzApi<M extends CoMap>.id: string
The ID of this `CoMap`
@categoryContent
id
} chat: {} & CoMapchat={chat: {} & CoMapchat} />
))} </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> ) : ( "Loading..." )} </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> ); }

Populating and evolving root and profile schemas with migrations

As you develop your app, you'll likely want to

  • initialise data in a user's root and profile
  • add more data to your root and profile schemas

You can achieve both by overriding the migrate() method on your Account schema class.

When migrations run

Migrations are run after account creation and every time a user logs in. Jazz waits for the migration to finish before passing the account to your app's context.

Initialising user data after account creation

export const 
const MyAppAccount: co.Account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<{
        name: z.z.ZodString;
        bookmarks: co.List<...>;
    }>;
}>
MyAppAccount
= import coco.
account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    }, unknown, Account | Group>;
    profile: co.Profile<{
        name: z.z.ZodString;
        bookmarks: co.List<...>;
    }>;
}>(shape?: {
    ...;
} | undefined): co.Account<...>
export account
Defines a collaborative account schema for Jazz applications. Creates an account schema that represents a user account with profile and root data. Accounts are the primary way to identify and manage users in Jazz applications.
@templateShape - The shape of the account schema extending BaseAccountShape@paramshape - The account schema shape. Defaults to a basic profile with name, inbox, and inboxInvite fields, plus an empty root object.@example```typescript // Basic account with default profile const BasicAccount = co.account(); // Custom account with specific profile and root structure const JazzAccount = co.account({ profile: co.profile({ name: z.string(), avatar: z.optional(z.string()), }), root: co.map({ organizations: co.list(Organization), draftOrganization: DraftOrganization, }), }).withMigration(async (account) => { // Migration logic for existing accounts if (!account.$jazz.has("profile")) { const group = Group.create(); account.$jazz.set("profile", co.profile().create( { name: getRandomUsername() }, group )); group.addMember("everyone", "reader"); } }); ```
account
({
root: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
root
:
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
}, unknown, Account | Group>
MyAppRoot
,
profile: co.Profile<{
    name: z.z.ZodString;
    bookmarks: co.List<co.Map<{}, unknown, Account | Group>>;
}>
profile
:
const MyAppProfile: co.Profile<{
    name: z.z.ZodString;
    bookmarks: co.List<co.Map<{}, unknown, Account | Group>>;
}>
MyAppProfile
,
}).
AccountSchema<{ root: CoMapSchema<{ myChats: CoListSchema<CoMapSchema<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: CoProfileSchema<...>; }>.withMigration(migration: (account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account, creationProps?: {
    name: string;
}) => void): co.Account<...>
withMigration
((
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account
account
,
creationProps: {
    name: string;
} | undefined
creationProps
?: { name: stringname: string }) => {
// we use has to check if the root has ever been set if (!
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account
account
.
Account.$jazz: AccountJazzApi<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.
$jazz
.AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account>.has(key: "root" | "profile"): booleanhas("root")) {
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account
account
.
Account.$jazz: AccountJazzApi<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.
$jazz
.
AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account>.set<"root">(key: "root", value: ({
    readonly myChats: CoList<({} & CoMap) | null> | null;
} & CoMap) | CoMapInit<{
    readonly myChats: CoList<({} & CoMap) | null> | null;
} & CoMap>): void
Set the value of a key in the account.
@paramkey The key to set.@paramvalue The value to set.@categoryContent
set
("root", {
myChats: never[]myChats: [], }); } if (!
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account
account
.
Account.$jazz: AccountJazzApi<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.
$jazz
.AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account>.has(key: "root" | "profile"): booleanhas("profile")) {
const const profileGroup: GroupprofileGroup = class Group
@categoryIdentity & Permissions
Group
.
Group.create<Group>(this: CoValueClass<Group>, options?: {
    owner: Account;
} | Account): Group
create
();
// Unlike the root, we want the profile to be publicly readable. const profileGroup: GroupprofileGroup.Group.makePublic(role?: "reader" | "writer"): Group
Make the group public, so that everyone can read it. Alias for `addMember("everyone", role)`.
@paramrole - Optional: the role to grant to everyone. Defaults to "reader".@returnsThe group itself.
makePublic
();
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account
account
.
Account.$jazz: AccountJazzApi<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
    } & CoMap) | null;
    readonly profile: ({
        readonly name: string;
        readonly bookmarks: CoList<({} & CoMap) | null> | null;
        readonly inbox: string | undefined;
        readonly inboxInvite: string | undefined;
    } & CoMap) | null;
} & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.
$jazz
.
AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account>.set<"profile">(key: "profile", value: ({
    readonly name: string;
    readonly bookmarks: CoList<({} & CoMap) | null> | null;
    readonly inbox: string | undefined;
    readonly inboxInvite: string | undefined;
} & CoMap & Profile) | CoMapInit<...>): void
Set the value of a key in the account.
@paramkey The key to set.@paramvalue The value to set.@categoryContent
set
(
"profile",
const MyAppProfile: co.Profile<{
    name: z.z.ZodString;
    bookmarks: co.List<co.Map<{}, unknown, Account | Group>>;
}>
MyAppProfile
.
CoMapSchema<{ name: ZodString; bookmarks: CoListSchema<CoMapSchema<{}, unknown, Account | Group>>; } & DefaultProfileShape, unknown, Group>.create(init: {
    name: string;
    bookmarks: CoList<({} & CoMap) | null> | readonly (({} & CoMap) | {})[];
    inbox?: string | undefined;
    inboxInvite?: string | undefined;
}, options?: {
    owner?: Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Group): {
    ...;
} & CoMap (+1 overload)
create
({
name: stringname:
creationProps: {
    name: string;
} | undefined
creationProps
?.name: string | undefinedname ?? "New user",
bookmarks: CoList<({} & CoMap) | null> | readonly (({} & CoMap) | {})[]bookmarks: import coco.
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>>
export list
list
(const Bookmark: co.Map<{}, unknown, Account | Group>Bookmark).
CoListSchema<CoMapSchema<{}, unknown, Account | Group>>.create(items: readonly (({} & CoMap) | {})[], options?: {
    owner: Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Group): CoListInstance<...> (+1 overload)
create
([], const profileGroup: GroupprofileGroup),
}, const profileGroup: GroupprofileGroup), ); } });

Adding/changing fields to root and profile

To add new fields to your root or profile schemas, amend their corresponding schema classes with new fields, and then implement a migration that will populate the new fields for existing users (by using initial data, or by using existing data from old fields).

To do deeply nested migrations, you might need to use the asynchronous $jazz.ensureLoaded() method before determining whether the field already exists, or is simply not loaded yet.

Now let's say we want to add a myBookmarks field to the root schema:

const 
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>;
}, unknown, Account | Group>
MyAppRoot
= import coco.
map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>;
}>(shape: {
    ...;
}): co.Map<...>
export map
map
({
myChats: co.List<co.Map<{}, unknown, Account | Group>>myChats: import coco.
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>>
export list
list
(const Chat: co.Map<{}, unknown, Account | Group>Chat),
myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>myBookmarks: import coco.
optional<co.List<co.Map<{}, unknown, Account | Group>>>(schema: co.List<co.Map<{}, unknown, Account | Group>>): co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>
export optional
optional
(import coco.
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>>
export list
list
(const Bookmark: co.Map<{}, unknown, Account | Group>Bookmark)),
}); export const
const MyAppAccount: co.Account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
        myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>;
    }, unknown, Account | Group>;
    profile: co.Profile<...>;
}>
MyAppAccount
= import coco.
account<{
    root: co.Map<{
        myChats: co.List<co.Map<{}, unknown, Account | Group>>;
        myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>;
    }, unknown, Account | Group>;
    profile: co.Profile<...>;
}>(shape?: {
    ...;
} | undefined): co.Account<...>
export account
Defines a collaborative account schema for Jazz applications. Creates an account schema that represents a user account with profile and root data. Accounts are the primary way to identify and manage users in Jazz applications.
@templateShape - The shape of the account schema extending BaseAccountShape@paramshape - The account schema shape. Defaults to a basic profile with name, inbox, and inboxInvite fields, plus an empty root object.@example```typescript // Basic account with default profile const BasicAccount = co.account(); // Custom account with specific profile and root structure const JazzAccount = co.account({ profile: co.profile({ name: z.string(), avatar: z.optional(z.string()), }), root: co.map({ organizations: co.list(Organization), draftOrganization: DraftOrganization, }), }).withMigration(async (account) => { // Migration logic for existing accounts if (!account.$jazz.has("profile")) { const group = Group.create(); account.$jazz.set("profile", co.profile().create( { name: getRandomUsername() }, group )); group.addMember("everyone", "reader"); } }); ```
account
({
root: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>;
}, unknown, Account | Group>
root
:
const MyAppRoot: co.Map<{
    myChats: co.List<co.Map<{}, unknown, Account | Group>>;
    myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>;
}, unknown, Account | Group>
MyAppRoot
,
profile: co.Profile<{
    name: z.z.ZodString;
    bookmarks: co.List<co.Map<{}, unknown, Account | Group>>;
}>
profile
:
const MyAppProfile: co.Profile<{
    name: z.z.ZodString;
    bookmarks: co.List<co.Map<{}, unknown, Account | Group>>;
}>
MyAppProfile
,
}).
AccountSchema<{ root: CoMapSchema<{ myChats: CoListSchema<CoMapSchema<{}, unknown, Account | Group>>; myBookmarks: CoOptionalSchema<CoListSchema<CoMapSchema<{}, unknown, Account | Group>>>; }, unknown, Account | Group>; profile: CoProfileSchema<...>; }>.withMigration(migration: (account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account, creationProps?: {
    name: string;
}) => void): co.Account<...>
withMigration
(async (
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account
account
) => {
if (!
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account
account
.
Account.$jazz: AccountJazzApi<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.
$jazz
.AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>.has(key: "root" | "profile"): booleanhas("root")) {
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account
account
.
Account.$jazz: AccountJazzApi<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.
$jazz
.
AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>.set<"root">(key: "root", value: ({
    readonly myChats: CoList<({} & CoMap) | null> | null;
    readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
} & CoMap) | CoMapInit<...>): void
Set the value of a key in the account.
@paramkey The key to set.@paramvalue The value to set.@categoryContent
set
("root", {
myChats: never[]myChats: [], }); } // We need to load the root field to check for the myContacts field const {
const root: {
    readonly myChats: CoList<({} & CoMap) | null> | null;
    readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
} & CoMap
root
} = await
account: {
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account
account
.
Account.$jazz: AccountJazzApi<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.
$jazz
.
AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>.ensureLoaded<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account, {
    ...;
}>(this: AccountJazzApi<...>, options: {
    ...;
}): Promise<...>
@categorySubscription & Loading
ensureLoaded
({
resolve: RefsToResolve<{
    readonly root: ({
        readonly myChats: CoList<({} & CoMap) | null> | null;
        readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
    } & CoMap) | null;
    readonly profile: ({
        ...;
    } & CoMap) | null;
} & Account, 10, []>
resolve
: {
root?: RefsToResolve<{
    readonly myChats: CoList<({} & CoMap) | null> | null;
    readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
} & CoMap, 10, [...]> | undefined
root
: true }
}); // we specifically need to check for undefined, // because myBookmarks might simply be not loaded (`null`) yet if (
const root: {
    readonly myChats: CoList<({} & CoMap) | null> | null;
    readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
} & CoMap
root
.myBookmarks: CoList<({} & CoMap) | null> | null | undefinedmyBookmarks === var undefinedundefined) {
const root: {
    readonly myChats: CoList<({} & CoMap) | null> | null;
    readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
} & CoMap
root
.
CoMap.$jazz: CoMapJazzApi<{
    readonly myChats: CoList<({} & CoMap) | null> | null;
    readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined;
} & CoMap>
Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.
$jazz
.CoMapJazzApi<{ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap>.set<"myBookmarks">(key: "myBookmarks", value: CoList<({} & CoMap) | null> | readonly (({} & CoMap) | CoMapInit<{} & CoMap> | null)[] | null | undefined): void
Set a value on the CoMap
@paramkey The key to set@paramvalue The value to set@categoryContent
set
("myBookmarks", import coco.
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>>
export list
list
(const Bookmark: co.Map<{}, unknown, Account | Group>Bookmark).
CoListSchema<CoMapSchema<{}, unknown, Account | Group>>.create(items: readonly (({} & CoMap) | {})[], options?: {
    owner: Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Group): CoListInstance<...> (+1 overload)
create
([], class Group
@categoryIdentity & Permissions
Group
.
Group.create<Group>(this: CoValueClass<Group>, options?: {
    owner: Account;
} | Account): Group
create
()));
} });