Skip to content

Ghost Theme Customization — Navigation, Custom Routes and Dynamic Routing

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you'll learn advanced Ghost theme customization — configuring navigation menus, creating custom routes with routes.yaml, setting up dynamic routing for custom collections, and extending your theme with custom templates.

What You'll Learn

  • Configuring primary and secondary navigation in Ghost
  • The routes.yaml file structure and syntax
  • Creating custom routes for content collections
  • Dynamic routing with channels and templates
  • Regular expressions in custom routes
  • Custom collections with filters and pagination
  • Custom page templates and template inheritance
  • Redirects and URL management with routes.yaml
  • Best practices for navigation and URL structure

Why It Matters

By default, Ghost shows all posts in reverse chronological order. Custom routes let you create entirely different site structures — a featured section at a specific URL, a paginated tutorial collection, or a custom landing page with its own template. If you want your site to look and feel unique, custom routes are the key. They give you complete control over your site's URL structure and content organization.

Real-World Use

A magazine site needs three content sections: "News" (daily articles), "Features" (long-form pieces), and "Podcast" (audio episodes). Each section needs its own URL (/news/, /features/, /podcast/), its own template with different styling, and separate RSS feeds. The developer uses routes.yaml to define three custom channels, each filtered by a specific tag, with its own template and pagination settings.

Learning Path

flowchart LR
  A["Custom Themes"] --> B["Theme Customization
You are here"]:::current B --> C["Membership System"] C --> D["Content Gating"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

Ghost has two navigation areas: primary and secondary.

Primary Navigation

Primary navigation appears in the main site header. Configure it in Settings > Navigation.

# Example primary navigation
- label: Home
  url: /
- label: Blog
  url: /blog/
- label: About
  url: /about/
- label: Contact
  url: /contact/

Secondary Navigation

Secondary navigation typically appears in the footer. Same configuration interface.

# Example secondary navigation
- label: Privacy Policy
  url: /privacy/
- label: Terms
  url: /terms/
- label: RSS
  url: /rss/
<!-- Primary navigation -->
<nav class="primary-nav">
  {{navigation}}
</nav>

<!-- Secondary navigation -->
<nav class="secondary-nav">
  {{navigation type="secondary"}}
</nav>
<!-- With custom class -->
<nav>{{navigation class="main-menu"}}</nav>

<!-- Current page highlighting -->
{{#foreach navigation}}
  <a href="{{url}}" class="{{#if current}}active{{/if}}">{{label}}</a>
{{/foreach}}

The current property is true when the link's URL matches the current page.

The routes.yaml File

routes.yaml is a configuration file in content/settings/ that defines custom routing rules for your Ghost site.

Location

content/settings/routes.yaml

Basic Structure

routes:
  /my-custom-url/:
    template: custom-template
    controller: channel
    filter: tags:[my-tag]
    limit: 10
    data: page.about

collections:
  /my-collection/:
    permalink: /my-collection/{slug}/
    template: my-collection
    filter: tags:[my-tag]
    limit: 10

taxonomies:
  tag: /topic/{slug}/
  author: /writer/{slug}/

Routes vs Collections vs Taxonomies

Section Purpose
routes Single URL → single template
collections URL + paginated content listing
taxonomies Override default tag/author URL patterns

Custom Routes

Routes map a single URL to a template.

routes:
  /about/:
    template: page-about
    data: page.about

  /contact/:
    template: page-contact
    data: page.contact

  /featured/:
    template: featured
    controller: channel
    filter: featured:true
    limit: 10

Route Options

Option Description
template The Handlebars template to use (without .hbs extension)
controller channel for content listings, omit for static routes
filter Ghost filter syntax to select content
limit Items per page (for channel controllers)
data Fetch a specific page/post by slug (e.g., page.about)
order Sort order for content (e.g., published_at desc)

Static Route Example

# /about/ renders page-about.hbs with content from the "about" page
routes:
  /about/:
    template: page-about
    data: page.about

This lets you create a page in the admin called "About" with slug "about," and it renders using your custom page-about.hbs template at /about/.

Channel Route Example

# /featured/ shows featured posts
routes:
  /featured/:
    template: featured
    controller: channel
    filter: featured:true
    limit: 12

This creates a paginated listing of all featured posts at /featured/, using featured.hbs.

Custom Collections

Collections are like channels but with permalink patterns.

collections:
  /news/:
    permalink: /news/{slug}/
    template: news
    filter: tags:[news]
    limit: 10

  /tutorials/:
    permalink: /tutorials/{slug}/
    template: tutorials
    filter: tags:[tutorial]
    limit: 6

Collection Options

Option Description
permalink URL pattern for individual posts in this collection
template Template for the collection listing
filter Content filter
limit Posts per page
order Sort order

Use {slug} in permalinks to include the post slug:

# Post URL: /tutorials/getting-started/
permalink: /tutorials/{slug}/

You can also include the date:

# Post URL: /news/2024/01/my-post/
permalink: /news/{year}/{month}/{slug}/

Custom Taxonomies

Override the default tag and author URL patterns:

taxonomies:
  tag: /topic/{slug}/
  author: /writer/{slug}/

This changes:

  • Tag page URLs from /tag/<a href="/programming-languages/javascript/">JavaScript</a>/ to /topic/javascript/
  • Author page URLs from /author/jane/ to /writer/jane/

RSS Feeds for Custom Collections

Custom collections automatically get their own RSS feeds:

/news/rss/
/tutorials/rss/
/featured/rss/

Redirects in routes.yaml

You can define redirects for moved content:

redirects:
  /old-url/: /new-url/
  /old-category/: /new-category/
  /legacy-post/: /blog/legacy-post/

Or use a separate redirects.yaml file at content/settings/redirects.yaml:

301:
  /old-article/: /new-article/
  /category/*: /blog/:splat/
302:
  /temporary-link/: /new-location/

Advanced Routing Examples

Multi-Section Magazine

routes:
  /featured/:
    template: featured
    controller: channel
    filter: featured:true
    limit: 10

collections:
  /news/:
    permalink: /news/{slug}/
    template: index
    filter: tags:[news]
    limit: 10
  /opinion/:
    permalink: /opinion/{slug}/
    template: index
    filter: tags:[opinion]
    limit: 10

taxonomies:
  tag: /topic/{slug}/

Podcast Site

collections:
  /episodes/:
    permalink: /episodes/{slug}/
    template: podcast
    filter: tags:[podcast]
    limit: 12

routes:
  /about/:
    template: page-about
    data: page.about

Documentation Site

collections:
  /docs/:
    permalink: /docs/{slug}/
    template: docs
    filter: tags:[documentation]
    limit: 50

taxonomies:
  tag: /docs/topic/{slug}/

Template File Locations

Ghost looks for template files in this order:

  1. The specified template name (e.g., featured.hbs)
  2. If not found, index.hbs
  3. If not found, Ghost shows an error

Place custom templates in your theme root alongside the standard templates.

Reloading routes.yaml

After editing routes.yaml, restart Ghost:

ghost restart

Or use the Ghost CLI to reload routes without a full restart:

ghost reload

Common Mistakes

  1. Forgetting to restart Ghost after editing routes.yaml: Changes to routes.yaml do not take effect until Ghost is restarted. Always run ghost restart or ghost reload after editing.

  2. Using absolute URLs in navigation instead of relative paths: Use /about/ not https://mysite.com/about/. Relative paths work regardless of domain changes and are required for subdirectory installations.

  3. Creating circular routes: If you create a route that matches all posts (/blog/) and also define a collection for the same content, you can create infinite loops. Keep routes and collections distinct.

  4. Not testing custom routes before going live: A typo in routes.yaml can break your entire site structure. Test on a staging site before applying to production.

  5. Overriding default taxonomies without updating templates: If you change /tag/ to /topic/ in taxonomies, update your theme's tag links accordingly. Old hardcoded /tag/ links will 404.

Practice Questions

  1. What is the difference between routes and collections in routes.yaml? Answer: routes map a single URL to a template (e.g., /about/). collections map a URL prefix to a paginated content listing with individual post URLs (e.g., /news/ with posts at /news/{slug}/).

  2. How do you create a custom RSS feed for a specific content section? Answer: Define a custom collection or channel in routes.yaml. Ghost automatically generates an RSS feed for each collection at the collection URL + /rss/ (e.g., /news/rss/).

  3. What happens if the template specified in routes.yaml does not exist? Answer: Ghost falls back to index.hbs for that route. If index.hbs also does not exist, Ghost returns an error. Always create the template files that your custom routes reference.

  4. Challenge: Design a routes.yaml for a multi-section publication. Create routes for: a featured posts channel at /picks/, a tutorials collection at /tutorials/ with posts at /tutorials/{slug}/, a custom about page using page.about data, and custom taxonomy URLs at /topic/{slug}/ instead of /tag/{slug}/.

FAQ

Can I have multiple routes.yaml files?

No. Ghost reads only content/settings/routes.yaml. All custom routes must be in that single file. Use YAML's merge features to organize complex configurations.

Does routes.yaml affect the Ghost admin?

No. Custom routes only affect the public site. The admin panel URL structure (/ghost/) is unchanged and always accessible at /ghost/.

Can I use regular expressions in routes.yaml?

No. Ghost does not support regex in routes.yaml. Use exact paths or static patterns. For complex URL matching, use a reverse proxy like Nginx.

How do I debug a custom route that is not working?

Check Ghost logs at content/logs/ for routing errors. Verify the template file exists in your theme. Ensure Ghost was restarted after editing routes.yaml. Test the URL directly in a browser.

Can I have a custom route that shows content from multiple tags?

Yes. Use the filter parameter with multiple tags: filter='tags:[tag1, tag2, tag3]'. This shows posts that have any of the specified tags.

Mini Project

Your task: Create a custom routes.yaml configuration for a magazine site.

  1. Define a custom route at /editors-picks/ showing featured posts with the template featured.hbs.
  2. Define a collection at /articles/ with posts at /articles/{slug}/, filtered by the "article" tag.
  3. Define a collection at /tutorials/ with posts at /tutorials/{slug}/, filtered by the "tutorial" tag.
  4. Override the tag taxonomy to use /topic/{slug}/.
  5. Create the corresponding template files (featured.hbs, with pagination).
  6. Create test posts with appropriate tags.
  7. Verify each URL works and shows the correct content.

This exercise gives you complete control over your site's URL structure and content organization.

What's Next

Now that you can customize themes, it is time to set up memberships:

Continue to Lesson 20: Membership System — Membership tiers, pricing, and Stripe integration setup.

Related lessons:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro