Claude-code-templates PocketBase API Rules
API rules and filter expressions for PocketBase access control. Use when setting permissions, writing filter expressions, configuring who can access what, or debugging 403/404 responses. Covers all 5 rule types, filter syntax, operators, request/collection macros, and field modifiers.
git clone https://github.com/davila7/claude-code-templates
T=$(mktemp -d) && git clone --depth=1 https://github.com/davila7/claude-code-templates "$T" && mkdir -p ~/.claude/skills && cp -r "$T/cli-tool/components/skills/pocketbase/pb-api-rules" ~/.claude/skills/davila7-claude-code-templates-pocketbase-api-rules && rm -rf "$T"
cli-tool/components/skills/pocketbase/pb-api-rules/SKILL.mdPocketBase API Rules & Filter Expressions
Rule Types
Each collection has 5 rule types. Each rule is a filter expression that must evaluate to
true for the request to proceed.
| Rule | Controls | Locked = | Empty string = |
|---|---|---|---|
| List | | superusers only | everyone can list |
| View | | superusers only | everyone can view |
| Create | | superusers only | everyone can create |
| Update | | superusers only | everyone can update |
| Delete | | superusers only | everyone can delete |
Critical:
null/locked means only superusers can perform the action (regular users and guests are denied). Empty string "" means EVERYONE including guests. Superusers always bypass API rules entirely — see below.
Superuser Bypass
Superusers (formerly admins) always bypass API rules. Rules only apply to regular auth records and guests.
Filter Syntax
Operators
| Operator | Meaning | Example |
|---|---|---|
| Equal | |
| Not equal | |
| Greater than | |
| Greater or equal | |
| Less than | |
| Less or equal | |
| LIKE (contains) | |
| NOT LIKE | |
| Any/has (array contains) | |
| None (array not contains) | |
| Any greater than | |
| Any greater or equal | |
| Any less than | |
| Any less or equal | |
| Any LIKE | |
| Any NOT LIKE | |
Critical: use
?= (not =) for multi-valued fields (multi-select, multi-relation, multi-file). = checks the raw JSON string, ?= checks individual values.
Logical Operators
status = "active" && author = @request.auth.id status = "active" || status = "featured"
Parentheses for grouping:
(a = 1 || b = 2) && c = 3
Values
- Strings:
or"value"'value' - Numbers:
,12345.67 - Booleans:
,truefalse
— empty/missing valuenull- Identifiers: field names, macros
Request Macros (@request.*
)
@request.*Access the current request context in rules:
| Macro | Type | Description |
|---|---|---|
| | Current auth record ID (empty if guest) |
| | Current auth record email |
| | Whether email is verified |
| | Auth collection ID |
| | Auth collection name |
| | Any field from the auth record |
| | Field value from request body |
| | URL query parameter |
| | Request header (lowercase key) |
| | HTTP method (GET/POST/PATCH/DELETE) |
Auth record relations
You can traverse relations on the auth record:
@request.auth.team.owner = @request.auth.id
Collection Macros (@collection.*
)
@collection.*Cross-collection lookups without explicit joins:
@collection.memberships.user ?= @request.auth.id && @collection.memberships.team ?= team
This checks if a record exists in the
memberships collection where the user matches the current auth user and the team matches the current record's team field.
Note:
@collection.* performs an implicit EXISTS subquery. It's powerful but can be slow on large datasets — add indexes.
Field Modifiers
Use in create/update rules to validate specific field behaviors:
| Modifier | Works on | Description |
|---|---|---|
| | True if the field was sent in the request (even if empty) |
| record field | True if the field value differs from current stored value (update only) |
| / | Returns the length |
| | Applies the condition to each element |
| | Lowercased value |
Examples
// Only allow changing status if user is owner status:changed = false || author = @request.auth.id // Prevent setting role on create @request.body.role:isset = false // Require at least 2 tags @request.body.tags:length >= 2 // Check each tag is from allowed list @request.body.tags:each ?= @collection.allowed_tags.id
Datetime Macros
| Macro | Example output |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
Arithmetic:
@now - 7d, @now + 1h, @now - 30m
geoDistance()
geoDistance()For location-based filtering:
geoDistance(lat, lon, 40.7128, -74.0060) <= 10000
Arguments:
geoDistance(latField, lonField, targetLat, targetLon) — returns meters.
Common Patterns
Owner-only access
// View/Update/Delete rule: author = @request.auth.id
Authenticated users only
@request.auth.id != ""
Verified users only
@request.auth.verified = true
Role-based access
@request.auth.role = "admin" || author = @request.auth.id
Team membership
@collection.team_members.user ?= @request.auth.id && @collection.team_members.team ?= team
Public read, owner write
// List/View: "" (empty = everyone) // Create: @request.auth.id != "" // Update/Delete: author = @request.auth.id
Prevent field modification
// Update rule: prevent changing `owner` after creation owner:changed = false
Time-limited access
expires > @now