Go server extensions

A package that needs to run Go code on the server - IMAP/SMTP, custom HTTP endpoints, long-lived workers, anything that doesn’t fit in PocketBase’s JS hooks - ships a server/ subdirectory with its own Go module. The generator wires it into the app shell’s go.mod and into the generated server/package_extensions.go entry point. For the contract the package itself implements (the Register(app) function, the module layout, testing), see Server. This page covers what the app shell does around that.

When to use Go

Most packages don’t need a Go server. If you can express your logic as:

Reach for Go only when you need:

Declaring the module

Two fields in the manifest:

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

package is the subdirectory name, by convention 'server'. module is the Go module path declared in that subdirectory’s go.mod - use the tinycld.org/packages/<slug> namespace to keep module paths out of collisions.

What the generator writes

On each npm run packages:generate, for every package with a server field, the generator:

  1. Appends a require <module> v0.0.0 line to the app shell’s server/go.mod, inside a tagged block:

    module tinycld.org/app
    
    go 1.25.0
    
    // --- package extensions (auto-generated, do not edit) ---
    require tinycld.org/packages/example v0.0.0
    // --- end package extensions ---
  2. Appends a matching replace directive pointing at the sibling repo’s real path:

    // --- package extensions (auto-generated, do not edit) ---
    replace tinycld.org/packages/example => ../../example/server
    // --- end package extensions ---

    The replace path is relative to tinycld/server/ and points directly at the sibling repo’s server/ subdirectory at ~/code/tinycld/<slug>/server/. (Bundled @tinycld/core is the exception - it lives inside the app shell and is referenced via replace tinycld.org/core => ../packages/@tinycld/core/server.)

  3. Regenerates server/package_extensions.go, a small Go file whose init() calls each package’s Register(app) in manifest order. The app shell’s main.go is in the same package and invokes it so every linked package gets a chance to wire in hooks, endpoints, and workers before app.Start().

The // --- package extensions (auto-generated, do not edit) --- markers let the generator rewrite just its own blocks without disturbing hand-written content in go.mod.

go.sum drift

Testing the Go side

Sibling Go modules are self-contained - they don’t need the app shell checked out to compile or test:

cd ../example/server
go test ./...

The module’s go.mod pins the same PocketBase version the app shell uses, so test code hits the same API surface it will in production. Once linked, the replace directive points the app shell’s import of tinycld.org/packages/example at this same directory, so any test that passes here also reflects what the app shell will build against.

For the package-side concerns (the Register function signature, the directory layout, what to import), see Server.