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:
| Rule | When it fires | Default if omitted |
|---|---|---|
listRule | filtered list / search | superuser-only |
viewRule | single-record fetch by id | superuser-only |
createRule | record creation | superuser-only |
updateRule | record update | superuser-only |
deleteRule | record delete | superuser-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
- Read-only after creation: set
updateRule: null(or just omit it). - Org admins only: walk to the user_org row and check the role:
owner.user = @request.auth.id && owner.role = 'admin'. - Time-limited access:
expires_at > @now(PocketBase fills@nowautomatically).
Where to find existing examples
Every present feature package’s pb-migrations/ is a working reference:
- Contacts:
~/code/tinycld/contacts/pb-migrations/1712000000_create_contacts.js - Mail:
~/code/tinycld/mail/pb-migrations/1713000000_create_mail_collections.js - Calendar:
~/code/tinycld/calendar/pb-migrations/1715000000_create_calendar_collections.js - Drive:
~/code/tinycld/drive/pb-migrations/1716000000_create_drive_collections.js
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.