baseclfdocs

Reference

Policy rules that stay close to the data.

Policies are JSON documents. The engine compiles them into parameterized SQL and attaches the predicate to every request; a table with no document refuses every caller.

Claims and operators

ExpressionMeaningNotes
$auth.uidThe verified user id, from the JWTBound as a SQL parameter. Absent means the anon role.
$auth.emailThe verified emailBound as a SQL parameter.
$auth.app.*Server-set metadataOnly operators and your server can write it, so policies may trust it.
$auth.user.*User-editable metadataRefused when the document is saved. End users can write it themselves, so a policy reading it would be self-service escalation.
_eq _neq _gt _gte _lt _lte _like _is_nullComparisonsEvery value is a bound parameter, never spliced into SQL.
_in _and _or _not _existsComposition_in compiles to json_each so a whole list costs one parameter; _exists is a correlated subquery for reaching related tables.

Example

posts.json · Policy document
{
  "table": "posts",
  "enabled": true,
  "binds": {
    "isPublished": { "status": { "_eq": "published" } },
    "isAuthor": { "author_id": { "_eq": "$auth.uid" } }
  },
  "policies": [
    {
      "name": "read_own_or_published",
      "for": "select",
      "to": ["authenticated"],
      "using": { "_or": [{ "$bind": "isPublished" }, { "$bind": "isAuthor" }] },
      "columns": ["id", "title", "body", "status", "author_id", "created_at"]
    }
  ]
}
Compiled SQL · Positional parameters
select "posts"."id", "posts"."title", "posts"."body",
       "posts"."status", "posts"."author_id", "posts"."created_at"
from "posts"
where (("posts"."status" = ?) or ("posts"."author_id" = ?))

Refusal behavior

A table with no document answers 404 for every caller. A denied read simply lacks the rows. A write that matches nothing answers 404 whether the row was absent or the policy withheld it: the two cases are deliberately indistinguishable, because a caller who could tell them apart could walk a range of ids and learn which exist. Writes compile to single guarded statements, so there is no partial state to clean up.

Performance

D1 bills rows scanned, not rows returned, so a policy column without an index is a recurring bill rather than a latency problem. baseclf policy lint names the columns and hands back the CREATE INDEX to paste, and every response carries an x-baseclf-rows-read header so you can watch it.