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:
- Collection index pages: does the path match a collection's listing page, such as
/blogor/blog/page/2? - Content paths: is there an entry at exactly this path?
- Automatic redirects: did an entry used to live here? If so, the visitor gets a 301 to its new URL.
- Redirect rules that you have written. These are consulted only when nothing above matched.
- Otherwise, the site returns a 404 page rendered inside your
themelayout.
Unpublished entries return 404 even though they have a content path.
Routing patterns
Each collection has a routing pattern that can use these placeholders:
| Placeholder | Replaced with |
|---|---|
:handle | The entry's handle |
:year | 4-digit year of published_at, or of the creation date if published_at is empty |
:month | 2-digit month, from the same date |
:day | 2-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
| Routing | Entry | URL |
|---|---|---|
:handle | about | /about |
blog/:handle | my-post | /blog/my-post |
:year/:month/:handle | hello, published June 2025 | /2025/06/hello |
units/:unit/lessons/:handle | lesson-1, whose unit field references unit-1 | /units/unit-1/lessons/lesson-1 |
Rules to know
routingmust 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_aton 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.
| Setting | Values |
|---|---|
| From path | A path such as old-blog. Leading and trailing slashes are removed. |
| Match type | prefix (default) matches the path and everything below it. exact matches only that path. |
| Destination | A path on your site (/news) or a full https:// URL |
| Status | 302 (default), 301, 307 or 308 |
| Domain | Optional. 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.txtand thefeed.*files. - Manage rules under Content → Redirects or with the MCP
redirect_rules_save,redirect_rules_list,redirect_rules_getandredirect_rules_deletetools. 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 exampleblogindex_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:
routing: ":handle"root_routing: true- 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.xmland/robots.txtare generated for every site. Entries withinclude_in_sitemap: falseare left out of the sitemap./feed.xml,/feed.rss,/feed.jsonand/<feed_path>/feed.xmlserve feeds. See Feeds./portalis the constituent member portal. See Constituents & CRM.- Form submissions post to
/cms/<form handle>/submit. See Form For Tag.