PocketBase uses echo (v5) for routing. The internal router is exposed in the app.OnBeforeServe event hook and you can register your own custom endpoints and/or middlewares the same way as using directly echo.

    Register new route

    import ( "net/http" "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" ) ... app.OnBeforeServe().Add(func(e *core.ServeEvent) error { // or you can also use the shorter e.Router.GET("/articles/:slug", handler, middlewares...) e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/articles/:slug", Handler: func(c echo.Context) error { record, err := app.Dao().FindFirstRecordByData("articles", "slug", c.PathParam("slug")) if err != nil { return apis.NewNotFoundError("The article does not exist.", err) } // enable ?expand query param support apis.EnrichRecord(c, app.Dao(), record) return c.JSON(http.StatusOK, record) }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) return nil })

    To avoid collisions with future internal routes it is recommended to use a unique prefix when adding routes to the /api base endpoint, eg. /api/yourapp/... path.

    Middlewares

    In addition to the echo middlewares, you can also make use of some PocketBase specific ones:

    apis.ActivityLogger(app) apis.RequireGuestOnly() apis.RequireRecordAuth(optCollectionNames) apis.RequireSameContextRecordAuth() apis.RequireAdminAuth() apis.RequireAdminOrRecordAuth(optCollectionNames) apis.RequireAdminOrOwnerAuth(ownerIdParam = "id")
    List with all apis middleware and documentation could be found in the godoc of apis/middlewares.go

    You could also create your own middleware by returning echo.MiddlewareFunc:

    func myCustomMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // eg. inspecting some header value before processing the request someHeaderVal := c.Request().Header.Get("some-header") ... return next(c) } } }

    Middlewares could be registed both to a single route or globally using e.Router.Use(myCustomMiddleware()).

    Error response

    PocketBase has a global error handler and every returned error from a route or middleware will be safely converted by default to a generic HTTP 400 error to avoid accidentally leaking sensitive information (the original error will be visible only in the Admin UI > Logs or when in --debug mode).

    To make it easier returning formatted json error responses, PocketBase provides apis.ApiError struct that implements the error interface and can be instantiated with the following factories:

    // if message is empty string, a default one will be set // data is optional and could be nil or the original error apis.NewNotFoundError(message string, data any) apis.NewBadRequestError(message string, data any) apis.NewForbiddenError(message string, data any) apis.NewUnauthorizedError(message string, data any) apis.NewApiError(status int, message string, data any)

    Get logged admin or app user

    The current request authentication state can be accessed from the echo.Context in a route handler, middleware or request hook.

    Get logged admin or app user in a route handler
    import ( "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/models" ) ... app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.GET("/hello", func(c echo.Context) error { // to get the authenticated admin: admin, _ := c.Get(apis.ContextAdminKey).(*models.Admin) if admin == nil { return apis.NewForbiddenError("Only admins can access this endpoint", nil) } // or to get the authenticated record: authRecord, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record) if authRecord == nil { return apis.NewForbiddenError("Only auth records can access this endpoint", nil) } ... return c.String(200, "Hello world!") }) return nil })
    Get logged admin or app user in a middleware
    import ( "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/models" ) ... func myCustomMiddleware(app core.App) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // to get the authenticated admin: admin, _ := c.Get(apis.ContextAdminKey).(*models.Admin) if admin == nil { return apis.NewForbiddenError("Only admins can access this endpoint", nil) } // or to get the authenticated record: authRecord, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record) if authRecord == nil { return apis.NewForbiddenError("Only auth records can access this endpoint", nil) } ... return next(c) } } } app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.Use(myCustomMiddleware(app)) return nil })
    Get logged admin or app user in a request hook

    All request hooks (aka. those ending with *Request) expose the current request context as e.HttpContext field.

    import ( "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/models" ) ... app.OnRecordBeforeCreateRequest().Add(func(e *core.RecordCreateEvent) error { // to get the authenticated admin: admin, _ := e.HttpContext.Get(apis.ContextAdminKey).(*models.Admin) if admin == nil { return apis.NewForbiddenError("Only admins can access this endpoint", nil) } // or to get the authenticated record: authRecord, _ := e.HttpContext.Get(apis.ContextAuthRecordKey).(*models.Record) if authRecord == nil { return apis.NewForbiddenError("Only auth records can access this endpoint", nil) } ... return nil })

    Sending request to custom routes using the SDKs

    The official PocketBase SDKs expose the internal send() method that could be used to send requests to your custom endpoint(s).

    import PocketBase from 'pocketbase'; const pb = new PocketBase('http://127.0.0.1:8090'); await pb.send("/hello", { // for all possible options check // https://developer.mozilla.org/en-US/docs/Web/API/fetch#options query: { "abc": 123 }, });
    import 'package:pocketbase/pocketbase.dart'; final pb = PocketBase('http://127.0.0.1:8090'); await pb.send("/hello", query: { "abc": 123 })