# Pocketbase Best Practices

> PocketBase development best practices covering collection design, API rules, authentication, SDK usage, query optimization, realtime subscriptions, file handling, deployment, and server-side extending (Go + JSVM). Current as of PocketBase v0.40 and JS SDK v0.28. Use when building PocketBase backends, designing schemas, implementing access control, setting up auth flows, or optimizing performance.

- Skill: `greendesertsnow/pocketbase-best-practices` (Agent Skill, multi-file: 78 files)
- Install (CLI): `npx skillmds@latest add greendesertsnow/pocketbase-best-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/greendesertsnow/pocketbase-best-practices/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: greendesertsnow (https://skillmd.com/u/greendesertsnow)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/greendesertsnow/pocketbase-best-practices

---


# PocketBase Best Practices

PocketBase is a single-binary backend (SQLite + REST + realtime + auth) that is easy to start with and easy to misuse at scale. This skill gives AI agents the guardrails: **64 rules across 9 categories**, each with an incorrect vs. correct code example, covering the mistakes agents actually make — insecure API rules, unindexed queries, broken auth flows, N+1 expands, leaked server-side handles.

**Updated for PocketBase v0.40 and JS SDK v0.28 (August 2026).** Works with v0.36+.

## How rules are prioritized

Every rule carries an impact level: **Critical** = security holes or broken data models, **High** = performance and correctness problems, **Medium/Low** = operational polish. When rules conflict, higher impact wins.

## What's covered

| Category | Impact | What it protects you from |
|----------|--------|---------------------------|
| Collection Design | Critical | Wrong field types, missing indexes, manual ID strings instead of relations |
| API Rules & Security | Critical | Data leaks from empty/incorrect access rules, filter injection |
| Authentication | Critical | Broken password/OAuth2/OTP/MFA flows, token mishandling |
| SDK Usage | High | Bad client setup, swallowed errors, cancelled-request bugs |
| Query Performance | High | N+1 queries, over-fetching, missing pagination |
| Realtime | Medium | Dropped subscriptions, unauthenticated realtime access |
| File Handling | Medium | Unvalidated uploads, wrong file URLs, missing thumbnails |
| Production & Deployment | Medium | No backups, no rate limits, exposed superuser access |
| Server-Side Extending | High | Go/JSVM hook mistakes, transaction misuse, unsafe filter binding |

## Quick Reference

### Collection Design (CRITICAL)
- **coll-field-types**: Use appropriate field types (json for objects, select for enums)
- **coll-auth-vs-base**: Extend auth collection for users, base for non-auth data
- **coll-relations**: Use relation fields, not manual ID strings
- **coll-indexes**: Create indexes on frequently filtered/sorted fields
- **coll-view-collections**: Use views for complex aggregations
- **coll-geopoint**: Store coordinates as json field with lat/lng

### API Rules (CRITICAL)
- **rules-basics**: Always set API rules; empty = public access
- **rules-filter-syntax**: Use @request.auth, @collection, @now in rules
- **rules-request-context**: Access request data via @request.body, @request.query; `@request.context` values: `default`/`oauth2`/`otp`/`password`/`realtime`/`protectedFile`
- **rules-cross-collection**: Use @collection.name.field for cross-collection checks
- **rules-locked-vs-open**: Start locked, open selectively
- **rules-strftime**: Use `strftime('%Y-%m-%d', created)` for date arithmetic (v0.36+)

### Authentication (CRITICAL)
- **auth-password**: Use authWithPassword for email/password login
- **auth-oauth2**: Configure OAuth2 providers via Admin UI; run ≥v0.38.2 (pre-linking hijack fix); provider logos come inline from `listAuthMethods()` (v0.37+)
- **auth-otp**: Two-step `requestOTP` → `authWithOTP`; rate-limit requestOTP and never leak email existence
- **auth-token-management**: Store tokens securely, refresh before expiry
- **auth-mfa**: Enable MFA for sensitive applications
- **auth-impersonation**: Use impersonation for admin actions on behalf of users

### SDK Usage (HIGH)
- **sdk-initialization**: Initialize client once, reuse instance
- **sdk-auth-store**: Use AsyncAuthStore for React Native/SSR
- **sdk-error-handling**: Catch ClientResponseError, check status codes
- **sdk-auto-cancellation**: Disable auto-cancel for concurrent requests
- **sdk-filter-binding**: Use filter binding to prevent injection

### Query Performance (HIGH)
- **query-expand**: Expand relations to avoid N+1 queries
- **query-field-selection**: Select only needed fields
- **query-pagination**: Use cursor pagination for large datasets
- **query-batch-operations**: Batch creates/updates when possible

### Realtime (MEDIUM)
- **realtime-subscribe**: Subscribe to specific records or collections
- **realtime-events**: Handle create, update, delete events separately
- **realtime-auth**: Realtime respects API rules automatically; auth unsets on password change; ~30min absolute connection cap (v0.38+)
- **realtime-reconnection**: Implement reconnection logic — periodic reconnects are by design, re-sync state instead of fighting them

### File Handling (MEDIUM)
- **file-upload**: Use FormData for uploads, set proper content types
- **file-serving**: Use pb.files.getURL() for file URLs
- **file-validation**: Validate file types and sizes server-side

### Deployment (MEDIUM)
- **deploy-backup**: Schedule regular backups of pb_data
- **deploy-configuration**: Use environment variables for config; mind v0.40 log caps (`Log.Data` ~16KB) and treat the SQL console (v0.39+) as debug-only
- **deploy-reverse-proxy**: Put behind nginx/caddy in production
- **deploy-sqlite-considerations**: Optimize SQLite for production workloads
- **deploy-rate-limiting**: Enable the built-in rate limiter (fixed-window as of v0.36.7); front with Nginx/Caddy for defense in depth
- **deploy-scaling**: Raise `ulimit -n` for realtime, set `GOMEMLIMIT`, enable settings encryption
- **deploy-superuser-ips**: Whitelist superuser access by IP/CIDR in production (v0.38+); recovery via the `superuser ips` console command

### Server-Side Extending (HIGH)
- **ext-go-setup**: Use `app.OnServe()` to register routes; use `e.App` inside hooks, not the parent-scope app; v0.40 needs Go 1.27 (`encoding/json/v2` — test before shipping)
- **ext-js-setup**: Drop `*.pb.js` in `pb_hooks/`; add `/// <reference path="../pb_data/types.d.ts" />`
- **ext-hooks-chain**: Always call `e.Next()`/`e.next()`; use `Bind` with an Id for later `Unbind`
- **ext-hooks-record-vs-request**: Use `OnRecordEnrich` to shape responses (incl. realtime); `OnRecordRequest` for HTTP-only
- **ext-routing-custom**: Namespace routes under `/api/{yourapp}/`; attach `RequireAuth()` middleware
- **ext-transactions**: Use the scoped `txApp` inside `RunInTransaction`; never capture the outer `app`
- **ext-filter-binding-server**: Bind user input with `{:name}` + `dbx.Params` in `FindFirstRecordByFilter` / `FindRecordsByFilter`
- **ext-filesystem**: `defer fs.Close()` on every `NewFilesystem()` / `NewBackupsFilesystem()` handle
- **ext-cron-jobs**: Register with `app.Cron().MustAdd(id, expr, fn)` / `cronAdd()`; stable ids, no `__pb*__` prefix
- **ext-go-migrations**: Versioned `.go` files under `migrations/`; `Automigrate: osutils.IsProbablyGoRun()`
- **ext-js-migrations**: `pb_migrations/<unix>_*.js` with `migrate(upFn, downFn)`; auto-discovered by filename
- **ext-mailer**: Resolve sender from `app.Settings().Meta` at send-time; never ship `no-reply@example.com`; create the mail client per send
- **ext-settings**: Read via `app.Settings()` at call time; set `PB_ENCRYPTION` (32 chars) to encrypt `_params` at rest
- **ext-testing**: `tests.NewTestApp(testDataDir)` + `tests.ApiScenario`; `defer app.Cleanup()`, assert `ExpectedEvents`
- **ext-compose-request-flow**: Composite walkthrough showing which app instance is active at each layer (route → tx → hook → enrich)
- **ext-go-custom-sqlite**: Only use `DBConnect` when you need FTS5/ICU; `DBConnect` is called twice (data.db + auxiliary.db)
- **ext-jsvm-scope**: Variables outside handlers are undefined at runtime — load shared config via `require()` inside the handler
- **ext-jsvm-modules**: Only CJS (`require()`) works in goja; bundle ESM first; avoid mutable module state

## Example Prompts

Try these with your AI agent to see the skill in action:

**Building a new feature:**
- "Design a PocketBase schema for an e-commerce app with products, orders, and reviews"
- "Implement OAuth2 login with Google and GitHub for my app"
- "Build a real-time notification system with PocketBase subscriptions"
- "Create a file upload form with image validation and thumbnail previews"

**Fixing issues:**
- "My list query is slow on 100k records -- optimize it"
- "I'm getting 403 errors on my batch operations"
- "Fix the N+1 query problem in my posts list that loads author data in a loop"
- "My realtime subscriptions stop working after a few minutes"

**Security review:**
- "Review my API rules -- users should only access their own data"
- "Set up proper access control: admins manage all content, users edit only their own"
- "Are my authentication cookies configured securely for SSR?"
- "Audit my collection rules for IDOR vulnerabilities"

**Going to production:**
- "Configure Nginx with HTTPS, rate limiting, and security headers for PocketBase"
- "Set up automated backups for my PocketBase database"
- "Optimize SQLite settings for a production workload with ~500 concurrent users"
- "Deploy PocketBase with Docker Compose and Caddy"

**Extending PocketBase:**
- "Add a custom Go route that sends a Slack notification after a record is created"
- "Write a pb_hooks script that validates an email domain before user signup"
- "Set up FTS5 full-text search with a custom SQLite driver in my Go app"
- "Share a config object across multiple pb_hooks files without race conditions"

## Detailed Rules

Load the relevant category for complete rule documentation with code examples:

- [Collection Design](references/collection-design.md) - Schema patterns, field types, relations, indexes
- [API Rules & Security](references/api-rules-security.md) - Access control, filter expressions, security patterns
- [Authentication](references/authentication.md) - Password auth, OAuth2, MFA, token management
- [SDK Usage](references/sdk-usage.md) - Client initialization, auth stores, error handling, hooks
- [Query Performance](references/query-performance.md) - Pagination, expansion, batch operations, N+1 prevention
- [Realtime](references/realtime.md) - SSE subscriptions, event handling, reconnection
- [File Handling](references/file-handling.md) - Uploads, serving, validation
- [Production & Deployment](references/production-deployment.md) - Backup, configuration, reverse proxy, SQLite optimization
- [Server-Side Extending](references/server-side-extending.md) - Go/JSVM setup, event hooks, custom routes, modules, custom SQLite

