Page collections #

Page collections are Glaze’s query-friendly wrapper around all discovered pages. You access them through $this (the SiteContext object) inside Sugar templates.

Collections are lazy and chainable – call as many methods as you need before iterating.

Accessing collections #

$this->pages()                         // all pages
$this->regularPages()                  // pages without a content type
$this->section('blog')?->pages()       // direct pages in a section
$this->section('blog')?->allPages()    // pages in section subtree (includes descendants)
$this->type('blog')                    // pages with content type 'blog'
$this->taxonomyTerm('tags', 'php')     // pages tagged 'php'

ContentPage properties #

Every item in a collection is a ContentPage object:

$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->draft            // bool
$page->type             // ?string -- content type name
$page->meta             // array<string, mixed> -- all frontmatter metadata
$page->taxonomies       // array<string, array<string>> -- e.g. ['tags' => ['php', 'oss']]

Access custom frontmatter values via $page->meta:

$page->meta['date']          // Chronos instance (when set)
$page->meta['description']   // string (when set)
$page->meta['weight']        // int (when set)
$page->meta['featured']      // bool (when set)

Or use dotted helper methods on ContentPage:

$page->meta('hero.title')                // nested lookup by dotted path
$page->meta('hero.cta.href', '/contact') // fallback when missing
$page->hasMeta('hero.highlights')        // true when key exists
  • $page->meta(string $path, mixed $default = null): mixed - Returns the full metadata map when $path is empty.
  • $page->hasMeta(string $path): bool - For empty $path, returns whether metadata is non-empty.

When iterating pages, use the site context to query related content assets:

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

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

For section-level assets, use:

$this->section('blog')?->assets()      // direct assets only
$this->section('blog')?->allAssets()   // includes child section assets recursively

Iterating #

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

Common helpers #

  • count() – total number of pages in collection
  • first() – first page or null
  • last() – last page or null
  • take(n) – first n pages
  • slice(offset, length) – a range of pages
  • reverse() – reverse the current order

Sorting #

// by any dot-path key
$this->pages()->by('meta.weight', 'asc')

// by date field
$this->section('blog')?->pages()->byDate('desc')                    // uses meta.date by default
$this->section('blog')?->pages()->byDate('desc', 'meta.published')  // custom date field

// alphabetically
$this->pages()->byTitle('asc')

Filtering with where #

where takes a dot-path key, an optional operator, and a value. When the operator is omitted, = is assumed.

Supported operators:

  • equality: =, ==, eq
  • inequality: !=, <>, ne
  • comparisons: >, >=, <, <=
  • set membership: in, not in
  • intersection: intersect
  • pattern match: like
// pages marked as featured
$this->where($this->pages(), 'meta.featured', true)

// pages with weight >= 10
$this->where($this->pages(), 'meta.weight', '>=', 10)

// pages tagged with either 'php' or 'oss'
$this->where($this->pages(), 'meta.tags', 'intersect', ['php', 'oss'])

Type helpers #

// via site context
$this->type('docs')

// via collection method
$this->pages()->whereType('docs')

// chained
$this->type('blog')->byDate('desc')->take(5)

Grouping #

// group by an arbitrary key
$this->pages()->groupBy('meta.category')
$this->pages()->groupBy('meta.category', 'asc')   // optional: sort group keys ascending
$this->pages()->groupBy('meta.category', 'desc')  // optional: sort group keys descending

// group by date period
$this->section('blog')?->pages()->groupByDate('Y-m', 'desc')                    // uses meta.date
$this->section('blog')?->pages()->groupByDate('Y', 'desc', 'meta.published')   // custom date field

A grouped collection is a plain array where keys are group names and values are PageCollection instances:

<section s:foreach="$this->section('blog')?->pages()->groupByDate('Y-m', 'desc') ?? [] as $month => $pages">
    <h2><?= $month ?></h2>
    <ul>
        <li s:foreach="$pages as $post">
            <a href="<?= $post->urlPath ?>"><?= $post->title ?></a>
        </li>
    </ul>
</section>

Pagination #

<?php $pager = $this->paginate($this->section('blog')?->pages() ?? [], 10, 1, '/blog/'); ?>

<ul>
    <li s:foreach="$pager->items() as $post">
        <a href="<?= $post->urlPath ?>"><?= $post->title ?></a>
    </li>
</ul>

<nav>
    <a s:if="$pager->hasPrev()" href="<?= $pager->prevUrl() ?>">Previous</a>
    <a s:if="$pager->hasNext()" href="<?= $pager->nextUrl() ?>">Next</a>
</nav>

Arguments: paginate(collection, perPage, currentPage, baseUrl).

Previous / next navigation #

Navigate across section boundaries in global display order:

<?php $prev = $this->previous(); ?>
<?php $next = $this->next(); ?>

<nav>
    <a s:if="$prev !== null" href="<?= $prev->urlPath ?>">&larr; <?= $prev->title ?></a>
    <a s:if="$next !== null" href="<?= $next->urlPath ?>"><?= $next->title ?> &rarr;</a>
</nav>

To navigate within the current section only, use previousInSection() / nextInSection():

<?php $prev = $this->previousInSection(); ?>
<?php $next = $this->nextInSection(); ?>

All navigation helpers accept an optional predicate callback so hidden entries can be skipped while preserving adjacency order:

<?php
$isNavigable = static fn(\Glaze\Content\ContentPage $candidate): bool => (bool)($candidate->meta('navigation') ?? true);

$prev = $this->previous($isNavigable);
$next = $this->next($isNavigable);

$sectionPrev = $this->previousInSection($isNavigable);
$sectionNext = $this->nextInSection($isNavigable);
?>

paginate() does not take a predicate parameter. Filter first, then paginate:

<?php
$isNavigable = static fn(\Glaze\Content\ContentPage $candidate): bool => (bool)($candidate->meta('navigation') ?? true);
$blog = $this->section('blog');
$blogPages = $blog ? $blog->pages()->filter($isNavigable) : [];
$pager = $this->paginate($blogPages, 10, 1, '/blog/');
?>

Checking the current page #

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