Go jobs and realtime messaging
Schedule Go jobs and control PocketBase realtime connections, subscriptions, and messages.
Use this guide to register a periodic Go job and add safe realtime behavior around PocketBase's SSE connection. The scheduler starts with serve; realtime handlers can validate connections, subscription changes, and outgoing messages.
Before you begin
You need a Go application that starts with app.Start(), and a client that can connect to PocketBase realtime. Choose a job ID owned by your extension. Do not use IDs beginning with __pb, which are used by system scheduled jobs.
Schedule a job
- Register a cron handler before starting the app. Use a stable ID and a five-field cron expression.
app.Cron().MustAdd("refresh-article-cache", "*/5 * * * *", func() {
log.Println("refreshing article cache")
})MustAdd panics for an invalid expression. Use app.Cron().Add(id, expression, handler) when you want an error return instead. Each scheduled job runs in its own goroutine.
- Start the application and observe the handler.
if err := app.Start(); err != nil {
log.Fatal(err)
}The scheduler starts automatically when the application serves. You can preview and trigger app-level jobs from Dashboard > Settings > Crons.
- Remove an extension job when it is no longer needed.
app.Cron().Remove("refresh-article-cache")The named job is removed. Avoid RemoveAll() and Stop() on the app scheduler because they can also affect system jobs such as log cleanup and automatic backups. Use a separate cron.New() instance for an independent scheduler.
Customize realtime connection flow
- Validate a new SSE connection. Bind
OnRealtimeConnectRequestand continue the event chain.
app.OnRealtimeConnectRequest().BindFunc(func(e *core.RealtimeConnectRequestEvent) error {
e.App.Logger().Debug("realtime connection requested", "clientId", e.Client.Id())
return e.Next()
})PocketBase creates a client, sends a PB_CONNECT message after the connection is established, and unregisters the client when the connection closes. The connection has a five-minute idle timeout and a 30-minute maximum lifetime by default. Code after e.Next() runs after disconnect.
- Validate subscription changes.
app.OnRealtimeSubscribeRequest().BindFunc(func(e *core.RealtimeSubscribeRequestEvent) error {
if len(e.Subscriptions) == 0 {
return e.BadRequestError("At least one subscription is required", nil)
}
return e.Next()
})On success, PocketBase updates the client's auth state, removes previous subscriptions, and subscribes to the submitted set. Auth changes other than a guest-to-auth upgrade are rejected, and a subscription request from a different client IP is rejected.
- Inspect or filter outgoing messages.
app.OnRealtimeMessageSend().BindFunc(func(e *core.RealtimeMessageEvent) error {
e.App.Logger().Debug("realtime message", "clientId", e.Client.Id())
return e.Next()
})The event runs before PocketBase writes the SSE message and flushes the response. Returning an error closes the connection path for that message.
PB_CONNECT, changing subscriptions, and observing the resulting record or custom message event.Troubleshoot safely
- If a job never runs, confirm the application reached
serve, the cron expression is valid, and the job ID was not removed. - If starting the app panics, replace
MustAddwithAddwhile validating the expression and handle its returned error. - If realtime subscription changes fail, confirm the client IP and authorization state did not change unexpectedly between connect and subscribe.
- If a connection closes after inactivity, reconnect and resubscribe; PocketBase applies an idle timeout and a maximum connection lifetime.
- If system maintenance jobs stop, restore the app scheduler instead of removing all jobs, or move custom work to a separate
cron.New()instance.
Next steps
For the complete event surface, see Go hooks and event lifecycle. For record work inside a scheduled handler, use Go records, queries, and database operations.