Contensio logo

Template hierarchy

How Contensio picks which Blade template to render — most-specific-first resolution order for every page type, with examples.

Contensio resolves templates using a most-specific-first strategy: for each request it builds a list of candidate template names in order of specificity, then renders the first one that exists in the active theme. If none exist, it falls back to index.blade.php.

This mirrors how WordPress template resolution works — if you've ever created a single-product.php to override WooCommerce, the concept is identical.

The resolution is handled by Contensio\Support\ThemeTemplateResolver. It checks candidates via view()->exists("theme::{$template}"), so no route changes or controller modifications are needed — you just create the file.


Resolution tables

Homepage

Triggered by the homepage route. The resolver checks whether the reading setting is set to Latest posts or A static page and builds the candidate list accordingly.

Priority Template When used
1 front-page.blade.php Always checked first — custom landing page
2 page.blade.php Static page mode only
2 home.blade.php Latest posts mode only
3 index.blade.php Ultimate fallback
// Internal resolution (simplified)
ThemeTemplateResolver::home(isStaticPage: true);
// checks: front-page → page → index

ThemeTemplateResolver::home(isStaticPage: false);
// checks: front-page → home → index

Single content item (post, page, custom type)

Priority Template Example
1 single-{type}-{slug}.blade.php single-post-hello-world.blade.php
2 single-{type}.blade.php single-post.blade.php, single-product.blade.php
3 single.blade.php Generic single item
4 index.blade.php Fallback

Blog post — slug "hello-world":

single-post-hello-world.blade.php  ← most specific
single-post.blade.php
single.blade.php
index.blade.php

Custom type "recipe" — slug "carbonara":

single-recipe-carbonara.blade.php
single-recipe.blade.php
single.blade.php
index.blade.php

Note on post.blade.php: The old convention used post.blade.php for blog posts. That still works in the default theme as a kept file, but new themes should use single-post.blade.php — it follows the same naming pattern as all other content types.


Standalone page

Priority Template Example
1 page-{slug}.blade.php page-about.blade.php
2 page.blade.php
3 index.blade.php
page-about.blade.php   ← for the /about page specifically
page.blade.php
index.blade.php

Use page-{slug}.blade.php when a specific page needs a completely different layout (e.g., a full-screen landing contact page).


Content type archive

Priority Template Example
1 archive-{type}.blade.php archive-recipe.blade.php
2 archive.blade.php
3 index.blade.php

Taxonomy term archive

Priority Template Example
1 taxonomy-{slug}.blade.php taxonomy-genre.blade.php
2 category.blade.php Hierarchical taxonomies
2 tag.blade.php Flat (non-hierarchical) taxonomies
3 taxonomy.blade.php
4 archive.blade.php
5 index.blade.php

Contensio passes is_hierarchical from the Taxonomy model to the resolver, so category.blade.php is used for tree-structured taxonomies (departments, categories) and tag.blade.php for flat ones (tags, labels).

taxonomy-genre.blade.php    ← if taxonomy slug is "genre"
category.blade.php          ← hierarchical (is_hierarchical = true)
tag.blade.php               ← flat (is_hierarchical = false)
taxonomy.blade.php
archive.blade.php
index.blade.php

Author archive

Priority Template
1 author.blade.php
2 archive.blade.php
3 index.blade.php

Search results

Priority Template
1 search.blade.php
2 index.blade.php

404 Not found

Priority Template
1 404.blade.php
2 index.blade.php

Practical examples

Override just one post

You have a post with the slug annual-report-2025 that needs a completely different layout — full-width, no sidebar, print-friendly:

views/
  single-post-annual-report-2025.blade.php   ← only this post
  single-post.blade.php                       ← all other posts unchanged

No route changes, no controller modifications. Just create the file.


Add a custom content type archive

You've registered a recipe content type and want a grid layout instead of a list:

views/
  archive.blade.php              ← existing — list layout
  archive-recipe.blade.php       ← new — grid layout for recipes

Taxonomy-specific layout

You have a genre taxonomy for music reviews and want it to look different from your post tags:

views/
  tag.blade.php                  ← generic flat taxonomy layout
  taxonomy-genre.blade.php       ← genre-specific layout with a banner

Minimum viable theme

A minimal theme only needs layout.blade.php and index.blade.php. Everything else is optional — the resolver falls back gracefully:

views/
  layout.blade.php    required
  index.blade.php     required — renders $posts if available, otherwise site name

A theme that adds progressively more specificity:

views/
  layout.blade.php
  index.blade.php      ← fallback
  home.blade.php       ← blog homepage
  single-post.blade.php ← blog posts
  page.blade.php       ← static pages
  taxonomy.blade.php   ← any term archive

Differences from WordPress

Aspect WordPress Contensio
Resolution mechanism File scan at request time view()->exists() check via ThemeTemplateResolver
Post template file single-post.php single-post.blade.php
Page-by-slug page-{slug}.php page-{slug}.blade.php
Custom type single single-{type}.php single-{type}.blade.php
Custom type archive archive-{type}.php archive-{type}.blade.php
Taxonomy specific taxonomy-{slug}.php taxonomy-{slug}.blade.php
Hierarchical taxonomy category.php category.blade.php
Flat taxonomy tag.php tag.blade.php
Static homepage front-page.php front-page.blade.php
Blog homepage home.php home.blade.php
Ultimate fallback index.php index.blade.php

The hierarchy is intentionally similar to WordPress so migrating theme logic is straightforward. The main differences are .blade.php extension, the theme:: namespace, and Blade syntax (@extends, @section, @yield) instead of WP template parts.


See also