Overview
pb-ext uses the functional options pattern for server configuration. This provides a flexible, type-safe way to customize server behavior without breaking changes.
Server Options
All server options are defined in core/server/server_options.go and re-exported through the public facade.
Available Options
WithConfig Provide a custom PocketBase configuration
WithPocketbase Use an existing PocketBase instance
InDeveloperMode Enable developer mode (hot reload, verbose logging)
InNormalMode Enable production mode (optimized, minimal logging)
Option Functions
InDeveloperMode / InNormalMode
The simplest way to configure the server:
func initApp ( devMode bool ) {
var opts [] app . Option
if devMode {
opts = append ( opts , app . InDeveloperMode ())
} else {
opts = append ( opts , app . InNormalMode ())
}
srv := app . New ( opts ... )
// ...
}
Implementation:
core/server/server_options.go
// InDeveloperMode is a shortcut to enable developer mode.
func InDeveloperMode () Option {
return func ( opts * options ) {
opts . developer_mode = true
log . Println ( "🔧 Developer mode" )
}
}
// InNormalMode is a shortcut to disable developer mode.
func InNormalMode () Option {
return func ( opts * options ) {
opts . developer_mode = false
log . Println ( "🚀 Production mode" )
}
}
Developer mode sets PocketBase’s DefaultDev flag, enabling features like auto-migrations and verbose logging.
WithConfig
Provide a custom PocketBase configuration:
pbConfig := & pocketbase . Config {
DefaultDev : true ,
DefaultDataDir : "./custom_pb_data" ,
}
srv := app . New ( app . WithConfig ( pbConfig ))
Implementation:
core/server/server_options.go
// WithConfig sets the PocketBase configuration to use.
// Using this together with WithPocketbase will panic.
func WithConfig ( config * pocketbase . Config ) Option {
return func ( opts * options ) {
opts . config = config
}
}
WithConfig and WithPocketbase cannot be used together. If both are provided, the server will panic with ErrConfigurationConflict.
WithPocketbase
Use an existing PocketBase instance:
pb := pocketbase . New ()
// Customize pb here...
srv := app . New ( app . WithPocketbase ( pb ))
Implementation:
core/server/server_options.go
// WithPocketbase sets a fully initialized PocketBase instance to use.
// Cannot be used together with WithConfig; will panic if a config is already set.
func WithPocketbase ( pocketbase * pocketbase . PocketBase ) Option {
return func ( opts * options ) {
if opts . config != nil {
pocketbase . Logger (). Error ( ErrConfigurationConflict . Error ())
panic ( ErrConfigurationConflict )
}
opts . pocketbase = pocketbase
}
}
Use WithPocketbase when you need fine-grained control over the PocketBase instance before pb-ext wraps it.
WithMode
Generic mode setter (used internally by InDeveloperMode / InNormalMode):
core/server/server_options.go
// WithMode sets whether developer mode is enabled.
func WithMode ( developer_mode bool ) Option {
return func ( opts * options ) {
opts . developer_mode = developer_mode
}
}
Configuration Patterns
Pattern 1: Simple Mode Toggle
The most common pattern - toggle between dev and production:
func main () {
devMode := flag . Bool ( "dev" , false , "Run in developer mode" )
flag . Parse ()
initApp ( * devMode )
}
func initApp ( devMode bool ) {
var opts [] app . Option
if devMode {
opts = append ( opts , app . InDeveloperMode ())
} else {
opts = append ( opts , app . InNormalMode ())
}
srv := app . New ( opts ... )
// ...
}
Pattern 2: Custom PocketBase Config
Provide a custom data directory or other PocketBase settings:
pbConfig := & pocketbase . Config {
DefaultDev : true ,
DefaultDataDir : "./custom_pb_data" ,
}
srv := app . New ( app . WithConfig ( pbConfig ))
Pattern 3: Existing PocketBase Instance
Use an existing PocketBase instance (useful for testing or advanced setups):
pb := pocketbase . New ()
pb . Logger (). SetLevel ( slog . LevelDebug )
// Other PocketBase customizations...
srv := app . New ( app . WithPocketbase ( pb ))
Pattern 4: Custom Port
Set a custom port using command-line arguments:
func main () {
devMode := flag . Bool ( "dev" , false , "Run in developer mode" )
port := flag . String ( "port" , "8090" , "HTTP port" )
flag . Parse ()
// Inject custom port into os.Args for PocketBase
os . Args = [] string { "app" , "serve" , fmt . Sprintf ( "--http=127.0.0.1: %s " , * port )}
initApp ( * devMode )
}
PocketBase reads port configuration from os.Args. Set it before calling srv.Start().
Environment Variables
While pb-ext doesn’t enforce specific environment variables, you can integrate them with standard Go patterns:
func main () {
// Read from environment
devMode := os . Getenv ( "DEV_MODE" ) == "true"
dataDir := os . Getenv ( "DATA_DIR" )
if dataDir == "" {
dataDir = "./pb_data"
}
pbConfig := & pocketbase . Config {
DefaultDev : devMode ,
DefaultDataDir : dataDir ,
}
srv := app . New ( app . WithConfig ( pbConfig ))
// ...
}
Common Environment Variables
Variable Purpose Example DEV_MODEEnable developer mode true / falseDATA_DIRPocketBase data directory ./pb_dataHTTP_PORTServer port 8090LOG_LEVELLogging level debug / info / warn / error
PocketBase Integration
Accessing PocketBase
The underlying PocketBase instance is accessible via srv.App():
srv := app . New ( app . InDeveloperMode ())
// Access PocketBase directly
pb := srv . App ()
// Use PocketBase APIs
pb . OnServe (). BindFunc ( func ( e * core . ServeEvent ) error {
// Custom route
e . Router . GET ( "/custom" , func ( c * core . RequestEvent ) error {
return c . JSON ( 200 , map [ string ] string { "message" : "Hello" })
})
return e . Next ()
})
PocketBase Configuration
The pocketbase.Config struct supports these fields:
type Config struct {
DefaultDev bool // Enable developer mode
DefaultDataDir string // Data directory path
DefaultDebug bool // Enable debug mode
}
Hook Registration
Register hooks on the PocketBase instance before starting the server:
srv := app . New ( app . InDeveloperMode ())
// Register collections
registerCollections ( srv . App ())
// Register routes
registerRoutes ( srv . App ())
// Register jobs
registerJobs ( srv . App ())
// Custom serve hook
srv . App (). OnServe (). BindFunc ( func ( e * core . ServeEvent ) error {
app . SetupRecovery ( srv . App (), e )
return e . Next ()
})
// Start server (triggers all hooks)
if err := srv . Start (); err != nil {
log . Fatal ( err )
}
Complete Example
Here’s the complete example from cmd/server/main.go:
package main
import (
" flag "
" log "
app " github.com/magooney-loon/pb-ext/core "
" github.com/pocketbase/pocketbase/core "
)
func main () {
devMode := flag . Bool ( "dev" , false , "Run in developer mode" )
generateSpecsDir := flag . String ( "generate-specs-dir" , "" , "Generate OpenAPI specs" )
validateSpecsDir := flag . String ( "validate-specs-dir" , "" , "Validate OpenAPI specs" )
flag . Parse ()
// OpenAPI spec generation mode
if * generateSpecsDir != "" {
gen := app . NewSpecGeneratorWithInitializer ( func () ( * app . APIVersionManager , error ) {
return initVersionedSystem (), nil
})
if err := gen . Generate ( * generateSpecsDir , "" ); err != nil {
log . Fatal ( err )
}
return
}
// OpenAPI spec validation mode
if * validateSpecsDir != "" {
gen := app . NewSpecGeneratorWithInitializer ( func () ( * app . APIVersionManager , error ) {
return initVersionedSystem (), nil
})
if err := gen . Validate ( * validateSpecsDir ); err != nil {
log . Fatal ( err )
}
return
}
initApp ( * devMode )
}
func initApp ( devMode bool ) {
var opts [] app . Option
if devMode {
opts = append ( opts , app . InDeveloperMode ())
} else {
opts = append ( opts , app . InNormalMode ())
}
// Option 1: Use a custom PocketBase config
// pbConfig := &pocketbase.Config{
// DefaultDev: true,
// DefaultDataDir: "./custom_pb_data",
// }
// opts = append(opts, app.WithConfig(pbConfig))
// Option 2: Use an existing PocketBase instance
// pb := pocketbase.New()
// opts = append(opts, app.WithPocketbase(pb))
// Set custom port programmatically
// os.Args = []string{"app", "serve", "--http=127.0.0.1:9090"}
srv := app . New ( opts ... )
app . SetupLogging ( srv )
registerCollections ( srv . App ())
registerRoutes ( srv . App ())
registerJobs ( srv . App ())
srv . App (). OnServe (). BindFunc ( func ( e * core . ServeEvent ) error {
app . SetupRecovery ( srv . App (), e )
return e . Next ()
})
if err := srv . Start (); err != nil {
srv . App (). Logger (). Error ( "Fatal application error" ,
"error" , err ,
"uptime" , srv . Stats (). StartTime ,
"total_requests" , srv . Stats (). TotalRequests . Load (),
)
log . Fatal ( err )
}
}
Options Internal Structure
The internal options struct:
core/server/server_options.go
type options struct {
config * pocketbase . Config
pocketbase * pocketbase . PocketBase
developer_mode bool
}
type Option func ( * options )
Configuration Conflict
Attempting to use both WithConfig and WithPocketbase results in a panic:
core/server/server_options.go
var ErrConfigurationConflict = errors . New (
`WithConfig cannot be used together with WithPocketbase, cause second ` +
`contains already initialized pocketbase.Config instance. Just pass your ` +
`config into pocketbase.NewWithConfig func, that's enough.` ,
)
Best Practices
Use Simple Mode Toggle
Prefer InDeveloperMode() / InNormalMode() for most use cases
Only Customize When Needed
Only use WithConfig / WithPocketbase if you need advanced PocketBase customization
Environment-Aware Configuration
Read mode and settings from environment variables or flags
Register Hooks Before Start
Always register user hooks before calling srv.Start()
Next Steps
Server Lifecycle Understand bootstrap, serve, and runtime phases
Job Management Learn how to register and manage cron jobs