templates

package module
v1.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 3, 2026 License: MIT Imports: 21 Imported by: 5

README

templates

License Coverage Go Report Card GoDoc

A secure, file-system-based Go template engine built on Google's safehtml/template. It provides a familiar structure of layouts, pages, and reusable blocks (partials) while ensuring output is safe from XSS vulnerabilities by default.

Why this library?

Go's standard html/template is good, but Google's safehtml/template is better, providing superior, context-aware automatic escaping that offers stronger security guarantees against XSS. However, safehtml/template can be complex to set up, especially for projects using a traditional layout/page/partial structure.

This library provides a simple, opinionated framework around safehtml/template so you can get the security benefits without the setup overhead.

Features

  • Secure by Default: Built on safehtml/template to provide contextual, automatic output escaping.
  • Optional Security Relaxing: Disable safehtml for rapid prototyping or projects using HTMX when the strict type system becomes a hindrance.
  • Layouts, Pages, and Blocks: Organizes templates into a familiar and powerful structure. Render pages within different layouts, or render blocks individually.
  • Live Reloading: Automatically re-parses templates on every request for a seamless development experience.
  • Production-Ready: Uses Go's embed.FS to compile all templates and assets into a single binary for production deployments.
  • Dynamic Rendering: Includes a d_block helper to render blocks dynamically by name—perfect for headless CMS integrations.
  • Convenient Helpers: Comes with a locals function to easily pass key-value data to blocks.
  • Framework Integrations: Provides optional, lightweight integration packages for net/http, Echo, chi, and gin-gonic/gin.

Installation

Add the library to your go.mod file:

go get github.com/dryaf/templates

Then import it in your code:

import "github.com/dryaf/templates"

Quick Start

  1. Create your template files:

    .
    └── files
        └── templates
            ├── layouts
            │   └── application.gohtml
            └── pages
                └── home.gohtml
    

    files/templates/layouts/application.gohtml:

    {{define "layout"}}
    <!DOCTYPE html>
    <html><body>
        <h1>Layout</h1>
        {{block "page" .}}{{end}}
    </body></html>
    {{end}}
    

    files/templates/pages/home.gohtml:

    {{define "page"}}
    <h2>Home Page</h2>
    <p>Hello, {{.}}!</p>
    {{end}}
    
  2. Write your Go application:

    package main
    
    import (
    	"log"
    	"net/http"
    
    	"github.com/dryaf/templates"
    	"github.com/dryaf/templates/integrations/stdlib"
    )
    
    func main() {
    	// For development, New() uses the local file system.
    	// For production, you would pass in an embed.FS.
    	tmpls := templates.New()
    	tmpls.AlwaysReloadAndParseTemplates = true // Recommended for development
    	tmpls.MustParseTemplates()
    
    	renderer := stdlib.FromTemplates(tmpls)
    
    	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    		err := renderer.Render(w, r, http.StatusOK, "home", "World")
    		if err != nil {
    			log.Println(err)
    			http.Error(w, "Internal Server Error", 500)
    		}
    	})
    
    	log.Println("Starting server on :8080")
    	http.ListenAndServe(":8080", nil)
    }
    
Configuration

The New function accepts Functional Options for flexible configuration:

  • WithFileSystem(fs fs.FS): Sets the filesystem (OS or Embed).
  • WithRoot(path string): Sets the root directory for templates.
  • WithFuncMap(fm template.FuncMap): Adds custom helper functions.
  • WithReload(enabled bool): Enables auto-reloading for development.
  • WithDisableSafeHTML(disabled bool): Disables the safehtml engine and falls back to standard text/template.
  • WithDisableTrustedLog(disabled bool): Disables INFO logs when using trusted_*_ctx helpers.
  • WithLogger(logger *slog.Logger): Sets a custom logger.
Rapid Prototyping Mode

If you find safehtml's strict type system too restrictive during early development, or for projects that don't require high security (like internal tools using HTMX), you can disable it. The engine will use text/template, and the trusted_* functions will act as literal passthroughs for your raw data.

tmpls := templates.New(
    templates.WithDisableSafeHTML(true),
    templates.WithDisableTrustedLog(true), // Suppress info logs for bypasses
)
Dev Mode (OS Filesystem)
// Defaults to OS filesystem, rooted at "files/templates"
tmpls := templates.New(
    templates.WithReload(true), // Enable hot-reloading
)
Production Mode (Embedded Filesystem)
//go:embed files/templates
var templatesFS embed.FS

// ...

tmpls := templates.New(
    templates.WithFileSystem(&templatesFS),
    templates.WithRoot("files/templates"),
)
Error Handling

The library returns structured errors that can be checked using errors.Is():

  • templates.ErrTemplateNotFound: The requested template or layout was not found.
  • templates.ErrBlockNotFound: The requested block was not found.
  • templates.ErrInvalidBlockName: The block name format is invalid (must start with _).
err := tmpls.RenderBlockAsHTMLString("_non_existent", nil)
if errors.Is(err, templates.ErrBlockNotFound) {
    // Handle missing block
}

Core Concepts

Directory Structure

The engine expects a specific directory structure by default, located at ./files/templates:

  • files/templates/layouts/: Contains layout templates. Each file defines a "layout".
  • files/templates/pages/: Contains page templates.
  • files/templates/blocks/: Contains reusable blocks (partials).
Important Template Rules
  1. Pages must define "page": Every template file in the pages directory must define its main content within {{define "page"}}...{{end}}.
  2. Blocks must define "_name": Every template file in the blocks directory must define a template, and that definition's name must match the filename and be prefixed with an underscore. For example, _form.gohtml must contain {{define "_form"}}...{{end}}.
Rendering Syntax

You have fine-grained control over how templates are rendered:

  • "page_name": Renders the page within the default layout (application.gohtml).
  • "layout_name:page_name": Renders the page within a specific layout.
  • ":page_name": Renders the page without any layout.
  • "_block_name": Renders a specific block by itself.
Dynamic Blocks (d_block)

To render a block whose name is determined at runtime (e.g., from a database or CMS API), you can use d_block. This is a powerful feature for dynamic page composition.

<!-- Instead of this, which requires the block name to be static: -->
{{block "_header" .}}{{end}}

<!-- You can do this: -->
{{d_block .HeaderBlockName .HeaderBlockData}}
Passing locals to Blocks

Passing maps as context to blocks can be verbose. The locals helper function makes it easy to create a map on the fly. It accepts a sequence of key-value pairs.

<!-- Standard block call with locals -->
{{block "_user_card" (locals "Name" "Alice" "Age" 30)}}{{end}}

<!-- Dynamic block call with locals -->
{{locals "Name" "Bob" "Age" 42 | d_block "_user_card"}}

_user_card.gohtml:

{{define "_user_card"}}
<div class="card">
    <h3>{{.Name}}</h3>
    <p>Age: {{.Age}}</p>
</div>
{{end}}
Passing references to Blocks

The references helper is similar to locals, but it ensures that every value passed to the map is a pointer. This is useful when your template logic expects pointers (e.g. for nil checks or to avoid copying large structs).

If a value is already a pointer, it is used as is. If it is not a pointer, a new pointer to a copy of the value is created.

<!-- Passing values as references -->
{{block "_user_edit" (references "User" .User "IsActive" true)}}
Security and trusted_* Functions

This library uses safehtml/template, which provides protection against XSS by default. It contextually escapes variables.

Sometimes, you receive data from a trusted source (like a headless CMS) that you know is safe and should not be escaped. For these cases, you can use the trusted_* template functions, which wrap the input string in the appropriate safehtml type.

  • trusted_html: For HTML content.
  • trusted_script: For JavaScript code.
  • trusted_style: For CSS style declarations.
  • trusted_stylesheet: For a full CSS stylesheet.
  • trusted_url: For a general URL.
  • trusted_resource_url: For a URL that loads a resource like a script or stylesheet.
  • trusted_identifier: For an HTML ID or name attribute.

Example:

<!-- This will be escaped: -->
<p>{{.UnsafeHTMLFromUser}}</p>

<!-- This will be rendered verbatim, because you are vouching for its safety: -->
<div>
    {{trusted_html .SafeHTMLFromCMS}}
</div>

Note: If DisableSafeHTML is enabled, these functions will simply return your objects as-is, allowing them to bypass the standard auto-escaper.

Context-Aware Helpers

For structured logging and auditing, this library provides context-aware versions of d_block and trusted_* functions. These helpers require a context.Context as the first argument.

d_block_ctx

Same as d_block, but logs errors (e.g. missing blocks) using the Logger attached to Templates.

// In template
{{ d_block_ctx .Ctx "my_block_name" .Data }}
trusted_*_ctx

Context-aware versions of all trusted_* functions. They log an INFO message when called, which is useful for security auditing to track when and where bypasses are used.

  • trusted_html_ctx
  • trusted_script_ctx
  • trusted_style_ctx
  • trusted_stylesheet_ctx
  • trusted_url_ctx
  • trusted_resource_url_ctx
  • trusted_identifier_ctx

Usage:

// In template
{{ trusted_html_ctx .Ctx "<b>Bold Content</b>" }}

You can disable these logs using WithDisableTrustedLog(true).

Integrations

net/http (stdlib)

The integrations/stdlib package provides a simple renderer for use with net/http.

import "github.com/dryaf/templates/integrations/stdlib"

// --- inside main ---
// Create the renderer, configuring the underlying templates instance with functional options
renderer := stdlib.New(
    templates.WithReload(true), // Example option: enable hot-reloading
)

// Use it in an http.HandlerFunc
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    renderer.Render(w, r, http.StatusOK, "home", "Data")
})

// Or create a handler that always renders the same template
http.Handle("/about", renderer.Handler("about", nil))
Echo

The integrations/echo package provides a renderer for the Echo framework.

import "github.com/dryaf/templates/integrations/echo"
// ...
e := echo.New()
e.Renderer = templates_echo.Renderer(tmpls)

e.GET("/", func(c echo.Context) error {
    return c.Render(http.StatusOK, "home", "World")
})
Chi

The integrations/chi package provides a renderer compatible with the chi router.

import "github.com/dryaf/templates/integrations/chi"
// ...
renderer := chi.FromTemplates(tmpls)
r := chi.NewRouter()

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    renderer.Render(w, r, http.StatusOK, "home", "Chi")
})
Gin

The integrations/gin package provides a renderer that implements gin.HTMLRender for the Gin framework.

import "github.com/dryaf/templates/integrations/gin"
// ...
router := gin.Default()
router.HTMLRender = templates_gin.New(tmpls)

router.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "home", "Gin")
})

Roadmap

The library is considered feature-complete and stable for a v1.0.0 release. Future development will be driven by community feedback and integration requests for new frameworks. Potential ideas include:

  • Additional template helper functions.
  • Performance optimizations.

Feel free to open an issue to suggest features or improvements.

License

This project is licensed under the MIT License.

Documentation

Overview

Package templates provides a secure, file-system-based Go template engine built on Google's safehtml/template library.

Core Concepts

This engine organizes templates into a specific directory structure:

  • layouts/: specific wrapper templates that define the document structure (e.g. <html>...</html>).
  • pages/: individual page templates that render inside a layout.
  • blocks/: reusable partials (e.g. forms, navigational elements) that can be included in pages or layouts.

Safety

Using safehtml/template ensures that your output is free from XSS vulnerabilities by default. Context-aware escaping is applied automatically. For cases where you strictly trust the input (e.g. from a CMS), special helper functions `trusted_*` are provided.

Rapid Prototyping and HTMX

If the strictness of safehtml is an obstacle during development or for projects using libraries like HTMX where security models might differ, it can be disabled via the DisableSafeHTML configuration. In this mode, the engine falls back to the standard library's text/template (producing literal output), and trusted_* functions return objects as-is.

Logging for trusted helper usage can also be disabled via DisableTrustedLog to reduce noise in projects that use them frequently.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrTemplateNotFound is returned when a requested template or layout is not in the parsed map.
	ErrTemplateNotFound = errors.New("template not found")
	// ErrBlockNotFound is returned when a requested block is not found.
	ErrBlockNotFound = errors.New("block not found")
	// ErrInvalidBlockName is returned when a block name does not adhere to naming conventions (e.g. must start with '_').
	ErrInvalidBlockName = errors.New("invalid block name")
)

Functions

func Locals

func Locals(args ...any) map[string]any

Locals is a template helper function that creates a map[string]any from a sequence of key-value pairs. This is useful for passing named arguments to templates. The arguments must be in pairs, e.g., `locals "key1", "value1", "key2", 2`.

func References added in v1.0.4

func References(args ...any) map[string]any

References is a template helper function that creates a map[string]any from a sequence of key-value pairs. Unlike Locals, it ensures that every value in the map is a pointer. If a value is already a pointer, it is used as is. If it is not a pointer, a new pointer to the value is created.

Types

type LayoutContextKey

type LayoutContextKey struct{}

LayoutContextKey is the key used to store and retrieve the desired layout name from a request's context. A middleware can set this value to dynamically change the layout for a request.

type Option added in v1.0.4

type Option func(*Templates)

Option defines a functional option for configuring the Templates engine.

func WithDisableSafeHTML added in v1.0.5

func WithDisableSafeHTML(disabled bool) Option

WithDisableSafeHTML enables or disables the use of safehtml/template. When disabled, the standard library's text/template is used for literal output.

func WithDisableTrustedLog added in v1.0.5

func WithDisableTrustedLog(disabled bool) Option

WithDisableTrustedLog enables or disables INFO logs for trusted helpers.

func WithFileSystem added in v1.0.4

func WithFileSystem(fsys fs.FS) Option

WithFileSystem sets the filesystem and root path for templates. The rootPath argument specifies the directory within the filesystem where templates are stored. If fs is nil, it uses the OS filesystem rooted at the given path.

func WithFuncMap added in v1.0.4

func WithFuncMap(fm template.FuncMap) Option

WithFuncMap adds custom functions to the template engine.

func WithLogger added in v1.0.4

func WithLogger(l *slog.Logger) Option

WithLogger sets a custom logger.

func WithReload added in v1.0.4

func WithReload(enabled bool) Option

WithReload enables or disables always reloading parsing templates on execution.

func WithRoot added in v1.0.4

func WithRoot(path string) Option

WithRoot sets the root path for templates.

type Templates

type Templates struct {
	// If true, templates will be re-parsed on every ExecuteTemplate call.
	// This is highly recommended for development to see changes without restarting
	// the application, but should be disabled in production for performance.
	AlwaysReloadAndParseTemplates bool

	// If true, disables the use of safehtml/template and uses standard text/template instead.
	// This is useful for rapid prototyping or for projects where safehtml is not required.
	// When true, NO auto-escaping occurs, and trusted_* functions return objects as-is.
	DisableSafeHTML bool

	// If true, suppresses the INFO log message normally generated when a
	// trusted_*_ctx helper function is used.
	DisableTrustedLog bool

	// The name of the default layout file (without extension) to use when a
	// layout is not explicitly specified in the template name.
	// Defaults to "application".
	DefaultLayout string

	// The file extension for template files. Defaults to ".gohtml".
	TemplateFileExtension string

	// The trusted source path for templates, used by the underlying safehtml/template
	// library for security checks.
	TemplatesPath template.TrustedSource

	// The subdirectory within the templates path for layout files.
	// Defaults to "layouts".
	LayoutsPath string

	// The subdirectory within the templates path for page files.
	// Defaults to "pages".
	PagesPath string

	// The subdirectory within the templates path for reusable block files.
	// Defaults to "blocks".
	BlocksPath string

	// If true, automatically adds helper functions like `d_block`, `locals`,
	// `references` and `trusted_*` to the template function map. Defaults to true.
	AddHeadlessCMSFuncMapHelpers bool

	// The logger to use for internal errors and debug messages.
	// Defaults to slog.Default().
	Logger *slog.Logger
	// contains filtered or unexported fields
}

Templates is the core engine for managing, parsing, and executing templates. It holds the parsed templates, configuration, and the underlying filesystem.

func New

func New(opts ...Option) *Templates

New creates a new Templates instance configured with the provided options.

By default, it looks for templates in "files/templates" within the OS filesystem. You can change this using `WithFileSystem` or `WithRoot`.

func (*Templates) AddDynamicBlockCtxToFuncMap added in v1.0.4

func (t *Templates) AddDynamicBlockCtxToFuncMap()

AddDynamicBlockCtxToFuncMap adds 'd_block_ctx' to the FuncMap.

func (*Templates) AddDynamicBlockToFuncMap

func (t *Templates) AddDynamicBlockToFuncMap()

AddDynamicBlockToFuncMap adds the 'd_block' function to the FuncMap. This powerful helper allows templates to render blocks dynamically by name, which is ideal for pages whose structure is determined by an API response (e.g., from a headless CMS). Usage in template: `{{ d_block "block_name_from_variable" .Data }}`

func (*Templates) AddFuncMapHelpers

func (t *Templates) AddFuncMapHelpers()

AddFuncMapHelpers populates the template function map with the default helpers if `AddHeadlessCMSFuncMapHelpers` is true. It will panic if a function name is already in use.

func (*Templates) AddLocalsToFuncMap

func (t *Templates) AddLocalsToFuncMap()

AddLocalsToFuncMap adds the 'locals' function to the FuncMap. This helper provides a convenient way to create a `map[string]any` inside a template, which is useful for passing structured data to blocks. Usage: `{{ block "_myblock" (locals "key1" "value1" "key2" 2) }}`

func (*Templates) AddReferencesToFuncMap added in v1.0.4

func (t *Templates) AddReferencesToFuncMap()

AddReferencesToFuncMap adds the 'references' function to the FuncMap. This helper provides a convenient way to create a `map[string]any` inside a template, where all values are pointers. Usage: `{{ block "_myblock" (references "key1" "value1" "key2" 2) }}`

func (*Templates) ExecuteTemplate

func (t *Templates) ExecuteTemplate(w io.Writer, r *http.Request, templateName string, data interface{}) error

ExecuteTemplate renders a template by name to the given writer.

The `templateName` parameter supports several syntaxes:

  • "page_name": Renders the page within the default layout (or a layout from context).
  • "layout_name:page_name": Renders the page within a specific layout.
  • ":page_name": Renders the page without any layout.
  • "_block_name": Renders a specific block by itself.

Parameters:

  • w: The io.Writer to write the output to (e.g., an http.ResponseWriter).
  • r: The *http.Request for the current request. Can be nil, but if provided, the engine will check its context for a LayoutContextKey to override the layout.
  • templateName: The name of the template to execute.
  • data: The data to pass to the template.

func (*Templates) ExecuteTemplateAsText

func (t *Templates) ExecuteTemplateAsText(r *http.Request, templateName string, data interface{}) (string, error)

ExecuteTemplateAsText is a testing helper that renders a template to a string.

func (*Templates) GetParsedTemplates

func (t *Templates) GetParsedTemplates() []string

GetParsedTemplates returns a sorted slice of the names of all parsed templates. This is primarily intended for debugging and testing purposes.

func (*Templates) HandlerRenderWithData

func (t *Templates) HandlerRenderWithData(templateName string, data interface{}) func(w http.ResponseWriter, r *http.Request)

HandlerRenderWithData returns a http.HandlerFunc that renders a template with the provided static data.

func (*Templates) HandlerRenderWithDataFromContext

func (t *Templates) HandlerRenderWithDataFromContext(templateName string, contextKey interface{}) func(w http.ResponseWriter, r *http.Request)

HandlerRenderWithDataFromContext returns a http.HandlerFunc that renders a template, taking its data from the request's context via the provided context key.

func (*Templates) MustParseTemplates

func (t *Templates) MustParseTemplates()

MustParseTemplates parses all template files from the configured filesystem. It will panic if any error occurs during parsing, making it suitable for application initialization.

func (*Templates) ParseTemplates

func (t *Templates) ParseTemplates() error

ParseTemplates reads and parses all template files from the configured layouts, pages, and blocks directories. It populates the internal template map. This method is safe for concurrent use.

func (*Templates) RenderBlockAsHTMLString

func (t *Templates) RenderBlockAsHTMLString(name string, data interface{}) (any, error)

RenderBlockAsHTMLString renders a specific block to a safehtml.HTML string (or raw string if safehtml is disabled). This is useful for rendering partials inside other logic. The block name must start with an underscore "_".

func (*Templates) RenderBlockAsHTMLStringWithContext added in v1.0.4

func (t *Templates) RenderBlockAsHTMLStringWithContext(ctx context.Context, blockname string, payload interface{}) (any, error)

RenderBlockAsHTMLStringWithContext renders a block to HTML string and logs errors with the provided context. Registered as 'd_block_ctx'.

Directories

Path Synopsis
integrations
chi module
chirender module
echo module
gin module
stdlib module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL