Frontmatter #
Frontmatter is optional metadata placed at the very top of a Djot page, before any content. Glaze reads it as NEON – the same format used by glaze.neon.
Fence syntax #
You can use either +++ or --- as opening and closing fences:
---
title: Hello world
slug: /
draft: false
template: page
tags:
- intro
- welcome
meta:
robots: index,follow
---
# Hello world
Welcome to Glaze.
Both fence styles are equivalent. Frontmatter is entirely optional – pages without it will have their title inferred from the filename.
Field reference #
Reserved keys are interpreted by Glaze runtime features:
-
title -
slug -
date -
weight -
draft -
unlisted -
type -
template -
description -
outputPath -
meta(page metadata override map used for generated<meta>defaults) -
taxonomy keys configured in
glaze.neon(tags,categories, …)
Common convention keys (used by templates/themes, not reserved by core):
-
section(override folder-derived section slug) -
navigation(include/exclude page from navigation) -
navigationTitle(alternate title for menus)
All other keys are treated as custom frontmatter data and are preserved as-is, including nested maps and lists.
title #
Human-readable page title. When omitted, Glaze infers a title from the slug (e.g. my-post becomes My Post).
---
title: Getting started with Glaze
---
slug #
Overrides the URL path derived from the file path.
A relative slug (no leading /) is resolved relative to the directory the file lives in.
A file at blog/my-post.dj with slug: custom-title produces the slug blog/custom-title:
---
slug: custom-title
---
An absolute slug (leading /) ignores the directory context and is used as-is.
This is the correct way to place a page at an arbitrary URL regardless of where the file lives:
---
slug: /custom-title
---
Useful for placing index.dj at /, or any page at a route that differs from its folder:
---
slug: /
---
Slug values are normalized (lowercased and slugified) in both cases.
date #
Publish date or datetime. Must be a value parseable as a date or datetime.
---
date: 2026-02-24
---
---
date: 2026-02-24T14:30:00+01:00
---
Glaze normalizes date to a Cake\Chronos\Chronos instance. If the value cannot be parsed, content discovery throws an exception.
In templates:
<?php $date = $page->meta['date'] ?? null; ?>
<?php if ($date instanceof \Cake\Chronos\Chronos): ?>
<time datetime="<?= $date->toIso8601String() ?>">
<?= $date->format('F j, Y') ?>
</time>
<?php endif; ?>
weight #
Integer sort order for page collections. Lower values sort first. Useful for controlling navigation menus, ordered documentation sections, or any list where file-path ordering is not sufficient.
---
weight: 10
---
In templates, sort a collection by weight:
<li s:foreach="$this->pages()->by('meta.weight', 'asc') as $p">
<a href="<?= $p->urlPath ?>"><?= $p->title ?></a>
</li>
Navigation-focused usage of weight, section, navigation, and navigationTitle is covered in Templating and Page Collections.
draft #
Marks the page as a draft. Draft pages are excluded from glaze build output unless --drafts is passed. The live server (glaze serve) includes drafts by default.
---
draft: true
---
unlisted #
Marks the page as unlisted. Unlisted pages are rendered and accessible by URL, but excluded from page collections (regularPages(), type(), pages()) by default. This is useful for section overview pages that should not appear alongside regular content in listings.
When the file is a section index page (for example content/docs/_index.dj), it still acts as section metadata. Its title is used for Section->label() and its weight controls section ordering, while the page remains excluded from section()->pages().
---
unlisted: true
---
Files named with a leading underscore (e.g. _index.dj) are automatically treated as unlisted. You can override this by setting unlisted: false explicitly in the frontmatter.
type #
Explicitly assigns a content type when contentTypes are configured in glaze.neon. Normally the type is resolved automatically from the file path. Use this to override:
---
type: blog
---
template #
Overrides the default page template for this page. The value is a template name (without .sugar.php extension) relative to templates/.
---
template: landing
---
This causes the page to render with templates/landing.sugar.php instead of the configured default.
outputPath #
Overrides the generated output file path and URL for a page. By default, a page at content/about.dj is written to about/index.html and served at /about/. Setting outputPath bypasses the standard /{slug}/ directory-index convention entirely.
The value is a relative path from the output root. Leading slashes are stripped automatically. Path traversal (..) is rejected with an error.
---
title: Page Not Found
outputPath: 404.html
unlisted: true
---
# Not Found
The page you are looking for does not exist.
When outputPath is set, the page’s URL is derived directly from the value: outputPath: 404.html produces a URL of /404.html. This differs from slug, which always produces a trailing-slash directory URL.
Custom 404 page #
The most common use case for outputPath is creating a custom 404 error page. When a content page has outputPath: 404.html, Glaze’s dev server automatically uses it as the “not found” response for any unmatched route, returned with a proper 404 status code. During glaze build, the file is written to public/404.html where most hosting providers (Netlify, Vercel, Cloudflare Pages, GitHub Pages, etc.) will pick it up as the custom error page automatically.
---
title: Page Not Found
outputPath: 404.html
unlisted: true
---
# 404 — Page not found
Sorry, the page you are looking for does not exist.
Pair outputPath with unlisted: true to prevent the page from appearing in page collections and navigation.
Nested output paths #
outputPath supports nested paths:
---
outputPath: errors/404.html
---
This writes the page to public/errors/404.html and serves it at /errors/404.html.
Other use cases #
Generate an XML or JSON file from a content page:
---
title: RSS Feed
outputPath: feed.xml
template: feed
unlisted: true
---
Place a file at a specific location unrelated to its content directory:
---
title: Robots
outputPath: robots.txt
template: robots
unlisted: true
---
Difference from slug #
| Feature | slug |
outputPath |
|---|---|---|
| URL style | Always /{slug}/ (trailing slash) |
Exact: /{outputPath} (no trailing slash) |
| Output file | {slug}/index.html |
Exactly as specified |
| Value normalization | Lowercased and slugified via Text::slug() |
Trimmed; \ normalized to /; leading / stripped |
| Path traversal | Silently sanitized (.. segments slugify to page) |
Rejected — .. segments throw an error |
Taxonomy fields #
Any key listed under taxonomies in glaze.neon is extracted as a taxonomy. Values can be a single string or a NEON list.
---
tags:
- php
- static-sites
categories:
- tutorials
---
Custom fields #
Use any non-reserved key for custom page data. Nested maps/lists are preserved and can be read with dotted access in templates.
---
hero:
title: Build fast static sites
primaryAction:
label: Read docs
href: /installation
---
In templates:
<?= $page->meta('hero.title') ?>
<?= $page->meta('hero.primaryAction.href') ?>
meta can still be used as a key if you want, but it is no longer required as a wrapper for nested data.
How Glaze processes frontmatter #
- All keys are normalized to lowercase
-
dateis converted toChronos; an unparseable value is an error -
Taxonomy keys are extracted into
$page->taxonomies; the rest lands in$page->meta -
Unknown keys are kept in
$page->metawithout validation -
Content type
defaultsfromglaze.neonare merged in, with page frontmatter taking precedence -
typeresolves via path matching first, then explicit frontmatter override
Next: Configuration and Templating.