Install
$ agentstack add skill-parkersweb-claude-pocketbase-skill-claude-pocketbase-skill ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
PocketBase Development Skill
Guide for working with PocketBase as a backend, with specialized focus on the two most error-prone areas: API Rules (permissions) and JavaScript Event Hooks.
Core Workflow
- Define schema - Create collections with appropriate fields
- Set API rules - Configure permissions (listRule, viewRule, createRule, updateRule, deleteRule)
- Implement hooks - Add server-side logic in
pb_hooks/directory - Test thoroughly - Verify permissions and hooks trigger correctly
Production Security (CRITICAL)
⚠️ These are ESSENTIAL for production deployments - not optional:
1. Encrypt Settings
By default, PocketBase stores SMTP passwords and S3 credentials as PLAIN TEXT in the database.
- Enable encryption: Settings > Application > "Encrypt settings"
- Do this BEFORE entering production credentials
2. Enable Rate Limiting
PocketBase v0.23+ includes built-in rate limiting to prevent abuse and DoS attacks.
- Configure: Dashboard > Settings > Application
- Prevents excessive auth requests, record creation spam
- Can use reverse proxy rate limiting for advanced needs
3. Protect Admin Endpoints
Use reverse proxy (nginx/caddy) to whitelist IPs for /api/admins/* endpoints.
4. Enable Superuser MFA
Add MFA to _superusers collection for additional security layer.
- Requires one-time password (email code) for superuser auth
- Settings > Collections > _superusers > Options
5. Use HTTPS
PocketBase supports auto-managed TLS with Let's Encrypt:
./pocketbase serve --https example.com:443
Or use reverse proxy (nginx, caddy) for more complex setups.
6. Regular Backups
Backup entire pb_data directory regularly (see references/production_checklist.md).
Never expose PocketBase directly to internet without these protections!
API Rules (Permissions)
Critical Concepts
Rules act as BOTH authorization AND filters:
- Empty string
""= locked (superuser only) null= public access (no restrictions)- Non-empty expression = conditional access
Return codes based on rule type:
- Unsatisfied
listRule→ 200 with empty array - Unsatisfied
createRule→ 400 - Unsatisfied
viewRule,updateRule,deleteRule→ 404 - Locked rule with non-superuser → 403
Superusers bypass ALL rules - use test accounts for rule validation
Common Permission Patterns
See [references/permissionpatterns.md](references/permissionpatterns.md) for detailed examples.
Key points:
- Use
@request.auth.id != ""to require authentication - Use
@request.auth.id = idfor owner-only access - Use
@request.body.fieldNameto validate submitted data - Use
:issetmodifier to check if field was submitted - Use
:lengthmodifier for array field item counts - Chain conditions with
&&(AND) and||(OR)
Permission Debugging
When permissions fail unexpectedly:
- Check if testing as superuser (rules are bypassed!)
- Verify
@request.authis populated (user is authenticated) - Test rule expression in isolation
- Check for typos in field names
- Remember:
@request.body.*for submitted data, not@request.data.*(renamed in v0.23+)
JavaScript Event Hooks
Critical Concepts
Hook files:
- Create
*.pb.jsfiles inpb_hooks/directory - Files auto-reload on save (Unix systems only)
- Use
///for typing
Hook execution chain:
- ALL handlers must call
e.next()to continue chain - Throwing error OR not calling
e.next()stops execution - Hooks share signature:
function(e) {}
Hook Categories & Timing
See [references/hookguide.md](references/hookguide.md) for comprehensive hook reference.
Model hooks vs Request hooks:
- Model hooks (
onRecordCreate, etc.) - No request context, triggered by ANY save/delete (API, hooks, console) - Request hooks (
onRecordCreateRequest, etc.) - Have request context, only trigger from API calls
Hook timing for operations:
BEFORE e.next() → before validation & DB operation
AFTER e.next() → after validation & DB operation (may not be persisted yet)
*AfterSuccess → after confirmed DB persistence
*AfterError → after DB operation failure
Common Hook Pitfalls
Problem: Hook doesn't trigger
- Using wrong hook type (Request vs Model)
- Collection name filter is case-sensitive and must match exactly
- Hook file not saved as
*.pb.js - Syntax error preventing hook registration
Problem: Infinite recursion
- Calling
$app.dao().saveRecord()inside update hook without preventing re-trigger - JavaScript Solution: Use conditional guards to prevent re-execution
- NEVER use
$app.unsafeWithoutHooks()- bypasses ALL validations including system checks - Better approach: Check if processing already done before modifying
// ❌ DANGEROUS - Bypasses system validations
onRecordUpdate((e) => {
e.next()
$app.unsafeWithoutHooks().save(e.record) // UNSAFE!
}, "tasks")
// ✅ CORRECT - Use conditional guard
onRecordUpdate((e) => {
if (e.record.get("processed") === true) {
e.next()
return
}
e.record.set("processed", true)
e.next()
}, "tasks")
// ✅ ACCEPTABLE - Update different collection without hooks
onRecordAfterUpdateSuccess((e) => {
const related = $app.dao().findRecordById("other", e.record.get("relatedId"))
related.set("count", related.get("count") + 1)
$app.dao().withoutHooks().saveRecord(related) // OK for related records
e.next()
}, "tasks")
Problem: Changes in hook not reflected
- Modifying record AFTER
e.next()in Request hooks won't affect response - Solution: Make changes BEFORE
e.next()or use Model hooks
Problem: Cascading hooks don't fire
- When updating Record A inside Record B's hook, Record A's hooks don't trigger
- Solution: Use
e.dao.saveRecord()(note.dao.withoutHooks()) if you want cascading
Hook Patterns
See [references/hookpatterns.md](references/hookpatterns.md) for specific examples.
Common patterns:
- Auto-populate fields on create
- Validate complex business logic
- Send notifications on changes
- Update related records
- Prevent certain field modifications
Accessing Record Data
In hooks:
// Get current value
const value = e.record.get("fieldName")
// Set value
e.record.set("fieldName", newValue)
// Get original (before changes)
const original = e.record.originalCopy()
// Parse to plain object for comparison
const parsed = JSON.parse(JSON.stringify(e.record))
In API rules:
// Current record field
fieldName = "value"
// Submitted request data
@request.body.fieldName = "value"
// Auth user field
@request.auth.fieldName = "value"
// Related record via relation field
relationField.otherField = "value"
Realtime Subscriptions & Security
How Realtime Respects API Rules
Critical understanding: Realtime subscriptions enforce API rules automatically.
- Single record subscription → Uses collection's
viewRule - Collection subscription → Uses collection's
listRule
// Client subscribes to collection
pb.collection('posts').subscribe('*', (data) => {
console.log(data.action, data.record)
})
// User only receives events for records they can LIST per listRule
No Client-Side Filtering
Important: You CANNOT filter subscriptions client-side for security.
// ❌ This filter parameter is IGNORED by PocketBase
pb.collection('posts').subscribe('*', {
filter: 'status="draft"' // Does nothing!
})
// ✅ Filter via API rules instead
// In collection's listRule:
status = "published" || @request.auth.id = author
Security Implications
- Users only receive realtime events for records matching their
listRule/viewRule - Records becoming inaccessible don't emit "removed" events (performance)
- Check
@request.context = "realtime"in rules if you need realtime-specific logic - Realtime is secure by default - respects same permissions as REST API
File Structure
project/
├── pb_hooks/
│ ├── main.pb.js # Primary hooks
│ └── additional.pb.js # Split by domain if needed
├── pb_data/
│ ├── data.db # SQLite database
│ └── types.d.ts # TypeScript definitions (auto-generated)
└── pocketbase # Executable
Additional Resources
- [references/permissionpatterns.md](references/permissionpatterns.md) - Common permission rules
- [references/hookguide.md](references/hookguide.md) - Complete hook reference
- [references/hookpatterns.md](references/hookpatterns.md) - Hook implementation examples
- [references/apireference.md](references/apireference.md) - Core API methods
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: parkersweb
- Source: parkersweb/claude-pocketbase-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.