Skip to content

Policies and Access

Policy workflows add dynamic access rules to Brezel queries. They are powerful because they can restrict records by role, relation, current user, or operation.

A policy workflow starts with event/policy and usually continues into query/where.

{
"identifier": "ProjectAccessPolicy",
"title": "Project access policy",
"async": false,
"entry": "projectPolicy",
"elements": [
{
"name": "projectPolicy",
"type": "event/policy",
"options": {
"module": "projects",
"roles": ["employee"],
"operations": ["read", "update"],
"allow": ["admin", "manager"],
"roleKey": "slug"
},
"to": {
"default": {
"whereAssigned": ["default"]
}
}
},
{
"name": "whereAssigned",
"type": "query/where",
"options": {
"where": [
[
{
"left": {
"type": "field",
"value": "assigned_users.id"
},
"operator": "=",
"right": {
"type": "recipe",
"value": "user().id"
}
}
]
]
}
}
],
"events": [
{
"identifier": "projectPolicy",
"type": "policy",
"module": "projects",
"roles": ["employee"],
"operations": ["read", "update"],
"allow": ["admin", "manager"],
"roleKey": "slug"
}
]
}

Users with admin or manager are allowed by the policy configuration. Users with employee receive the additional query constraint.

Policies can target multiple operations, but the safest policy is often operation-specific. Reading a record and deleting a record can require different constraints.

Use separate entry elements when the logic differs:

  • projectReadPolicy
  • projectUpdatePolicy
  • projectDeletePolicy

This keeps each query path short and auditable.

For customer-facing roles, filter by a relation to the current user instead of trusting request data.

{
"name": "whereOwnCustomer",
"type": "query/where",
"options": {
"where": [
[
{
"left": {
"type": "field",
"value": "customer.id"
},
"operator": "IN",
"right": {
"type": "recipe",
"value": "user().customers[*].id"
}
}
]
]
}
}

Never grant access based only on IDs submitted by the browser.

  • The policy is synchronous.
  • The module, roles, and operations are explicit.
  • Privileged roles are listed in allow.
  • Non-privileged roles receive a query/where constraint.
  • The constraint is based on user() or trusted relations, not request input.
  • Read/update/delete behavior is tested separately when the rules differ.
  • Denied users see no records rather than records with hidden fields.