Converting templates
How to turn Go templates into Tera templates for fugo — a side-by-side table of syntax and functions, and the rules that catch most mistakes.
A Go template and its fugo version side by side:
{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ range where .Pages "Params.featured" true }}
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ with .Params.subtitle }}<p>{{ . }}</p>{{ end }}
{{ end }}
{{ partial "pagination.html" . }}
{{ end }}{% extends "baseof.html" %}
{% block main %}
<h1>{{ page.title }}</h1>
{% for p in [x for x in page.pages if x.params.featured] %}
<h2><a href="{{ p.rel_permalink }}">{{ p.link_title }}</a></h2>
{% if p.params.subtitle %}<p>{{ p.params.subtitle }}</p>{% endif %}
{% endfor %}
{% include "_partials/pagination.html" %}
{% endblock main %}The main changes: the context is named (page, site) instead of .; fields are snake case (.RelPermalink → rel_permalink); define/block become extends and block; range, with and where become for, if and comprehensions.
Syntax and functions#
| Go | Tera |
|---|---|
and or not eq ne lt le gt ge | and or not == != < <= > >=; pages compare by .id, pagers by .page_number, dates by .unix |
cond c a b | a if c else b |
print, printf | ~, plus the filters pad_start, pad_end, round, format_number, jsonify; a literal U+00A0 character for %c of 160; '"' ~ x ~ '"' for %q in attributes |
dict, slice | map and array literals {"k": v}, [a, b]; computed keys: [[k, v]] | from_pairs |
index m k | m[k], m.k |
in, strings.Contains | x in l, "x" in s; pages p.id in [q.id for q in l] |
isset m "k" | "k" in m, is defined |
first N, last N, after N | l[:N], l[-N:], l[N:] |
where | [p for p in pages if p.params.x == v]; in/intersect via ids; like via is matching(pat=) |
apply l "float" "." | [x | float for x in l] |
newScratch, .Scratch.* | {% set %}, {% set_global %}, merge |
add sub mul div mod | + - * / %; // for Go’s integer div |
.GetTerms "tags" | page.terms.tags |
.Data.Singular/Plural/Term/Terms | page.taxonomy.singular/plural/terms, page.term.term |
.OutputFormats.Get "rss", .AlternativeOutputFormats, .MediaType | page.output_formats.rss, page.alternative_output_formats, f.media_type.type |
The site-info object’s Version / Environment / IsProduction / IsDevelopment / IsServer / Generator | build.version (fugo’s version), build.environment, build.is_production, build.is_development, build.is_server, build.generator |
.Site.ServerPort | site.server_port (the base URL’s port, 0 without one) |
.Site.Config.Privacy.* | site.config.privacy.* |
.Data.Integrity, .Width, .Height | r.data.integrity, r.width, r.height |
partial "x" . (shares the context) | {% include "_partials/x.html" %} |
partial "x" (dict …) with a literal name | a component defined in _partials/ ({% component x(page, sep="/", @lang) %}), called as {{ <x page={page} /> }} |
debug.Timer | removed |
css.TailwindCSS | removed: run Tailwind’s standalone CLI next to fugo (tailwindcss -i assets/css/in.css -o assets/css/site.css --watch) and use its output as an asset |
babel, js.Babel | removed: js_build compiles TypeScript and JSX and lowers modern JavaScript for the browser targets |
postCSS, css.PostCSS | removed: minify adds vendor prefixes for the site’s browserslist and minifies; purge_css purges per page |
Every function has its own page in the function reference, with the Go-template name it replaces. Every object field lists its Go-template name in the object reference.
Conversion rules#
Names and paths#
- Files use v0.146 names (
_partials/,_shortcodes/,_markup/,home|section|taxonomy|term|single|list|all|<layout>[.<lang>][.<fmt>].<ext>,baseof.html). Include and extends literals are lower-case, new-style names. .Title→page.title;.Site.Xandsite.X→site.x.- Params are lower-cased:
.Site.Params.HomeTitle→site.params.hometitle. Data keys keep their case:item.Name. - Reserved front-matter keys stay available in params:
where … "Params.type"→[p for p in l if p.params.type == "snacks"].
Missing values#
- Printed params that may be missing →
page.params.x or ""(printing an undefined value is an error). - Nested optional lookups use
?.:page.params.a?.b,page.parent?.title or "". - Go’s
default→default_if_empty(value=). Do not useorfor bools. x == noneis false whenxis undefined; useis undefined,is noneor truthiness instead.
Comparisons#
eq $p $currentSection→p.id == current_section.id. Pagers compare bypage_number, dates by.unix.- Mixed int/string comparisons get an explicit
intorstr.
Control flow#
{{ with X }}…{{ else with Y }}→{% if X %}{% set x = X %}…{% elif Y %}….range $k, $v := m→{% for k, v in m %}; template map literals need| sort_keysfirst.range $i, $e := l→{% for e in l %}withloop.index0/loop.first.where→ a list comprehension withif;apply→ a comprehension;seq N→range(start=1, end=N+1).- Variable reassignment inside a block →
set_global(discarded inside includes).
Templates and partials#
define/blockin children →{% extends "baseof.html" %}plus{% block %}; delete blocks the parent does not define.- Partials → include, component or
partial(); component calls pass arguments asname={expr},name="literal"or the shorthandname. try→optional=trueonget_remoteandto_math(none on an error), or anonecheck.
Formatting#
- Go
printf→~,pad_start/pad_end,round/format_number,jsonify. - Tera 2.4 strings know only the escapes
\n\t\r\"\'\/\\: write other characters such as U+00A0 literally ("\u{a0}"is an error), and double a regex backslash (pattern="\\s+"). - Go date layouts → strftime:
"2006-01-02"→"%Y-%m-%d","Jan 2, 2006"→"%b %-d, %Y"..Formatstays English;time.FormatanddateFormatlocalize names, so addlocale=lang.
Removed Go-template idioms#
range .Paginator.Pages→{% set pager = paginator() %}{% for p in pager.pages %}; delete a second.Paginatethat follows.Paginator.{{ $noop := .WordCount }}→ delete.- Another page’s
.Contentinside a shortcode →page_content(page=p). .Scratch/newScratch→set,set_global,merge; the page store only for cross-template flags.
Components#
- A component that calls site-bound functions passes
page=or declares@__nh.
Escaping#
html/htmlEscape→html_escape. In<script>, usejsonify | safe. In query strings, useurlencode.safeHTMLand the othersafe*→safe.
Assets and i18n#
- Assets used with
execute_as_templateare Tera templates:{{ .api }}→{{ data.api }}. - i18n files stay in Go-template syntax, limited to
{{ . }}and{{ .Field }}.
Tera facts worth knowing#
__nhis an ordinary identifier (a leading_is allowed) and@__nha valid implicit component argument, resolved through the callers’ scopes; a component that does not declare it cannot see it.- Component call arguments are
name={expr},name="literal"or the shorthandname;name=expris a syntax error. ==and!=never fail on an undefined final path segment: the value is undefined, and undefined equals only undefined (x == noneis false). A missing non-final segment is an error, even insideif.- Printing an undefined value is an error;
x or ""anddefault(value=)are the fallbacks. ?.(and?[) yield undefined when the receiver is undefined or none; the result must still not be printed bare.- Built-in kwargs:
split(pat=),nth(n=),replace(from=, to=),trim(pat=),join(sep=),round(method=, precision=),truncate(length=, end=),default(value=, boolean=),get(key=, default=),range(start=, end=, step_by=). - Unknown filters, tests, functions, components and include targets are errors when templates are added; kwargs are checked only when called, so the contract test checks them statically.
- tera-contrib 0.3 names:
b64_encode/b64_decode,filesize_format,regex_replace(pattern=, rep=),matching(pat=),urlencode,urlencode_strict,date(format=, locale=, timezone=).