Structure sections are one of the most powerful features in Craft CMS. Unlike Channels, which are flat lists of entries, Structures allow you to define a specific order and a nested hierarchy. This makes them perfect for documentation, services pages, or any content that requires a parent-child relationship.

If you are building an overview page—where a top-level entry needs to display a list of its nested sub-pages—you need to know how to query those relationships efficiently. In this guide, you will learn multiple ways to fetch child entries from a Structure using Twig, ranging from simple helper methods to advanced element queries.

Understanding Structure Levels

In Craft CMS, every entry in a Structure is assigned a level. Top-level entries are Level 1. Their immediate children are Level 2, and so on. When you are on a Level 1 "Overview" page and want to list the content pages tucked beneath it, you are essentially asking Craft to find all Level 2 entries that are descendants of the current page.

Before diving into the code, ensure your section is actually a Structure. You can verify this in the Craft Control Panel under Settings > Sections. If it is a Channel, hierarchy features like children and descendantOf will not be available.

Method 1: Using the entry.children() Shortcut

The simplest way to get children for a specific parent entry is to use the children() method directly on the entry model. If you are currently viewing the parent entry (the entry variable is automatically available in your template), you can fetch its immediate children like this:

{# Fetch all immediate children of the current entry #}
{% set children = entry.children().all() %}

{% if children|length %}
    <ul>
        {% for child in children %}
            <li><a href="{{ child.url }}">{{ child.title }}</a></li>
        {% endfor %}
    </ul>
{% endif %}

Targeting Specific Levels

If your structure has many levels (e.g., Level 3 or 4) but you specifically only want the Level 2 entries, you can chain the level() parameter onto the query:

{% set children = entry.children().level(2).all() %}

This is highly efficient because entry.children() returns a pre-baked Entry Query object that is already scoped to the current entry’s descendants.

Method 2: Using the descendantOf Parameter

Sometimes you might need more control over the query, or you might be trying to fetch children from an entry that isn't the "current" page. In these cases, using a standalone craft.entries query with the descendantOf parameter is the best approach.

This method allows you to filter by entry types, custom fields, or other criteria while still respecting the hierarchy:

{% set subPages = craft.entries()
    .section('services')
    .descendantOf(entry.id)
    .level(2)
    .all() 
%}

{% for subPage in subPages %}
    <article>
        <h3>{{ subPage.title }}</h3>
        <p>{{ subPage.summary }}</p>
        <a href="{{ subPage.url }}">Read More</a>
    </article>
{% endfor %}

In this example, we pass entry.id to descendantOf. You can also pass the entire entry object, and Craft will automatically extract the ID for you.

Method 3: Fetching All Entries at a Specific Level

What if you aren't on a parent page, but you want to list all content pages that exist at Level 2 across your entire section? You can bypass the parent requirement entirely and query by level alone.

{% set allLevelTwoEntries = craft.entries()
    .section('yourSectionHandle')
    .level(2)
    .all() 
%}

Using Advanced Level Operators

Craft also allows you to use comparison operators within the level() parameter. This is useful if you want to fetch everything below a certain depth:

{# Fetch everything that is Level 2 or deeper #}
{% set deepEntries = craft.entries()
    .section('yourSectionHandle')
    .level('>= 2')
    .all() 
%}

Common Mistakes to Avoid

1. Forgetting .all()

In Craft CMS 3, 4, and 5, calling entry.children() or craft.entries() returns an Element Query object, not the results themselves. You must append .all() to execute the query and return an array of entry models. Forgetting this will result in an error or an empty loop.

2. Performance Issues with Deep Nesting

If you have a massive structure and you are looping through children, then looping through their children (nested loops), you may run into performance bottlenecks. In these cases, consider using Eager Loading to fetch all levels in a single database query.

3. Confusing Children vs. Descendants

  • entry.children() refers to the immediate level below the entry.
  • descendantOf(entry) refers to any level below the entry (children, grandchildren, etc.). If you only want the direct children, use children() or set .level(entry.level + 1).

Frequently Asked Questions

How do I get the parent of an entry?

To go the other direction in the hierarchy, use the parent property: {% set parentEntry = entry.parent %}. If the entry is already at Level 1, this will return null.

How do I get siblings of the current entry?

Siblings are entries that share the same parent. You can fetch them using: {% set siblings = entry.getSiblings().all() %}. Note that this will include the current entry itself in the results unless you filter it out.

Can I sort children by a custom field?

Yes! Even though Structures have a default "Structure Order," you can override this in your query: {% set children = entry.children().orderBy('postDate desc').all() %}.

Wrapping Up

Navigating hierarchical content in Craft CMS is straightforward once you understand the relationship between Entry Models and Element Queries. Whether you use the quick entry.children() method or the more robust descendantOf() parameter, you have full control over how your nested content is displayed.

When building overview pages, remember to: 1. Verify your section type is a Structure. 2. Use .level(2) to target specific depths. 3. Always use .all() to execute your queries. 4. Consider the user experience: if a parent has no children, provide a fallback or hide the section using an if statement.

By mastering these query parameters, you can build complex, dynamic navigation systems that update automatically as your clients move entries around in the Craft Control Panel.