↑ ↓ to navigate
↵ to select
esc to close
Core Concepts Intermediate

URL Routing Guide

How entry URLs are generated from routing patterns, and how index pages, the home page, per-entry overrides and redirects work.

Overview

Every entry's URL is generated from a routing pattern and stored as the entry's content path. When a request arrives, Airogel CMS checks these in order:

  1. Collection index pages: does the path match a collection's listing page, such as /blog or /blog/page/2?
  2. Content paths: is there an entry at exactly this path?
  3. Automatic redirects: did an entry used to live here? If so, the visitor gets a 301 to its new URL.
  4. Redirect rules that you have written. These are consulted only when nothing above matched.
  5. Otherwise, the site returns a 404 page rendered inside your theme layout.

Unpublished entries return 404 even though they have a content path.

Routing patterns

Each collection has a routing pattern that can use these placeholders:

PlaceholderReplaced with
:handleThe entry's handle
:year4-digit year of published_at, or of the creation date if published_at is empty
:month2-digit month, from the same date
:day2-digit day, from the same date
:<field_handle>The handle of the entry referenced by the entity field with that handle. For a multiple entity field, the first referenced entry is used.

Examples

RoutingEntryURL
:handleabout/about
blog/:handlemy-post/blog/my-post
:year/:month/:handlehello, published June 2025/2025/06/hello
units/:unit/lessons/:handlelesson-1, whose unit field references unit-1/units/unit-1/lessons/lesson-1

Rules to know

  • routing must never be empty. An empty pattern gives an empty path, and an empty path cannot be stored. For listing pages, use the index settings below. For the home page, use root routing.
  • Set published_at on entries in date-routed collections. Otherwise the URL uses the entry's creation date, which may not be the date you want.
  • Entity placeholders need a value. If an entry has not set the entity field that a placeholder names, no path is stored. A new entry has no URL until the field is set. An existing entry keeps its previous URL. The path is generated as soon as the field gets a value, and it updates when the referenced entry changes.
  • Paths are matched exactly and are unique per account. If two entries would produce the same path, the second one keeps its previous URL, or has none.

Per-entry routing override

An entry can replace its collection's pattern with its own routing_override, which uses the same placeholders. For example, in a collection routed units/:unit/lessons/:handle, an entry with routing_override: "units/intro/:handle" is served at /units/intro/<handle>, whatever its unit field says. Set it in the entry editor or with the MCP entries_save tool. The REST API returns it but does not accept it.

When URLs change

An entry's path is regenerated when any of these change:

  • its handle
  • its published_at
  • its routing_override
  • an entity field used in the pattern
  • the collection's routing (all entries in the collection are regenerated)

The old path is saved as an automatic redirect to the entry, served with a 301, so existing links and search results keep working. Two exceptions: no redirect is created if another entry already lives at the old path, and if an entry later claims a redirected path, that redirect is removed. Automatic redirects appear under Content → Redirects, where you can delete them.

Redirect rules

Redirect rules (available since v0.15.1) are redirects you write yourself, for example to move a whole section or send old URLs to another site.

SettingValues
From pathA path such as old-blog. Leading and trailing slashes are removed.
Match typeprefix (default) matches the path and everything below it. exact matches only that path.
DestinationA path on your site (/news) or a full https:// URL
Status302 (default), 301, 307 or 308
DomainOptional. Limits the rule to one of your domains. With no domain, the rule applies on every domain your site serves.

A prefix rule carries over the rest of the path and the query string. With old-blog → https://blog.example.com, a request for /old-blog/2024/post?ref=x goes to https://blog.example.com/2024/post?ref=x.

  • Rules only run when a request would otherwise return a 404, so a rule can never hide a live page, listing page or asset. If you later publish content at a rule's path, the content wins.
  • Rules that would redirect into another rule, and so loop, are rejected when you save them.
  • You cannot write rules for paths the platform reserves: portal, sitemap.xml, robots.txt and the feed.* files.
  • Manage rules under Content → Redirects or with the MCP redirect_rules_save, redirect_rules_list, redirect_rules_get and redirect_rules_delete tools. The REST API has no redirect endpoints.

Index (listing) pages

A collection gets a paginated listing page when both of these are set:

  • index_routing: the listing path without a leading slash, for example blog
  • index_template_handle: the template that renders the listing

index_per_page sets the page size (default 10). The listing is served at /blog, /blog/page/2, /blog/page/3 and so on. Pagination uses the path, not a ?page= query parameter. Only published entries are listed, in the order set by the collection's orderable setting (see Collections). Index routes are checked before entry paths, so a listing page wins over an entry with the same path.

In the index template, the collection is available under its handle, and page holds the current page number. {% paginate %} builds the /page/N links for you:

{% paginate posts.entries by 10 %}
  {% for post in paginate.items %}
    <h2><a href="{{ post.content_path }}">{{ post.title }}</a></h2>
  {% endfor %}
  {% if paginate.has_previous %}<a href="{{ paginate.previous_url }}">Newer</a>{% endif %}
  {% if paginate.has_next %}<a href="{{ paginate.next_url }}">Older</a>{% endif %}
{% endpaginate %}

See Liquid Templates for all paginate properties.

The home page (root routing)

The collection that serves / needs all three of these:

  1. routing: ":handle"
  2. root_routing: true
  3. an entry with handle index
Collection:
  handle: page
  routing: ":handle"
  root_routing: true
  template_handle: page

Entry:
  handle: index
  title: Home

The index entry's content path is /index. With root_routing: true it is also served at /. Without it, / returns a 404, although /index still works. This setting stops an unrelated collection from taking over your home page just because it has an entry named index. The Page collection created with every new account is already configured like this. Set root_routing through the Collections API or the MCP collections_save tool.

Common mistake: setting routing: "" on the home page collection. An empty pattern gives the index entry no usable path, and / does not work. Use :handle together with root_routing.

Templates and layouts

  • An entry is rendered with its own template if it has one, otherwise with its collection's template. If neither exists, the page returns 404.
  • The layout is chosen in this order: the entry's layout, then the collection's layout, then the template with handle theme.
  • A listing page uses the collection's index template, inside the collection's layout or theme.

Other built-in paths

  • /sitemap.xml and /robots.txt are generated for every site. Entries with include_in_sitemap: false are left out of the sitemap.
  • /feed.xml, /feed.rss, /feed.json and /<feed_path>/feed.xml serve feeds. See Feeds.
  • /portal is the constituent member portal. See Constituents & CRM.
  • Form submissions post to /cms/<form handle>/submit. See Form For Tag.