Server

A package can extend the app shell’s Go server - add custom API endpoints, register PocketBase record hooks in Go, run background tickers, or bind IMAP/SMTP services. The app shell loads each present package’s server module at startup via a single generated entry point. Most packages do not need this: if all your server-side logic fits in PocketBase JS hooks, stick with pb-hooks/ instead.

For the Go module plumbing (the generated go.work, how the build picks up each member’s module), see Go server integration. This page covers the layout a package author owns.

Declaring a server module

Add a server field to the manifest with two strings:

server: { package: 'server', module: 'tinycld.org/packages/example' },

The generator lists each present feature’s server module in a generated tinycld/server/go.work, so the build resolves your module’s location automatically. On pnpm run packages:generate the wiring stays in sync with the set of present members.

Directory layout

A minimal server package looks like this:

example/
    server/
        go.mod
        go.sum
        register.go
        endpoints.go

go.mod declares the module path you put in the manifest, and requires core’s module:

module tinycld.org/packages/example

go 1.25.0

require (
    github.com/pocketbase/pocketbase v0.38.1
    tinycld.org/core v0.0.0
)

Keep the PocketBase version aligned with the app shell’s (tinycld/server/go.mod) - the app’s go.mod is the source of truth, and tinycld/server/go.work lashes every module to the same PB build. Bumping the shell is what drags every package’s go.mod along.

Depend only on what your package actually uses. @tinycld/core’s Go module (tinycld.org/core, nested at tinycld/core/server/) carries the shared dependencies (audit logging, auth helpers, the PocketBase runtime itself) and exposes them under tinycld.org/core/... subpackages you can import. You do not add a replace directive for it - the generated tinycld/server/go.work resolves every member module’s location.

The Register function

Each package server must export func Register(app *pocketbase.PocketBase). The app shell’s generated tinycld/server/package_extensions.go calls it once (via registerPackageExtensions(app)), before the server starts. This is where you bind endpoints, register hooks, and kick off any long-lived goroutines:

package example

import (
    "net/http"

    "github.com/pocketbase/pocketbase"
    "github.com/pocketbase/pocketbase/core"
    "tinycld.org/core/audit"
)

func Register(app *pocketbase.PocketBase) {
    audit.RegisterCollection(app, "example_items", &audit.CollectionConfig{
        ExtractLabel: audit.LabelFromField("title"),
    })

    app.OnServe().BindFunc(func(e *core.ServeEvent) error {
        e.Router.GET("/api/example/ping", func(c *core.RequestEvent) error {
            return c.JSON(http.StatusOK, map[string]string{"ok": "pong"})
        })
        return e.Next()
    })
}

Register runs whether or not the package has any migrations or collections - it’s wired by manifest alone. If you need access to app state across requests, hold it in package-level variables (as @tinycld/mail does with its settings cache) or attach it to a struct you construct inside Register.

When you do not need server code

Skip the server field entirely if your package only needs:

Go server extensions exist for the cases where JS hooks aren’t enough: network servers (IMAP, SMTP, WebRTC signalling), background workers, HTTP endpoints that need streaming or binary handling, or logic that has to run outside a record event.

Testing server code

Standard Go test files (*_test.go) live next to the package source inside server/. Run them from inside the member:

cd ~/code/tinycld/example/server
go test ./...

The member’s Go module is self-contained for testing - it pins the same PocketBase version core uses, so test code hits the same API surface it will in production. Within an assembled workspace, the generated tinycld/server/go.work ties this module, core, and the app together, so a build from tinycld/server/ reflects exactly what the app ships.