How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Render a list of child pages under the current page

A section landing page lists its own pages from the tree, so nobody maintains the list by hand. Published children come back in position order; guard for the admin preview, where unpublished ones appear too.

Context

Context. A "Services" landing page should list the six service pages beneath it, and keep listing them as pages are added, moved or unpublished. The page tree already knows the answer. getChildren() on the current page returns its children in position order, and for a visitor only the published ones. Signed in to the admin, unpublished children appear too, so the loop guards for that. No query, no author-maintained list.

Steps

  1. In the page's layout template, fetch the children once into a variable.
  2. Skip any child that is not published, so an admin preview matches what the public sees.
  3. Link each child with getFullPath(), never a path built from its slug.
  4. Show a summary if the child's layout has one, guarded with hasAreaWithName(), so children on a layout without that layout area render without an error.

Template code

Template code
{$children = $page->getChildren()}
{if $children->count() > 0}
<ul class="section-index">
{foreach $children as $child}
  {if !$child->isPublished()}{continue}{/if}
  <li>
    <a href="{$child->getFullPath()}">{$child->getName()|escape}</a>
    {if $child->hasAreaWithName('summary')}<p>{$child->summary}</p>{/if}
  </li>
{/foreach}
</ul>
{/if}
{% set children = page.getChildren() %}
{% if children|length > 0 %}
<ul class="section-index">
{% for child in children %}
  {% if child.isPublished() %}
  <li>
    <a href="{{ child.getFullPath() }}">{{ child.getName()|e }}</a>
    {% if child.hasAreaWithName('summary') %}<p>{{ child.summary }}</p>{% endif %}
  </li>
  {% endif %}
{% endfor %}
</ul>
{% endif %}

Why this and not that

Not _query: the children are a relation the page already carries, filtered and ordered by the platform; _query is for records the controller did not provide. Not a records area the author fills: an author-picked list goes stale the day a page is added. Not a path built from the child's slug: a custom URL or a moved parent would break it, while getFullPath() follows both.

Go deeper

Was this page helpful?

Your answer goes to the docs team.