A common task when creating custom routes or emails is the need of generating HTML output.

    There are plenty of Go template-engines available that you can use for this, but often for simple cases the Go standard library html/template package should work just fine.

    To make it slightly easier to load template files concurrently and on the fly, PocketBase also provides a thin wrapper around the standard library in the github.com/pocketbase/pocketbase/tools/template utility package.

    import "github.com/pocketbase/pocketbase/tools/template" data := map[string]any{"name": "John"} html, err := template.LoadFiles( "views/base.html", "views/partial1.html", "views/partial2.html", ).Render(data)

    The general flow when working with composed and nested templates is that you create "base" template(s) that defines various placeholders using the {{template "placeholderName" .}} or {{block "placeholderName" .}}default...{{end}} actions.

    Then in the partials, you define the content for those placeholders using the {{define "placeholderName"}}custom...{{end}} action.

    The dot object (.) in the above represents the data passed to the templates via the Render(data) method.

    For more information about the template syntax please refer to the html/template and text/template package godocs. Another great resource is also the Hashicorp's Learn Go Template Syntax tutorial.

    Consider the following app directory structure:

    myapp/ views/ layout.html hello.html main.go

    We define the content for layout.html as:

    <!DOCTYPE html> <html lang="en"> <head> <title>{{block "title" .}}Default app title{{end}}</title> </head> <body> Header... {{block "body" .}} Default app body... {{end}} Footer... </body> </html>

    We define the content for hello.html as:

    {{define "title"}} Page 1 {{end}} {{define "body"}} <p>Hello from {{.name}}</p> {{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/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/template" ) func main() { app := pocketbase.New() app.OnBeforeServe().Add(func(e *core.ServeEvent) error { // this is safe to be used by multiple goroutines // (it acts as store for the parsed templates) registry := template.NewRegistry() e.Router.GET("/hello/:name", func(c echo.Context) error { name := c.PathParam("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 apis.NewNotFoundError("", err) } return c.HTML(http.StatusOK, html) }) return nil }) if err := app.Start(); err != nil { log.Fatal(err) } }