Skip to content

Templates Overview

pidgn_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 pidgn_template to your build.zig.zon:

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

Then in your build.zig, add the module:

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

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

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

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

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

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

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.pidgn 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.pidgnApplication 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)

pidgn_template can be used outside of the pidgn web framework as a standalone library:

const std = @import("std");
const tmpl = @import("pidgn_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!
}