Policies
Use grant and deny rules to control who can read, write, update, and delete entity rows.
Policies live on entities and decide who can read, insert, update, or delete rows. The runtime enforces them everywhere data is accessed.
Evaluation Model
- Look up the policies for the entity and action.
- Find the grants that match the current principal.
- Union matching grants together.
- Apply deny rules after grants.
- If nothing grants access, deny the operation.
Key rules:
- No grants means no access.
- Deny always wins.
- The same model applies across reads, writes, includes, routes, and live queries.
Grant Syntax
entity Document {
title: string,
ownerId: __User.id,
@grant read, write where resource.ownerId == principal.id
@grant read to role(Admin)
}Actions
read— controls visibility.write— shorthand for insert and update.delete— controls row deletion.
Principals
*— any authenticated user.@public— unauthenticated access.role(Name)— a specific role in the principal context.
Deny Syntax
entity AuditLog {
action: string,
@grant read to role(Admin)
@deny write to *
}Use deny rules for invariants that must always hold. Deny is the backstop, not the primary modeling tool.
Ownership, Roles, And Public Access
Ownership
entity Project {
name: string,
ownerId: __User.id,
@grant read, write, delete where resource.ownerId == principal.id
}Use when data belongs to one user.
Roles
entity Invoice {
total: decimal(10, 2),
accountId: Account.id,
@grant read to role(Admin)
@grant read where resource.accountId == principal.accountId
}Use when access is organizational rather than personal.
Public Reads
entity PublicPost {
title: string,
published: boolean = false,
@grant read to @public where resource.published == true
}Use @public only when anonymous access is part of the product.
Field-Level Grants
Narrow which fields a principal can read from a given row.
entity PatientRecord {
patientId: __User.id,
diagnosis: string,
billingCode: string,
@grant read(patientId, billingCode) to role(Billing)
@grant read to role(Doctor)
}Pairs well with .fields() in server and browser reads to return only what the screen needs.
Via Grants
Grant access through a related entity instead of a direct field on the resource.
entity Doc {
title: string,
@grant read to * via DocShare
}
entity DocShare {
docId: Doc.id,
userId: __User.id,
@unique([docId, userId])
}Use for sharing, memberships, invitations, and other relationship-based access models.
Recommended Usage
- Start with ownership rules.
- Add role-based grants for admin and team flows.
- Use denies for invariants that must always win.
- Keep policies on the entity that owns the data.
- Prefer readable conditions over clever policy expressions.
Design Progression
- Ownership first.
- Role-based reads next.
- Role-based writes when needed.
- Field-level restrictions for sensitive surfaces.
viarules for sharing and membership models.
What Policies Do Not Replace
Policies decide row- and field-level data access. They do not replace:
- Function-level admission checks for workflow access
- UI decisions about what to render
- Business logic inside exposed functions
The split: policies own data access, functions and routes own workflows, frontend code owns presentation.
Related guide: Authorization