## Documentation: [ Introduction ](/docs) [ ├ How to use PocketBase ](/docs/how-to-use) [ ├ Collections ](/docs/collections) [ ├ API rules and filters ](/docs/api-rules-and-filters) [ ├ Authentication ](/docs/authentication) [ ├ Files upload and handling ](/docs/files-handling) [ ├ Working with relations ](/docs/working-with-relations) [ └ Extending PocketBase ](/docs/use-as-framework) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Introduction Introduction Please keep in mind that PocketBase is still under active development and full backward compatibility is not guaranteed before reaching v1.0.0. PocketBase is NOT recommended for production critical applications yet, unless you are fine with reading the [changelog](https://github.com/pocketbase/pocketbase/blob/master/CHANGELOG.md) and applying some manual migration steps from time to time. PocketBase is an open source backend consisting of embedded database (SQLite) with realtime subscriptions, built-in auth management, convenient dashboard UI and simple REST-ish API. It can be used both as Go framework and as standalone application. The easiest way to get started is to download the prebuilt minimal PocketBase executable: x64 ARM64 - [ Download v0.28.0 for Linux x64](https://github.com/pocketbase/pocketbase/releases/download/v0.28.0/pocketbase_0.28.0_linux_amd64.zip) (~11MB zip) - [ Download v0.28.0 for Windows x64](https://github.com/pocketbase/pocketbase/releases/download/v0.28.0/pocketbase_0.28.0_windows_amd64.zip) (~11MB zip) - [ Download v0.28.0 for macOS x64](https://github.com/pocketbase/pocketbase/releases/download/v0.28.0/pocketbase_0.28.0_darwin_amd64.zip) (~11MB zip) - [ Download v0.28.0 for Linux ARM64](https://github.com/pocketbase/pocketbase/releases/download/v0.28.0/pocketbase_0.28.0_linux_arm64.zip) (~11MB zip) - [ Download v0.28.0 for Windows ARM64](https://github.com/pocketbase/pocketbase/releases/download/v0.28.0/pocketbase_0.28.0_windows_arm64.zip) (~11MB zip) - [ Download v0.28.0 for macOS ARM64](https://github.com/pocketbase/pocketbase/releases/download/v0.28.0/pocketbase_0.28.0_darwin_arm64.zip) (~11MB zip) See the [GitHub Releases page](https://github.com/pocketbase/pocketbase/releases) for other platforms and more details. Once you've extracted the archive, you could start the application by running `./pocketbase serve` in the extracted directory. And that's it! The first time it will generate an installer link that should be automatically opened in the browser to setup your first superuser account (you can also create the first superuser manually via `./pocketbase superuser create EMAIL PASS`) . The started web server has the following default routes: - [`http://127.0.0.1:8090`](http://127.0.0.1:8090) - if `pb_public` directory exists, serves the static content from it (html, css, images, etc.) - [`http://127.0.0.1:8090/_/`](http://127.0.0.1:8090/_/) - superusers dashboard - [`http://127.0.0.1:8090/api/`](http://127.0.0.1:8090/api/) - REST-ish API The prebuilt PocketBase executable will create and manage 2 new directories alongside the executable: - `pb_data` - stores your application data, uploaded files, etc. (usually should be added in `.gitignore`). - `pb_migrations` - contains JS migration files with your collection changes (can be safely committed in your repository). You can even write custom migration scripts. For more info check the [JS migrations docs](/docs/js-migrations). You could find all available commands and their options by running `./pocketbase --help` or `./pocketbase [command] --help` [Next: How to use PocketBase ](/docs/how-to-use) ## Documentation: go-overview [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Overview Overview ### [ Getting started ](#getting-started) PocketBase can be used as regular Go package that exposes various helpers and hooks to help you implement you own custom portable application. A new PocketBase instance is created via [`pocketbase.New()`](https://pkg.go.dev/github.com/pocketbase/pocketbase#New) or [`pocketbase.NewWithConfig(config)`](https://pkg.go.dev/github.com/pocketbase/pocketbase#NewWithConfig) . Once created you can register your custom business logic via the available [event hooks](/docs/go-event-hooks/) and call [`app.Start()`](https://pkg.go.dev/github.com/pocketbase/pocketbase#PocketBase.Start) to start the application. Below is a minimal example: - [Install Go 1.23+](https://go.dev/doc/install) - Create a new project directory with `main.go` file inside it. As a reference, you can also explore the prebuilt executable [`example/base/main.go`](https://github.com/pocketbase/pocketbase/blob/master/examples/base/main.go) file. `package main import ( "log" "os" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnServe().BindFunc(func(se *core.ServeEvent) error { // serves static files from the provided public dir (if exists) se.Router.GET("/{path...}", apis.Static(os.DirFS("./pb_public"), false)) return se.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` - To init the dependencies, run `go mod init myapp && go mod tidy`. - To start the application, run `go run . serve`. - To build a statically linked executable, run `go build` and then you can start the created executable with `./myapp serve`. ### [ Custom SQLite driver ](#custom-sqlite-driver) The general recommendation is to use the builtin SQLite setup but if you need more advanced configuration or extensions like ICU, FTS5, etc. you'll have to specify a custom driver/build. Note that PocketBase by default doesn't require CGO because it uses the pure Go SQLite port [modernc.org/sqlite ](https://pkg.go.dev/modernc.org/sqlite), but this may not be the case when using a custom SQLite driver! PocketBase v0.23+ added supported for defining a `DBConnect` function as app configuration to load custom SQLite builds and drivers compatible with the standard Go `database/sql`. The `DBConnect` function is called twice - once for `pb_data/data.db` (the main database file) and second time for `pb_data/auxiliary.db` (used for logs and other ephemeral system meta information). If you want to load your custom driver conditionally and fallback to the default handler, then you can call [`core.DefaultDBConnect`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#DefaultDBConnect) . As a side-note, if you are not planning to use `core.DefaultDBConnect` fallback as part of your custom driver registration you can exclude the default pure Go driver with `go build -tags no_default_driver` to reduce the binary size a little (~4MB). Below are some minimal examples with commonly used external SQLite drivers: [ github.com/mattn/go-sqlite3 ](#github-commattngo-sqlite3) For all available options please refer to the [`github.com/mattn/go-sqlite3`](https://github.com/mattn/go-sqlite3) README. `package main import ( "database/sql" "log" "github.com/mattn/go-sqlite3" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" ) // register a new driver with default PRAGMAs and the same query // builder implementation as the already existing sqlite3 builder func init() { // initialize default PRAGMAs for each new connection sql.Register("pb_sqlite3", &sqlite3.SQLiteDriver{ ConnectHook: func(conn *sqlite3.SQLiteConn) error { _, err := conn.Exec(` PRAGMA busy_timeout = 10000; PRAGMA journal_mode = WAL; PRAGMA journal_size_limit = 200000000; PRAGMA synchronous = NORMAL; PRAGMA foreign_keys = ON; PRAGMA temp_store = MEMORY; PRAGMA cache_size = -16000; `, nil) return err }, }, ) dbx.BuilderFuncMap["pb_sqlite3"] = dbx.BuilderFuncMap["sqlite3"] } func main() { app := pocketbase.NewWithConfig(pocketbase.Config{ DBConnect: func(dbPath string) (*dbx.DB, error) { return dbx.Open("pb_sqlite3", dbPath) }, }) // any custom hooks or plugins... if err := app.Start(); err != nil { log.Fatal(err) } }` [ github.com/ncruces/go-sqlite3 ](#github-comncrucesgo-sqlite3) For all available options please refer to the [`github.com/ncruces/go-sqlite3`](https://github.com/ncruces/go-sqlite3) README. `package main import ( "log" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" _ "github.com/ncruces/go-sqlite3/driver" _ "github.com/ncruces/go-sqlite3/embed" ) func main() { app := pocketbase.NewWithConfig(pocketbase.Config{ DBConnect: func(dbPath string) (*dbx.DB, error) { const pragmas = "?_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=journal_size_limit(200000000)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)&_pragma=temp_store(MEMORY)&_pragma=cache_size(-16000)" return dbx.Open("sqlite3", "file:"+dbPath+pragmas) }, }) // custom hooks and plugins... if err := app.Start(); err != nil { log.Fatal(err) } }` [ github.com/tursodatabase/libsql-client-go/libsql ](#github-comtursodatabaselibsql-client-golibsql) For all available options please refer to the [Turso Go docs](https://docs.turso.tech/sdk/go/quickstart#remote-only) . `package main import ( "log" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" _ "github.com/tursodatabase/libsql-client-go/libsql" ) // register the libsql driver to use the same query builder // implementation as the already existing sqlite3 builder func init() { dbx.BuilderFuncMap["libsql"] = dbx.BuilderFuncMap["sqlite3"] } func main() { app := pocketbase.NewWithConfig(pocketbase.Config{ DBConnect: func(dbPath string) (*dbx.DB, error) { if strings.Contains(dbPath, "data.db") { return dbx.Open("libsql", "libsql://[data.db DATABASE].turso.io?authToken=[TOKEN]") } // optionally for the logs (aka. pb_data/auxiliary.db) use the default local filesystem driver return core.DefaultDBConnect(dbPath) }, }) // any custom hooks or plugins... if err := app.Start(); err != nil { log.Fatal(err) } }` [Next: Event hooks ](/docs/go-event-hooks) ## Documentation: go-event-hooks [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Event hooks Event hooks The standard way to modify PocketBase is through event hooks in your Go code. All hooks have 3 main methods: - [`Bind(handler)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/hook#Hook.Bind) adds a new handler to the specified event hook. A handler has 3 fields: `Id` (optional) - the name of the handler (could be used as argument for `Unbind`) - `Priority` (optional) - the execution order of the handler (if empty fallbacks to the order of registration in the code). - `Func` (required) - the handler function. - [`BindFunc(func)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/hook#Hook.BindFunc) is similar to `Bind` but registers a new handler from just the provided function. The registered handler is added with a default 0 priority and the id is autogenerated (the returned string value). - [`Trigger(event, oneOffHandlerFuncs...)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/hook#Hook.Trigger) triggers the event hook. This method rarely has to be called manually by users. To remove an already registered hook handler, you can use the handler id and pass it to `Unbind(id)` or remove all handlers with `UnbindAll()` (!including system handlers). All hook handler functions share the same `func(e T) error` signature and expect the user to call `e.Next()` if they want to proceed with the execution chain. If you need to access the app instance from inside a hook handler, prefer using the `e.App` field instead of reusing a parent scope app variable because the hook could be part of a DB transaction and can cause deadlock. Also avoid using global mutex locks inside a hook handler because it could be invoked recursively (e.g. cascade delete) and can cause deadlock. You can explore all available hooks below: ### [ App hooks ](#app-hooks) [ OnBootstrap ](#onbootstrap) `OnBootstrap` hook is triggered when initializing the main application resources (db, app settings, etc). Note that attempting to access the database before the `e.Next()` call will result in an error. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error { if err := e.Next(); err != nil { return err } // e.App return nil }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnServe ](#onserve) `OnServe` hook is triggered when the app web server is started (after starting the TCP listener but before initializing the blocking serve task), allowing you to adjust its options and attach new routes or middlewares. `package main import ( "log" "net/http" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnServe().BindFunc(func(e *core.ServeEvent) error { // register new "GET /hello" route e.Router.GET("/hello", func(e *core.RequestEvent) error { return e.String(200, "Hello world!") }).Bind(apis.RequireAuth()) return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnSettingsReload ](#onsettingsreload) `OnSettingsReload` hook is triggered every time when the `App.Settings()` is being replaced with a new state. Calling `e.App.Settings()` after `e.Next()` returns the new state. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnSettingsReload().BindFunc(func(e *core.SettingsReloadEvent) error { if err := e.Next(); err != nil { return err } // e.App.Settings() return nil }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnBackupCreate ](#onbackupcreate) `OnBackupCreate` is triggered on each `App.CreateBackup` call. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnBackupCreate().BindFunc(func(e *core.BackupEvent) error { // e.App // e.Name - the name of the backup to create // e.Exclude - list of pb_data dir entries to exclude from the backup return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnBackupRestore ](#onbackuprestore) `OnBackupRestore` is triggered before app backup restore (aka. on `App.RestoreBackup` call). `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnBackupRestore().BindFunc(func(e *core.BackupEvent) error { // e.App // e.Name - the name of the backup to restore // e.Exclude - list of dir entries to exclude from the backup return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnTerminate ](#onterminate) `OnTerminate` hook is triggered when the app is in the process of being terminated (ex. on `SIGTERM` signal). Note that the app could be terminated abruptly without awaiting the hook completion. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error { // e.App // e.IsRestart return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ### [ Mailer hooks ](#mailer-hooks) [ OnMailerSend ](#onmailersend) `OnMailerSend` hook is triggered every time when a new email is being send using the `App.NewMailClient()` instance. It allows intercepting the email message or to use a custom mailer client. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnMailerSend().BindFunc(func(e *core.MailerEvent) error { // e.App // e.Mailer // e.Message // ex. change the mail subject e.Message.Subject = "new subject" return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnMailerRecordAuthAlertSend ](#onmailerrecordauthalertsend) `OnMailerRecordAuthAlertSend` hook is triggered when sending a new device login auth alert email, allowing you to intercept and customize the email message that is being sent. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnMailerRecordAuthAlertSend().BindFunc(func(e *core.MailerRecordEvent) error { // e.App // e.Mailer // e.Message // e.Record // e.Meta // ex. change the mail subject e.Message.Subject = "new subject" return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnMailerRecordPasswordResetSend ](#onmailerrecordpasswordresetsend) `OnMailerRecordPasswordResetSend` hook is triggered when sending a password reset email to an auth record, allowing you to intercept and customize the email message that is being sent. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnMailerRecordPasswordResetSend().BindFunc(func(e *core.MailerRecordEvent) error { // e.App // e.Mailer // e.Message // e.Record // e.Meta // ex. change the mail subject e.Message.Subject = "new subject" return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnMailerRecordVerificationSend ](#onmailerrecordverificationsend) `OnMailerRecordVerificationSend` hook is triggered when sending a verification email to an auth record, allowing you to intercept and customize the email message that is being sent. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnMailerRecordVerificationSend().BindFunc(func(e *core.MailerRecordEvent) error { // e.App // e.Mailer // e.Message // e.Record // e.Meta // ex. change the mail subject e.Message.Subject = "new subject" return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnMailerRecordEmailChangeSend ](#onmailerrecordemailchangesend) `OnMailerRecordEmailChangeSend` hook is triggered when sending a confirmation new address email to an auth record, allowing you to intercept and customize the email message that is being sent. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnMailerRecordEmailChangeSend().BindFunc(func(e *core.MailerRecordEvent) error { // e.App // e.Mailer // e.Message // e.Record // e.Meta // ex. change the mail subject e.Message.Subject = "new subject" return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnMailerRecordOTPSend ](#onmailerrecordotpsend) `OnMailerRecordOTPSend` hook is triggered when sending an OTP email to an auth record, allowing you to intercept and customize the email message that is being sent. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnMailerRecordOTPSend().BindFunc(func(e *core.MailerRecordEvent) error { // e.App // e.Mailer // e.Message // e.Record // e.Meta // ex. change the mail subject e.Message.Subject = "new subject" return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ### [ Realtime hooks ](#realtime-hooks) [ OnRealtimeConnectRequest ](#onrealtimeconnectrequest) `OnRealtimeConnectRequest` hook is triggered when establishing the SSE client connection. Any execution after e.Next() of a hook handler happens after the client disconnects. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnRealtimeConnectRequest().BindFunc(func(e *core.RealtimeConnectRequestEvent) error { // e.App // e.Client // e.IdleTimeout // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRealtimeSubscribeRequest ](#onrealtimesubscriberequest) `OnRealtimeSubscribeRequest` hook is triggered when updating the client subscriptions, allowing you to further validate and modify the submitted change. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnRealtimeSubscribeRequest().BindFunc(func(e *core.RealtimeSubscribeRequestEvent) error { // e.App // e.Client // e.Subscriptions // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRealtimeMessageSend ](#onrealtimemessagesend) `OnRealtimeMessageSend` hook is triggered when sending an SSE message to a client. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnRealtimeMessageSend().BindFunc(func(e *core.RealtimeMessageEvent) error { // e.App // e.Client // e.Message // and all original connect RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ### [ Record model hooks ](#record-model-hooks) These are lower level Record model hooks and could be triggered from anywhere (custom console command, scheduled cron job, when calling `e.Save(record)`, etc.) and therefore they have no access to the request context! If you want to intercept the builtin Web APIs and to access their request body, query parameters, headers or the request auth state, then please use the designated [Record `*Request` hooks](#request-hooks) . [ OnRecordEnrich ](#onrecordenrich) `OnRecordEnrich` is triggered every time when a record is enriched - as part of the builtin Record responses, during realtime message serialization, or when `apis.EnrichRecord` is invoked. It could be used for example to redact/hide or add computed temporary Record model props only for the specific request info. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnRecordEnrich("posts").BindFunc(func(e *core.RecordEnrichEvent) error { // hide one or more fields e.Record.Hide("role") // add new custom field for registered users if e.RequestInfo.Auth != nil && e.RequestInfo.Auth.Collection().Name == "users" { e.Record.WithCustomData(true) // for security requires explicitly allowing it e.Record.Set("computedScore", e.Record.GetInt("score") * e.RequestInfo.Auth.GetInt("base")) } return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordValidate ](#onrecordvalidate) `OnRecordValidate` is a Record proxy model hook of `OnModelValidate`. `OnRecordValidate` is called every time when a Record is being validated, e.g. triggered by `App.Validate()` or `App.Save()`. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordValidate().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordValidate("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Record model create hooks ](#record-model-create-hooks) [ OnRecordCreate ](#onrecordcreate) `OnRecordCreate` is a Record proxy model hook of `OnModelCreate`. `OnRecordCreate` is triggered every time when a new Record is being created, e.g. triggered by `App.Save()`. Operations BEFORE the `e.Next()` execute before the Record validation and the INSERT DB statement. Operations AFTER the `e.Next()` execute after the Record validation and the INSERT DB statement. Note that successful execution doesn't guarantee that the Record is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnRecordAfterCreateSuccess` or `OnRecordAfterCreateError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordCreate().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordCreate("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordCreateExecute ](#onrecordcreateexecute) `OnRecordCreateExecute` is a Record proxy model hook of `OnModelCreateExecute`. `OnRecordCreateExecute` is triggered after successful Record validation and right before the model INSERT DB statement execution. Usually it is triggered as part of the `App.Save()` in the following firing order: `OnRecordCreate` -> `OnRecordValidate` (skipped with `App.SaveNoValidate()`) -> `OnRecordCreateExecute` Note that successful execution doesn't guarantee that the Record is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnRecordAfterCreateSuccess` or `OnRecordAfterCreateError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordCreateExecute().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordCreateExecute("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAfterCreateSuccess ](#onrecordaftercreatesuccess) `OnRecordAfterCreateSuccess` is a Record proxy model hook of `OnModelAfterCreateSuccess`. `OnRecordAfterCreateSuccess` is triggered after each successful Record DB create persistence. Note that when a Record is persisted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordAfterCreateSuccess().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordAfterCreateSuccess("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAfterCreateError ](#onrecordaftercreateerror) `OnRecordAfterCreateError` is a Record proxy model hook of `OnModelAfterCreateError`. `OnRecordAfterCreateError` is triggered after each failed Record DB create persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Save()` failure - delayed on transaction rollback `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordAfterCreateError().BindFunc(func(e *core.RecordErrorEvent) error { // e.App // e.Record // e.Error return e.Next() }) // fires only for "users" and "articles" records app.OnRecordAfterCreateError("users", "articles").BindFunc(func(e *core.RecordErrorEvent) error { // e.App // e.Record // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Record model update hooks ](#record-model-update-hooks) [ OnRecordUpdate ](#onrecordupdate) `OnRecordUpdate` is a Record proxy model hook of `OnModelUpdate`. `OnRecordUpdate` is triggered every time when a new Record is being updated, e.g. triggered by `App.Save()`. Operations BEFORE the `e.Next()` execute before the Record validation and the UPDATE DB statement. Operations AFTER the `e.Next()` execute after the Record validation and the UPDATE DB statement. Note that successful execution doesn't guarantee that the Record is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnRecordAfterUpdateSuccess` or `OnRecordAfterUpdateError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordUpdate().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordUpdate("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordUpdateExecute ](#onrecordupdateexecute) `OnRecordUpdateExecute` is a Record proxy model hook of `OnModelUpdateExecute`. `OnRecordUpdateExecute` is triggered after successful Record validation and right before the model UPDATE DB statement execution. Usually it is triggered as part of the `App.Save()` in the following firing order: `OnRecordUpdate` -> `OnRecordValidate` (skipped with `App.SaveNoValidate()`) -> `OnRecordUpdateExecute` Note that successful execution doesn't guarantee that the Record is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnRecordAfterUpdateSuccess` or `OnRecordAfterUpdateError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordUpdateExecute().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordUpdateExecute("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAfterUpdateSuccess ](#onrecordafterupdatesuccess) `OnRecordAfterUpdateSuccess` is a Record proxy model hook of `OnModelAfterUpdateSuccess`. `OnRecordAfterUpdateSuccess` is triggered after each successful Record DB update persistence. Note that when a Record is persisted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordAfterUpdateSuccess().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordAfterUpdateSuccess("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAfterUpdateError ](#onrecordafterupdateerror) `OnRecordAfterUpdateError` is a Record proxy model hook of `OnModelAfterUpdateError`. `OnRecordAfterUpdateError` is triggered after each failed Record DB update persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Save()` failure - delayed on transaction rollback `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordAfterUpdateError().BindFunc(func(e *core.RecordErrorEvent) error { // e.App // e.Record // e.Error return e.Next() }) // fires only for "users" and "articles" records app.OnRecordAfterUpdateError("users", "articles").BindFunc(func(e *core.RecordErrorEvent) error { // e.App // e.Record // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Record model delete hooks ](#record-model-delete-hooks) [ OnRecordDelete ](#onrecorddelete) `OnRecordDelete` is a Record proxy model hook of `OnModelDelete`. `OnRecordDelete` is triggered every time when a new Record is being deleted, e.g. triggered by `App.Delete()`. Operations BEFORE the `e.Next()` execute before the Record validation and the UPDATE DB statement. Operations AFTER the `e.Next()` execute after the Record validation and the UPDATE DB statement. Note that successful execution doesn't guarantee that the Record is deleted from the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted deleted events, you can bind to `OnRecordAfterDeleteSuccess` or `OnRecordAfterDeleteError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordDelete().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordDelete("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordDeleteExecute ](#onrecorddeleteexecute) `OnRecordDeleteExecute` is a Record proxy model hook of `OnModelDeleteExecute`. `OnRecordDeleteExecute` is triggered after the internal delete checks and right before the Record the model DELETE DB statement execution. Usually it is triggered as part of the `App.Delete()` in the following firing order: `OnRecordDelete` -> internal delete checks -> `OnRecordDeleteExecute` Note that successful execution doesn't guarantee that the Record is deleted from the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnRecordAfterDeleteSuccess` or `OnRecordAfterDeleteError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordDeleteExecute().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordDeleteExecute("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAfterDeleteSuccess ](#onrecordafterdeletesuccess) `OnRecordAfterDeleteSuccess` is a Record proxy model hook of `OnModelAfterDeleteSuccess`. `OnRecordAfterDeleteSuccess` is triggered after each successful Record DB delete persistence. Note that when a Record is deleted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordAfterDeleteSuccess().BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) // fires only for "users" and "articles" records app.OnRecordAfterDeleteSuccess("users", "articles").BindFunc(func(e *core.RecordEvent) error { // e.App // e.Record return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAfterDeleteError ](#onrecordafterdeleteerror) `OnRecordAfterDeleteError` is a Record proxy model hook of `OnModelAfterDeleteError`. `OnRecordAfterDeleteError` is triggered after each failed Record DB delete persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Delete()` failure - delayed on transaction rollback `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every record app.OnRecordAfterDeleteError().BindFunc(func(e *core.RecordErrorEvent) error { // e.App // e.Record // e.Error return e.Next() }) // fires only for "users" and "articles" records app.OnRecordAfterDeleteError("users", "articles").BindFunc(func(e *core.RecordErrorEvent) error { // e.App // e.Record // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ### [ Collection model hooks ](#collection-model-hooks) These are lower level Collection model hooks and could be triggered from anywhere (custom console command, scheduled cron job, when calling `e.Save(collection)`, etc.) and therefore they have no access to the request context! If you want to intercept the builtin Web APIs and to access their request body, query parameters, headers or the request auth state, then please use the designated [Collection `*Request` hooks](#collection-request-hooks) . [ OnCollectionValidate ](#oncollectionvalidate) `OnCollectionValidate` is a Collection proxy model hook of `OnModelValidate`. `OnCollectionValidate` is called every time when a Collection is being validated, e.g. triggered by `App.Validate()` or `App.Save()`. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionValidate().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionValidate("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Collection mode create hooks ](#collection-mode-create-hooks) [ OnCollectionCreate ](#oncollectioncreate) `OnCollectionCreate` is a Collection proxy model hook of `OnModelCreate`. `OnCollectionCreate` is triggered every time when a new Collection is being created, e.g. triggered by `App.Save()`. Operations BEFORE the `e.Next()` execute before the Collection validation and the INSERT DB statement. Operations AFTER the `e.Next()` execute after the Collection validation and the INSERT DB statement. Note that successful execution doesn't guarantee that the Collection is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnCollectionAfterCreateSuccess` or `OnCollectionAfterCreateError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionCreate().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionCreate("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionCreateExecute ](#oncollectioncreateexecute) `OnCollectionCreateExecute` is a Collection proxy model hook of `OnModelCreateExecute`. `OnCollectionCreateExecute` is triggered after successful Collection validation and right before the model INSERT DB statement execution. Usually it is triggered as part of the `App.Save()` in the following firing order: `OnCollectionCreate` -> `OnCollectionValidate` (skipped with `App.SaveNoValidate()`) -> `OnCollectionCreateExecute` Note that successful execution doesn't guarantee that the Collection is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnCollectionAfterCreateSuccess` or `OnCollectionAfterCreateError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionCreateExecute().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionCreateExecute("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionAfterCreateSuccess ](#oncollectionaftercreatesuccess) `OnCollectionAfterCreateSuccess` is a Collection proxy model hook of `OnModelAfterCreateSuccess`. `OnCollectionAfterCreateSuccess` is triggered after each successful Collection DB create persistence. Note that when a Collection is persisted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionAfterCreateSuccess().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionAfterCreateSuccess("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionAfterCreateError ](#oncollectionaftercreateerror) `OnCollectionAfterCreateError` is a Collection proxy model hook of `OnModelAfterCreateError`. `OnCollectionAfterCreateError` is triggered after each failed Collection DB create persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Save()` failure - delayed on transaction rollback `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionAfterCreateError().BindFunc(func(e *core.CollectionErrorEvent) error { // e.App // e.Collection // e.Error return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionAfterCreateError("users", "articles").BindFunc(func(e *core.CollectionErrorEvent) error { // e.App // e.Collection // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Collection mode update hooks ](#collection-mode-update-hooks) [ OnCollectionUpdate ](#oncollectionupdate) `OnCollectionUpdate` is a Collection proxy model hook of `OnModelUpdate`. `OnCollectionUpdate` is triggered every time when a new Collection is being updated, e.g. triggered by `App.Save()`. Operations BEFORE the `e.Next()` execute before the Collection validation and the UPDATE DB statement. Operations AFTER the `e.Next()` execute after the Collection validation and the UPDATE DB statement. Note that successful execution doesn't guarantee that the Collection is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnCollectionAfterUpdateSuccess` or `OnCollectionAfterUpdateError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionUpdate().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionUpdate("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionUpdateExecute ](#oncollectionupdateexecute) `OnCollectionUpdateExecute` is a Collection proxy model hook of `OnModelUpdateExecute`. `OnCollectionUpdateExecute` is triggered after successful Collection validation and right before the model UPDATE DB statement execution. Usually it is triggered as part of the `App.Save()` in the following firing order: `OnCollectionUpdate` -> `OnCollectionValidate` (skipped with `App.SaveNoValidate()`) -> `OnCollectionUpdateExecute` Note that successful execution doesn't guarantee that the Collection is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnCollectionAfterUpdateSuccess` or `OnCollectionAfterUpdateError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionUpdateExecute().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionUpdateExecute("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionAfterUpdateSuccess ](#oncollectionafterupdatesuccess) `OnCollectionAfterUpdateSuccess` is a Collection proxy model hook of `OnModelAfterUpdateSuccess`. `OnCollectionAfterUpdateSuccess` is triggered after each successful Collection DB update persistence. Note that when a Collection is persisted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionAfterUpdateSuccess().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionAfterUpdateSuccess("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionAfterUpdateError ](#oncollectionafterupdateerror) `OnCollectionAfterUpdateError` is a Collection proxy model hook of `OnModelAfterUpdateError`. `OnCollectionAfterUpdateError` is triggered after each failed Collection DB update persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Save()` failure - delayed on transaction rollback `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionAfterUpdateError().BindFunc(func(e *core.CollectionErrorEvent) error { // e.App // e.Collection // e.Error return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionAfterUpdateError("users", "articles").BindFunc(func(e *core.CollectionErrorEvent) error { // e.App // e.Collection // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Collection mode delete hooks ](#collection-mode-delete-hooks) [ OnCollectionDelete ](#oncollectiondelete) `OnCollectionDelete` is a Collection proxy model hook of `OnModelDelete`. `OnCollectionDelete` is triggered every time when a new Collection is being deleted, e.g. triggered by `App.Delete()`. Operations BEFORE the `e.Next()` execute before the Collection validation and the UPDATE DB statement. Operations AFTER the `e.Next()` execute after the Collection validation and the UPDATE DB statement. Note that successful execution doesn't guarantee that the Collection is deleted from the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted deleted events, you can bind to `OnCollectionAfterDeleteSuccess` or `OnCollectionAfterDeleteError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionDelete().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionDelete("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionDeleteExecute ](#oncollectiondeleteexecute) `OnCollectionDeleteExecute` is a Collection proxy model hook of `OnModelDeleteExecute`. `OnCollectionDeleteExecute` is triggered after the internal delete checks and right before the Collection the model DELETE DB statement execution. Usually it is triggered as part of the `App.Delete()` in the following firing order: `OnCollectionDelete` -> internal delete checks -> `OnCollectionDeleteExecute` Note that successful execution doesn't guarantee that the Collection is deleted from the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnCollectionAfterDeleteSuccess` or `OnCollectionAfterDeleteError` hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionDeleteExecute().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionDeleteExecute("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionAfterDeleteSuccess ](#oncollectionafterdeletesuccess) `OnCollectionAfterDeleteSuccess` is a Collection proxy model hook of `OnModelAfterDeleteSuccess`. `OnCollectionAfterDeleteSuccess` is triggered after each successful Collection DB delete persistence. Note that when a Collection is deleted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionAfterDeleteSuccess().BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionAfterDeleteSuccess("users", "articles").BindFunc(func(e *core.CollectionEvent) error { // e.App // e.Collection return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionAfterDeleteError ](#oncollectionafterdeleteerror) `OnCollectionAfterDeleteError` is a Collection proxy model hook of `OnModelAfterDeleteError`. `OnCollectionAfterDeleteError` is triggered after each failed Collection DB delete persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Delete()` failure - delayed on transaction rollback `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnCollectionAfterDeleteError().BindFunc(func(e *core.CollectionErrorEvent) error { // e.App // e.Collection // e.Error return e.Next() }) // fires only for "users" and "articles" collections app.OnCollectionAfterDeleteError("users", "articles").BindFunc(func(e *core.CollectionErrorEvent) error { // e.App // e.Collection // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ### [ Request hooks ](#request-hooks) The request hooks are triggered only when the corresponding API request endpoint is accessed. ###### [ Record CRUD request hooks ](#record-crud-request-hooks) [ OnRecordsListRequest ](#onrecordslistrequest) `OnRecordsListRequest` hook is triggered on each API Records list request. Could be used to validate or modify the response before returning it to the client. Note that if you want to hide existing or add new computed Record fields prefer using the [`OnRecordEnrich`](#onrecordenrich) hook because it is less error-prone and it is triggered by all builtin Record responses (including when sending realtime Record events). `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnRecordsListRequest().BindFunc(func(e *core.RecordsListRequestEvent) error { // e.App // e.Collection // e.Records // e.Result // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "articles" collections app.OnRecordsListRequest("users", "articles").BindFunc(func(e *core.RecordsListRequestEvent) error { // e.App // e.Collection // e.Records // e.Result // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordViewRequest ](#onrecordviewrequest) `OnRecordViewRequest` hook is triggered on each API Record view request. Could be used to validate or modify the response before returning it to the client. Note that if you want to hide existing or add new computed Record fields prefer using the [`OnRecordEnrich`](#onrecordenrich) hook because it is less error-prone and it is triggered by all builtin Record responses (including when sending realtime Record events). `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnRecordViewRequest().BindFunc(func(e *core.RecordRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "articles" collections app.OnRecordViewRequest("users", "articles").BindFunc(func(e *core.RecordRequestEvent) error { log.Println(e.HttpContext) log.Println(e.Record) return nil }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordCreateRequest ](#onrecordcreaterequest) `OnRecordCreateRequest` hook is triggered on each API Record create request. Could be used to additionally validate the request data or implement completely different persistence behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnRecordCreateRequest().BindFunc(func(e *core.RecordRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "articles" collections app.OnRecordCreateRequest("users", "articles").BindFunc(func(e *core.RecordRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordUpdateRequest ](#onrecordupdaterequest) `OnRecordUpdateRequest` hook is triggered on each API Record update request. Could be used to additionally validate the request data or implement completely different persistence behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnRecordUpdateRequest().BindFunc(func(e *core.RecordRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "articles" collections app.OnRecordUpdateRequest("users", "articles").BindFunc(func(e *core.RecordRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordDeleteRequest ](#onrecorddeleterequest) `OnRecordDeleteRequest` hook is triggered on each API Record delete request. Could be used to additionally validate the request data or implement completely different delete behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every collection app.OnRecordDeleteRequest().BindFunc(func(e *core.RecordRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "articles" collections app.OnRecordDeleteRequest("users", "articles").BindFunc(func(e *core.RecordRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Record auth request hooks ](#record-auth-request-hooks) [ OnRecordAuthRequest ](#onrecordauthrequest) `OnRecordAuthRequest` hook is triggered on each successful API record authentication request (sign-in, token refresh, etc.). Could be used to additionally validate or modify the authenticated record data and token. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordAuthRequest().BindFunc(func(e *core.RecordAuthRequestEvent) error { // e.App // e.Record // e.Token // e.Meta // e.AuthMethod // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordAuthRequest("users", "managers").BindFunc(func(e *core.RecordAuthRequestEvent) error { // e.App // e.Record // e.Token // e.Meta // e.AuthMethod // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAuthRefreshRequest ](#onrecordauthrefreshrequest) `OnRecordAuthRefreshRequest` hook is triggered on each Record auth refresh API request (right before generating a new auth token). Could be used to additionally validate the request data or implement completely different auth refresh behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordAuthRefreshRequest().BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordAuthRefreshRequest("users", "managers").BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAuthWithPasswordRequest ](#onrecordauthwithpasswordrequest) `OnRecordAuthWithPasswordRequest` hook is triggered on each Record auth with password API request. `e.Record` could be `nil` if no matching identity is found, allowing you to manually locate a different Record model (by reassigning `e.Record`). `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordAuthWithPasswordRequest().BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error { // e.App // e.Collection // e.Record (could be nil) // e.Identity // e.IdentityField // e.Password // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordAuthWithPasswordRequest("users", "managers").BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error { // e.App // e.Collection // e.Record (could be nil) // e.Identity // e.IdentityField // e.Password // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAuthWithOAuth2Request ](#onrecordauthwithoauth2request) `OnRecordAuthWithOAuth2Request` hook is triggered on each Record OAuth2 sign-in/sign-up API request (after token exchange and before external provider linking). If `e.Record` is not set, then the OAuth2 request will try to create a new auth record. To assign or link a different existing record model you can change the `e.Record` field. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordAuthWithOAuth2Request().BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error { // e.App // e.Collection // e.ProviderName // e.ProviderClient // e.Record (could be nil) // e.OAuth2User // e.CreateData // e.IsNewRecord // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordAuthWithOAuth2Request("users", "managers").BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error { // e.App // e.Collection // e.ProviderName // e.ProviderClient // e.Record (could be nil) // e.OAuth2User // e.CreateData // e.IsNewRecord // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordRequestPasswordResetRequest ](#onrecordrequestpasswordresetrequest) `OnRecordRequestPasswordResetRequest` hook is triggered on each Record request password reset API request. Could be used to additionally validate the request data or implement completely different password reset behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordRequestPasswordResetRequest().BindFunc(func(e *core.RecordRequestPasswordResetRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordRequestPasswordResetRequest("users", "managers").BindFunc(func(e *core.RecordRequestPasswordResetRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordConfirmPasswordResetRequest ](#onrecordconfirmpasswordresetrequest) `OnRecordConfirmPasswordResetRequest` hook is triggered on each Record confirm password reset API request. Could be used to additionally validate the request data or implement completely different persistence behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordConfirmPasswordResetRequest().BindFunc(func(e *core.RecordConfirmPasswordResetRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordConfirmPasswordResetRequest("users", "managers").BindFunc(func(e *core.RecordConfirmPasswordResetRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordRequestVerificationRequest ](#onrecordrequestverificationrequest) `OnRecordRequestVerificationRequest` hook is triggered on each Record request verification API request. Could be used to additionally validate the loaded request data or implement completely different verification behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordRequestVerificationRequest().BindFunc(func(e *core.RecordRequestVerificationRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordRequestVerificationRequest("users", "managers").BindFunc(func(e *core.RecordRequestVerificationRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordConfirmVerificationRequest ](#onrecordconfirmverificationrequest) `OnRecordConfirmVerificationRequest` hook is triggered on each Record confirm verification API request. Could be used to additionally validate the request data or implement completely different persistence behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordConfirmVerificationRequest().BindFunc(func(e *core.RecordConfirmVerificationRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordConfirmVerificationRequest("users", "managers").BindFunc(func(e *core.RecordConfirmVerificationRequestEvent) error { // e.App // e.Collection // e.Record // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordRequestEmailChangeRequest ](#onrecordrequestemailchangerequest) `OnRecordRequestEmailChangeRequest` hook is triggered on each Record request email change API request. Could be used to additionally validate the request data or implement completely different request email change behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordRequestEmailChangeRequest().BindFunc(func(e *core.RecordRequestEmailChangeRequestEvent) error { // e.App // e.Collection // e.Record // e.NewEmail // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordRequestEmailChangeRequest("users", "managers").BindFunc(func(e *core.RecordRequestEmailChangeRequestEvent) error { // e.App // e.Collection // e.Record // e.NewEmail // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordConfirmEmailChangeRequest ](#onrecordconfirmemailchangerequest) `OnRecordConfirmEmailChangeRequest` hook is triggered on each Record confirm email change API request. Could be used to additionally validate the request data or implement completely different persistence behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordConfirmEmailChangeRequest().BindFunc(func(e *core.RecordConfirmEmailChangeRequestEvent) error { // e.App // e.Collection // e.Record // e.NewEmail // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordConfirmEmailChangeRequest("users", "managers").BindFunc(func(e *core.RecordConfirmEmailChangeRequestEvent) error { // e.App // e.Collection // e.Record // e.NewEmail // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordRequestOTPRequest ](#onrecordrequestotprequest) `OnRecordRequestOTPRequest` hook is triggered on each Record request OTP API request. `e.Record` could be `nil` if no user with the requested email is found, allowing you to manually create a new Record or locate a different Record model (by reassigning `e.Record`). `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordRequestOTPRequest().BindFunc(func(e *core.RecordCreateOTPRequestEvent) error { // e.App // e.Collection // e.Record (could be nil) // e.Password // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordRequestOTPRequest("users", "managers").BindFunc(func(e *core.RecordCreateOTPRequestEvent) error { // e.App // e.Collection // e.Record (could be nil) // e.Password // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnRecordAuthWithOTPRequest ](#onrecordauthwithotprequest) `OnRecordAuthWithOTPRequest` hook is triggered on each Record auth with OTP API request. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth collection app.OnRecordAuthWithOTPRequest().BindFunc(func(e *core.RecordAuthWithOTPRequestEvent) error { // e.App // e.Collection // e.Record // e.OTP // and all RequestEvent fields... return e.Next() }) // fires only for "users" and "managers" auth collections app.OnRecordAuthWithOTPRequest("users", "managers").BindFunc(func(e *core.RecordAuthWithOTPRequestEvent) error { // e.App // e.Collection // e.Record // e.OTP // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Batch request hooks ](#batch-request-hooks) [ OnBatchRequest ](#onbatchrequest) `OnBatchRequest` hook is triggered on each API batch request. Could be used to additionally validate or modify the submitted batch requests. This hook will also fire the corresponding `OnRecordCreateRequest`, `OnRecordUpdateRequest`, `OnRecordDeleteRequest` hooks, where `e.App` is the batch transactional app. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnBatchRequest().BindFunc(func(e *core.BatchRequestEvent) error { // e.App // e.Batch // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ File request hooks ](#file-request-hooks) [ OnFileDownloadRequest ](#onfiledownloadrequest) `OnFileDownloadRequest` hook is triggered before each API File download request. Could be used to validate or modify the file response before returning it to the client. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnFileDownloadRequest().BindFunc(func(e *core.FileDownloadRequestEvent) error { // e.App // e.Collection // e.Record // e.FileField // e.ServedPath // e.ServedName // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnFileTokenRequest ](#onfiletokenrequest) `OnFileTokenRequest` hook is triggered on each auth file token API request. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every auth model app.OnFileTokenRequest().BindFunc(func(e *core.FileTokenRequestEvent) error { // e.App // e.Record // e.Token // and all RequestEvent fields... return e.Next(); }) // fires only for "users" app.OnFileTokenRequest("users").BindFunc(func(e *core.FileTokenRequestEvent) error { // e.App // e.Record // e.Token // and all RequestEvent fields... return e.Next(); }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Collection request hooks ](#collection-request-hooks) [ OnCollectionsListRequest ](#oncollectionslistrequest) `OnCollectionsListRequest` hook is triggered on each API Collections list request. Could be used to validate or modify the response before returning it to the client. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnCollectionsListRequest().BindFunc(func(e *core.CollectionsListRequestEvent) error { // e.App // e.Collections // e.Result // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionViewRequest ](#oncollectionviewrequest) `OnCollectionViewRequest` hook is triggered on each API Collection view request. Could be used to validate or modify the response before returning it to the client. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnCollectionViewRequest().BindFunc(func(e *core.CollectionRequestEvent) error { // e.App // e.Collection // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionCreateRequest ](#oncollectioncreaterequest) `OnCollectionCreateRequest` hook is triggered on each API Collection create request. Could be used to additionally validate the request data or implement completely different persistence behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnCollectionCreateRequest().BindFunc(func(e *core.CollectionRequestEvent) error { // e.App // e.Collection // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionUpdateRequest ](#oncollectionupdaterequest) `OnCollectionUpdateRequest` hook is triggered on each API Collection update request. Could be used to additionally validate the request data or implement completely different persistence behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnCollectionUpdateRequest().BindFunc(func(e *core.CollectionRequestEvent) error { // e.App // e.Collection // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionDeleteRequest ](#oncollectiondeleterequest) `OnCollectionDeleteRequest` hook is triggered on each API Collection delete request. Could be used to additionally validate the request data or implement completely different delete behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnCollectionDeleteRequest().BindFunc(func(e *core.CollectionRequestEvent) error { // e.App // e.Collection // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnCollectionsImportRequest ](#oncollectionsimportrequest) `OnCollectionsImportRequest` hook is triggered on each API collections import request. Could be used to additionally validate the imported collections or to implement completely different import behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnCollectionsImportRequest().BindFunc(func(e *core.CollectionsImportRequestEvent) error { // e.App // e.CollectionsData // e.DeleteMissing return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Settings request hooks ](#settings-request-hooks) [ OnSettingsListRequest ](#onsettingslistrequest) `OnSettingsListRequest` hook is triggered on each API Settings list request. Could be used to validate or modify the response before returning it to the client. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnSettingsListRequest().BindFunc(func(e *core.SettingsListRequestEvent) error { // e.App // e.Settings // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnSettingsUpdateRequest ](#onsettingsupdaterequest) `OnSettingsUpdateRequest` hook is triggered on each API Settings update request. Could be used to additionally validate the request data or implement completely different persistence behavior. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() app.OnSettingsUpdateRequest().BindFunc(func(e *core.SettingsUpdateRequestEvent) error { // e.App // e.OldSettings // e.NewSettings // and all RequestEvent fields... return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ### [ Base model hooks ](#base-model-hooks) The Model hooks are fired for all PocketBase structs that implements the Model DB interface - Record, Collection, Log, etc. For convenience, if you want to listen to only the Record or Collection DB model events without doing manual type assertion, you can use the [`OnRecord*`](#record-model-hooks) and [`OnCollection*`](#collection-model-hooks) proxy hooks above. [ OnModelValidate ](#onmodelvalidate) `OnModelValidate` is called every time when a Model is being validated, e.g. triggered by `App.Validate()` or `App.Save()`. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelValidate().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelValidate("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Base model create hooks ](#base-model-create-hooks) [ OnModelCreate ](#onmodelcreate) `OnModelCreate` is triggered every time when a new Model is being created, e.g. triggered by `App.Save()`. Operations BEFORE the `e.Next()` execute before the Model validation and the INSERT DB statement. Operations AFTER the `e.Next()` execute after the Model validation and the INSERT DB statement. Note that successful execution doesn't guarantee that the Model is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnModelAfterCreateSuccess` or `OnModelAfterCreateError` hooks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelCreate().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelCreate("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelCreateExecute ](#onmodelcreateexecute) `OnModelCreateExecute` is triggered after successful Model validation and right before the model INSERT DB statement execution. Usually it is triggered as part of the `App.Save()` in the following firing order: `OnModelCreate` -> `OnModelValidate` (skipped with `App.SaveNoValidate()`) -> `OnModelCreateExecute` Note that successful execution doesn't guarantee that the Model is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnModelAfterCreateSuccess` or `OnModelAfterCreateError` hooks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelCreateExecute().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelCreateExecute("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelAfterCreateSuccess ](#onmodelaftercreatesuccess) `OnModelAfterCreateSuccess` is triggered after each successful Model DB create persistence. Note that when a Model is persisted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelAfterCreateSuccess().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelAfterCreateSuccess("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelAfterCreateError ](#onmodelaftercreateerror) `OnModelAfterCreateError` is triggered after each failed Model DB create persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Save()` failure - delayed on transaction rollback For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelAfterCreateError().BindFunc(func(e *core.ModelErrorEvent) error { // e.App // e.Model // e.Error return e.Next() }) // fires only for "users" and "articles" models app.OnModelAfterCreateError("users", "articles").BindFunc(func(e *core.ModelErrorEvent) error { // e.App // e.Model // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Base model update hooks ](#base-model-update-hooks) [ OnModelUpdate ](#onmodelupdate) `OnModelUpdate` is triggered every time when a new Model is being updated, e.g. triggered by `App.Save()`. Operations BEFORE the `e.Next()` execute before the Model validation and the UPDATE DB statement. Operations AFTER the `e.Next()` execute after the Model validation and the UPDATE DB statement. Note that successful execution doesn't guarantee that the Model is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnModelAfterUpdateSuccess` or `OnModelAfterUpdateError` hooks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelUpdate().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelUpdate("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelUpdateExecute ](#onmodelupdateexecute) `OnModelUpdateExecute` is triggered after successful Model validation and right before the model UPDATE DB statement execution. Usually it is triggered as part of the `App.Save()` in the following firing order: `OnModelUpdate` -> `OnModelValidate` (skipped with `App.SaveNoValidate()`) -> `OnModelUpdateExecute` Note that successful execution doesn't guarantee that the Model is persisted in the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnModelAfterUpdateSuccess` or `OnModelAfterUpdateError` hooks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelUpdateExecute().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelUpdateExecute("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelAfterUpdateSuccess ](#onmodelafterupdatesuccess) `OnModelAfterUpdateSuccess` is triggered after each successful Model DB update persistence. Note that when a Model is persisted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelAfterUpdateSuccess().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelAfterUpdateSuccess("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelAfterUpdateError ](#onmodelafterupdateerror) `OnModelAfterUpdateError` is triggered after each failed Model DB update persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Save()` failure - delayed on transaction rollback For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelAfterUpdateError().BindFunc(func(e *core.ModelErrorEvent) error { // e.App // e.Model // e.Error return e.Next() }) // fires only for "users" and "articles" models app.OnModelAfterUpdateError("users", "articles").BindFunc(func(e *core.ModelErrorEvent) error { // e.App // e.Model // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` ###### [ Base model delete hooks ](#base-model-delete-hooks) [ OnModelDelete ](#onmodeldelete) `OnModelDelete` is triggered every time when a new Model is being deleted, e.g. triggered by `App.Delete()`. Operations BEFORE the `e.Next()` execute before the Model validation and the UPDATE DB statement. Operations AFTER the `e.Next()` execute after the Model validation and the UPDATE DB statement. Note that successful execution doesn't guarantee that the Model is deleted from the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted deleted events, you can bind to `OnModelAfterDeleteSuccess` or `OnModelAfterDeleteError` hooks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelDelete().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelDelete("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelDeleteExecute ](#onmodeldeleteexecute) `OnModelDeleteExecute` is triggered after the internal delete checks and right before the Model the model DELETE DB statement execution. Usually it is triggered as part of the `App.Delete()` in the following firing order: `OnModelDelete` -> internal delete checks -> `OnModelDeleteExecute` Note that successful execution doesn't guarantee that the Model is deleted from the database since its wrapping transaction may not have been committed yet. If you want to listen to only the actual persisted events, you can bind to `OnModelAfterDeleteSuccess` or `OnModelAfterDeleteError` hooks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelDeleteExecute().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelDeleteExecute("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelAfterDeleteSuccess ](#onmodelafterdeletesuccess) `OnModelAfterDeleteSuccess` is triggered after each successful Model DB delete persistence. Note that when a Model is deleted as part of a transaction, this hook is delayed and executed only AFTER the transaction has been committed. This hook is NOT triggered in case the transaction fails/rollbacks. For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelAfterDeleteSuccess().BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) // fires only for "users" and "articles" models app.OnModelAfterDeleteSuccess("users", "articles").BindFunc(func(e *core.ModelEvent) error { // e.App // e.Model return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ OnModelAfterDeleteError ](#onmodelafterdeleteerror) `OnModelAfterDeleteError` is triggered after each failed Model DB delete persistence. Note that the execution of this hook is either immediate or delayed depending on the error: - immediate on `App.Delete()` failure - delayed on transaction rollback For convenience, if you want to listen to only the Record or Collection models events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks. `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func main() { app := pocketbase.New() // fires for every model app.OnModelAfterDeleteError().BindFunc(func(e *core.ModelErrorEvent) error { // e.App // e.Model // e.Error return e.Next() }) // fires only for "users" and "articles" models app.OnModelAfterDeleteError("users", "articles").BindFunc(func(e *core.ModelErrorEvent) error { // e.App // e.Model // e.Error return e.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ Prev: Overview](/docs/go-overview) [Next: Routing ](/docs/go-routing) ## Documentation: go-routing [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Routing Routing PocketBase routing is built on top of the standard Go [`net/http.ServeMux`](https://pkg.go.dev/net/http#ServeMux). The router can be accessed via the `app.OnServe()` hook allowing you to register custom endpoints and middlewares. ### [ Routes ](#routes) ##### [ Registering new routes ](#registering-new-routes) Every route have a path, handler function and eventually middlewares attached to it. For example: `app.OnServe().BindFunc(func(se *core.ServeEvent) error { // register "GET /hello/{name}" route (allowed for everyone) se.Router.GET("/hello/{name}", func(e *core.RequestEvent) error { name := e.Request.PathValue("name") return e.String(http.StatusOK, "Hello " + name) }) // register "POST /api/myapp/settings" route (allowed only for authenticated users) se.Router.POST("/api/myapp/settings", func(e *core.RequestEvent) error { // do something ... return e.JSON(http.StatusOK, map[string]bool{"success": true}) }).Bind(apis.RequireAuth()) return se.Next() })` There are several routes registration methods available, but the most common ones are: `se.Router.GET(path, action) se.Router.POST(path, action) se.Router.PUT(path, action) se.Router.PATCH(path, action) se.Router.DELETE(path, action) // If you want to handle any HTTP method define only a path (e.g. "/example") // OR if you want to specify a custom one add it as prefix to the path (e.g. "TRACE /example") se.Router.Any(pattern, action)` The router also supports creating groups for routes that share the same base path and middlewares. For example: `g := se.Router.Group("/api/myapp") // group middleware g.Bind(apis.RequireAuth()) // group routes g.GET("", action1) g.GET("/example/{id}", action2) g.PATCH("/example/{id}", action3).BindFunc( /* custom route specific middleware func */ ) // nested group sub := g.Group("/sub") sub.GET("/sub1", action4)` The example registers the following endpoints (all require authenticated user access): - GET /api/myapp -> action1 - GET /api/myapp/example/{id} -> action2 - PATCH /api/myapp/example/{id} -> action3 - GET /api/myapp/example/sub/sub1 -> action4 Each router group and route could define [middlewares](#middlewares) in a similar manner to the regular app hooks via the `Bind/BindFunc` methods, allowing you to perform various BEFORE or AFTER action operations (e.g. inspecting request headers, custom access checks, etc.). ##### [ Path parameters and matching rules ](#path-parameters-and-matching-rules) Because PocketBase routing is based on top of the Go standard router mux, we follow the same pattern matching rules. Below you could find a short overview but for more details please refer to [`net/http.ServeMux`](https://pkg.go.dev/net/http#ServeMux). In general, a route pattern looks like `[METHOD ][HOST]/[PATH]` (the METHOD prefix is added automatically when using the designated `GET()`, `POST()`, etc. methods)). Route paths can include parameters in the format `{paramName}`. You can also use `{paramName...}` format to specify a parameter that target more than one path segment. A pattern ending with a trailing slash `/` acts as anonymous wildcard and matches any requests that begins with the defined route. If you want to have a trailing slash but to indicate the end of the URL then you need to end the path with the special `{$}` parameter. If your route path starts with `/api/` consider combining it with your unique app name like `/api/myapp/...` to avoid collisions with system routes. Here are some examples: `// match "GET example.com/index.html" se.Router.GET("example.com/index.html") // match "GET /index.html" (for any host) se.Router.GET("/index.html") // match "GET /static/", "GET /static/a/b/c", etc. se.Router.GET("/static/") // match "GET /static/", "GET /static/a/b/c", etc. // (similar to the above but with a named wildcard parameter) se.Router.GET("/static/{path...}") // match only "GET /static/" (if no "/static" is registered, it is 301 redirected) se.Router.GET("/static/{$}") // match "GET /customers/john", "GET /customer/jane", etc. se.Router.GET("/customers/{name}")` In the following examples `e` is usually [`*core.RequestEvent`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#RequestEvent) value. ##### [ Reading path parameters ](#reading-path-parameters) `id := e.Request.PathValue("id")` ##### [ Retrieving the current auth state ](#retrieving-the-current-auth-state) The request auth state can be accessed (or set) via the `RequestEvent.Auth` field. `authRecord := e.Auth isGuest := e.Auth == nil // the same as "e.Auth != nil && e.Auth.IsSuperuser()" isSuperuser := e.HasSuperuserAuth()` Alternatively you could also access the request data from the summarized request info instance (usually used in hooks like the `OnRecordEnrich` where there is no direct access to the request) . `info, err := e.RequestInfo() authRecord := info.Auth isGuest := info.Auth == nil // the same as "info.Auth != nil && info.Auth.IsSuperuser()" isSuperuser := info.HasSuperuserAuth()` ##### [ Reading query parameters ](#reading-query-parameters) `search := e.Request.URL.Query().Get("search") // or via the parsed request info info, err := e.RequestInfo() search := info.Query["search"]` ##### [ Reading request headers ](#reading-request-headers) `token := e.Request.Header.Get("Some-Header") // or via the parsed request info // (the header value is always normalized per the @request.headers.* API rules format) info, err := e.RequestInfo() token := info.Headers["some_header"]` ##### [ Writing response headers ](#writing-response-headers) `e.Response.Header().Set("Some-Header", "123")` ##### [ Retrieving uploaded files ](#retrieving-uploaded-files) `// retrieve the uploaded files and parse the found multipart data into a ready-to-use []*filesystem.File files, err := e.FindUploadedFiles("document") // or retrieve the raw single multipart/form-data file and header mf, mh, err := e.Request.FormFile("document")` ##### [ Reading request body ](#reading-request-body) Body parameters can be read either via [`e.BindBody`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#Event.BindBody) OR through the parsed request info (requires manual type assertions). The `e.BindBody` argument must be a pointer to a struct or `map[string]any`. The following struct tags are supported (the specific binding rules and which one will be used depend on the request Content-Type): - `json` (json body)- uses the builtin Go JSON package for unmarshaling. - `xml` (xml body) - uses the builtin Go XML package for unmarshaling. - `form` (form data) - utilizes the custom [`router.UnmarshalRequestData`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#UnmarshalRequestData) method. NB! When binding structs make sure that they don't have public fields that shouldn't be bindable and it is advisable such fields to be unexported or define a separate struct with just the safe bindable fields. `// read/scan the request body fields into a typed struct data := struct { // unexported to prevent binding somethingPrivate string Title string `json:"title" form:"title"` Description string `json:"description" form:"description"` Active bool `json:"active" form:"active"` }{} if err := e.BindBody(&data); err != nil { return e.BadRequestError("Failed to read request data", err) } // alternatively, read the body via the parsed request info info, err := e.RequestInfo() title, ok := info.Body["title"].(string)` ##### [ Writing response body ](#writing-response-body) For all supported methods, you can refer to [`router.Event`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#Event) . `// send response with JSON body // (it also provides a generic response fields picker/filter if the "fields" query parameter is set) e.JSON(http.StatusOK, map[string]any{"name": "John"}) // send response with string body e.String(http.StatusOK, "Lorem ipsum...") // send response with HTML body // (check also the "Rendering templates" section) e.HTML(http.StatusOK, "
Hello from {{.name}}
{{end}}` Then to output the final page, we'll register a custom `/hello/:name` route: `// main.go package main import ( "log" "net/http" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/template" ) func main() { app := pocketbase.New() app.OnServe().BindFunc(func(se *core.ServeEvent) error { // this is safe to be used by multiple goroutines // (it acts as store for the parsed templates) registry := template.NewRegistry() se.Router.GET("/hello/{name}", func(e *core.RequestEvent) error { name := e.Request.PathValue("name") html, err := registry.LoadFiles( "views/layout.html", "views/hello.html", ).Render(map[string]any{ "name": name, }) if err != nil { // or redirect to a dedicated 404 HTML page return e.NotFoundError("", err) } return e.HTML(http.StatusOK, html) }) return se.Next() }) if err := app.Start(); err != nil { log.Fatal(err) } }` [ Prev: Sending emails](/docs/go-sending-emails) [Next: Console commands ](/docs/go-console-commands) ## Documentation: go-console-commands [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Console commands Console commands You can register custom console commands using `app.RootCmd.AddCommand(cmd)`, where `cmd` is a [cobra](https://pkg.go.dev/github.com/spf13/cobra) command. Here is an example: `package main import ( "log" "github.com/pocketbase/pocketbase" "github.com/spf13/cobra" ) func main() { app := pocketbase.New() app.RootCmd.AddCommand(&cobra.Command{ Use: "hello", Run: func(cmd *cobra.Command, args []string) { log.Println("Hello world!") }, }) if err := app.Start(); err != nil { log.Fatal(err) } }` To run the command you can build your Go application and execute: `# or "go run main.go hello" ./myapp hello` Keep in mind that the console commands execute in their own separate app process and run independently from the main `serve` command (aka. hook events between different processes are not shared with one another). [ Prev: Rendering templates](/docs/go-rendering-templates) [Next: Realtime messaging ](/docs/go-realtime) ## Documentation: go-realtime [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Realtime messaging Realtime messaging By default PocketBase sends realtime events only for Record create/update/delete operations (and for the OAuth2 auth redirect), but you are free to send custom realtime messages to the connected clients via the [`app.SubscriptionsBroker()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.SubscriptionsBroker) instance. [`app.SubscriptionsBroker().Clients()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/subscriptions#Broker.Clients) returns all connected [`subscriptions.Client`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/subscriptions#Client) indexed by their unique connection id. [`app.SubscriptionsBroker().ChunkedClients(size)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/subscriptions#Broker.ChunkedClients) is similar but return the result as a chunked slice allowing you to split the iteration across several goroutines (usually combined with [`errgroup`](https://pkg.go.dev/golang.org/x/sync/errgroup) ). The current auth record associated with a client could be accessed through `client.Get(apis.RealtimeClientAuthKey)` Note that a single authenticated user could have more than one active realtime connection (aka. multiple clients). This could happen for example when opening the same app in different tabs, browsers, devices, etc. Below you can find a minimal code sample that sends a JSON payload to all clients subscribed to the "example" topic: `func notify(app core.App, subscription string, data any) error { rawData, err := json.Marshal(data) if err != nil { return err } message := subscriptions.Message{ Name: subscription, Data: rawData, } group := new(errgroup.Group) chunks := app.SubscriptionsBroker().ChunkedClients(300) for _, chunk := range chunks { group.Go(func() error { for _, client := range chunk { if !client.HasSubscription(subscription) { continue } client.Send(message) } return nil }) } return group.Wait() } err := notify(app, "example", map[string]any{"test": 123}) if err != nil { return err }` From the client-side, users can listen to the custom subscription topic by doing something like: JavaScript Dart `import PocketBase from 'pocketbase'; const pb = new PocketBase('http://127.0.0.1:8090'); ... await pb.realtime.subscribe('example', (e) => { console.log(e) })` `import 'package:pocketbase/pocketbase.dart'; final pb = PocketBase('http://127.0.0.1:8090'); ... await pb.realtime.subscribe('example', (e) { print(e) })` [ Prev: Console commands](/docs/go-console-commands) [Next: Filesystem ](/docs/go-filesystem) ## Documentation: go-filesystem [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Filesystem Filesystem PocketBase comes with a thin abstraction between the local filesystem and S3. To configure which one will be used you can adjust the storage settings from Dashboard > Settings > Files storage section. The filesystem abstraction can be accessed programmatically via the [`app.NewFilesystem()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.NewFilesystem) method. Below are listed some of the most common operations but you can find more details in the [`filesystem`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem) subpackage. Always make sure to call `Close()` at the end for both the created filesystem instance and the retrieved file readers to prevent leaking resources. ### [ Reading files ](#reading-files) To retrieve the file content of a single stored file you can use [`GetReader(key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.GetReader) . Note that file keys often contain a prefix (aka. the "path" to the file). For record files the full key is `collectionId/recordId/filename`. To retrieve multiple files matching a specific prefix you can use [`List(prefix)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.List) . The below code shows a minimal example how to retrieve a single record file and copy its content into a `bytes.Buffer`. `record, err := app.FindAuthRecordByEmail("users", "test@example.com") if err != nil { return err } // construct the full file key by concatenating the record storage path with the specific filename avatarKey := record.BaseFilesPath() + "/" + record.GetString("avatar") // initialize the filesystem fsys, err := app.NewFilesystem() if err != nil { return err } defer fsys.Close() // retrieve a file reader for the avatar key r, err := fsys.GetReader(avatarKey) if err != nil { return err } defer r.Close() // do something with the reader... content := new(bytes.Buffer) _, err = io.Copy(content, r) if err != nil { return err }` ### [ Saving files ](#saving-files) There are several methods to save (aka. write/upload) files depending on the available file content source: - [`Upload([]byte, key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.Upload) - [`UploadFile(*filesystem.File, key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.UploadFile) - [`UploadMultipart(*multipart.FileHeader, key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.UploadFile) Most users rarely will have to use the above methods directly because for collection records the file persistence is handled transparently when saving the record model (it will also perform size and MIME type validation based on the collection `file` field options). For example: `record, err := app.FindRecordById("articles", "RECORD_ID") if err != nil { return err } // Other available File factories // - filesystem.NewFileFromBytes(data, name) // - filesystem.NewFileFromURL(ctx, url) // - filesystem.NewFileFromMultipart(mh) f, err := filesystem.NewFileFromPath("/local/path/to/file") // set new file (can be single *filesytem.File or multiple []*filesystem.File) // (if the record has an old file it is automatically deleted on successful Save) record.Set("yourFileField", f) err = app.Save(record) if err != nil { return err }` ### [ Deleting files ](#deleting-files) Files can be deleted from the storage filesystem using [`Delete(key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.Delete) . Similar to the previous section, most users rarely will have to use the `Delete` file method directly because for collection records the file deletion is handled transparently when removing the existing filename from the record model (this also ensure that the db entry referencing the file is also removed). For example: `record, err := app.FindRecordById("articles", "RECORD_ID") if err != nil { return err } // if you want to "reset" a file field (aka. deleting the associated single or multiple files) // you can set it to nil record.Set("yourFileField", nil) // OR if you just want to remove individual file(s) from a multiple file field you can use the "-" modifier // (the value could be a single filename string or slice of filename strings) record.Set("yourFileField-", "example_52iWbGinWd.txt") err = app.Save(record) if err != nil { return err }` [ Prev: Realtime messaging](/docs/go-realtime) [Next: Logging ](/docs/go-logging) ## Documentation: go-logging [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Logging Logging `app.Logger()` provides access to a standard `slog.Logger` implementation that writes any logs into the database so that they can be later explored from the PocketBase Dashboard > Logs section. For better performance and to minimize blocking on hot paths, logs are written with debounce and on batches: - 3 seconds after the last debounced log write - when the batch threshold is reached (currently 200) - right before app termination to attempt saving everything from the existing logs queue ### [ Log methods ](#log-methods) All standard [`slog.Logger`](https://pkg.go.dev/log/slog) methods are available but below is a list with some of the most notable ones. ##### [ Debug(message, attrs...) ](#debugmessage-attrs-) `app.Logger().Debug("Debug message!") app.Logger().Debug( "Debug message with attributes!", "name", "John Doe", "id", 123, )` ##### [ Info(message, attrs...) ](#infomessage-attrs-) `app.Logger().Info("Info message!") app.Logger().Info( "Info message with attributes!", "name", "John Doe", "id", 123, )` ##### [ Warn(message, attrs...) ](#warnmessage-attrs-) `app.Logger().Warn("Warning message!") app.Logger().Warn( "Warning message with attributes!", "name", "John Doe", "id", 123, )` ##### [ Error(message, attrs...) ](#errormessage-attrs-) `app.Logger().Error("Error message!") app.Logger().Error( "Error message with attributes!", "id", 123, "error", err, )` ##### [ With(attrs...) ](#withattrs-) `With(atrs...)` creates a new local logger that will "inject" the specified attributes with each following log. `l := app.Logger().With("total", 123) // results in log with data {"total": 123} l.Info("message A") // results in log with data {"total": 123, "name": "john"} l.Info("message B", "name", "john")` ##### [ WithGroup(name) ](#withgroupname) `WithGroup(name)` creates a new local logger that wraps all logs attributes under the specified group name. `l := app.Logger().WithGroup("sub") // results in log with data {"sub": { "total": 123 }} l.Info("message A", "total", 123)` ### [ Logs settings ](#logs-settings) You can control various log settings like logs retention period, minimal log level, request IP logging, etc. from the logs settings panel: ### [ Custom log queries ](#custom-log-queries) The logs are usually meant to be filtered from the UI but if you want to programmatically retrieve and filter the stored logs you can make use of the [`app.LogQuery()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.LogsQuery) query builder method. For example: `logs := []*core.Log{} // see https://pocketbase.io/docs/go-database/#query-builder err := app.LogQuery(). // target only debug and info logs AndWhere(dbx.In("level", -4, 0). // the data column is serialized json object and could be anything AndWhere(dbx.NewExp("json_extract(data, '$.type') = 'request'")). OrderBy("created DESC"). Limit(100). All(&logs)` [ Prev: Filesystem](/docs/go-filesystem) [Next: Testing ](/docs/go-testing) ## Documentation: go-testing [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Testing Testing PocketBase exposes several test mocks and stubs (eg. `tests.TestApp`, `tests.ApiScenario`, `tests.MockMultipartData`, etc.) to help you write unit and integration tests for your app. You could find more information in the [`github.com/pocketbase/pocketbase/tests`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tests) sub package, but here is a simple example. ### [ 1. Setup ](#1-setup) Let's say that we have a custom API route `GET /my/hello` that requires superuser authentication: `// main.go package main import ( "log" "net/http" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" ) func bindAppHooks(app core.App) { app.OnServe().BindFunc(func(se *core.ServeEvent) error { se.Router.Get("/my/hello", func(e *core.RequestEvent) error { return e.JSON(http.StatusOK, "Hello world!") }).Bind(apis.RequireSuperuserAuth()) return se.Next() }) } func main() { app := pocketbase.New() bindAppHooks(app) if err := app.Start(); err != nil { log.Fatal(err) } }` ### [ 2. Prepare test data ](#2-prepare-test-data) Now we have to prepare our test/mock data. There are several ways you can approach this, but the easiest one would be to start your application with a custom `test_pb_data` directory, e.g.: `./pocketbase serve --dir="./test_pb_data" --automigrate=0` Go to your browser and create the test data via the Dashboard (both collections and records). Once completed you can stop the server (you could also commit `test_pb_data` to your repo). ### [ 3. Integration test ](#3-integration-test) To test the example endpoint, we want to: - ensure it handles only GET requests - ensure that it can be accessed only by superusers - check if the response body is properly set Below is a simple integration test for the above test cases. We'll also use the test data created in the previous step. `// main_test.go package main import ( "net/http" "testing" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tests" ) const testDataDir = "./test_pb_data" func generateToken(collectionNameOrId string, email string) (string, error) { app, err := tests.NewTestApp(testDataDir) if err != nil { return "", err } defer app.Cleanup() record, err := app.FindAuthRecordByEmail(collectionNameOrId, email) if err != nil { return "", err } return record.NewAuthToken() } func TestHelloEndpoint(t *testing.T) { recordToken, err := generateToken("users", "test@example.com") if err != nil { t.Fatal(err) } superuserToken, err := generateToken(core.CollectionNameSuperusers, "test@example.com") if err != nil { t.Fatal(err) } // setup the test ApiScenario app instance setupTestApp := func(t testing.TB) *tests.TestApp { testApp, err := tests.NewTestApp(testDataDir) if err != nil { t.Fatal(err) } // no need to cleanup since scenario.Test() will do that for us // defer testApp.Cleanup() bindAppHooks(testApp) return testApp } scenarios := []tests.ApiScenario{ { Name: "try with different http method, e.g. POST", Method: http.MethodPost, URL: "/my/hello", ExpectedStatus: 405, ExpectedContent: []string{"\"data\":{}"}, TestAppFactory: setupTestApp, }, { Name: "try as guest (aka. no Authorization header)", Method: http.MethodGet, URL: "/my/hello", ExpectedStatus: 401, ExpectedContent: []string{"\"data\":{}"}, TestAppFactory: setupTestApp, }, { Name: "try as authenticated app user", Method: http.MethodGet, URL: "/my/hello", Headers: map[string]string{ "Authorization": recordToken, }, ExpectedStatus: 401, ExpectedContent: []string{"\"data\":{}"}, TestAppFactory: setupTestApp, }, { Name: "try as authenticated superuser", Method: http.MethodGet, URL: "/my/hello", Headers: map[string]string{ "Authorization": superuserToken, }, ExpectedStatus: 200, ExpectedContent: []string{"Hello world!"}, TestAppFactory: setupTestApp, }, } for _, scenario := range scenarios { scenario.Test(t) } }` [ Prev: Logging](/docs/go-logging) [Next: Miscellaneous ](/docs/go-miscellaneous) ## Documentation: go-miscellaneous [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Miscellaneous Miscellaneous ### [ app.Store() ](#app-store) [`app.Store()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.Store) returns a concurrent-safe application memory store that you can use to store anything for the duration of the application process (e.g. cache, config flags, etc.). You can find more details about the available store methods in the [`store.Store`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/store#Store) documentation but the most commonly used ones are `Get(key)`, `Set(key, value)` and `GetOrSet(key, setFunc)`. `app.Store().Set("example", 123) v1 := app.Store().Get("example").(int) // 123 v2 := app.Store().GetOrSet("example2", func() any { // this setter is invoked only once unless "example2" is removed // (e.g. suitable for instantiating singletons) return 456 }).(int) // 456` Keep in mind that the application store is also used internally usually with `pb*` prefixed keys (e.g. the collections cache is stored under the `pbAppCachedCollections` key) and changing these system keys or calling `RemoveAll()`/`Reset()` could have unintended side-effects. If you want more advanced control you can initialize your own store independent from the application instance via `store.New[K, T](nil)`. ### [ Security helpers ](#security-helpers) Below are listed some of the most commonly used security helpers but you can find detailed documentation for all available methods in the [`security`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/security) subpackage. ##### [ Generating random strings ](#generating-random-strings) `secret := security.RandomString(10) // e.g. a35Vdb10Z4 secret := security.RandomStringWithAlphabet(5, "1234567890") // e.g. 33215` ##### [ Compare strings with constant time ](#compare-strings-with-constant-time) `isEqual := security.Equal(hash1, hash2)` ##### [ AES Encrypt/Decrypt ](#aes-encryptdecrypt) `// must be random 32 characters string const key = "e0gjSKuq6MVAHIWvkzNqIZ8RXEyEREej" encrypted, err := security.Encrypt([]byte("test"), key) if err != nil { return err } decrypted := security.Decrypt(encrypted, key) // []byte("test")` [ Prev: Testing](/docs/go-testing) [Next: Record proxy ](/docs/go-record-proxy) ## Documentation: go-record-proxy [ Introduction ](/docs) [ Going to production ](/docs/going-to-production) [ Web APIs reference ](/docs/api-records) [Extend with Go](/docs/go-overview) [Extend with JavaScript](/docs/js-overview) [Go Overview ](/docs/go-overview) [Go Event hooks ](/docs/go-event-hooks) [Go Routing ](/docs/go-routing) [Go Database ](/docs/go-database) [Go Record operations ](/docs/go-records) [Go Collection operations ](/docs/go-collections) [Go Migrations ](/docs/go-migrations) [Go Jobs scheduling ](/docs/go-jobs-scheduling) [Go Sending emails ](/docs/go-sending-emails) [Go Rendering templates ](/docs/go-rendering-templates) [Go Console commands ](/docs/go-console-commands) [Go Realtime messaging ](/docs/go-realtime) [Go Filesystem ](/docs/go-filesystem) [Go Logging ](/docs/go-logging) [Go Testing ](/docs/go-testing) [Go Miscellaneous ](/docs/go-miscellaneous) [Go Record proxy ](/docs/go-record-proxy) [JS Overview ](/docs/js-overview) [JS Event hooks ](/docs/js-event-hooks) [JS Routing ](/docs/js-routing) [JS Database ](/docs/js-database) [JS Record operations ](/docs/js-records) [JS Collection operations ](/docs/js-collections) [JS Migrations ](/docs/js-migrations) [JS Jobs scheduling ](/docs/js-jobs-scheduling) [JS Sending emails ](/docs/js-sending-emails) [JS Rendering templates ](/docs/js-rendering-templates) [JS Console commands ](/docs/js-console-commands) [JS Sending HTTP requests ](/docs/js-sending-http-requests) [JS Realtime messaging ](/docs/js-realtime) [JS Filesystem ](/docs/js-filesystem) [JS Logging ](/docs/js-logging) [JS Types reference ](/jsvm/index.html) Extend with Go - Record proxy Record proxy The available [`core.Record` and its helpers](/docs/go-records) are usually the recommended way to interact with your data, but in case you want a typed access to your record fields you can create a helper struct that embeds [`core.BaseRecordProxy`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseRecordProxy) (which implements the `core.RecordProxy` interface) and define your collection fields as getters and setters. By implementing the `core.RecordProxy` interface you can use your custom struct as part of a `RecordQuery` result like a regular record model. In addition, every DB change through the proxy struct will trigger the corresponding record validations and hooks. This ensures that other parts of your app, including 3rd party plugins, that don't know or use your custom struct will still work as expected. Below is a sample `Article` record proxy implementation: `// article.go package main import ( "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/types" ) // ensures that the Article struct satisfy the core.RecordProxy interface var _ core.RecordProxy = (*Article)(nil) type Article struct { core.BaseRecordProxy } func (a *Article) Title() string { return a.GetString("title") } func (a *Article) SetTitle(title string) { a.Set("title", title) } func (a *Article) Slug() string { return a.GetString("slug") } func (a *Article) SetSlug(slug string) { a.Set("slug", slug) } func (a *Article) Created() types.DateTime { return a.GetDateTime("created") } func (a *Article) Updated() types.DateTime { return a.GetDateTime("updated") }` Accessing and modifying the proxy records is the same as for the regular records. Continuing with the above `Article` example: `func FindArticleBySlug(app core.App, slug string) (*Article, error) { article := &Article{} err := app.RecordQuery("articles"). AndWhere(dbx.NewExp("LOWER(slug)={:slug}", dbx.Params{ "slug": strings.ToLower(slug), // case insensitive match })). Limit(1). One(article) if err != nil { return nil, err } return article, nil } ... article, err := FindArticleBySlug(app, "example") if err != nil { return err } // change the title article.SetTitle("Lorem ipsum...") // persist the change while also triggering the original record validations and hooks err = app.Save(article) if err != nil { return err }` If you have an existing `*core.Record` value you can also load it into your proxy using the `SetProxyRecord` method: `// fetch regular record record, err := app.FindRecordById("articles", "RECORD_ID") if err != nil { return err } // load into proxy article := &Article{} article.SetProxyRecord(record)` [ Prev: Miscellaneous](/docs/go-miscellaneous)