PocketBase auth rules

Every collection in TinyCld needs auth rules. Without them, PocketBase falls back to “superusers only” and every insert, list, view, update, and delete from a non-superuser session fails with:

Only superusers can perform this action.

You add rules at collection-creation time inside your pb-migrations/<timestamp>_create_<thing>.js file.

The five rules

PocketBase collections accept five auth rules:

RuleWhen it firesDefault if omitted
listRulefiltered list / searchsuperuser-only
viewRulesingle-record fetch by idsuperuser-only
createRulerecord creationsuperuser-only
updateRulerecord updatesuperuser-only
deleteRulerecord deletesuperuser-only

Each rule is a string in PocketBase’s filter language. It evaluates against the record being accessed plus the special @request.auth and @request.data namespaces. When the expression is truthy, the action is allowed.

Org-scoped (most common)

Every TinyCld org-scoped collection has an owner relation pointing at a user_org row. The user-facing rule is “the calling user owns the user_org that owns this row”:

new Collection({
    type: 'base',
    name: 'todo_items',
    listRule: 'owner.user = @request.auth.id',
    viewRule: 'owner.user = @request.auth.id',
    createRule: 'owner.user = @request.auth.id',
    updateRule: 'owner.user = @request.auth.id',
    deleteRule: 'owner.user = @request.auth.id',
    fields: [
        { name: 'name', type: 'text', required: true, max: 200 },
        {
            name: 'owner',
            type: 'relation',
            required: true,
            collectionId: 'pbc_user_org_01',
            cascadeDelete: true,
            maxSelect: 1,
        },
        // ...
    ],
})

pbc_user_org_01 is the stable id of the bundled user_org collection. The dot-traversal owner.user walks the owner relation to the user_org row, then reads its user field — a relation to the user record — and compares it to the calling auth id.

This is the pattern used by @tinycld/contacts, @tinycld/calendar, and friends.

User-scoped (no org)

If your data isn’t org-scoped — user preferences, theme settings, anything tied to a single user — the rule becomes simpler. You’d typically add an owner relation directly to the users collection (id _pb_users_auth_):

listRule: 'owner = @request.auth.id',
viewRule: 'owner = @request.auth.id',
createRule: 'owner = @request.auth.id',
updateRule: 'owner = @request.auth.id',
deleteRule: 'owner = @request.auth.id',

Public read, owner write

Public share-link content, blog posts, anything where read is open but writes are gated:

listRule: '',                          // empty string = anyone
viewRule: '',
createRule: 'owner = @request.auth.id',
updateRule: 'owner = @request.auth.id',
deleteRule: 'owner = @request.auth.id',

Empty string '' means the rule allows everyone. null (or omitting the rule) means superusers only — which is rarely what you want at the API level.

Locked down

If a collection is only ever written by Go server hooks (audit logs, system events), set every rule to null:

listRule: null,
viewRule: null,
createRule: null,
updateRule: null,
deleteRule: null,

The Go side bypasses rules with app.Save(record) calls in hooks; null rules at the API level enforce that no client can write directly.

Common patterns

Where to find existing examples

Every present feature package’s pb-migrations/ is a working reference:

The @tinycld/bootstrap scaffolder writes a starter migration that already includes the org-scoped rules and an owner field; rename or remove the field as your data model evolves.

Diagnostics

If you see “Only superusers can perform this action” at runtime, the rule for the action you tried (insert → createRule, list → listRule, etc.) is null or missing. Run pnpm run db:reset from tinycld/ after editing the migration so the new rules take effect — PocketBase doesn’t hot-reload rule changes from a previously-applied migration.