Skip to main content
A policy is a boolean expression evaluated on every row access. It lives beside your entities, not buried in middleware.

What the expressions see

Roles are checked with the auth.hasRole("x") / auth.hasAnyRole("a", "b") functions (below) — there is no auth.roles array and no auth.email binding in policy expressions. (Email/roles live on the session and the SSR page auth prop, just not in the policy evaluator.)

Each action

  • allowRead — runs on query results. If false, row is filtered out.
  • allowInsert — runs against the proposed data (the incoming row).
  • allowUpdate — runs against the current stored row (bound as both data and existing), so ownership checks see the truth, not the caller’s patch.
  • allowDelete — runs against the current row (data / existing).
If a policy is omitted, that action is denied by default.

Operators

The policy language is deliberately tiny. The complete operator set is:
Plus three built-in forms:
Ordering (< <= > >=) compares numbers numerically, two strings chronologically when both are valid ISO-8601 timestamps (else lexicographically), and is deny-safe otherwise: comparing null, a boolean, or a number against a non-numeric string is always false. This is what makes data.publishAt <= now deny rows that aren’t published yet. There is still no in, no ends_with/starts_with, and no arithmetic (+ - * /). Membership is expressed with exists(...), not in. String prefix/suffix matching belongs in a function.

Examples

Public read, author-only write

Members of an org

Membership lives in a join entity (OrgMember { orgId, userId }) and is checked with exists(...):

Admin override

Time window (scheduled publishing)

now plus an ordering comparison gates rows by time — public once published, with the author still able to preview:

Policy composition

Multiple policies on the same entity AND together — all must pass. Use this to layer a broad rule with a narrow exception:

Server functions bypass policies

Policies guard the raw /api/entities/* endpoints. Server functions (ctx.db.insert, ctx.db.update, etc.) run with elevated access — they trust you to enforce your own checks inside the handler. This lets you write escape-hatch operations (admin tools, batch imports) without contorting your policy language.

Next

Functions

Write server logic for anything policies can’t express.

Live queries

Policies also filter subscription results.