For the JSVM version please refer to /v023upgrade/jsvm.

    Please note that you don't have to upgrade to v0.23.0 if you are not planning further developing your existing app or are satisfied with the v0.22.x features set. There are no critical issues with PocketBase v0.22.x and in the case of reported bugs and security vulnerabilities, the fixes will be backported for at least until Q1 of 2025 (if not longer).

    If you don't plan upgrading just make sure to pin the SDKs version to their latest PocketBase v0.22.x compatible:

    • JS SDK: <0.22.0
    • Dart SDK: <0.19.0

    Before upgrading make sure to backup your current pb_data directory somewhere safe.

    PocketBase v0.23+ comes with many changes and requires applying manual migration steps to your custom Go code.
    Existing data in pb_data/data.db will be automatically upgraded once the PocketBase v0.23.0 executable is started.
    But there is no easy and automated Go code upgrade solution so be prepared because it might take you some time - from 1h to entire weekend!

    Suggested upgrade path:

    1. Download a backup of your production pb_data so that you can adjust your code and test the upgrade locally.
    2. Ensure that you are already using PocketBase v0.22.x (you can find the version number in the bottom-right corner in the UI). If you are using an older version, please upgrade first to PocketBase v0.22.x (there are no major breaking changes in the recent v0.22.x releases so it should be enough to download the specific executable and run it against your pb_data).
    3. If you are using the SDKs, apply the necessary changes to your client-side code by following:
    4. Temporary comment the migrations import statement (if you use migrations).
    5. Apply the necessary Go code changes based on the notes listed below in this document.
    6. Upgrade the migration files (if any) to the new APIs. If your existing migrations files are all autogenerated, you may find it easier to upgrade by deleting all of them and run with the v0.23.0 version of your app go run main.go migrate collections which will create a full snapshot of your latest collections configuration.
      Optionally, after that you can also run go run migrate history-sync to remove the deleted migrations from the applied migrations history.
    7. Uncomment the migrations import statement.
    8. Build your Go executable and test run your local changes (including a test against no existing pb_data).
    9. If everything is working fine, create another more recent production backup for just in case.
      After that deploy your app executable to the production server and restart the app service (e.g. systemctl restart pocketbase).

    The below listed changes doesn't cover everything but it should be a good starting point.

    If you need help with the "translation" of your old code to the new APIs, feel free to open a Q&A discussion with the specific code sample and I'll try to assist you.

    For consistency with the Go standard library and to silent commonly used linters the following initilisms case normalization was applied for all exported bindings:

    Old New
    %Json% %JSON%
    %Ip% %IP%
    %Url% %URL%
    %Smtp% %SMTP%
    %Tls% %TLS%
    %Jwt% %JWT%

    The Dao abstraction has been removed and most of the old Dao methods are now part of the core.App instance.

    The app settings related to the OAuth2 and email templates are moved in the collection options to allow more granular customizations.

    For more details about all new app fields and methods, please refer to the core.App interface docs.

    App.Cache()
    App.Store()
    App.RefreshSettings()
    App.ReloadSettings()
    App.Dao().AdminQuery()
    App.RecordQuery(core.CollectionNameSuperusers)
    App.Dao().FindAdminById(id)
    App.FindRecordById(core.CollectionNameSuperusers, id)
    App.Dao().FindAdminByEmail(email)
    App.FindAuthRecordByEmail(core.CollectionNameSuperusers, email)
    App.Dao().FindAdminByToken(token, baseTokenKey)
    // tokenType could be: // - "auth" (core.TokenTypeAuth) // - "file" (core.TokenTypeFile) // - "verification" (core.TokenTypeVerification) // - "passwordReset" (core.TokenTypePasswordReset) // - "emailChange" (core.TokenTypeEmailChange) App.FindAuthRecordByToken(token, [tokenType])
    App.Dao().TotalAdmins()
    App.CountRecords(core.CollectionNameSuperusers, [exprs...])
    App.Dao().DeleteAdmin(admin)
    App.Delete(admin)
    App.Dao().SaveAdmin(admin)
    App.Save(admin)
    App.Dao().RecordQuery(collectionModelOrIdentifier)
    App.RecordQuery(collectionModelOrIdentifier)
    App.Dao().FindRecordById(collectionNameOrId, recordId, [optFilters...])
    App.FindRecordById(collectionModelOrIdentifier, recordId, [optFilters...])
    App.Dao().FindRecordsByIds(collectionNameOrId, recordIds, [optFilters...])
    App.FindRecordsByIds(collectionModelOrIdentifier, recordIds, [optFilters...])
    App.Dao().FindRecordsByExpr(collectionNameOrId, [exprs...])
    App.FindAllRecords(collectionModelOrIdentifier, [exprs...])
    App.Dao().FindFirstRecordByData(collectionNameOrId, key, value)
    App.FindFirstRecordByData(collectionModelOrIdentifier, key, value)
    App.Dao().FindRecordsByFilter(collectionNameOrId, filter, sort, limit, offset, [params])
    App.FindRecordsByFilter(collectionModelOrIdentifier, filter, sort, limit, offset, [params])
    N/A
    App.CountRecords(collectionModelOrIdentifier, [exprs...])
    App.Dao().FindAuthRecordByToken(token, baseTokenKey)
    // tokenType could be: // - auth // - file // - verification // - passwordReset // - emailChange App.FindAuthRecordByToken(token, [tokenType])
    App.Dao().FindAuthRecordByEmail(collectionNameOrId, email)
    App.FindAuthRecordByEmail(collectionModelOrIdentifier, email)
    App.Dao().FindAuthRecordByUsername(collectionNameOrId, username)
    // The "username" field is no longer required and has no special meaning. App.FindFirstRecordByData(collectionModelOrIdentifier, "username", username)
    App.Dao().SuggestUniqueAuthRecordUsername(ollectionNameOrId, baseUsername, [excludeIds...])
    // The "username" field is no longer required and has no special meaning. As a workaound you can use something like: func suggestUniqueAuthRecordUsername( collectionModelOrIdentifier, baseUsername, ) string { username := baseUsername for i = 0; i < 10; i++ { // max 10 attempts total, err := App.CountRecords( collectionModelOrIdentifier, dbx.NewExp("LOWER([[username]])={:username}", dbx.Params{"username": username.toLowerCase()}), ) if err == nil && total == 0 { break // already unique } username = baseUsername + security.RandomStringWithAlphabet(3+i, "123456789") } return username }
    App.Dao().CanAccessRecord(record, requestInfo, accessRule)
    App.CanAccessRecord(record, requestInfo, accessRule)
    App.Dao().ExpandRecord(record, expands, optFetchFunc)
    App.ExpandRecord(record, expands, optFetchFunc)
    App.Dao().ExpandRecords(records, expands, optFetchFunc)
    App.ExpandRecords(records, expands, optFetchFunc)
    App.Dao().SaveRecord(record)
    App.Save(record)
    App.Dao().DeleteRecord(record)
    App.Delete(record)
    App.Dao().FindAllExternalAuthsByRecord(authRecord)
    App.FindAllExternalAuthsByRecord(authRecord)
    N/A
    App.FindAllExternalAuthsByCollection(collection)
    App.Dao().FindFirstExternalAuthByExpr(expr)
    App.FindFirstExternalAuthByExpr(expr)
    App.Dao().FindExternalAuthByRecordAndProvider(authRecord)
    App.FindFirstExternalAuthByExpr(dbx.HashExp{ "collectionRef": authRecord.Collection().Id, "recordRef": authRecord.Id, "provider": provider, })
    App.Dao().DeleteExternalAuth(model)
    App.Delete(model)
    App.Dao().SaveExternalAuth(model)
    App.Save(model)
    App.Dao().CollectionQuery()
    App.CollectionQuery()
    App.Dao().FindCollectionsByType(collectionType)
    App.FindAllCollections(collectionTypes...)
    App.Dao().FindCollectionByNameOrId(nameOrId)
    App.FindCollectionByNameOrId(nameOrId)
    App.Dao().IsCollectionNameUnique(name, excludeIds...)
    App.IsCollectionNameUnique(name, excludeIds...)
    App.Dao().FindCollectionReferences(collection, excludeIds...)
    App.FindCollectionReferences(collection, excludeIds...)
    App.Dao().DeleteCollection(collection)
    App.Delete(collection)
    App.Dao().SaveCollection(collection)
    App.Save(collection)
    App.Dao().ImportCollections(importedCollections, deleteMissing, afterSync)
    App.ImportCollections(importSliceOfMap, deleteMissing)
    N/A
    App.ImportCollectionsByMarshaledJSON(sliceOfMapsBytes, deleteMissing)
    App.Dao().SyncRecordTableSchema(newCollection, oldCollection)
    App.SyncRecordTableSchema(newCollection, oldCollection)
    App.Dao().LogQuery()
    App.LogQuery()
    App.Dao().FindLogById(id)
    App.FindLogById(id)
    App.Dao().LogsStats(expr)
    App.LogsStats(expr)
    App.Dao().DeleteOldLogs(createdBefore)
    App.DeleteOldLogs(createdBefore)
    App.Dao().SaveLog(log)
    App.AuxSave(log)
    App.Dao().SaveSettings(newSettings, [optEncryptionKey])
    App.Save(newSettings)
    titles := []string{"title1", "title2", "title3"} collection, err := App.Dao().FindCollectionByNameOrId("articles") if err != nil { return err } err = App.Dao().RunInTransaction((txDao) => { // create new record for each title for _, title := range titles { record := models.NewRecord(collection) record.Set("title", title) err := txDao.SaveRecord(record) if err != nil { return err } } return nil }) if err != nil { return err }
    titles := []string{"title1", "title2", "title3"} collection, err := App.FindCollectionByNameOrId("articles") if err != nil { return err } err = App.RunInTransaction((txApp) => { // create new record for each title for _, title := range titles { record := core.NewRecord(collection) record.Set("title", title) err := txApp.Save(record) if err != nil { return err } } return nil }) if err != nil { return err }
    App.Dao().Vacuum()
    App.Vacuum() / App.AuxVacuum()
    App.Dao().HasTable(tableName)
    App.HasTable(tableName)
    App.Dao().TableColumns(tableName)
    App.TableColumns(tableName)
    App.Dao().TableInfo(tableName)
    App.TableInfo(tableName)
    App.Dao().TableIndexes(tableName)
    App.TableIndexes(tableName)
    App.Dao().DeleteTable(tableName)
    App.DeleteTable(tableName)
    App.Dao().DeleteView(name)
    App.DeleteView(name)
    App.Dao().SaveView(name, selectQuery)
    App.SaveView(name, selectQuery)
    App.Dao().CreateViewSchema(selectQuery)
    App.CreateViewFields(selectQuery)

    For all changes please refer to the Record Go struct

    The models and its subpackages are merged in core.

    The Record create/update operations are simplified and the Dao and forms.RecordUpsert abstractions have been removed.

    App.Save(record) both validate and persist the record. Use App.SaveNoValidate(record) if you want to skip the validations.

    Files are treated as regular field value, so for uploading a new record file is enough to call record.Set("yourFileField", *filesystem.File).

    Additionally, the record.Set() method now supports all field modifiers that are available with the Web APIs. For example: record.Set("total+", 10) adds 10 to the existing total record field value. More information about the available modifiers you can find in the main documentation.

    record, err := app.Dao().FindRecordById("articles", "RECORD_ID") if err != nil { return err } form := forms.NewRecordUpsert(app, record) form.LoadData(map[string]any{ "title": "Lorem ipsum", "active": true, }) // upload file(s) f1, _ := filesystem.NewFileFromPath("/path/to/file1") f2, _ := filesystem.NewFileFromPath("/path/to/file2") form.AddFiles("documents", f1, f2) // mark file(s) for deletion form.RemoveFiles("featuredImages", "demo_xzihx0w.png") err = form.Submit(); if err != nil { return err }
    record, err := app.FindRecordById("articles", "RECORD_ID") if err != nil { return err } // there is also record.Load(map[string]any{ ... }) record.Set("title", "Lorem ipsum") record.Set("active", true) // upload file(s) f1, _ := filesystem.NewFileFromPath("/path/to/file1") f2, _ := filesystem.NewFileFromPath("/path/to/file2") record.Set("documents", []any{f1, f2}) // mark file(s) for deletion record.Set("featuredImages-", "demo_xzihx0w.png") err = App.Save(record) if err != nil { return err }
    record.SchemaData()
    record.FieldsData()
    record.UnknownData()
    record.CustomData()
    record.WithUnknownData(state)
    record.WithCustomData(state)
    record.OriginalCopy()
    record.Original()
    record.CleanCopy()
    record.Fresh()
    record.GetTime(fieldName)
    record.GetDateTime(fieldName).Time()
    tokens.NewRecordAuthToken(app, record)
    record.NewAuthToken()
    tokens.NewRecordVerifyToken(app, record)
    record.NewVerificationToken()
    tokens.NewRecordResetPasswordToken(app, record)
    record.NewPasswordResetToken()
    tokens.NewRecordChangeEmailToken(app, record, newEmail)
    record.NewEmailChangeToken()
    tokens.NewRecordFileToken(app, record)
    record.NewFileToken()

    For all changes please refer to the Collection Go struct

    Themodels and its subpackages are merged in core.

    There are now new dedicated collection type factories: core.NewBaseCollection(name) core.NewAuthCollection(name) core.NewViewCollection(name)

    Collection.Schema is renamed to Collection.Fields and now has a type of core.FieldsList (which is just a convenient alias for regular slice).
    There are now also dedicated typed fields in the core package:

    All previous dynamic Collection.Options.* fields are flattened for consistency with the JSON Web APIs:

    Collection.Options.Query = "..."
    Collection.ViewQuery = "..."
    Collection.Options.ManageRule = "..."
    Collection.ManageRule = "..."
    Collection.Options.OnlyVerified = true
    Collection.AuthRule = "verified = true"
    Collection.Options.OnlyVerified = true
    Collection.AuthRule = "verified = true"
    Collection.Options.AllowOAuth2Auth = true
    // note: providers can be set via Collection.OAuth2.Providers Collection.OAuth2.Enabled = true
    Collection.Options.AllowUsernameAuth = true
    Collection.PasswordAuth.Enabled = true Collection.PasswordAuth.IdentityFields = []string{"username"}
    Collection.Options.AllowEmailAuth = true
    Collection.PasswordAuth.Enabled = true Collection.PasswordAuth.IdentityFields = []string{"email"}
    Collection.Options.RequireEmail = true Collection.Options.ExceptEmailDomains = []string{"example.com"} Collection.Options.OnlyEmailDomains = []string{"example.com"}
    // you can adjust the system "email" field options in the same way as the regular Collection.Fields, e.g.: Collection.Fields.GetByName("email").Require = true Collection.Fields.GetByName("email").ExceptDomains = []string{"example.com"} Collection.Fields.GetByName("email").OnlyDomains = []string{"example.com"}
    Collection.Options.MinPasswordLength = 10
    // you can adjust the system "password" field options in the same way as the regular Collection.Fields. e.g.: Collection.Fields.GetByName("password").Min = 10

    The Collection create/update operations are also simplified and the Dao and forms.CollectionUpsert abstractions have been removed.

    App.Save(collection) both validate and persist the collection. Use App.SaveNoValidate(collection) if you want to skip the validations.

    Below is an example forms.CollectionUpsert migration:

    collection := &models.Collection{} form := forms.NewCollectionUpsert(app, collection) form.Name = "example" form.Type = models.CollectionTypeBase form.ViewRule = types.Pointer("@request.auth.id != ''") form.CreateRule = types.Pointer("") form.UpdateRule = types.Pointer("@request.auth.id != ''") form.Schema.AddField(&schema.SchemaField{ Name: "title", Type: schema.FieldTypeText, Required: true, Options: &schema.TextOptions{ Max: types.Pointer(10), }, }) form.Schema.AddField(&schema.SchemaField{ Name: "users", Type: schema.FieldTypeRelation, Required: true, Options: &schema.RelationOptions{ MaxSelect: types.Pointer(5), CollectionId: "ae40239d2bc4477", CascadeDelete: true, }, }) err := form.Submit() if err != nil { return err }
    collection := core.NewBaseCollection("example") collection.ViewRule = types.Pointer("@request.auth.id != ''") collection.CreateRule = types.Pointer("") collection.UpdateRule = types.Pointer("@request.auth.id != ''") collection.Fields.Add(&core.TextField{ Name: "title", Required: true, Max: 10, }) collection.Fields.Add(&core.RelationField{ Name: "users", Required: true, MaxSelect: 5, CollectionId: "ae40239d2bc4477", CascadeDelete: true, }) err := app.Save(collection); if err != nil { return err }

    If your existing migrations are all autogenerated, you may find it easier to upgrade by deleting all of them and run with your PocketBase v0.23.0 app go run main.go migrate collections which will create a full snapshot of your latest collections configuration.

    Optionally, after that you can also run go run main.go migrate history-sync to remove the deleted migrations from the applied migrations history.

    The migrations.Register method accepts the transactional app instance as argument instead of dbx.Builder. This allow access not only to the database but also to the app settings, filesystem, mailer, etc.

    m.Register(func(db dbx.Builder) error { dao := daos.New(db) settings, _ := dao.FindSettings() settings.Meta.AppName = "test" settings.Logs.MaxDays = 2 return dao.SaveSettings(settings) }, nil)
    m.Register(func(app core.App) error { settings := app.Settings() settings.Meta.AppName = "test" settings.Logs.MaxDays = 2 return app.Save(settings) }, nil)

    PocketBase v0.23.0 comes with app level cron scheduler - app.Cron() (this will allow us in the future to show the registered jobs in the UI).

    You can still initialize a cron scheduler manually if you want to (especially if you use a custom cron package), but app.Cron() will be the recommended way shown in the documentation:

    app.OnBeforeServe().Add(func(e *core.ServeEvent) error { scheduler := cron.New() // prints "Hello!" every 2 minutes scheduler.MustAdd("hello", "*/2 * * * *", func() { println("Hello!") }) scheduler.Start() return nil })
    // prints "Hello!" every 2 minutes app.Cron().MustAdd("hello", "*/2 * * * *", func() { println("Hello!") })

    PocketBase v0.23.0 ships with a new router built on top of the standarad Go 1.22 mux enhancements .
    Some of the more notable differences are:

    • Route path parameters must comply with the Go 1.22 mux syntax .
      Or in other words :param must be replaced with {param}. For static path parameter instead of * you have to use special {path...} param.
    • Similar to the new hook handlers, middlewares must call e.Next() in order to proceed with the execution chain.
    • To access the auth state in a route you can call e.Auth. Similarly, to set a new logged auth state you can simply assign a new auth record to the e.Auth field.
    • Because admins are now _superusers auth collection records, e.Auth will be set to the authenticated superuser.
      To check whether e.Auth is a superuser you can use the e.HasSuperuserAuth() helper (or alternatively check the record method e.Auth.IsSuperuser() or e.Auth.Collection().Name == "_superusers").
    • apis.RequestInfo(c) has been replaced with e.RequestInfo().
      Note also that the requestInfo.Data field was renamed to requestInfo.Body for consistency with the @request.body.* API rules change.

    Below you can find a short comparision table with the new routing interface:

    app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.GET("/hello/:name", func(c echo.Context) error { name := c.PathParam("name") return c.JSON(http.StatusOK, map[string]string{ "message": "Hello " + name, }) }, apis.RequireRecordAuth()) return nil })
    app.OnServe().BindFunc(func(se *core.ServeEvent) error { se.Router.GET("/hello/{name}", func(e *core.RequestEvent) error { name := e.Request.PathValue("name") return e.JSON(http.StatusOK, map[string]string{ "message": "Hello " + name, }) }).Bind(apis.RequireAuth()) return se.Next() })
    app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.GET( "/*", apis.StaticDirectoryHandler(os.DirFS("/path/to/public"), false), ) return nil })
    app.OnServe().BindFunc(func(se *core.ServeEvent) error { se.Router.GET( "/{path...}", apis.Static(os.DirFS("/path/to/public"), false), ) return se.Next() })
    app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.Use((next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // e.g. inspect some header value before processing the request header := c.Request().Header.Get("Some-Header") if header == "" { return apis.NewBadRequestError("Invalid request", nil) } return next(c) // proceed with the request chain } }) return nil })
    app.OnServe().BindFunc(func(se *core.ServeEvent) error { se.Router.BindFunc(func(e *core.RequestEvent) error { // e.g. inspect some header value before processing the request header := e.Request.Header.Get("Some-Header") if header == "" { return e.BadRequestError("Invalid request", nil) } return e.Next() // proceed with the request chain }) return se.Next() })
    // "c" is the echo.Context admin := c.Get("admin") // admin model record := c.Get("authRecord") // auth record model // you can also read the auth state from the cached request info info := apis.RequestInfo(c) admin := info.Admin record := info.AuthRecord isAdminAuth := admin != null
    // "e" is core.RequestEvent auth := e.Auth // auth record model // you can also read the auth state from the cached request info info, _ := e.RequestInfo() auth := info.Auth // or e.Auth != nil && e.Auth.IsSuperuser() isSuperuserAuth := e.HasSuperuserAuth()
    id := c.PathParam("id")
    id := e.Request.PathValue("id")
    search := c.QueryParam("search") // or via the cached request object info := apis.RequestInfo(c) search := info.Query["search"]
    search := e.Request.URL.Query().Get("test") // or via the cached request object info, _ := e.RequestInfo() search := info.Query["search"]
    token := c.Request().Header.Get("Some-Header") // or via the cached request object (the header value is always normalized) info := apis.RequestInfo(c) token := info.Headers["some_header"]
    token := e.Request.Header.Get("Some-Header") // or via the cached request object (the header value is always normalized) info, _ := e.RequestInfo() token := info.Headers["some_header"]
    c.Response().Header().Set("Some-Header", "123")
    e.Response.Header().Set("Some-Header", "123")
    // read/scan the request body fields into a typed object // (note that the echo request body cannot be read twice with "bind") body := struct { Title string `json:"title" form:"title"` }{} if err := c.Bind(&body); err != nil { return apis.NewBadRequestError("Failed to read request body", err) } // read single multipart/form-data field title := c.FormValue("title") // read single multipart/form-data file doc := c.FormFile("document") // convert the multipart/form-data file into *filesystem.File f, err := filesystem.NewFileFromMultipart(doc)
    // read/scan the request body fields into a typed object // (it safe to be read multiple times) body := struct { Title string `json:"title" form:"title"` }{} if err := e.BindBody(&body); err != nil { return e.BadRequestError("Failed to read request body", err) } // read single multipart/form-data field title := e.Request.FormValue("title") // read single multipart/form-data file doc := e.Request.FormFile("document") // return the uploaded files as []*filesystem.File files, err := e.FindUploadedFiles("document")
    // send response with json body c.JSON(200, {"name": "John"}) // send response with string body c.String(200, "Lorem ipsum...") // send response with html body // (check also the "Rendering templates" section) c.HTML(200, "<h1>Hello!</h1>") // redirect c.Redirect(307, "https://example.com") // send response with no body c.NoContent(204)
    // send response with json body e.JSON(200, {"name": "John"}) // send response with string body e.String(200, "Lorem ipsum...") // send response with html body // (check also the "Rendering templates" section) e.HTML(200, "<h1>Hello!</h1>") // redirect e.Redirect(307, "https://example.com") // send response with no body e.NoContent(204)
    apis.RequireRecordAuth([optCollectionNames...])
    apis.RequireAuth([optCollectionNames...])
    apis.RequireSameContextRecordAuth()
    apis.RequireSameCollectionContextAuth("")
    apis.RequireAdminAuth()
    // the same as apis.RequireAuth("_superusers") apis.RequireSuperuserAuth()
    apis.RequireAdminAuthOnlyIfAny(app)
    N/A
    apis.RequireAdminOrRecordAuth("users")
    apis.RequireAuth("_superusers", "users")
    apis.RequireAdminOrOwnerAuth(ownerIdParam)
    apis.RequireSuperuserOrOwnerAuth(ownerIdParam)
    apis.ActivityLogger(app)
    // No longer needed because it is registered by default for all routes. // If you want to disable the log of successful route execution you can attach the apis.SkipSuccessActivityLog() middleware.
    middleware.Gzip()
    apis.Gzip()
    apis.RecordAuthResponse(app, c, record, meta)
    apis.RecordAuthResponse(e, record, authMethod, meta)
    apis.EnrichRecord(c, app.Dao(), record, "categories") apis.EnrichRecords(c, app.Dao(), records, "categories")
    apis.EnrichRecord(e, record, "categories") apis.EnrichRecords(e, records, "categories")
    • Renamed ApiScenario.Url to ApiScenario.URL.
    • Renamed ApiScenario.RequestHeaders to ApiScenario.Headers.
    • Changed the ApiScenario.TestAppFactory field function signature:

      TestAppFactory: func(t *testing.T) *TestApp { ... }
      TestAppFactory: func(t testing.TB) *TestApp { ... }
    • Changed the ApiScenario.BeforeTestFunc field function signature:

      BeforeTestFunc: func(t *testing.T, app *TestApp, e *echo.Echo) { ... }
      BeforeTestFunc: func(t testing.TB, app *TestApp, e *core.ServeEvent) { ... }
    • Changed the ApiScenario.AfterTestFunc field function signature:

      AfterTestFunc: func(t *testing.T, app *TestApp, res *http.Response) { ... }
      AfterTestFunc: func(t testing.TB, app *TestApp, res *http.Response) { ... }
    • ApiScenario.ExpectedEvents is now optional and performs partial match (see issue#5333 ):

      In the rare cases where you want to ensure that no other events except those listed have fired, you can use the wildcard "*": 0 entry:

      ExpectedEvents: map[string]int{ "*": 0, "EventA": 1, "EventB": 2, }
    • Converted TestMailer.SentMessages field to TestMailer.Messages() method.
    • Converted TestMailer.TotalSend field to TestMailer.TotalSend() method.
    • Converted TestMailer.LastMessage field to TestMailer.LastMessage() method.
    • Added TestMailer.FirstMessage() method.

    For more details about all new hooks and their event properties, please refer to the On* method docs in the core.App interface docs.

    app.OnBeforeBootstrap().Add(func(e *core.BootstrapEvent) error { // ... return nil })
    app.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error { // ... return e.Next() })
    app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error { // ... return nil })
    app.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error { if err := e.Next(); err != nil { return err } // ... return nil })
    app.OnBeforeServe().Add(func(e *core.ServeEvent) error { // ... return nil })
    app.OnServe().BindFunc(func(e *core.ServeEvent) error { // ... return e.Next() })
    app.OnBeforeApiError().Add(func(e *core.ApiErrorEvent) error { // ... return nil })
    app.OnServe().BindFunc(func(se *core.ServeEvent) error { se.Router.BindFunc((e *core.RequestEvent) error { err := e.Next() // ... return err }) return se.Next() })
    app.OnTerminate().Add(func(e *core.TerminateEvent) error { // ... return nil })
    app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error { // ... return e.Next() })
    app.OnModelBeforeCreate().Add(func(e *core.ModelEvent) error { // e.Model ... return nil })
    // note: OnModelCreate is available if you are targetting non-Record/Collection models app.OnRecordCreate().BindFunc(func(e *core.RecordEvent) error { // e.Record ... return e.Next() })
    app.OnModelAfterCreate().Add(func(e *core.ModelEvent) error { // e.Model ... return nil })
    // note: OnModelAfterCreateSuccess is available if you are targetting non-Record/Collection models app.OnRecordAfterCreateSuccess().BindFunc(func(e *core.RecordEvent) error { // e.Record ... return e.Next() })
    app.OnModelBeforeUpdate().Add(func(e *core.ModelEvent) error { // e.Model ... return nil })
    // note: OnModelUpdate is available if you are targetting non-Record/Collection models app.OnRecordUpdate().BindFunc(func(e *core.RecordEvent) error { // e.Record ... return e.Next() })
    app.OnModelAfterUpdate().Add(func(e *core.ModelEvent) error { // e.Model ... return nil })
    // note: OnModelAfterUpdateSuccess is available if you are targetting non-Record/Collection models app.OnRecordAfterUpdateSuccess().BindFunc(func(e *core.RecordEvent) error { // e.Record ... return e.Next() })
    app.OnModelBeforeDelete().Add(func(e *core.ModelEvent) error { // e.Model ... return nil })
    // note: OnModelDelete is available if you are targetting non-Record/Collection models app.OnRecordBeforeDelete().BindFunc(func(e *core.RecordEvent) error { // e.Record ... return e.Next() })
    app.OnModelAfterDelete().Add(func(e *core.ModelEvent) error { // e.Model ... return nil })
    // note: OnModelAfterDeleteSuccess is available if you are targetting non-Record/Collection models app.OnRecordAfterDeleteSuccess().BindFunc(func(e *core.RecordEvent) error { // e.Record ... return e.Next() })
    app.OnMailerBeforeAdminResetPasswordSend().Add(func(e *core.MailerAdminEvent) error { // ... return nil })
    app.OnMailerRecordPasswordResetSend().BindFunc(func(e *core.MailerRecordEvent) error { // e.Admin -> e.Record ... return e.Next() })
    app.OnMailerAfterAdminResetPasswordSend().Add(func(e *core.MailerAdminEvent) error { // ... return nil })
    app.OnMailerRecordPasswordResetSend().BindFunc(func(e *core.MailerRecordEvent) error { if err := e.Next(); err != nil { return err } // e.Admin -> e.Record ... return nil })
    app.OnMailerBeforeRecordResetPasswordSend().Add(func(e *core.MailerRecordEvent) error { // ... return nil })
    app.OnMailerRecordPasswordResetSend().BindFunc(func(e *core.MailerRecordEvent) error { // ... return e.Next() })
    app.OnMailerAfterRecordResetPasswordSend().Add(func(e *core.MailerRecordEvent) error { // ... return nil })
    app.OnMailerRecordPasswordResetSend().BindFunc(func(e *core.MailerRecordEvent) error { if err := e.Next(); err != nil { return err } // ... return nil })
    app.OnMailerBeforeRecordVerificationSend().Add(func(e *core.MailerRecordEvent) error { // ... return nil })
    app.OnMailerRecordVerificationSend().BindFunc(func(e *core.MailerRecordEvent) error { // ... return e.Next() })
    app.OnMailerAfterRecordVerificationSend().Add(func(e *core.MailerRecordEvent) error { // ... return nil })
    app.OnMailerRecordVerificationSend().BindFunc(func(e *core.MailerRecordEvent) error { if err := e.Next(); err != nil { return err } // ... return nil })
    app.OnMailerBeforeRecordChangeEmailSend().Add(func(e *core.MailerRecordEvent) error { // ... return nil })
    app.OnMailerRecordEmailChangeSend().BindFunc(func(e *core.MailerRecordEvent) error { // ... return e.Next() })
    app.OnMailerAfterRecordChangeEmailSend().Add(func(e *core.MailerRecordEvent) error { // ... return nil })
    app.OnMailerRecordEmailChangeSend().BindFunc(func(e *core.MailerRecordEvent) error { if err := e.Next(); err != nil { return err } // ... return nil })

    Side-note: if you are using this hook simply for adding/removing record fields you may want to check onRecordEnrich instead

    app.OnRecordsListRequest().Add(func(e *core.RecordsListEvent) error { // ... return nil })
    app.OnRecordsListRequest().BindFunc(func(e *core.RecordsListRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })

    Side-note: if you are using this hook simply for adding/removing record fields you may want to check onRecordEnrich instead

    app.OnRecordViewRequest().Add(func(e *core.RecordViewEvent) error { // ... return nil })
    app.OnRecordViewRequest().BindFunc(func(e *core.RecordRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })

    With v0.23+ the hook fires before the record validations allowing you to adjust "Nonempty" fields before checking the constraint.

    app.OnRecordBeforeCreateRequest().Add(func(e *core.RecordCreateEvent) error { // ... return nil })
    app.OnRecordCreateRequest().BindFunc(func(e *core.RecordRequestEvent) error { // uploaded files are simple values as part of the e.Record; // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error { // ... return nil })
    app.OnRecordCreateRequest().BindFunc(func(e *core.RecordRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })

    With v0.23+ the hook fires before the record validations allowing you to adjust "Nonempty" fields before checking the constraint.

    app.OnRecordBeforeUpdateRequest().Add(func(e *core.RecordUpdateEvent) error { // ... return nil })
    app.OnRecordUpdateRequest().BindFunc(func(e *core.RecordRequestEvent) error { // uploaded files are simple values as part of the e.Record; // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterUpdateRequest().Add(func(e *core.RecordUpdateEvent) error { // ... return nil })
    app.OnRecordUpdateRequest().BindFunc(func(e *core.RecordRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeDeleteRequest().Add(func(e *core.RecordDeleteEvent) error { // ... return nil })
    app.OnRecordDeleteRequest().BindFunc(func(e *core.RecordRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordBeforeDeleteRequest().Add(func(e *core.RecordDeleteEvent) error { // ... return nil })
    app.OnRecordDeleteRequest().BindFunc(func(e *core.RecordRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordAuthRequest().Add(func(e *core.RecordAuthEvent) error { // ... return nil })
    app.OnRecordAuthRequest().BindFunc(func(e *core.RecordAuthRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordBeforeAuthWithPasswordRequest().Add(func(e *core.RecordAuthWithPasswordEvent) error { // ... return nil })
    app.OnRecordAuthWithPasswordRequest().BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterAuthWithPasswordRequest().Add(func(e *core.RecordAuthWithPasswordEvent) error { // ... return nil })
    app.OnRecordAuthWithPasswordRequest().BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeAuthWithOAuth2Request().Add(func(e *core.RecordAuthWithOAuth2Event) error { // ... return nil })
    app.OnRecordAuthWithOAuth2Request().BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterAuthWithOAuth2Request().Add(func(e *core.RecordAuthWithOAuth2Event) error { // ... return nil })
    app.OnRecordAuthWithOAuth2Request().BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeAuthRefreshRequest().Add(func(e *core.RecordAuthRefreshEvent) error { // ... return nil })
    app.OnRecordAuthRefreshRequest().BindFunc(func(e *core.RecordAuthRefreshRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterAuthRefreshRequest().Add(func(e *core.RecordAuthRefreshEvent) error { // ... return nil })
    app.OnRecordAuthRefreshRequest().BindFunc(func(e *core.RecordAuthRefreshRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    External auths are converted to system _externalAuths collection records.
    app.OnRecordListExternalAuthsRequest().Add(func(e *core.RecordListExternalAuthsEvent) error { // ... return nil })
    app.OnRecordsListRequest(core.CollectionNameExternalAuths).BindFunc(func(e *core.RecordsListRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    External auths are converted to system _externalAuths collection records.
    app.OnRecordBeforeUnlinkExternalAuthRequest().Add(func(e *core.RecordUnlinkExternalAuthEvent) error { // ... return nil })
    app.OnRecordDeleteRequest(core.CollectionNameExternalAuths).BindFunc(func(e *core.RecordRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    External auths are converted to system _externalAuths collection records.
    app.OnRecordAfterUnlinkExternalAuthRequest().Add(func(e *core.RecordUnlinkExternalAuthEvent) error { // ... return nil })
    app.OnRecordDeleteRequest(core.CollectionNameExternalAuths).BindFunc(func(e *core.RecordRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeRequestPasswordResetRequest().Add(func(e *core.RecordRequestPasswordResetEvent) error { // ... return nil })
    app.OnRecordRequestPasswordResetRequest().BindFunc(func(e *core.RecordRequestPasswordResetRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterRequestPasswordResetRequest().Add(func(e *core.RecordRequestPasswordResetEvent) error { // ... return nil })
    app.OnRecordRequestPasswordResetRequest().BindFunc(func(e *core.RecordRequestPasswordResetRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeConfirmPasswordResetRequest().Add(func(e *core.RecordConfirmPasswordResetEvent) error { // ... return nil })
    app.OnRecordConfirmPasswordResetRequest().BindFunc(func(e *core.RecordConfirmPasswordResetRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterConfirmPasswordResetRequest().Add(func(e *core.RecordConfirmPasswordResetEvent) error { // ... return nil })
    app.OnRecordConfirmPasswordResetRequest().BindFunc(func(e *core.RecordConfirmPasswordResetRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeRequestVerificationRequest().Add(func(e *core.RecordRequestVerificationEvent) error // ... return nil })
    app.OnRecordRequestVerificationRequest().BindFunc(func(e *core.RecordRequestVerificationRequestEvent) error // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterRequestVerificationRequest().Add(func(e *core.RecordRequestVerificationEvent) error // ... return nil })
    app.OnRecordRequestVerificationRequest().BindFunc(func(e *core.RecordRequestVerificationRequestEvent) error if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeConfirmVerificationRequest().Add(func(e *core.RecordConfirmVerificationEvent) error // ... return nil })
    app.OnRecordConfirmVerificationRequest().BindFunc(func(e *core.RecordConfirmVerificationRequestEvent) error // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterConfirmVerificationRequest().Add(func(e *core.RecordConfirmVerificationEvent) error // ... return nil })
    app.OnRecordConfirmVerificationRequest().BindFunc(func(e *core.RecordConfirmVerificationRequestEvent) error if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeRequestEmailChangeRequest().Add(func(e *core.RecordRequestEmailChangeEvent) error { // ... return nil })
    app.OnRecordRequestEmailChangeRequest().BindFunc(func(e *core.RecordRequestEmailChangeRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterRequestEmailChangeRequest().Add(func(e *core.RecordRequestEmailChangeEvent) error { // ... return nil })
    app.OnRecordRequestEmailChangeRequest().BindFunc(func(e *core.RecordRequestEmailChangeRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRecordBeforeConfirmEmailChangeRequest().Add(func(e *core.RecordConfirmEmailChangeEvent) error { // ... return nil })
    app.OnRecordConfirmEmailChangeRequest().BindFunc(func(e *core.RecordConfirmEmailChangeRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRecordAfterConfirmEmailChangeRequest().Add(func(e *core.RecordConfirmEmailChangeEvent) error { // ... return nil })
    app.OnRecordConfirmEmailChangeRequest().BindFunc(func(e *core.RecordConfirmEmailChangeRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRealtimeConnectRequest().Add(func(e *core.RealtimeConnectEvent) error { // ... return nil })
    app.OnRealtimeConnectRequest().BindFunc(func(e *core.RealtimeConnectRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRealtimeDisconnectRequest().Add(func(e *core.RealtimeDisconnectEvent) error { // ... return nil })
    app.OnRealtimeConnectRequest().BindFunc(func(e *core.RealtimeConnectRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnRealtimeBeforeMessageSend().Add(func(e *core.RealtimeMessageEvent) error { // ... return nil })
    app.OnRealtimeMessageSend().BindFunc(func(e *core.RealtimeMessageEvent) error { // ... return e.Next() })
    app.OnRealtimeAfterMessageSend().Add(func(e *core.RealtimeMessageEvent) error { // ... return nil })
    app.OnRealtimeMessageSend().BindFunc(func(e *core.RealtimeMessageEvent) error { if err := e.Next(); err != nil { return err } // ... return nil })
    app.OnRealtimeBeforeSubscribeRequest().Add(func(e *core.RealtimeSubscribeEvent) error { // ... return nil })
    app.OnRealtimeSubscribeRequest().BindFunc(func(e *core.RealtimeSubscribeRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnRealtimeAfterSubscribeRequest().Add(func(e *core.RealtimeSubscribeEvent) error { // ... return nil })
    app.OnRealtimeSubscribeRequest().BindFunc(func(e *core.RealtimeSubscribeRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnFileDownloadRequest().Add(func(e *core.FileDownloadEvent) error { // ... return nil })
    app.OnFileDownloadRequest().BindFunc(func(e *core.FileDownloadRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnFileBeforeTokenRequest().Add(func(e *core.FileTokenEvent) error { // ... return nil })
    app.OnFileTokenRequest().BindFunc(func(e *core.FileTokenRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnFileAfterTokenRequest().Add(func(e *core.FileTokenEvent) error { // ... return nil })
    app.OnFileTokenRequest().BindFunc(func(e *core.FileTokenRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnCollectionsListRequest().Add(func(e *core.CollectionsListEvent) error { // ... return nil })
    app.OnCollectionsListRequest().BindFunc(func(e *core.CollectionsListRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnCollectionViewRequest().Add(func(e *core.CollectionViewEvent) error { // ... return nil })
    app.OnCollectionViewRequest().BindFunc(func(e *core.CollectionRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })

    With v0.23+ the hook fires before the collection validations.

    app.OnCollectionBeforeCreateRequest().Add(func(e *core.CollectionCreateEvent) error { // ... return nil })
    app.OnCollectionCreateRequest().BindFunc(func(e *core.CollectionRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnCollectionAfterCreateRequest().Add(func(e *core.CollectionCreateEvent) error { // ... return nil })
    app.OnCollectionCreateRequest().BindFunc(func(e *core.CollectionRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })

    With v0.23+ the hook fires before the collection validations allowing you to adjust "Nonempty" fields before checking the constraint.

    app.OnCollectionBeforeUpdateRequest().Add(func(e *core.CollectionUpdateEvent) error { // ... return nil })
    app.OnCollectionUpdateRequest().BindFunc(func(e *core.CollectionRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnCollectionAfterUpdateRequest().Add(func(e *core.CollectionUpdateEvent) error { // ... return nil })
    app.OnCollectionUpdateRequest().BindFunc(func(e *core.CollectionRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnCollectionBeforeDeleteRequest().Add(func(e *core.CollectionDeleteEvent) error { // ... return nil })
    app.OnCollectionDeleteRequest().BindFunc(func(e *core.CollectionRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnCollectionAfterDeleteRequest().Add(func(e *core.CollectionDeleteEvent) error { // ... return nil })
    app.OnCollectionDeleteRequest().BindFunc(func(e *core.CollectionRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnCollectionsBeforeImportRequest().Add(func(e *core.CollectionsImportEvent) error { // ... return nil })
    app.OnCollectionImportRequest().BindFunc(func(e *core.CollectionsImportRequestEvent) error { // "e.Collections" is replaced with "e.CollectionsData" map... return e.Next() })
    app.OnCollectionAfterImportRequest().Add(func(e *core.CollectionsImportEvent) error { // ... return nil })
    app.onCollectionImportRequest().BindFunc(func(e *core.CollectionsImportRequestEvent) error { if err := e.Next(); err != nil { return err } // "e.Collections" is replaced with "e.CollectionsData" map... return nil })
    app.OnSettingsListRequest().Add(func(e *core.SettingsListEvent) error { // ... return nil })
    app.OnSettingsListRequest().BindFunc(func(e *core.SettingsListRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })

    With v0.23+ the hook fires before the settings validations.

    app.OnSettingsBeforeUpdateRequest().Add(func(e *core.SettingsUpdateEvent) error { // ... return nil })
    app.OnSettingsUpdateRequest().BindFunc(func(e *core.SettingsUpdateRequestEvent) error { // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    onSettingsAfterUpdateRequest((e) => { // ... return nil })
    onSettingsUpdateRequest((e) => { if err := e.Next(); err != nil { return err } // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnAdminsListRequest().Add(func(e *core.AdminsListEvent) error { // ... return nil })
    app.OnRecordsListRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordsListRequestEvent) error { // e.Admins -> e.Records // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() }, )
    app.OnAdminViewRequest().Add(func(e *core.AdminViewEvent) error { // ... return nil })
    app.OnRecordViewRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminBeforeCreateRequest().Add(func(e *core.AdminCreateEvent) error { // ... return nil })
    app.OnRecordCreateRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminAfterCreateRequest().Add(func(e *core.AdminCreateEvent) error { // ... return nil })
    app.OnRecordCreateRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordRequestEvent) error { if err := e.Next(); err != nil { return err } // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnAdminBeforeUpdateRequest().Add(func(e *core.AdminUpdateEvent) error { // ... return nil })
    app.OnRecordUpdateRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminAfterUpdateRequest().Add(func(e *core.AdminUpdateEvent) error { // ... return nil })
    app.OnRecordUpdateRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordRequestEvent) error { if err := e.Next(); err != nil { return err } // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnAdminBeforeDeleteRequest().Add(func(e *core.AdminDeleteEvent) error { // ... return nil })
    app.OnRecordDeleteRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminAfterDeleteRequest().Add(func(e *core.AdminDeleteEvent) error { // ... return nil })
    app.OnRecordDeleteRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordRequestEvent) error { if err := e.Next(); err != nil { return err } // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnAdminAuthRequest().Add(func(e *core.AdminAuthEvent) error { // ... return nil })
    app.OnRecordAuthRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordAuthRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminBeforeAuthWithPasswordRequest().Add(func(e *core.AdminAuthWithPasswordEvent) error { // ... return nil })
    app.OnRecordAuthWithPasswordRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminAfterAuthWithPasswordRequest().Add(func(e *core.AdminAuthWithPasswordEvent) error { // ... return nil })
    app.OnRecordAuthWithPasswordRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error { if err := e.Next(); err != nil { return err } // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnAdminBeforeAuthRefreshRequest().Add(func(e *core.AdminAuthRefreshEvent) error { // ... return nil })
    app.OnRecordAuthRefreshRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordAuthRefreshRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminAfterAuthRefreshRequest().Add(func(e *core.AdminAuthRefreshEvent) error { // ... return nil })
    app.OnRecordAuthRefreshRequest(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordAuthRefreshRequestEvent) error { if err := e.Next(); err != nil { return err } // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnAdminBeforeRequestPasswordResetRequest().Add(func(e *core.AdminRequestPasswordResetEvent) error { // ... return nil })
    app.OnRecordRequestPasswordResetRequest(core.CollectionNameSuperusers) .BindFunc(func(e *core.RecordRequestPasswordResetRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminAfterRequestPasswordResetRequest().Add(func(e *core.AdminRequestPasswordResetEvent) error { // ... return nil })
    app.OnRecordRequestPasswordResetRequest(core.CollectionNameSuperusers) .BindFunc(func(e *core.RecordRequestPasswordResetRequestEvent) error { if err := e.Next(); err != nil { return err } // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })
    app.OnAdminBeforeConfirmPasswordResetRequest().Add(func(e *core.AdminConfirmPasswordResetEvent) error { // ... return nil })
    app.OnRecordConfirmPasswordResetRequest(core.CollectionNameSuperusers) .BindFunc(func(e *core.RecordConfirmPasswordResetRequestEvent) error { // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return e.Next() })
    app.OnAdminAfterConfirmPasswordResetRequest().Add(func(e *core.AdminConfirmPasswordResetEvent) error { // ... return nil })
    app.OnRecordConfirmPasswordResetRequest(core.CollectionNameSuperusers) .BindFunc(func(e *core.RecordConfirmPasswordResetRequestEvent) error { if err := e.Next(); err != nil { return err } // e.Admin -> e.Record // "e.HttpContext" is no longer available because "e" is the request event itself ... return nil })