Layouts
Layouts wrap your page content in shared HTML structure. Every content page is rendered into a layout, which injects the page body via {{ content }}.
The {{ content }} variable holds the fully rendered body of the current page. For Markdown files, this is already converted to HTML before the layout is applied.
Layout resolution order
Alloy resolves layouts through a predictable lookup chain. The configured engine determines the extension checked at each step: the Liquid engine tries .liquid first, then falls back to the bare extension (e.g., .html); the Go template engine checks .html only.
Blog-like sections
Sections with date-based permalink patterns (containing :year, :month, or :day tokens in _data.yaml) use special resolution.
Index file (content/blog/index.md):
layout:from front matter or_data.yamlcascadelayouts/blog.liquid→layouts/blog.html(section name)layouts/default.liquid→layouts/default.html(fallback)- Build error
Child file (content/blog/my-post.md):
layout:from front matter or_data.yamlcascadelayouts/post.liquid→layouts/post.html(child of date-based section)layouts/default.liquid→layouts/default.html(fallback)- Build error
Regular sections and standalone pages
Pages in sections without date-based permalinks resolve through a shorter chain.
Any file (content/docs/getting-started.md):
layout:from front matter or_data.yamlcascadelayouts/default.liquid→layouts/default.html(fallback)- Build error
Layouts do not match by filename. If you relied on layouts/getting-started.liquid being picked up automatically, add layout: "getting-started" to the page’s front matter or _data.yaml.
Taxonomy pages
Auto-generated taxonomy pages check their own path first:
layouts/taxonomies/<name>.liquid→layouts/taxonomies/<name>.html(e.g.,layouts/taxonomies/tags.liquid)layouts/<name>.liquid→layouts/<name>.html
Specifying a layout
Set the layout explicitly in front matter:
Or apply a layout to an entire directory via _data.yaml:
All pages in content/docs/ and subdirectories inherit this layout unless they override it in their own front matter. Front matter always takes priority over the cascade.
Extension-bearing layout names
A layout name that includes a recognized extension (.liquid, .html, .xml, .json, .txt) is used as a literal filename – no engine extension is appended:
Bare names (without an extension) get the engine-specific lookup: base → layouts/base.liquid → layouts/base.html for the Liquid engine.
Extension-bearing layout names cannot be used with output formats – layout: "article.liquid" with outputs: ["html", "json"] produces a build error with a fix-it message suggesting layout: "article" instead.
Disabling layout wrapping
Set layout: false to output the page body without any layout wrapper:
This is useful for pages that are complete HTML documents on their own, or for data-only pages consumed by other templates.
Layout chaining
Layouts can reference a parent layout via a layout: directive in their front matter. The pipeline renders inside-out: page content flows into the innermost layout, which flows into the parent, and so on up the chain.
A page using layout: "has-toc" renders as: page body -> has-toc -> base. Each level injects {{ content }} from the level below.
Layout front matter is stripped before rendering – only the layout: directive is used. Other front matter keys in layout files are ignored.
Circular layout detection
Alloy scans all layout files at build start and fails the build if a cycle exists (e.g., a -> b -> a). Layout chains are capped at 10 levels to prevent infinite loops from malformed configurations.
Accessing page data
All front matter fields are available inside layouts via the page object:
Custom front matter fields work the same way. If your content defines author: "Alice" in front matter, the layout accesses it as {{ page.author }}.
Partials and includes
Partials are reusable template fragments stored in layouts/partials/ (by convention – any path under layouts/ works). Both engines resolve partials from the layouts directory.
Plugin-registered filters work inside partials in both engines – the same filter dispatch mechanism applies to all template files. Partials can include other partials (nesting is capped at 100 levels).
Go template engine
With the Go template engine (engine: "gotemplate" or "go"), layouts are .html files using Go syntax. The same context is available with a leading dot: {{ .page.title }}, {{ .site.title }}, {{ .content }}:
Layout chaining works identically in both engines – a layout: directive in the layout’s front matter names the parent (see Layout chaining). Cross-file includes use the {{ include }} function (see Partials and includes). {% render %} and {% inline %} are Liquid-only tags.
Two helper functions are registered for working with ordered map data (JSON data files preserve key order via an ordered map type that Go’s index syntax cannot address):
Content-relative file inlining
The {% inline %} tag reads a file relative to the current content file and inserts its raw contents. No template processing occurs – the file is inserted verbatim.
This is useful for SVGs that need to respond to CSS custom properties and cannot be loaded as <img> tags.
Rules:
- Paths must start with
./or../– bare paths like{% inline "diagram.svg" %}are rejected - The resolved path must stay within the content root directory
- Only text file types are accepted:
.svg,.html,.htm,.txt,.css,.js,.json,.xml,.toml,.yaml,.yml,.md. Binary types like.pngproduce a build error suggesting<img>instead {% inline %}is a Liquid tag; it is not available in the Go template engine
Symlinks in layouts
Symlinks inside the layouts/ directory are followed. A symlink at layouts/partials/shared.liquid pointing to a file outside the project root is valid – {% include %} and {{ include }} read the symlink target. The path traversal check applies to the logical path within the layouts tree, not the symlink’s physical target.
Table of contents
Alloy extracts heading structure from Markdown pages and exposes it as page.toc. Build a TOC partial to render navigation:
Each TOC entry has id (heading anchor), text (plain text), level (2-6), and children (nested headings). TOC extraction is controlled by content.markdown.toc (default: true). Heading anchor IDs are controlled separately by content.markdown.goldmark.autoHeadingID – disabling TOC does not disable heading IDs (see Content).