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

$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

Site context ($this) #

Inside every template, $this is a SiteContext instance with helpers for querying all site content:

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

Content assets in templates #

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()

See Page collections for filtering, sorting, grouping, and pagination. 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="<?= $post->urlPath ?>"><?= $post->title ?></a>
</li>

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

<!-- conditional class -->
<a href="<?= $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.

Basic usage:

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

Multiple entries:

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

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

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

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 --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.