Grav Page Collections — Listing, Filtering and Traversing Pages
In this tutorial, you'll learn Grav page collections — how to list child pages, filter pages by taxonomy, traverse sibling and parent pages, and build modular content collections for dynamic page listings.
What You'll Learn
- What page collections are and how Grav defines them
- Listing child pages, sibling pages, and parent pages
- Filtering collections by taxonomy, date, and custom criteria
- Modular collections and
@self.modular - Collection ordering, pagination, and limiting
- Using collections in Twig templates
Why It Matters
In WordPress, listing posts requires WP_Query with complex arguments. In Drupal, you build a View with a UI. In Grav, collections are defined in frontmatter or Twig using a simple query syntax. Collections are how you build blog listings, portfolio grids, documentation tables of contents, and any page that lists other pages. Without collections, every page is an isolated island. With collections, your site becomes a connected system where pages group themselves dynamically.
Real-World Use
A documentation site needs a "Related Articles" section at the bottom of every page. Using a collection that queries pages with matching taxonomy tags, Grav automatically shows related content. When a new article is added with the same tags, it appears on every related page without manual linking.
Learning Path
flowchart LR
A["Page Meta & Frontmatter"] --> B["Page Collections
← You are here"]:::current
B --> C["Markdown & Shortcodes"]
C --> D["Twig Filters & Functions"]
D --> E["Twig Macros"]
E --> F["Twig Inheritance"]
F --> G["Twig Debugging"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
What Is a Page Collection?
A collection is a group of pages that match certain criteria. You define the criteria in the page's frontmatter under content::
---
title: Blog
content:
items: '@self.children'
order:
by: date
dir: desc
---
This tells Grav: "Collect all child pages of the current page, order by date descending." The result is an array of page objects you can iterate in Twig.
Collection Types
@self.children — Direct Children
Lists immediate child pages:
user/pages/03.blog/
├── blog.md # ← Start here (self)
├── 01.first-post/
│ └── item.md
└── 02.second-post/
└── item.md # → These are children
{% for child in page.collection %}
<article>
<h2>{{ child.title }}</h2>
<p>{{ child.summary }}</p>
</article>
{% endfor %}
@self.siblings — Sibling Pages
Lists pages at the same level:
user/pages/03.blog/
├── 01.first-post/ # ← Sibling
├── 02.second-post/ # ← Current page
└── 03.third-post/ # ← Sibling
---
title: Second Post
content:
items: '@self.siblings'
---
@self.parent — Parent and Siblings
Gets the parent page and its other children:
---
title: Second Post
content:
items: '@self.parent'
---
@self.modular — Modular Page Children
Used in modular pages to collect module sections:
---
title: Home
content:
items: '@self.modular'
order:
by: default
dir: asc
---
@page — Specific Page By Route
---
title: Custom
content:
items:
- '@page': '/blog'
- '@page': '/about'
---
This creates a collection containing exactly two pages: the blog page and the about page.
Filtering Collections
By Taxonomy
---
title: PHP Tutorials
content:
items:
'@self.children':
taxonomy:
category: php
tag: [tutorial, beginner]
order:
by: date
dir: desc
---
This collects child pages that have taxonomy.category: php AND taxonomy.tag containing either "tutorial" or "beginner".
By Date Range
---
title: Posts This Year
content:
items:
'@self.children':
date:
after: 2026-01-01
before: 2026-12-31
order:
by: date
dir: desc
---
By Page Type
---
title: All Blog Posts
content:
items:
'@self.children':
type: item
---
Only collects pages that use the item template type (blog posts).
Custom Field Filter
---
title: Featured Content
content:
items:
'@self.children':
featured: true
order:
by: date
dir: desc
---
This collects only children where the frontmatter includes featured: true.
Ordering Collections
content:
order:
by: default # folder order (number prefix)
by: title # alphabetical by title
by: date # by date field
by: modified # by last modified date
by: header.xxx # by any custom frontmatter field
dir: asc # ascending (a-z, oldest first)
dir: desc # descending (z-a, newest first)
Pagination
Break large collections into pages:
---
title: Blog
content:
items: '@self.children'
order:
by: date
dir: desc
pagination: true
limit: 10
---
In Twig, render pagination:
{% for child in page.collection %}
<article>
<h2>{{ child.title }}</h2>
<p>{{ child.summary }}</p>
</article>
{% endfor %}
{% if config.plugins.pagination.enabled %}
{% include 'partials/pagination.html.twig' with { 'pagination': page.collection.params.pagination } %}
{% endif %}
Navigate pages with ?page=2 in the URL.
Limiting Collections
content:
items: '@self.children'
limit: 5 # Show only 5 items
Without pagination, limit shows the first N items. With pagination: true, it shows N items per page.
Collection in Twig Without Frontmatter
You can build collections directly in Twig:
{% set related = page.collection({
'items': {
'@self.children': {
'taxonomy': {
'tag': page.taxonomy.tag
}
}
},
'order': {
'by': 'date',
'dir': 'desc'
},
'limit': 3
}) %}
<h3>Related Articles</h3>
{% for item in related %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
This creates a "Related Articles" section that finds sibling pages sharing the same tags.
Combining Collections
Multiple criteria in one collection:
---
title: Combined
content:
items:
'@page': /featured
'@self.children':
taxonomy:
featured: true
order:
by: date
dir: desc
---
Adds the /featured page AND all children with featured: true to the same collection.
Collection as JSON
Collections can output JSON for API consumption:
/page-collection.json
Add .json to any collection page URL and Grav returns the collection as JSON.
Learning Path
flowchart LR
A["Custom Page Types"] --> B["Page Meta & Frontmatter"]
B --> C["Page Collections
← You are here"]:::current
C --> D["Markdown & Shortcodes"]
D --> E["Twig Filters & Functions"]
E --> F["Twig Macros"]
F --> G["Twig Inheritance"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Common Mistakes
Using the wrong collection type:
@self.childrengets direct children only. For all descendants (including grandchildren), you would need a custom plugin or recursive loop.Forgetting
order.byandorder.dir: Without ordering, collections appear in folder order (number prefix). This often gives unexpected results when listing blog posts.Collection returns empty: If the collection returns no pages, check: the page type filter (
type: item), taxonomy filter spelling, and whether child pages exist at all.Pagination not working: Pagination requires the Pagination plugin to be installed and
pagination: truein the collection frontmatter. Without the plugin, pages show all results.Taxonomy filter mismatches taxonomy key: The taxonomy filter uses the taxonomy key (e.g.,
tag,category), not the value. Verify the taxonomy key matches what is in the child page frontmatter.
Practice Questions
What does
@self.childrenreturn compared to@self.siblings? Answer:@self.childrenreturns the direct child pages of the current page.@self.siblingsreturns other pages at the same level as the current page (same parent).How do you order a collection by a custom frontmatter field? Answer: Use
order.by: header.CUSTOMFIELD. For example,order.by: header.priorityorders by apriority:value in frontmatter.How do you limit a collection to only show featured pages? Answer: Add a filter:
items: { '@self.children': { 'featured': true } }. Only children withfeatured: truein frontmatter will appear.What is the difference between
limit: 5andlimit: 5withpagination: true? Answer: Without pagination,limit: 5shows the first 5 pages in the collection. With pagination, it shows 5 pages per page with next/previous navigation.Challenge: Build a complete blog system with: a blog listing page that shows 6 posts per page with pagination, a sidebar that shows recent 5 posts (using a separate limited collection), related posts at the bottom of each article (matched by shared taxonomy tags), a tag archive page that lists all posts for a specific tag, and a featured posts section on the homepage that collects pages with
featured: true. Test pagination with at least 20 blog posts.
FAQ
Mini Project
Goal: Build a documentation site with dynamic page collections.
- Create a docs section with 15-20 pages organized into sub-sections (getting-started, guides, api-reference, troubleshooting)
- Each page should have taxonomy tags (topic, difficulty, product)
- Create a docs landing page that lists all sections using
@self.children - Create a "Related Articles" template partial that finds pages with matching tags
- Add a "Latest Updates" sidebar that shows the 5 most recently modified pages
- Create a tag archive page that lists all pages for a given tag
- Add pagination to the search results page (6 per page)
- Create a featured articles collection on the homepage
- Test that collections update automatically when new pages are added
- Output a collection as JSON and verify the structure
What's Next
Now you can build dynamic page listings. Next, learn Markdown extras and shortcodes:
Continue to Lesson 14: Markdown & Shortcodes — Grav's Markdown extras, custom shortcodes, and content formatting.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro