Skip to main content
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

1

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.
2

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

1

Create Project Directory

mkdir my-pb-project
cd my-pb-project
2

Initialize Go Module

go mod init my-pb-project
This creates a go.mod file that tracks your dependencies.
3

Create Main Application File

Create the directory structure and main file:
mkdir -p cmd/server
Create cmd/server/main.go with the following content:
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")
    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.
4

Download Dependencies

go mod tidy
This downloads pb-ext and all its dependencies (PocketBase, gopsutil, etc.).
5

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"
6

Run Your Server

pb-cli --run-only
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.
1

Create Routes File

Create cmd/server/routes.go:
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.
2

Restart the Server

Stop the server (Ctrl+C) and restart it:
pb-cli --run-only
3

Test Your Endpoint

curl http://127.0.0.1:8090/api/v1/time
Expected response:
{
  "time": "2026-03-04T10:30:45Z"
}
4

View Auto-Generated Docs

Open http://127.0.0.1:8090/api/docs/v1/swaggerYou’ll see a Swagger UI with your /api/v1/time endpoint fully documented, including the response schema automatically inferred from your code.

Add a Cron Job

Let’s schedule a background task that runs every minute.
1

Create Jobs File

Create cmd/server/jobs.go:
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")
        },
    )
}
2

Restart and View Job Logs

Restart the server:
pb-cli --run-only
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.
1

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
}
2

Restart Server

pb-cli --run-only
The “todos” collection will be automatically created on startup.
3

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

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:
go mod tidy
The dashboard requires admin authentication. Make sure you:
  1. Created an admin account at /_/
  2. 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