Skip to main content
Contextual tuples are relationships and attributes that you send inline with a permission check request. They are processed together with the persisted data in the database and influence the check result — but they are never written to storage. When the request completes, they disappear. This makes contextual tuples ideal for:
  • Dynamic context: data that changes per request (IP address, time of day, network location) and cannot be stored as static relationships.
  • “What-if” scenarios: testing whether a permission would be granted if a relationship existed, without actually creating it.
  • Session-level data: attaching short-lived context such as a user’s current role or active session attributes to a single check.

How contextual tuples are evaluated

When the check engine processes a request, it merges the contextual tuples with the results from the database query. The NewContextualTuples function in internal/storage/context/tuples.go creates an in-memory tuple iterator from the tuples provided in context.tuples. That iterator is combined with the database iterator via NewUniqueTupleIterator, ensuring duplicates are deduplicated before the check logic runs. The same pattern applies to attributes: NewContextualAttributes in internal/storage/context/attributes.go handles the context.attributes field.

Example: IP-based access control

Consider an internal HR application where an employee can view another employee’s details only if they are an HR manager and are connected through the branch’s internal network. The network address is a dynamic value — it changes per request and cannot be modelled as a static relation.

Authorization model

entity user {}

entity organization {

    relation employee @user
    relation hr_manager @user @organization#employee

    relation ip_address_range @ip_address_range

    action view_employee = hr_manager and ip_address_range.user

}

entity ip_address_range {
    relation user @user
}
The ip_address_range entity type represents the contextual variable. The view_employee action requires the user to be an HR manager and have a relation through the ip_address_range entity. Because ip_address_range is dynamic, you cannot write it as a static tuple. Instead, you pass it at check time.

Access check with contextual tuples

Assume:
  • User 1 has the tuple organization:1#hr_manager@user:1 stored in the database.
  • User 1 is connecting from IP 192.158.1.38, which belongs to the branch’s internal network.
curl --location --request POST 'localhost:3476/v1/tenants/t1/permissions/check' \
--header 'Content-Type: application/json' \
--data-raw '{
  "metadata": {
    "snap_token": "",
    "schema_version": "",
    "depth": 20
  },
  "entity": {
    "type": "organization",
    "id": "1"
  },
  "permission": "view_employee",
  "subject": {
    "type": "user",
    "id": "1",
    "relation": ""
  },
  "context": {
    "tuples": [
      {
        "entity": { "type": "ip_address_range", "id": "192.158.1.38" },
        "relation": "user",
        "subject": { "type": "user", "id": "1", "relation": "" }
      }
    ]
  }
}'

The context field

The context object accepted by Check, LookupEntity, and LookupSubject requests supports two sub-fields:
FieldTypeDescription
context.tuplesarray of tuplesTemporary relationship tuples merged with database results during evaluation. Not persisted.
context.attributesarray of attributesTemporary attribute values merged with database results during evaluation. Not persisted.
context.dataobjectArbitrary key-value data accessible inside CEL rule expressions via context.data.
Contextual tuples are evaluated in memory during the request and are never written to the relation_tuples table. They have no effect on future requests unless you send them again.

Build docs developers (and LLMs) love