Templates Overview
Alloy uses the Liquid template language by default. Templates live in the layouts/ directory, are parsed once at startup, and rendered per page with full access to the site’s data cascade.
# alloy.config.yaml
templates:
engine: "liquid" # default; also supports "go" (Go html/template)
<!-- layouts/default.liquid -->
<!DOCTYPE html>
<html>
<head><title>{{ page.title }}</title></head>
<body>
{% include "partials/header" %}
{{ content }}
{% include "partials/footer" %}
</body>
</html>
Every content page is rendered through its resolved layout. The layout receives the page’s rendered body as {{ content }} and can access all front matter fields via the page object.
Template engines
Alloy supports two built-in (Tier 1) template engines. The engine is a global, project-wide setting – one engine is active per build.
| Engine | Config value | File extension | Syntax |
|---|---|---|---|
| Liquid | "liquid" (default) |
.liquid |
{{ var }}, {% tag %} |
| Go templates | "go" |
.html |
{{ .var }}, {{ range }} |
Both engines receive the same map[string]any context from the data cascade. All built-in filters are registered in both engines at startup.
A third-party engine (Nunjucks, EJS, Pug) can be registered via the Node bridge as a Tier 3 plugin engine, though every page render becomes an IPC round-trip with significant performance cost.
Template context
Every template receives these top-level variables:
| Variable | Description |
|---|---|
page |
Current page data: page.title, page.url, page.date, page.summary, page.toc, plus all front matter fields |
content |
The rendered body of the current page (Markdown already converted to HTML) |
site |
Site-wide data: site.title, site.baseURL, site.language, site.data.*, site.pages |
collections |
Section-based collections: collections.blog, collections.docs, etc. |
taxonomies |
Taxonomy groups: taxonomies.tags.javascript, taxonomies.categories.tutorials, etc. |
pagination |
Pagination context (only on paginated pages): pagination.pageNumber, pagination.totalPages, pagination.nextPage, pagination.previousPage |
<h1>{{ page.title }}</h1>
<time>{{ page.date | date: "%B %d, %Y" }}</time>
{{ content }}
<h2>Recent posts</h2>
{% for post in collections.blog limit: 5 %}
<a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}
Template resolution
The .liquid extension marks a file as a Liquid template. The extension before .liquid determines the output format:
layouts/default.liquid --> HTML output (default)
layouts/feed.xml.liquid --> XML output
layouts/api.json.liquid --> JSON output
When the Liquid engine cannot find a .liquid file, it falls back to the bare extension and parses it as Liquid. The Go engine uses bare extensions directly (.html, .xml, .json) and never reads .liquid files.
layouts/
├── default.liquid <-- used when engine: "liquid"
├── default.html <-- used when engine: "go" (or as Liquid fallback)
├── feed.xml.liquid <-- used when engine: "liquid"
├── feed.xml <-- used when engine: "go" (or as Liquid fallback)
└── robots.txt <-- static content, used by either engine
If a file contains syntax for the wrong engine, the build fails with a parse error. Alloy does not inspect file contents to determine the engine.
Rendering pipeline
Alloy renders content in a strict order:
- Markdown rendering – Goldmark parses
.mdfiles into HTML. Template tags ({{ }},{% %}) are preserved through Markdown via a custom goldmark extension. - Template rendering – The configured engine evaluates Liquid or Go template syntax in the rendered output and in layouts.
- Layout wrapping – The page body is injected into its resolved layout via
{{ content }}. Layouts can chain to parent layouts.
Template tags inside <code> blocks in Markdown files are automatically escaped so they display as literal text rather than being evaluated.
Directory structure
layouts/
├── default.liquid # Fallback layout for all pages
├── blog.liquid # Blog index layout (matches section name)
├── post.liquid # Blog post layout (child of date-based section)
└── partials/ # Reusable template fragments
├── header.liquid
└── footer.liquid
The layouts directory is configurable:
# alloy.config.yaml
structure:
layouts: "./docs/layouts/" # default: "layouts"
Next steps
- Layouts – layout chaining, resolution order, partials
- Filters – built-in and custom filter reference
- Shortcodes – reusable content snippets with parameters
- Output Formats – multi-format rendering (HTML, JSON, XML)