Groups as permission scopes
Every CoValue has an owner, which can be a Group or an Account.
You can use a Group to grant access to a CoValue to multiple users. These users can
have different roles, such as "writer", "reader" or "admin".
CoValues owned by an Account can only be accessed by that Account. Additional collaborators cannot be added, and the ownership cannot be transferred to another Account. This makes account ownership very rigid.
Creating a Group for every new CoValue is a best practice, even if the Group only has a single user in it (this is the default behavior when creating a CoValue with no explicit owner).
While creating CoValues with Accounts as owners is still technically possible for backwards compatibility, it will be removed in a future release.
Role Matrix
| Role | admin | manager | writer | writeOnly | reader |
|---|---|---|---|---|---|
| Summary | Full control | Delegated management | Standard writer | Blind submissions | Viewer |
| Can add admins* | ✅ | ❌ | ❌ | ❌ | ❌ |
| Can add/remove managers | ✅ | ❌ | ❌ | ❌ | ❌ |
| Can add/remove readers and writers | ✅ | ✅ | ❌ | ❌ | ❌ |
| Can write | ✅ | ✅ | ✅ | ✅** | ❌ |
| Can read | ✅ | ✅ | ✅ | ❌*** | ✅ |
* admin users cannot be removed by anyone else, they must leave the group themselves.
** writeOnly users can only create and edit their own updates/submissions.
*** writeOnly cannot read updates from other users.
Creating a Group
Here's how you can create a Group.
import { Group } from "jazz-tools"; const group = Group.create();
The Group itself is a CoValue, and whoever owns it is the initial admin.
You typically add members using public sharing or invites. But if you already know their ID, you can add them directly (see below).
Adding group members by ID
You can add group members by ID by using co.account().load and Group.addMember.
import { co } from "jazz-tools"; const bob = await co.account().load(bobsId); if (bob.$isLoaded) { group.addMember(bob, "writer"); }
Changing a member's role
To change a member's role, use the addMember method.
if (bob.$isLoaded) { group.addMember(bob, "reader"); }
Bob just went from a writer to a reader.
Note: only admins and managers can change a member's role.
Removing a member
To remove a member, use the removeMember method.
if (bob.$isLoaded) { group.removeMember(bob); }
Rules:
- All roles can remove themselves
- Admins can remove all roles (except other admins)
- Managers can remove users with less privileged roles (writer, writeOnly, reader)
Getting the Group of an existing CoValue
You can get the group of an existing CoValue by using coValue.$jazz.owner.
const owningGroup = existingCoValue.$jazz.owner; const newValue = MyCoMap.create({ color: "red" }, { owner: group });
Checking the permissions
You can check the permissions of an account on a CoValue by using the canRead, canWrite, canManage and canAdmin methods.
const red = MyCoMap.create({ color: "red" }); const me = co.account().getMe(); if (me.canAdmin(red)) { console.log("I can add users of any role"); } else if (me.canManage(red)) { console.log("I can share value with others"); } else if (me.canWrite(red)) { console.log("I can edit value"); } else if (me.canRead(red)) { console.log("I can view value"); } else { console.log("I cannot access value"); }
To check the permissions of another account, you need to load it first:
const blue = MyCoMap.create({ color: "blue" }); const alice = await co.account().load(alicesId); if (alice.$isLoaded) { if (alice.canAdmin(blue)) { console.log("Alice can share value with others"); } else if (alice.canWrite(blue)) { console.log("Alice can edit value"); } else if (alice.canRead(blue)) { console.log("Alice can view value"); } else { console.log("Alice cannot access value"); } }
Defining permissions at the schema level
You can define permissions at the schema level by using the withPermissions method on any CoValue schema. Whenever you create a new CoValue using the schema (i.e., with the .create() method), the permissions will be applied automatically.
const Dog = co.map({ name: z.string(), }).withPermissions({ onInlineCreate: "sameAsContainer", }); const Person = co.map({ pet: Dog, }).withPermissions({ default: () => Group.create().makePublic(), }); // All Person CoValues will be public, and each Dog CoValue will share the same owner // as the Person that references it. const person = Person.create({ pet: { name: "Rex", }, });
withPermissions supports several options that let you customize how permissions are applied when creating or composing CoValues.
default
default defines a group to be used when calling .create() without an explicit owner.
onInlineCreate
onInlineCreate allows you to choose the behaviour when a CoValue is created inline
This configuration is not applied when using .create() for nested CoValues. In that case, default is used.
onInlineCreate supports the following options:
"extendsContainer"- create a new group that includes the container CoValue's owner as a member, inheriting all permissions from the container. This is the default if noonInlineCreateoption is provided."sameAsContainer"- reuse the same owner as the container CoValue"newGroup"- create a new group for inline CoValues, with the active account as admin{ extendsContainer: "reader" }- similar to“extendsContainer”, but allows overriding the role of the container’s owner in the new groupgroupConfigurationCallback- create a new group and configure it as needed
onCreate
onCreate is a callback that runs every time a CoValue is created. It can be used to configure the CoValue's owner.
It runs both when creating CoValues with .create() and when creating inline CoValues.
Configuring permissions globally
You can configure the default permissions for all CoValue schemas by using setDefaultSchemaPermissions.
This is useful if you want to modify inline CoValue creation to always re-use the container CoValue's owner.
import { setDefaultSchemaPermissions } from "jazz-tools"; setDefaultSchemaPermissions({ onInlineCreate: "sameAsContainer", });