Skip to content

Store entities

Store the users, teams, folders and documents that your policies talk about. An entity has a type, an id, attributes, parents and tags.

First complete Write policies. The checks on the next pages use the policies and the entities from this page. An admin role runs these calls.

Pass an array of entities to permissions.cedar.admin.putEntities. This is Cedar’s entity JSON format, with uid as an object. An entity has these keys:

Key Content
uid {"type": "Document", "id": "q3"}. The type is a name such as User or App::User. The id is any string.
attrs The attributes, a JSON object. The default is {}.
parents The direct parents, an array of uids. The default is [].
tags The tags, a JSON object. The default is {}.

This call stores two teams, three users, a folder and two documents. The team design is in the team eng. Carol is in design. Alice is in eng:

const entities = [
{ uid: { type: 'Team', id: 'eng' } },
{
uid: { type: 'Team', id: 'design' },
parents: [{ type: 'Team', id: 'eng' }],
},
{
uid: { type: 'user', id: 'alice' },
parents: [{ type: 'Team', id: 'eng' }],
},
{ uid: { type: 'user', id: 'bob' } },
{
uid: { type: 'user', id: 'carol' },
parents: [{ type: 'Team', id: 'design' }],
},
{ uid: { type: 'Folder', id: 'reports' } },
{
uid: { type: 'Document', id: 'q3' },
attrs: {
owner: { __entity: { type: 'user', id: 'bob' } },
level: 'internal',
},
parents: [{ type: 'Folder', id: 'reports' }],
tags: { region: 'eu' },
},
{
uid: { type: 'Document', id: 'salaries' },
attrs: {
owner: { __entity: { type: 'user', id: 'alice' } },
level: 'confidential',
},
},
]
const storedEntities = await permissions.cedar.admin.putEntities(entities)
if (storedEntities.error) throw storedEntities.error

Each supplied entity replaces the stored entity with the same uid, including its attributes, parents and tags. Entities omitted from the array stay unchanged. A call stores up to 10,000 entities in one transaction.

Subjects are entities of the subject type. The user alice above is the entity user::"alice". This is the principal when the current subject is alice (see the overview). A user does not need a stored entity to be a principal. A policy that tests principal in Team::"eng" or principal.level needs the stored entity.

An attribute value is a Cedar value in JSON:

Cedar type JSON
Boolean true
Long An integer from -2^63 to 2^63-1. A value such as 1.5 is an error.
String "text"
Set An array, ["a", "b"]
Record An object, {"city": "Oslo"}
Entity {"__entity": {"type": "user", "id": "bob"}}
Extension value {"__extn": {"fn": "decimal", "arg": "12.34"}}

The key __entity marks an entity reference. The key __extn marks a value of an extension type. See Extension types. A record that has only the key __entity or the key __extn is read as an entity or an extension value. Cedar’s JSON has the same rule.

The extension reads no Cedar schema. For this reason, write __entity and __extn in every place that needs them. A bare string is a string.

A value that is not valid is the error cedar:invalid_entity, with the index of the entity in the array. Sets and records can have at most 32 levels of depth.

Store direct parents in parents; the evaluator follows the hierarchy. The expression a in b is true when a equals b or b is an ancestor of a. Carol belongs to design, which belongs to eng, so user::"carol" in Team::"eng" is true and eng-view-reports applies to her.

  • A parent does not need to be a stored entity. An entity that does not exist has no parents, and it is not an error.
  • The hierarchy cannot have a cycle. A put that makes an entity its own ancestor raises cedar:invalid_entity. Cedar rejects cycles too.
  • Parents are for in. Use an attribute that holds an entity for other relations, such as the owner of a document.

This put makes a cycle, and it fails:

const cycle = await permissions.cedar.admin.putEntities([
{
uid: { type: 'Team', id: 'eng' },
parents: [{ type: 'Team', id: 'design' }],
},
])
// cycle.error.details: cedar:invalid_entity uid='Team::"design"'

Puts that include parent links are serialized until their transactions finish, so concurrent writes cannot introduce a cycle that neither sees. Keep these transactions short. Under REPEATABLE READ or SERIALIZABLE, a competing write can raise SQLSTATE 40001; retry the whole transaction. Batches with no parent links do not take this shared write guard.

readEntities returns an array in the same format as putEntities. Pass type to read one type, and ids to read some of its entities. ids needs type:

const { data: docs } = await permissions.cedar.admin.readEntities({
type: 'Document',
ids: ['q3'],
})
// docs[0].parents: [{ type: 'Folder', id: 'reports' }]

An extension value comes back in its canonical text, which can differ from the text that you put ("1.5" comes back as "1.5000"). A put of the output changes nothing.

Pass an array of UIDs to deleteEntities. It returns the number of entities removed and skips unknown UIDs.

const { data: deleted } = await permissions.cedar.admin.deleteEntities([
{ type: 'Document', id: 'salaries' },
])
// deleted: 1

A delete removes the entity and its own parent links. It keeps the links of other entities that name it as a parent, as Cedar allows a parent that does not exist. To forget a user in every model, use delete_subject. It also removes the links that name the user as a parent.

Put the entity again to restore it for the next pages:

const restored = await permissions.cedar.admin.putEntities([
{
uid: { type: 'Document', id: 'salaries' },
attrs: {
owner: { __entity: { type: 'user', id: 'alice' } },
level: 'confidential',
},
},
])
if (restored.error) throw restored.error

Update entities when users join a team or documents move. Write the entity changes and the corresponding application changes in the same transaction so they become visible together.

A row of your own tables does not have to be an entity. Your backend can pass trusted resource attributes to a check; an RLS policy can pass them from the row. See Application checks and Cedar in RLS. Store an entity when a policy uses its parents, or when you list resources with cedar_list_resources_current. The list function returns stored entities only.

Continue with Use policy templates, then Check access.