Views

Template Data And Helpers

Pass controller data to templates and use registered helper functions.

By Guillermo Alvarez - Published Updated

Set template data

Controllers pass data to templates with Set:

func (c *PostsController) Show(
    _ http.ResponseWriter,
    r *http.Request,
) error {
    post, ok := c.posts.Get(r.PathValue("post_id"))
    if !ok {
        return lazycontroller.Error(http.StatusNotFound, fmt.Errorf("post not found"))
    }

    c.Set("title", post.Title)
    c.Set("post", post)
    return nil
}

The view reads those values by name:

<h1>{{.title}}</h1>
<article>{{.post.Body}}</article>

Use built-in helpers

lazyapp registers route, asset, form, and Turbo helpers:

<a href="{{path_for "posts"}}">Posts</a>
{{stylesheet "/styles.css"}}
{{importmap "/assets/importmap.json"}}

Helpers return ordinary strings or trusted fragments, depending on what the helper renders.

Register app helpers

Application helpers are passed to lazyapp.New:

func RegisterHelpers() map[string]any {
    return map[string]any{
        "read_time": ReadTime,
    }
}

Wire them during startup:

func App() *lazyapp.App {
    return lazyapp.New(lazyapp.Config{
        Name:    "sample_app",
        Drawer:  Draw,
        Public:  app.Public,
        Views:   app.Views,
        Context: Context,
        Helpers: lazyapp.Helpers{helpers.RegisterHelpers()},
    })
}

Then call the helper from a template:

<p>{{read_time .post.Body}} min read</p>

Add SEO metadata

lazyapp installs SEO helpers automatically. Configure application defaults in lazyapp.Config.SEO:

func App() *lazyapp.App {
    return lazyapp.New(lazyapp.Config{
        Name: "sample_app",
        SEO: []lazyseo.Option{
            lazyseo.SiteName("GoLazy"),
            lazyseo.Description("A GoLazy application."),
            lazyseo.Language("en"),
            lazyseo.TwitterCardType("summary"),
        },
    })
}

Application helpers should only register app-owned helpers:

func RegisterHelpers() map[string]any {
    return map[string]any{
        "read_time": ReadTime,
    }
}

Call {{seo_lang}} from the opening <html> tag and {{seo}} from the layout <head>:

<html lang="{{seo_lang}}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {{seo}}
  </head>
</html>

Set per-action metadata from controllers:

func (c *PostsController) Show(postID string) error {
    post, ok := c.posts.Get(postID)
    if !ok {
        return lazycontroller.Error(http.StatusNotFound, fmt.Errorf("post not found"))
    }

    c.Metadata(post)
    c.Alternate("de", "https://example.com/de/posts/" + post.Param)
    c.Set("post", post)
    return nil
}

Metadata reads small optional interfaces from the model. The minimum useful interface is Title() string; common optional interfaces include Description() string, Canonical() string, Image() string, Kind() lazyseo.PageKind, LastUpdated() time.Time, OpenGraph() lazyseo.OpenGraph, TwitterCard() lazyseo.TwitterCard, and JSONLD() any. If the model has enough information and does not provide explicit JSON-LD, GoLazy emits a conventional JSON-LD node automatically.

Use controller helpers to make page-specific edits after Metadata:

c.OpenGraph(lazyseo.OpenGraph{
    Description: "Social preview description",
    Image: "/posts/hello-og.png",
})
c.TwitterCard(lazyseo.TwitterCard{
    Card: "summary_large_image",
    Image: "/posts/hello-twitter.png",
})

Kind accepts shared page-kind constants such as lazyseo.Article, lazyseo.WebPage, lazyseo.WebSite, lazyseo.Product, and lazyseo.Restaurant; the same value carries the Open Graph type and the schema.org type name for automatic JSON-LD work. Canonical renders <link rel="canonical">. Alternate renders <link rel="alternate" hreflang="...">. JSONLD renders a application/ld+json script in the head. Use the golazy.dev/lazyseo/jsonld subpackage for explicit common schema.org values such as jsonld.NewArticle, jsonld.NewWebPage, jsonld.NewWebSite, jsonld.NewOrganization, and jsonld.NewBreadcrumbList.

Generate robots and sitemaps

lazyapp serves /robots.txt with permissive defaults. Add sitemap entries in application startup:

func App() *lazyapp.App {
    return lazyapp.New(lazyapp.Config{
        Name:    "sample_app",
        Drawer:  Draw,
        Public:  app.Public,
        Views:   app.Views,
        Context: Context,
        Sitemap: lazyapp.SitemapConfig{
            BaseURL: "https://example.com",
            URLs: []lazyapp.SitemapURL{
                {
                    Location: "/",
                    LastUpdated: time.Date(2026, 6, 20, 0, 0, 0, 0, time.UTC),
                    ChangeFreq: "weekly",
                    Priority: 1,
                },
            },
        },
    })
}

Use SitemapSourceFunc when URLs come from a service:

Sitemap: lazyapp.SitemapConfig{
    BaseURL: "https://example.com",
    Sources: []lazyapp.SitemapSource{
        lazyapp.SitemapSourceFunc(func() ([]lazyapp.SitemapURL, error) {
            return postSitemapURLs(), nil
        }),
    },
},

Configure or disable robots explicitly when needed:

Robots: lazyapp.RobotsConfig{
    Rules: []lazyapp.RobotsRule{{
        UserAgent: "*",
        Disallow: []string{"/admin"},
    }},
},

Set Robots.Disabled or Sitemap.Disabled to stop serving the generated file. The newest sitemap LastUpdated value is also used as the generated Last-Modified header for cache validation.

Use view variants

View variants are server-side template variants for the same route and format. They use the Rails-style +variant segment after the format:

app/views/posts/show.svg.tpl
app/views/posts/show.svg+square.tpl

Rendering show as svg with variant square tries the square template first and falls back to show.svg.tpl. Use this for generated image shapes such as a default social image and a square social image. These are not SEO alternates; SEO alternates are public URLs declared with c.Alternate(...).

Controllers can render that SVG content as a string:

svg, err := c.RenderSVGString("show", "square")
if err != nil {
    return err
}

That gives image-generation code a normal Go string to write, rasterize, or register as an asset before setting the public URL with c.SEOImage(...).