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 proposeddata(the incoming row).allowUpdate— runs against the current stored row (bound as bothdataandexisting), so ownership checks see the truth, not the caller’s patch.allowDelete— runs against the current row (data/existing).
Operators
The policy language is deliberately tiny. The complete operator set is:< <= > >=) 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.