Project Structure
A standard Alloy project follows this directory layout:
Config file
Alloy detects config files by extension in this order: alloy.config.yaml, alloy.config.yml, alloy.config.toml, alloy.config.json. The first match wins.
Front matter supports three formats, detected by delimiter:
| Delimiter | Format | Example |
|---|---|---|
--- |
YAML | --- title: "My Post" --- |
+++ |
TOML | +++ title = "My Post" +++ |
{ |
JSON | { "title": "My Post" } |
content/
Content files are Markdown or HTML with front matter. Front matter is required on all content files – empty front matter (---\n---) is valid, but missing delimiters is a build error.
Files whose extension does not match content.formats (default: ["md", "html"]) are treated as passthrough – copied directly to output without processing. This lets you colocate assets with content:
HTML files without front matter are classified based on content: full documents (starting with <!DOCTYPE or <html>) become passthrough; fragments become content pages with empty front matter, inheriting layout from the _data.yaml cascade.
layouts/
Template files rendered by the configured engine. The Liquid engine looks for .liquid files first, falling back to bare extensions (e.g., .html) when no .liquid file is found. The Go template engine (engine: "gotemplate" or "go") resolves .html files only.
Layout lookup follows an explicit chain:
layout:from front matter or_data.yamlcascade- Convention match (e.g.,
post.liquidfor blog children, section name for index pages) default.liquidfallback- Build error if nothing matches
Layouts can chain via front matter layout: directives, enabling multi-level composition:
data/
Global data files accessible in templates as site.data.*. Supports YAML, TOML, JSON, and CSV. The filename (without extension) becomes the key:
Two files with the same stem name (e.g., team.csv and team.yaml) cause a build error – no silent overwrites.
External data files can be mapped into the data namespace via config:
These are accessible as site.data.tokens and site.data.cem, identical to local data files.
_data.yaml
Directory-level data files that cascade to all descendant content. Each _data.yaml deep-merges with its parent’s data. Common uses include setting a shared layout, permalink pattern, or default tags for an entire section:
Every page in content/blog/ and its subdirectories inherits these values unless overridden in front matter.
static/
Files copied verbatim to the output root with no processing:
assets/
Processed assets. Copied to output and available for plugin hooks like onAssetProcess.
plugins/
Optional directory for plugin files. Alloy detects the plugin tier from the file:
.jsfiles withoutruntime: "node"run on embedded QuickJS (Tier 2).wasmfiles run as compiled WASM via wazero (Tier 2).jsfiles withruntime: "node"run as Node subprocess (Tier 3)
The Node bridge is only spawned when the project has Tier 3 plugins.
Custom directory structure
Override default paths with the structure: config key. All paths are relative to the project root:
The pipeline, file watcher, and dev server all use the configured paths. When a key is omitted, it defaults to its standard name.
Passthrough copy
Map external directories into the output via config:
Passthrough supports glob patterns and exclude filters:
See also CLI Reference for commands and flags.