Policies
Use grant and deny rules to control who can read, write, update, and delete entity rows.
Policies are the core of Kizaki’s data access model. They live on entities and decide who can read, insert, update, or delete rows. The runtime enforces them everywhere that data can be accessed, which is why they are one of the most important parts of the schema.
The Evaluation Model
The public model is:
- Kizaki looks up the policies for the entity and action
- it finds the grants that match the current principal
- it unions those matching grants together
- it applies deny rules after grants
- if nothing grants access, the operation is denied
A few rules fall out of that model:
- no grants means no access
- deny always wins
- the same policy 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)
}Use grants to express the positive rule: who should be allowed to act and under what condition.
What Actions Mean
readcontrols visibilitywriteis shorthand for insert and update behaviordeletecontrols row deletion
Start simple. In most products, read, write, and delete are the only policy actions you need to think about at first.
Principals
Kizaki policies are usually written against one of three principal shapes:
*for any authenticated user@publicfor unauthenticated accessrole(Name)for a specific role in the principal context
That gives you the core building blocks for personal apps, team apps, public content, and admin surfaces.
Deny Syntax
entity AuditLog {
action: string,
@grant read to role(Admin)
@deny write to *
}Use deny rules for invariants that must always hold. A deny is not your main modeling tool. It is the backstop for actions that should never happen through the normal application path.
Ownership, Roles, And Public Access
These are the three patterns you will use most often.
Ownership
entity Project {
name: string,
ownerId: __User.id,
@grant read, write, delete where resource.ownerId == principal.id
}Use ownership when the data belongs to one user and the rule is naturally “you can act on your own rows.”
Roles
entity Invoice {
total: decimal(10, 2),
accountId: Account.id,
@grant read to role(Admin)
@grant read where resource.accountId == principal.accountId
}Use roles 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 genuinely part of the product.
Field-Level Grants
Policies do not only control which rows are visible. They can also narrow which fields may be returned for a given principal.
entity PatientRecord {
patientId: __User.id,
diagnosis: string,
billingCode: string,
@grant read(patientId, billingCode) to role(Billing)
@grant read to role(Doctor)
}This is useful when different roles should see different slices of the same row. It also pairs well with .fields() in server and browser reads so the app only returns what the screen actually needs.
Via Grants
Use via when access should flow through a related entity rather than 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])
}This is the right pattern 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 simple, readable conditions over clever policy expressions
Practical Design Guidance
For most products, the clean progression is:
- ownership first
- role-based reads next
- role-based writes when needed
- field-level restrictions for sensitive surfaces
viarules for sharing and membership models
If you follow that order, the schema usually stays understandable even as the app grows.
What Policies Do Not Replace
Policies decide row- and field-level data access. They do not replace:
- function-level admission checks for “who can invoke this workflow”
- UI decisions about what to render
- business logic inside your exposed functions
The clean split is:
- policies own data access
- functions and routes own workflows
- frontend code owns presentation
Related guide: Authorization