PocketBase — Structure#
Layout pattern#
Framework-specific / Custom — PocketBase does not follow the standard Go layout (cmd/internal/pkg). Instead, the root package is the library API (pocketbase.go), the actual binary entry point lives in examples/base/main.go, and utilities are collected under a flat tools/ subtree. There is no internal/ directory; every package is publicly importable, reflecting the dual-mode design (standalone binary + embeddable library).
Directory map#
pocketbase/
├── pocketbase.go # Root package: PocketBase struct (implements core.App), library facade
├── pocketbase_test.go
├── modernc_versions_check.go # Build-time check for modernc SQLite version alignment
│
├── cmd/ # Cobra subcommands (package cmd, NOT a main package)
│ ├── serve.go # "serve" command — starts the HTTP server
│ └── superuser.go # "superuser" command — manage superuser accounts
│
├── core/ # Domain layer: app interface, models, DB, settings (~123 files)
│ ├── app.go # core.App interface — the central contract for the entire application
│ ├── base.go # BaseApp: concrete implementation of core.App
│ ├── db.go / db_connect.go / db_builder.go # SQLite wiring and query builder
│ ├── collection_model.go / record_model.go / ... # Domain models
│ ├── auth_origin_model.go / mfa_model.go / otp_model.go # Auth models
│ └── validators/ # Field and model validation logic
│
├── apis/ # HTTP API layer: route handlers and middleware (~60 files)
│ ├── base.go # Route registration and server bootstrap
│ ├── record_crud.go # Generic CRUD handlers for collections
│ ├── record_auth*.go # Auth endpoints (password, OAuth2, OTP, MFA, etc.)
│ ├── realtime.go # SSE-based realtime subscriptions
│ ├── middlewares*.go # CORS, GZIP, rate limiting, body limit
│ └── serve.go # HTTP server start helper
│
├── forms/ # Input validation / request binding for complex operations
│ ├── record_upsert.go # Record create/update form with field validation
│ └── apple_client_secret_create.go # Apple OAuth2 client secret generator
│
├── mails/ # Email sending utilities
│ └── templates/ # HTML email templates (verification, reset, OTP, etc.)
│
├── migrations/ # Built-in system DB migrations (applied on startup)
│
├── plugins/ # Optional, independently registerable plugins
│ ├── ghupdate/ # GitHub self-update mechanism
│ ├── jsvm/ # Goja JavaScript runtime for pb_hooks and pb_migrations
│ │ └── internal/types/ # TypeScript type definitions for the JS API (generated)
│ └── migratecmd/ # CLI "migrate" command with Go and JS template generation
│
├── tests/ # Integration test helpers and test fixture data
│ └── data/ # SQLite test DB, sample file uploads (storage/)
│
├── tools/ # Reusable utility sub-library (each sub-dir is its own package)
│ ├── archive/ # ZIP archive creation
│ ├── auth/ # OAuth2 provider abstractions (Google, GitHub, etc.)
│ │ └── internal/jwk/ # JWK parsing for OIDC
│ ├── cron/ # Cron job scheduler
│ ├── dbutils/ # DB query helpers
│ ├── filesystem/ # File storage abstraction (local disk + S3-compatible)
│ │ ├── blob/ # Blob storage interface
│ │ └── internal/ # fileblob (local) and s3blob (S3) implementations
│ ├── hook/ # Typed event hook system (core extensibility mechanism)
│ ├── inflector/ # Pluralize/singularize/camelize string utilities
│ ├── list/ # Generic list/slice helpers
│ ├── logger/ # Structured logger (wraps stdlib slog)
│ ├── mailer/ # SMTP / sendmail abstraction
│ ├── osutils/ # OS-level helpers (process detection, etc.)
│ ├── picker/ # JSON field picking / response shaping
│ ├── router/ # Custom HTTP router (wraps stdlib net/http)
│ ├── routine/ # Safe goroutine launcher with panic recovery
│ ├── search/ # Filtering, sorting, and pagination helpers
│ ├── security/ # JWT signing, token generation, encryption utilities
│ ├── store/ # Generic thread-safe in-memory key-value store
│ ├── subscriptions/ # SSE client subscription management
│ ├── template/ # HTML template rendering helpers
│ ├── tokenizer/ # Lexer for the filter expression language
│ └── types/ # Custom JSON-serializable scalar types (DateTime, JsonMap, etc.)
│
├── examples/
│ └── base/
│ └── main.go # THE canonical entry point — production binary wired with all plugins
│
└── ui/ # Svelte-based Admin UI (pre-built; not compiled at Go build time)
├── dist/ # Pre-built static assets embedded into the binary
└── src/ # Svelte source (components, stores, actions) — for UI dev onlyEntry points#
| File | Binary | Purpose |
|---|---|---|
examples/base/main.go | pocketbase | The standalone binary. Instantiates pocketbase.New(), registers all plugins (jsvm, migratecmd, ghupdate), wires the static file route, then calls app.Start(). This is what goreleaser builds and what users download from GitHub Releases. |
There is no separate cmd/*/main.go. The cmd/ package provides Cobra subcommands (serve, superuser) that are registered programmatically by the PocketBase struct in pocketbase.go. Users embedding PocketBase as a library get these commands for free via app.RootCmd.
Package organization#
Root package (
github.com/pocketbase/pocketbase): The public library API.PocketBasestruct embedscore.Appand exposesStart(),Bootstrap(), and the Cobra root command. This is what library consumers import.core/: The domain and infrastructure layer. Defines theAppinterface, all domain models (Collections, Records, Auth, MFA, OTP, Logs, Settings), the SQLite connection, query builder, and system migrations. This is the heaviest package (~123 source files). All other packages depend oncore, butcorehas no dependencies onapis,forms, orplugins.apis/: HTTP presentation layer. Registers all REST routes on acore.Appinstance. Pure handler code with no business logic of its own — delegates tocoreandforms.forms/: Input validation and binding for operations that are too complex for in-handler validation (record upsert with dynamic schema, Apple secret generation).mails/: Outbound email composition using Go’shtml/templateand thetools/mailerabstraction.migrations/: Built-in schema migrations that are auto-applied on startup. Uses a simple sequential migration registry (not an external tool).plugins/: Opt-in extensions. Each plugin receivescore.Appand registers hooks and CLI commands on it. Completely decoupled fromapis/andforms/.tools/: A flat collection of ~20 single-purpose utility packages. Each is independently usable and has minimal coupling to the rest of PocketBase.tools/hookis the most architecturally critical — it underpins the entire event system.tests/: Shared test helpers and fixture data (not atestutilpackage used by all packages; each package has its own_test.gofiles).Layering: Roughly layered, with
coreat the bottom,tools/as a peer utility layer, andapis/forms/mailsas the presentation layer above. Plugins sit outside the main layer graph and hook in via the event system. The rootpocketbasepackage is the assembly/wiring layer at the top.
Build system#
- Build tool:
makefor development tasks (lint, test, jstypes);goreleaserfor release artifact generation. - Key targets:
make test—go test ./... -v --covermake lint— golangci-lintmake jstypes— regenerates TypeScript type definitions for the JS plugin APIgoreleaser release— cross-compiles for linux/windows/darwin × amd64/arm64/arm/s390x/ppc64le
- Binary:
CGO_ENABLED=0— pure Go, no C dependencies. The SQLite driver (modernc.org/sqlite) is a transpiled pure-Go port, making cross-compilation straightforward. - Docker: None in this repository. PocketBase ships as a static binary; users bring their own container runtime if desired.
- UI: The Svelte Admin UI is pre-built and committed to
ui/dist/. It is embedded into the Go binary at compile time usinggo:embed. The Svelte source inui/src/is only for UI developers; it is not part of the Go build.
Notable structural decisions#
examples/baseas the production binary. The canonical binary entry point is namedexamples/base/main.gorather thancmd/pocketbase/main.go. This makes the repository simultaneously a working application (rungo run ./examples/base) and a library that others embed. The naming is intentional: the “example” serves as the canonical, supported reference implementation rather than a trivial demo.Root package as library facade. Placing
pocketbase.goat the module root (not inpkg/) means library consumers writeimport "github.com/pocketbase/pocketbase"— a clean, memorable path that doubles as the package name. The struct at this layer is thin: it mostly delegates tocore.App.No
internal/anywhere. Every package is publicly importable, which is a deliberate choice for library users who may need to extend any layer. This trades encapsulation for flexibility.tools/as a micro-utility library. Rather than one bigutilpackage, PocketBase splits utilities into ~20 fine-grained packages (hook,router,store,types,search, etc.). Each can be imported independently. This also makes the dependency graph explicit and testable.Pre-built UI committed to source. The Svelte Admin UI’s built artifacts live in
ui/dist/and are committed to the repository. This avoids requiring Node.js during the Go build and keeps the single-binary promise trivially achievable for anyone runninggo install. The trade-off is a larger git history and occasional large diff commits when the UI is updated.