This guide will walk you through creating a minimal pb-ext application from scratch. By the end, you’ll have a running server with monitoring, logging, and API documentation.
Prerequisites
Install Go
pb-ext requires Go 1.25.7 or higher . # Check your Go version
go version
If you need to install or upgrade Go, visit golang.org/dl .
Verify Installation
Ensure Go is properly installed and in your PATH: # This should display the Go version
go version
# Expected output: go version go1.25.7 linux/amd64
Create Your First pb-ext Project
Create Project Directory
mkdir my-pb-project
cd my-pb-project
Initialize Go Module
go mod init my-pb-project
This creates a go.mod file that tracks your dependencies.
Create Main Application File
Create the directory structure and main file: Create cmd/server/main.go with the following content: 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" )
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 ... )
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 )
}
}
func registerCollections ( app core . App ) {
// We'll add collections later
}
func registerRoutes ( app core . App ) {
// We'll add routes later
}
func registerJobs ( app core . App ) {
// We'll add jobs later
}
This is the exact structure from cmd/server/main.go in the pb-ext repository. The file location matters: pb-cli expects your entry point at cmd/server/main.go.
Download Dependencies
This downloads pb-ext and all its dependencies (PocketBase, gopsutil, etc.).
Install pb-cli Toolchain
go install github.com/magooney-loon/pb-ext/cmd/pb-cli@latest
The pb-cli toolchain automates building, running, and deploying pb-ext applications. Make sure your $GOPATH/bin or $HOME/go/bin is in your PATH. If pb-cli is not found, add this to your shell profile: export PATH = " $PATH :$( go env GOPATH)/bin"
Run Your Server
You should see output similar to: > Server: http://127.0.0.1:8090
├─ REST API: http://127.0.0.1:8090/api/
└─ Admin UI: http://127.0.0.1:8090/_
└─ pb-ext Dashboard: http://127.0.0.1:8090/_/_
The --run-only flag skips frontend building (useful when you don’t have a frontend yet).
Access Your Application
PocketBase Admin Panel Open http://127.0.0.1:8090/_ Create your first admin account to access the PocketBase admin UI.
pb-ext Dashboard Open http://127.0.0.1:8090// View system metrics, analytics, and cron job logs (requires admin auth).
Add Your First API Endpoint
Let’s add a simple API endpoint to see how pb-ext auto-generates documentation.
Create Routes File
Create cmd/server/routes.go: package main
// API_SOURCE
import (
" time "
" github.com/magooney-loon/pb-ext/core/server/api "
" github.com/pocketbase/pocketbase/core "
)
func registerRoutes ( app core . App ) {
versionManager := initVersionedSystem ()
app . OnServe (). BindFunc ( func ( e * core . ServeEvent ) error {
if err := versionManager . RegisterAllVersionRoutes ( e ); err != nil {
return err
}
return e . Next ()
})
versionManager . RegisterWithServer ( app )
}
func initVersionedSystem () * api . APIVersionManager {
v1Config := & api . APIDocsConfig {
Title : "My API" ,
Description : "My first pb-ext API" ,
Version : "1.0.0" ,
Status : "stable" ,
BaseURL : "http://127.0.0.1:8090/" ,
Enabled : true ,
PublicSwagger : true ,
}
return api . InitializeVersionedSystemWithRoutes ( map [ string ] * api . VersionSetup {
"v1" : {
Config : v1Config ,
Routes : registerV1Routes ,
},
}, "v1" )
}
// API_DESC Returns the current server time
// API_TAGS utilities
func timeHandler ( e * core . RequestEvent ) error {
return e . JSON ( 200 , map [ string ] string {
"time" : time . Now (). Format ( time . RFC3339 ),
})
}
func registerV1Routes ( router * api . VersionedAPIRouter ) {
router . GET ( "/api/v1/time" , timeHandler )
}
The // API_SOURCE comment at the top tells pb-ext to parse this file for OpenAPI metadata. The API_DESC and API_TAGS directives add documentation to your endpoint.
Restart the Server
Stop the server (Ctrl+C) and restart it:
Test Your Endpoint
curl http://127.0.0.1:8090/api/v1/time
Expected response: {
"time" : "2026-03-04T10:30:45Z"
}
Add a Cron Job
Let’s schedule a background task that runs every minute.
Create Jobs File
Create cmd/server/jobs.go: package main
import (
" fmt "
" time "
" github.com/magooney-loon/pb-ext/core/jobs "
" github.com/pocketbase/pocketbase/core "
)
func registerJobs ( app core . App ) {
app . OnServe (). BindFunc ( func ( e * core . ServeEvent ) error {
if err := helloJob ( app ); err != nil {
app . Logger (). Error ( "Failed to register hello job" , "error" , err )
return err
}
app . Logger (). Info ( "Cron jobs registered successfully" )
return e . Next ()
})
}
func helloJob ( app core . App ) error {
jm := jobs . GetManager ()
if jm == nil {
return fmt . Errorf ( "job manager not initialized" )
}
return jm . RegisterJob (
"helloWorld" ,
"Hello World Job" ,
"A demonstration job that runs every minute" ,
"*/1 * * * *" , // Every minute
func ( el * jobs . ExecutionLogger ) {
el . Start ( "Hello World Job" )
el . Info ( "Current time: %s " , time . Now (). Format ( "2006-01-02 15:04:05" ))
el . Progress ( "Processing task..." )
// Simulate work
time . Sleep ( 100 * time . Millisecond )
el . Success ( "Task completed successfully" )
el . Complete ( "Job finished" )
},
)
}
Restart and View Job Logs
Restart the server: After waiting 1 minute, check the dashboard at http://127.0.0.1:8090// to see your job execution logs.
Add a Database Collection
Let’s create a simple “todos” collection.
Create Collections File
Create cmd/server/collections.go: cmd/server/collections.go
package main
import (
" github.com/pocketbase/pocketbase/core "
)
func registerCollections ( app core . App ) {
app . OnServe (). BindFunc ( func ( e * core . ServeEvent ) error {
if err := todoCollection ( e . App ); err != nil {
app . Logger (). Error ( "Failed to create todo collection" , "error" , err )
}
return e . Next ()
})
}
func todoCollection ( app core . App ) error {
existingCollection , _ := app . FindCollectionByNameOrId ( "todos" )
if existingCollection != nil {
return nil // Already exists
}
collection := core . NewBaseCollection ( "todos" )
collection . Fields . Add ( & core . TextField {
Name : "title" ,
Required : true ,
Max : 200 ,
})
collection . Fields . Add ( & core . BoolField {
Name : "completed" ,
})
collection . Fields . Add ( & core . AutodateField {
Name : "created" ,
OnCreate : true ,
})
// Public access for demo
collection . ViewRule = nil
collection . CreateRule = nil
collection . UpdateRule = nil
collection . DeleteRule = nil
if err := app . Save ( collection ); err != nil {
return err
}
app . Logger (). Info ( "Created todos collection" )
return nil
}
Restart Server
The “todos” collection will be automatically created on startup.
Test CRUD Operations
Create a todo: curl -X POST http://127.0.0.1:8090/api/collections/todos/records \
-H "Content-Type: application/json" \
-d '{"title": "Learn pb-ext", "completed": false}'
List todos: curl http://127.0.0.1:8090/api/collections/todos/records
Project Structure
Your project should now look like this:
my-pb-project/
├── cmd/
│ └── server/
│ ├── main.go # Entry point
│ ├── routes.go # API routes
│ ├── collections.go # Database schemas
│ └── jobs.go # Cron jobs
├── pb_data/ # Created automatically (database)
├── pb_public/ # Static files (optional)
├── go.mod # Go dependencies
└── go.sum # Dependency checksums
You can restructure your project however you like, but cmd/server/main.go is the conventional entry point expected by pb-cli.
Next Steps
Troubleshooting
pb-cli: command not found
Make sure $GOPATH/bin is in your PATH: export PATH = " $PATH :$( go env GOPATH)/bin"
Add this to your .bashrc or .zshrc to make it permanent.
Change the port by passing flags to the server: pb-cli --run-only -- serve --http=127.0.0.1:9090
Or set it programmatically in main.go: os . Args = [] string { "app" , "serve" , "--http=127.0.0.1:9090" }
Run go mod tidy to ensure all dependencies are downloaded:
The dashboard requires admin authentication. Make sure you:
Created an admin account at /_/
Logged in before accessing /_/_
Example Project
Want to see a complete working example? The pb-ext repository includes a full example server in cmd/server/ with:
Multiple API versions (v1, v2)
CRUD routes with OpenAPI docs
Cron jobs with logging
Collection definitions
Request middleware
Clone the repository to explore:
git clone https://github.com/magooney-loon/pb-ext.git
cd pb-ext
pb-cli --run-only