Skip to main content
A policy is a boolean expression evaluated on every row access. It lives beside your entities, not buried in middleware.
import { policy } from "@pylonsync/sdk";

const noteOwner = policy({
  name: "note_owner",
  entity: "Note",
  allowRead: "true",
  allowInsert: "auth.userId == data.authorId",
  allowUpdate: "auth.userId == data.authorId",
  allowDelete: "auth.userId == data.authorId",
});

What the expressions see

BindingTypeContains
auth.userIdstring | nullCurrent user’s id, null if unauthenticated
auth.emailstring | nullEmail of the authenticated user
auth.rolesstring[]Role names attached to the session
data.*objectThe row being written (insert/update/delete)
existing.*objectThe current row in storage (update/delete only)

Each action

  • allowRead — runs on query results. If false, row is filtered out.
  • allowInsert — runs against the proposed data.
  • allowUpdate — runs against both data (the patch) and existing (the current row).
  • allowDelete — runs against existing.
If a policy is omitted, that action is denied by default.

Operators

==    !=    <    <=    >    >=
&&    ||    !
+     -     *     /     %
Plus membership:
auth.userId in data.memberIds
"admin" in auth.roles
And string checks:
data.email ends_with "@example.com"
data.title starts_with "[draft]"

Examples

Public read, author-only write

policy({
  name: "post_public",
  entity: "Post",
  allowRead: "true",
  allowInsert: "auth.userId == data.authorId",
  allowUpdate: "auth.userId == existing.authorId",
  allowDelete: "auth.userId == existing.authorId",
});

Members of an org

policy({
  name: "org_member",
  entity: "Document",
  allowRead: "auth.userId in existing.memberIds",
  allowInsert: "auth.userId in data.memberIds",
  allowUpdate: "auth.userId in existing.memberIds",
});

Admin override

policy({
  name: "admin_all",
  entity: "AuditLog",
  allowRead: "'admin' in auth.roles",
  allowInsert: "'admin' in auth.roles",
});

Policy composition

Multiple policies on the same entity AND together — all must pass. Use this to layer a broad rule with a narrow exception:
const readable = policy({ name: "readable", entity: "Doc", allowRead: "true" });
const noDrafts = policy({
  name: "no_drafts",
  entity: "Doc",
  allowRead: "existing.status != 'draft' || auth.userId == existing.authorId",
});

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.