Templating #

Glaze uses Sugar for page rendering – a PHP template engine with inheritance, blocks, components, and directives.

Templates live in templates/ and use the .sugar.php extension. Sugar automatically escapes output based on context (HTML, attribute, URL, JS, CSS), so you do not need a manual htmlspecialchars() call for most values.

This page covers Glaze-specific integration. For the full directive and language reference, see the Sugar documentation.

Template inheritance #

Sugar uses s:extends and s:block for layout inheritance. A base layout defines named blocks; child templates fill them in.

templates/layout/base.sugar.php:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title s:block="title"><?= $site->title ?></title>
</head>
<body>
    <main s:block="content"></main>
</body>
</html>

templates/page.sugar.php:

<s-template s:extends="layout/base">

<title s:prepend="title"><?= $page->title ?> | </title>

<s-template s:block="content">
    <article>
        <h1><?= $page->title ?></h1>
        <?= $content |> raw() ?>
    </article>
</s-template>

The |> raw() pipe disables escaping for the rendered $content HTML. All other output is escaped automatically by Sugar.

For full inheritance options (s:prepend, s:append, s:block nesting), see the Sugar inheritance docs.

Per-page template override #

Override the template for a single page in frontmatter:

---
title: Landing page
template: landing
---

This renders the page with templates/landing.sugar.php instead of the configured default.

Available variables #

These variables are injected into every page template by Glaze:

Variable Type Description
$title string Page title
$content string Rendered Djot HTML
$url string Current URL path
$page ContentPage Full page object
$meta array Merged page metadata
$site SiteConfig Global site config
$debug bool true when running glaze serve, false during glaze build

$page properties #

$page->title            // string
$page->urlPath          // string -- e.g. /blog/my-post/
$page->slug             // string -- e.g. blog/my-post
$page->relativePath     // string -- e.g. blog/my-post.dj
$page->source           // string -- raw Djot source
$page->draft            // bool
$page->type             // ?string -- resolved content type name
$page->meta             // array<string, mixed> -- all frontmatter metadata (keys lowercased)
$page->taxonomies       // array<string, array<string>>
$page->toc              // list<TocEntry> -- TOC entries collected during rendering (empty until rendered)

$page->meta('hero.title')                  // dotted access to nested frontmatter
$page->meta('hero.primaryAction.href', '/') // dotted access with default fallback
$page->hasMeta('hero.highlights')          // existence check

Metadata helper methods #

ContentPage provides dotted metadata helpers for nested frontmatter values:

  • $page->meta(string $path, mixed $default = null): mixed - Returns the metadata value at the dotted path. - Returns $default when the path does not exist. - Returns the full metadata map when $path is empty.
  • $page->hasMeta(string $path): bool - Returns true when metadata exists at the dotted path. - Returns whether metadata is non-empty when $path is empty.

$site properties #

$site->title            // ?string
$site->description      // ?string
$site->baseUrl          // ?string
$site->basePath         // ?string -- e.g. /blog for subfolder deploys

$site->meta('hero.title')                 // dotted access to custom site.* config keys
$site->siteMeta('hero.title')             // alias for readability
$site->hasSiteMeta('hero.primaryAction')  // existence check

Tip: Do not prepend $site->basePath manually to paths in templates. Use $this->url('/path/') instead – it applies basePath correctly and also supports fully-qualified URLs via the second argument.

Site context ($this) #

Inside every template, $this is a SiteContext instance. Its helpers are grouped into three areas: querying pages and sections, generating URLs, and accessing content assets.

Querying pages & sections #

$this->pages()                          // all pages
$this->regularPages()                   // pages without a content type
$this->type('blog')                     // pages with content type 'blog'
$this->taxonomyTerm('tags', 'php')      // pages tagged 'php'
$this->tree()                           // root Section node
$this->section('blog')                  // ?Section for a path (supports nesting: docs/guides)
$this->sections()                       // array<string, Section> — top-level sections, ordered by weight
$this->rootPages()                      // PageCollection — root-level pages (no section)
$this->isCurrent('/blog/')              // bool -- true when current page matches path
$this->previous(?callable $predicate = null)           // ?ContentPage — previous in global display order
$this->next(?callable $predicate = null)               // ?ContentPage — next in global display order
$this->previousInSection(?callable $predicate = null)  // ?ContentPage
$this->nextInSection(?callable $predicate = null)      // ?ContentPage

See Page collections for filtering, sorting, grouping, and pagination.

URL helpers #

url() and canonicalUrl() handle base-path prefixing and absolute URL construction so templates never need to reference $site->basePath directly.

$this->url('/blog/')                 // '/blog/' or '/sub/blog/' when basePath is /sub
$this->url('/blog/', true)           // 'https://example.com/sub/blog/' with baseUrl configured
$this->url($page->urlPath)           // apply basePath to any page URL
$this->canonicalUrl()                // fully-qualified URL for the current page

Use $this->url() for all internal href and src attributes. Pass true as the second argument when a fully-qualified URL is needed, for example for Open Graph or canonical link tags:

<link rel="canonical" href="<?= $this->canonicalUrl() ?>" />
<meta property="og:url" content="<?= $this->canonicalUrl() ?>" />
<meta property="og:image" content="<?= $this->url('/og-image.png', true) ?>" />
<a href="<?= $this->url('/quick-start/') ?>">Get started</a>

Content assets #

Use content asset helpers to discover non-Djot files from content directories:

$this->pageAssets()                         // assets next to current page source
$this->pageAssets('gallery')                // assets in current page subfolder
$this->assets('shared')                     // assets from content/shared/
$this->assetsFor($item, 'gallery')          // assets for a page in a loop

$this->section('blog')?->assets()           // direct assets in content/blog/
$this->section('blog')?->allAssets()        // recursive assets in content/blog/**

When iterating a page collection, use $this->assetsFor($item, ...):

<ul>
    <li s:foreach="$this->section('blog')?->pages() ?? [] as $item">
        <h3><?= $item->title ?></h3>

        <img
            s:if="!$this->assetsFor($item, 'gallery')->images()->isEmpty()"
            src="<?= $this->assetsFor($item, 'gallery')->images()->first()?->urlPath ?>"
            alt="<?= $item->title ?>"
        >
    </li>
</ul>

Each item is a ContentAsset with fields like relativePath, urlPath, filename, extension, and size, plus helper checks:

$asset->is('png', 'jpg')
$asset->isImage()

Extensions #

Invoke registered project extensions from any template:

$this->extension('name')          // call a named extension
$this->extension('name', $arg)    // pass arguments to the extension

For custom PHP logic callable from templates, see Extensions.

Sugar directives #

Sugar provides attribute-based directives for control flow, looping, and conditional class merging. A brief example:

<!-- loop -->
<li s:foreach="$this->section('blog')?->pages()->take(5) ?? [] as $post">
    <a href="<?= $this->url($post->urlPath) ?>"><?= $post->title ?></a>
</li>

<!-- conditional -->
<span s:if="$page->draft">Draft</span>
<span s:else>Published</span>

<!-- conditional class -->
<a href="<?= $this->url($post->urlPath) ?>" s:class="['active' => $this->isCurrent($post->urlPath)]">
    <?= $post->title ?>
</a>

For the complete directive reference – including s:unless, s:forelse, s:switch, s:isset, loop metadata ($loop->first, $loop->last), components, and the pipe syntax – see the Sugar documentation.

Vite template integration (s:vite) #

When Vite is enabled in Glaze config, Sugar’s Vite extension is automatically registered and you can render dev/prod asset tags directly from templates.

Both syntaxes are equivalent:

<s-template s:vite="'assets/css/site.css'" />
<s-vite src="assets/css/site.css" />

Multiple entries:

<s-template s:vite="['assets/css/site.css', 'assets/js/site.js']" />
<s-vite src="['assets/css/site.css', 'assets/js/site.js']" />

Default entry (from build.vite.defaultEntry or devServer.vite.defaultEntry):

<s-template s:vite="true" />
<s-vite />

Behavior by mode:

  • glaze serve --vite (live mode): emits dev server tags (@vite/client + module entry tags)
  • glaze build with build.vite.enabled: true: resolves and emits production tags from Vite manifest

To scaffold all related files/config quickly:

glaze init my-site --preset vite

Debug helpers #

Available in all templates:

  • dump(...) – formatted variable dump
  • debug(...) – alias for dump
  • dd(...) – dump and die

Output is rendered in a styled <pre> block when served via glaze serve.