Skip to content

Pipes & Helpers

Pipes transform template values inline using the | operator. They are applied at runtime after the field value is resolved, and can be chained to compose multiple transformations.

Add pipes after a variable name, separated by |:

{{name | upper}}
{{title | truncate:20}}
{{bio | lower | truncate:100}}

Pipes work with both escaped ({{ }}) and raw ({{{ }}}) output:

<!-- HTML-escaped after pipe -->
{{content | truncate:50}}
<!-- Raw output after pipe -->
{{{content | truncate:50}}}

Some pipes accept arguments after a colon:

{{title | truncate:30}}

Quoted arguments use double quotes:

{{name | default:"Anonymous"}}

Multiple arguments are separated by colons with each one quoted:

{{count | pluralize:"item":"items"}}

Converts all ASCII lowercase letters to uppercase.

{{name | upper}}
InputOutput
helloHELLO
Hello WorldHELLO WORLD
123abc123ABC

Converts all ASCII uppercase letters to lowercase.

{{name | lower}}
InputOutput
HELLOhello
Hello Worldhello world

Truncates the value to the specified number of characters and appends ... if the original was longer.

{{title | truncate:20}}
InputPipeOutput
Hello Worldtruncate:5Hello...
Hitruncate:5Hi

The argument must be a positive integer. If the value is already shorter than the limit, it is returned unchanged.

Returns a fallback value when the input is an empty string.

{{nickname | default:"N/A"}}
InputPipeOutput
"" (empty)default:"N/A"N/A
Alicedefault:"N/A"Alice

The argument must be quoted with double quotes.

Appends the singular or plural form of a word based on a numeric input. Takes two quoted arguments: the singular form and the plural form.

{{count | pluralize:"item":"items"}}
InputPipeOutput
1pluralize:"item":"items"1 item
3pluralize:"item":"items"3 items
0pluralize:"item":"items"0 items

The input value is parsed as an integer. If the count is exactly 1, the singular form is used; otherwise the plural form is used. The output includes both the number and the word.

pluralize works with both string and integer field values:

// String field
try ctx.render(Tmpl, .ok, .{ .count = "3" });
// Integer field
try ctx.render(Tmpl, .ok, .{ .count = @as(u32, 3) });

Formats a Unix timestamp (seconds since epoch) into a human-readable date string. The format pattern uses these tokens:

TokenMeaningExample
YYYYFour-digit year2023
MMTwo-digit month0112
MMMAbbreviated month nameJan, Feb, …, Dec
DDTwo-digit day0131
HHTwo-digit hour (24h)0023
mmTwo-digit minute0059
ssTwo-digit second0059

Any other characters in the pattern are passed through literally:

{{created_at | format_date:"YYYY-MM-DD"}}
{{updated_at | format_date:"MMM DD, YYYY"}}
{{timestamp | format_date:"YYYY-MM-DD HH:mm:ss"}}
Input (timestamp)PatternOutput
1699963200YYYY-MM-DD2023-11-14
1699963200MMM DD, YYYYNov 14, 2023
1699963200YYYY-MM-DD HH:mm:ss2023-11-14 12:00:00

If the input is not a valid integer, it is returned unchanged (passthrough).

format_date accepts both string and integer timestamp fields:

// String timestamp
try ctx.render(Tmpl, .ok, .{ .created_at = "1699963200" });
// Integer timestamp
try ctx.render(Tmpl, .ok, .{ .created_at = @as(i64, 1699963200) });

Multiple pipes are applied left to right. Each pipe receives the output of the previous one:

{{name | upper | truncate:10}}

This first converts name to uppercase, then truncates to 10 characters.

More examples:

<!-- Uppercase then truncate -->
{{title | upper | truncate:25}}
<!-- Default then lowercase -->
{{nickname | default:"anonymous" | lower}}

When using double braces ({{ }}), HTML escaping is applied after all pipes have been processed:

{{name | upper}}

If name is <b>, this produces &lt;B&gt; — the pipe transforms to <B>, then escaping converts it to safe HTML entities.

To skip escaping (for trusted content), use triple braces:

{{{html_content | truncate:200}}}

If a pipe name does not match any built-in pipe, the value passes through unchanged. This is a compile-time-safe passthrough — no error is raised, and the input value is returned as-is.

Here is a blog post template that uses multiple pipes:

<article>
<h1>{{title | truncate:60}}</h1>
<p class="meta">
By {{author | upper}} |
{{created_at | format_date:"MMM DD, YYYY"}} |
{{comment_count | pluralize:"comment":"comments"}}
</p>
<div class="excerpt">
{{excerpt | default:"No excerpt available."}}
</div>
</article>
const BlogPost = zzz.template(@embedFile("../templates/post.html.zzz"));
fn showPost(ctx: *zzz.Context) !void {
try ctx.render(BlogPost, .ok, .{
.title = "Building Web Apps with Zig and the Zzz Framework",
.author = "alice",
.created_at = "1699963200",
.comment_count = "5",
.excerpt = "",
});
}

Output:

<article>
<h1>Building Web Apps with Zig and the Zzz Framework</h1>
<p class="meta">
By ALICE |
Nov 14, 2023 |
5 comments
</p>
<div class="excerpt">
No excerpt available.
</div>
</article>