Skip to content

Templates Overview

zzz_template is a standalone, comptime template engine inspired by Mustache and Handlebars. Templates are parsed entirely at compile time, producing zero-allocation rendering code with automatic HTML escaping. At runtime, rendering walks a pre-built segment tree and writes into a single buffer.

  • Comptime parsing — templates are parsed during compilation with zero runtime parsing overhead
  • Automatic HTML escaping{{var}} escapes &, <, >, ", ' by default
  • Raw output{{{var}}} bypasses escaping for trusted HTML
  • Conditionals{{#if}} / {{else}} / {{/if}} with bool, optional, and slice truthiness
  • Loops{{#each items}} iterates over slices
  • Partials{{> name}} inlines sub-templates at comptime with optional arguments
  • Layouts{{{yield}}} and named yields ({{{yield_head}}}) for layout wrapping
  • Pipes{{value | upper | truncate:20}} for chained value transformations
  • Dot notation{{user.name}} resolves nested struct fields
  • Comments{{! ignored }} produces no output
  • Raw blocks{{{{raw}}}}...{{{{/raw}}}} passes content through without processing
  • Integer support — integer fields are automatically formatted as strings

Add zzz_template to your build.zig.zon:

.dependencies = .{
.zzz_template = .{
.path = "../zzz_template",
},
},

Then in your build.zig, add the module:

const zzz_template = b.dependency("zzz_template", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zzz_template", zzz_template.module("zzz_template"));

If you are using the full zzz framework, zzz_template is already re-exported through the main zzz package. You can use zzz.template() and zzz.templateWithPartials() directly.

  1. Create a template file with the .html.zzz extension:

    src/templates/greeting.html.zzz
    <h1>Hello, {{name}}!</h1>
    <p>{{description}}</p>
  2. Compile the template at comptime in your controller:

    const zzz = @import("zzz");
    const GreetingTemplate = zzz.template(
    @embedFile("../templates/greeting.html.zzz"),
    );
  3. Render from a handler by passing a data struct:

    fn greet(ctx: *zzz.Context) !void {
    try ctx.render(GreetingTemplate, .ok, .{
    .name = "World",
    .description = "Welcome to zzz.",
    });
    }

The render method renders the template with the provided data struct and sends the result as an text/html response with the given status code. Every field referenced in the template must be present in the data struct — missing fields cause a compile error.

ConventionDescription
.html.zzz extensionDistinguishes template files from static HTML
src/templates/ directoryStandard location for page templates
src/templates/partials/ directoryStandard location for partial templates
src/templates/layout.html.zzzApplication layout with {{{yield}}} placeholder
@embedFile at comptimeTemplates are embedded into the binary; no filesystem reads at runtime

The Context object provides several rendering methods:

MethodPurpose
ctx.render(Tmpl, status, data)Render a template directly
ctx.renderWithLayout(Layout, Content, status, data)Render content inside a layout
ctx.renderWithLayoutAndYields(Layout, Content, status, data, yields)Render with layout and named yield blocks
ctx.renderPartial(Tmpl, status, data)Render without layout wrapping (for fragments)

zzz_template can be used outside of the zzz web framework as a standalone library:

const std = @import("std");
const tmpl = @import("zzz_template");
const Greeting = tmpl.template("Hello, {{name}}!");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const result = try Greeting.render(allocator, .{ .name = "World" });
defer allocator.free(result);
std.debug.print("{s}\n", .{result}); // Hello, World!
}