Manual · 10

Dynamic pages (Catch-All)

You want a list under /blog and the article under /blog/mein-artikel – without creating a separate page for each article? That’s what Catch-All is for.

  1. Create the page /blog and check Catch-All under Settings.
  2. Create a field slug in the Collection blog (the URL part of each article).
  3. HTML of the page /blog:
{% if route.slug %}
  {% with post=item("blog", "slug", route.slug) %}
    {% if not post %}{{ not_found() }}{% endif %}
    <article>
      <h1>{{ post.titel }}</h1>
      <p class="datum">{{ post.datum }}</p>
      {{ image(post.bild, "(min-width: 900px) 60vw, 100vw") }}
      {{ post.text|markdown }}
      <a href="/blog">← Alle Artikel</a>
    </article>
  {% endwith %}
{% else %}
  <h1>Blog</h1>
  {% for post in data("blog", "sort", "-datum") %}
    <a class="teaser" href="/blog/{{ post.slug }}">
      <h2>{{ post.titel }}</h2>
      <p>{{ post.text|truncatechars:160 }}</p>
    </a>
  {% endfor %}
{% endif %}
  1. SEO title of the page:
{% if route.slug %}{{ item("blog", "slug", route.slug).titel }} – {% endif %}Blog

How it works:

  • /blog → route.slug is empty → list.
  • /blog/mein-artikel → there is no page mein-artikel → the Catch-All page responds with route.slug = "mein-artikel".
  • /blog/gibts-nicht → item() finds nothing → not_found() returns the 404 page with status 404.
  • Real subpages take precedence: If you create /blog/archiv as a normal page, that one is shown.
  • Multi-level paths: /blog/2025/rueckblick results in route.slug = "2025/rueckblick", route.rest.0 = "2025", route.rest.1 = "rueckblick".

For testing, the preview of Catch-All pages has the Subpath field. If the template calls not_found() (e.g. without a subpath), the preview shows a notice with existing subpaths instead of an error – from the sitemap source or the detected Collection.

Sitemap: If the HTML, the SEO title or the Head field of the Catch-All page contains item("<collection>", "<feld>", route.slug), IllusionCMS adds all published entries of this Collection to /sitemap.xml as detail pages (not drafts and not entries without a value in the field). Real subpages take precedence.

Detail pages from other sources: If the page reads its data differently – from IllusionDB, a REST source or via a Workflow –, you set the source under Settings of the Catch-All page at Detail pages for sitemap and static export:

  • IllusionDB table: connection, table, the column with the URL part (e.g. slug), optionally a column with the modification date (<lastmod>), and rules or a saved view as a filter – e.g. only aktiv = true.
  • Workflow: the HTTP input of a Workflow (as with workflow()) that returns a list – objects with the URL field or simply texts. It receives sitemap=1 in msg.payload. This way REST sources and computed lists also get into the sitemap.
  • Check shows how many detail pages result, and the first addresses. Values with / result in multi-level paths (berlin/zahnarzt → /leistungen/berlin/zahnarzt).
  • From IllusionDB, only the column with the URL part and the date are queried.
  • The list is cached for 10 minutes. If the source is ever unreachable, the last state stays in the sitemap instead of thousands of addresses disappearing.
  • From 50,000 addresses on, /sitemap.xml automatically becomes a sitemap index with /sitemap-1.xml, /sitemap-2.xml, … (the search engines’ limit per file).
  • The static export includes the same detail pages – even if nothing links to them.

Data from IllusionDB: Detail pages can also fetch their records directly from a database – via a Workflow with the IllusionDB node, see Workflows → IllusionDB.