Skip to content

Share objects and manage tuples

A tuple records a relationship, such as a user viewing a document or belonging to a team.

First write the model. Keep its model variables and server client in scope. Run the examples below in order as an admin; the next page checks the access they grant.

A tuple says that a subject has a relation to an object. This is a tuple key in JSON: {"user": "user:bob", "relation": "member", "object": "team:eng"}. In a tuple key, the user field holds the subject.

Write tuples with fga_write:

const writes = [
{
user: 'user:bob',
relation: 'member',
object: 'team:eng',
},
{
user: 'team:design#member',
relation: 'member',
object: 'team:eng',
},
{
user: 'user:carl',
relation: 'member',
object: 'team:design',
},
{
user: 'user:anne',
relation: 'owner',
object: 'folder:projects',
},
{
user: 'team:eng#member',
relation: 'viewer',
object: 'folder:projects',
},
{
user: 'folder:projects',
relation: 'parent',
object: 'folder:roadmaps',
},
{
user: 'folder:roadmaps',
relation: 'parent',
object: 'document:roadmap',
},
{
user: 'user:dana',
relation: 'editor',
object: 'document:roadmap',
},
{
user: 'user:carl',
relation: 'blocked',
object: 'document:roadmap',
},
{
user: 'user:*',
relation: 'viewer',
object: 'document:handbook',
},
]
const shared = await permissions.fga.admin.write({ writes })
if (shared.error) throw shared.error

The tuples grant the following relationships:

  • Bob is in team eng. Everyone in team design is also in team eng. Carl is in team design.
  • Anne owns the projects folder. Team eng views it.
  • The roadmaps folder is inside projects. The roadmap document is inside roadmaps.
  • Dana edits the roadmap. Carl has the blocked relation to the roadmap.
  • Everyone views the handbook.

Use writeTuple and deleteTuple for a single unconditional relationship. You can repeat either call safely.

const tuple = {
user: 'user:erin',
relation: 'owner',
object: 'document:notes',
}
const addedOwner = await permissions.fga.admin.writeTuple(tuple)
if (addedOwner.error) throw addedOwner.error
const sameOwner = await permissions.fga.admin.writeTuple(tuple)
if (sameOwner.error) throw sameOwner.error
// no error: already there
const removedOwner = await permissions.fga.admin.deleteTuple(tuple)
if (removedOwner.error) throw removedOwner.error

Use admin.write to apply several tuple changes in one transaction. Pass arrays in writes and deletes. To skip existing grants or missing deletions, set onDuplicate or onMissing to "ignore". In SQL, wrap the corresponding array in an object with tuple_keys and on_duplicate or on_missing.

const additions = [
{
user: 'user:erin',
relation: 'editor',
object: 'document:notes',
},
]
const deletes = [
{
user: 'user:zed',
relation: 'editor',
object: 'document:notes',
},
]
const changedTuples = await permissions.fga.admin.write({
writes: additions,
deletes,
onMissing: 'ignore',
})
if (changedTuples.error) throw changedTuples.error

Without an option, fga_write raises an error when you write a tuple that exists. It also raises an error when you delete a tuple that does not exist. This is the OpenFGA behavior.

const repeatedGrant = [
{
user: 'user:erin',
relation: 'editor',
object: 'document:notes',
},
]
const duplicate = await permissions.fga.admin.write({ writes: repeatedGrant })
// duplicate.error.message:
// cannot write a tuple which already exists: ...
// duplicate.error.details:
// openfga:2017 write_failed_due_to_invalid_input
// To skip the duplicate, pass onDuplicate: 'ignore'.

The extension checks each tuple against the type restrictions of the model. The relation editor is [user, team#member], so a wildcard is not valid:

const invalidGrant = [
{
user: 'user:*',
relation: 'editor',
object: 'document:notes',
},
]
const invalid = await permissions.fga.admin.write({ writes: invalidGrant })
// invalid.error.message:
// Invalid tuple 'document:notes#editor@user:*'. Reason: ...
// invalid.error.details: openfga:2027 invalid_tuple
// invalid.error.hint:
// 'document#editor' allows users of type: team#member, user.

fga_write accepts at most 100 tuple keys in each call. See Errors and limits.

Continue with Check and list access.