Structuring Custom URLs in Ghost with routes.yaml
Learn how to use routes.yaml in Ghost 5.x to create custom URL structures, collections, and taxonomies without modifying core code.
09 Aug 2025, 13:55 UTC

The Problem: Rigid URL Structures
By default, Ghost uses a predictable URL pattern for posts and tags. However, when building a specialized site—like a recipe directory or a documentation hub—you often need specific URL paths like /cuisine/italian/ or /guides/setup/. Hard-coding these into a theme or relying on simple redirects is fragile and difficult to scale as your content grows.
The Solution: Declarative Routing
Ghost 5.x provides a routes.yaml file that allows you to define custom URL patterns, collections, and taxonomies without modifying the core application code. This file acts as a routing table, mapping specific URL paths to filtered sets of content and assigning them to specific Handlebars templates.
Collections and Taxonomies
In routes.yaml, you primarily work with two concepts:
- Collections: Groups of posts filtered by a specific attribute (e.g., a tag). These are useful for creating distinct sections of a site, like a "News" section versus a "Tutorials" section.
- Taxonomies: Dynamic archives based on a term (the
{slug}). These are ideal for categories where you have many terms, such as cuisines, authors, or product categories.
Template Resolution Logic
When a request matches a route, Ghost looks for a template to render in this specific order of priority:
- The explicit
templatedefined in theroutes.yamlentry. - The default template for that type (e.g.,
index.hbsfor collections,tag.hbsfor taxonomies). - A custom file named
custom-route-name.hbs. - The global
index.hbsfallback.
Worked Example: A Recipe Directory
Suppose you want a site where all "Recipe" posts are grouped under /recipes/, and you want a dynamic archive for different cuisines under /cuisine/.
Edit your content/settings/routes.yaml file as follows:
collections:
/recipes/:
filter: 'tag:recipe'
template: recipes-index
taxonomies:
/cuisine/{slug}/:
template: cuisine-archive
How this works in practice:
- A request to
/recipes/triggers the collection filter. Ghost fetches all posts taggedrecipeand renders them usingrecipes-index.hbs. - A request to
/cuisine/italian/triggers the taxonomy route. Ghost extractsitalianas the slug, filters posts by that tag, and renders them usingcuisine-archive.hbs. The template receives atagobject containing the term's metadata.
Trade-offs and Technical Limitations
While powerful, routes.yaml has specific constraints:
- Process Coupling: Changes to the YAML file require a
ghost reloador a full restart to take effect. In production environments using MySQL, a restart is often necessary to clear the internal cache. - Sitemap Exclusion: Custom routes defined here are not automatically added to the generated
sitemap.xml. You must manually update your sitemap or use a custom integration to ensure search engines discover these paths. - API Behavior: The Content API does not automatically mirror these custom routes. If you are building a headless frontend, you must replicate the filtering logic in your API calls (e.g.,
GET /ghost/api/content/posts/?filter=tag:recipe).
Verification and Deployment
To implement and verify these changes, follow these steps:
- Validate Syntax: YAML is sensitive to indentation. Use a YAML linter or
ghost doctorto ensure there are no syntax errors before deploying. - Apply Changes: Run
ghost reloadfrom the command line in your Ghost installation directory. - Verify Response: Visit the new URL in a browser. Use browser developer tools to confirm a
200 OKstatus. - Debug Context: If the wrong content is appearing, add
{{log pagination}}to your Handlebars template and runghost log -fto see exactly which posts are being passed to the view.
Rollback: If the site returns 404s or 500s after a reload, revert routes.yaml to its previous state and run ghost reload again.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.