Dynamic Routing

Define custom URLs, post collections, and feeds

Beta Feature

Dynamic Routing is currently in beta. This means there may be missing functionality and known issues. There are many different use-cases for dynamic routing, so if you notice anything odd or have questions please let us know on our forum.

Dynamic Routing is a flexible configuration layer which allows you to control the structure of your site in almost any way you want by mapping content and templates to URL patterns.

This configuration layer unlocks many custom publication options. The top 3 use cases are:

  1. Custom home pages
  2. Rendering a taxonomy at a non-standard URL, e.g. /category/recipes/ instead of /tag/recipes/
  3. Creating distinct collections of content, e. g. /blog/ and /recipes/

Routing Configurations

The routes configuration file is a YAML file and needs to be located in content/settings/routes.yaml.

The routes.yaml file divided into three sections: routes, collections, and taxonomies. The default file looks like this:

routes:

collections:
  /:
    permalink: /{slug}/
    template:
      - index

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

Strict indentation

Important: YAML files use whitespace indentation to denote structure. Having mismatched indentation is the most common reason for a YAML file not being valid. Indenting with tabs is never allowed 😉

Trailing slashes

When specifying any URL, whether that's an index URL or a permalink, the trailing slash is required. So, /about/: is valid but /about: is not. If the trailing slash is missed you'll get an error when uploading the routes.yaml file in the admin. If you've modified the file directly on the server then Ghost will refuse to start.

Routes

A route is a single URL that is assigned a specific template and/or set of data. Routes allow you to create custom content and templates such as a home page, an about page or a custom RSS feed.

Structure:

routes:
  /custom-slug/: # index URL
    controller:   # type of route, 'channel' is the only supported value (optional)
    filter:          # a filter string (optional)
    data:         # a data object (optional)
    rss:          # true/false (optional, default: true)
    content_type: # content type, eg 'rss' for RSS (optional, default: html)

Single route template configuation

Single routes are mapped to a template, for example:

routes:
  /: home
  /about/careers/: about-careers
  /about/team/: about-team

The example above would set up three static URLs that will render their specified templates, if that template is not found then the fallback template will be used instead.

When rendering a route that is not / there is no implicit context but you can use the data attribute on the route or {{get}} helper in the route's template to fetch any dynamic content that you want to render.

Collections

A collection describes a group of posts that match a filter. A collection will alter the URL for all posts that match it's filter. Each collection has an index URL where all posts in the collection will be listed (and paginated where necessary) plus a permalink URL that is used for all posts within that collection.

It's possible to have a single collection that contains all of your posts (like the default collection) or you can use multiple collections to split your site into distinct areas such as /blog/ and /podcast/.

The order that collections are listed in routes.yaml is very important! Posts can match the filters of multiple collections, when that happens the first collection with a match will take ownership of that post so it won't appear under any other collections.

Structure:

collections:
  /blog/: # index URL
    permalink: # post URL (required)
    rss:       # true/false (optional, default: true)
    filter:    # a filter string (optional)
    data:      # a data object (optional)
    template:  # template or list of templates to use (optional)

Taxonomies

Taxonomies are used to group posts based on a common relation, e.g. the same author or the same tag. Unlike a collection, a taxonomy will not alter the URL of posts and doesn't offer any filtering ability beyond the direct relationship between an author or tag and it's posts.

routes.yaml structure:

taxonomies:
  type: /url/{slug}/

Where type is one of tag or author and /url/{slug}/ is the permalink for that taxonomy. {slug} represents the URL value of the author or tag and it must be present in the taxonomy configuration.

As an example if you had a podcast focused publication you could use taxonomies to adjust the URLs to be more appropriate:

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

Such a setup would give you the following URLs:

If the the tag or author taxonomies are not present in routes.yaml then the URLs won't exist, the default author and tag URLs will return 404s.

Properties

You can use properties in the routes.yaml file to build numerous flexible routing configurations.

controller (channels)

The controller property has one supported value: channel. A channel is a paginated index of posts that match a specified filter, such as all posts tagged with "news" and "fashion".

This means you are able to create subsets and supersets by combining or dividing existing content. Unlike Collections, Channels do not have any effect on URLs, but they do allow for creating a view of content along with pagination that is not possible to do with the {{get}} helper alone.

routes:
  /rumours/mobile/:
    controller: channel
    filter: tag:[iphone,ipad]
  /rumours/desktop/:
    controller: channel
    filter: tag:[imac,mac-pro]

You need to use filter in combination with the controller: channel configuration to select a specific list of posts for display using the {{posts}} template variable. See "Using the filter property to collect posts".

filter

filter is used to select a specific group of posts that belong to a Collection or Channel. In a Collection it's important to remember that a post will live in the first collection where it matches the filter so it's best to keep more generic filters at the bottom of the filters list.

Beta requirement: Specify inverse filters!
In the current beta version of Dynamic Routing it's necessary to specify the inverse of filters in earlier collections so that pagination is correct.

collections:
  /es/:
    permalink: /es/{slug}/
    filter: tag:es
  /fr/:
    permalink: /fr/{slug}/
    filter: tag:fr
  /:
    permalink: /{slug}/
    filter: tag:-[fr,es]

Using the filter property to collect posts

The filter property follows the same syntax as the API's filter parameter and is used to restrict which posts appear in your Channel or Collection.

Some examples for filtering posts might be:

filter: tag:x+tag:y # must have both "x" and "y" tags (+ = AND)
filter: tag:x,tag:y # can have either "x" or "y" tags (, = OR)
filter: tag:-x+tag:y # must have tag "y" but not tag "x" (- = NOT)
filter: tag:[x,y,z] # must have either "x", "y", or "z" tags ([] = IN)
filter: tag:-[x,y,z] # must not have any one of "x", "y", or "z" tags (-[] = NOT IN)
filter: author:steve+tag:x # must be written by "steve" and have tag "x"

data

The data property is used to fetch posts or tags in addition to the collection's posts list for use in the collection's template.

Using the data property to fetch resources

The data property is used to easily fetch arbitrary posts/pages/tags/authors using Ghost's API. This data is then made available in the handlebars template under specified variable names avoiding the need to use {{get}} everywhere.

It's also used to associate resources with particular URLs so that accessing a resource from a different URL will automatically redirect, avoiding duplicate content and confusing site structures.

Shorthand structure:

routes:
  /:
    data: resource-type.slug

or

collections:
  /:
    permalink: /games/{slug}/
    data:
      data-name: resource-type.slug
      data-name: resource-type.slug

Where resource-type is one of tag, author, post, or page - this will also set the variable name in the template, e.g. {{tag}}, {{post}} and so on. The slug part should match the URL field of the resource that you want to make available in the rendered template.

The second stucture example shows how you can fetch multiple resources for use within the handlebars template. In this case data-name can be whatever you want to call the variable in the template. See the example below for a demonstration...

Shorthand example:

routes:
  /about/careers/:
    data: post.careers
    template: about-careers

collections:
  /games/:
    permalink: /games/{slug}/
    filter: 'tag:games'
    data:
      header: page.games
      curator: author.bob

  /:
    permalink: /{slug}/
    filter: 'tag:-games'
    template: index

When accessing https://example.com/about/careers/ the about-careers.hbs template will be rendered and a {{post}} variable will be available, containing the page that has the slug careers so that you can render it's content.

In the games.hbs template used for the /games/ collection two variables - {{header}} and {{curator}} - will be available in addition to the default {{posts}} variable. With that setup you could pull in dynamic content from the "games" page to display above the list of posts and also use bob's author details to show some information about the collection's curator.

It's important to note the data property also affects URLs unless the redirect: false attribute is present (see longform stucture below). When a resource is accessed from a URL that isn't the first route with a matching data query it will automatically redirect to the first route with a match. Continuing the above example the careers post has the following behaviour:

Longform structure:

data:
  name: # variable name in the template - {{name}}
    resource:   # tags/users/posts (required)
    type:       # read/browse (required)
    limit:      # number of resources to fetch (optional)
    order:      # how to order the fetched resources (optional)
    include:    # relationships to include when fetching (optional)
    filter:     # API filter param to select specific resources (optional)
    status:     # draft/scheduled/published - posts specific (optional)
    visibility: # visible/hidden - tags specific (optional)
    slug:       # URL value of a single resource to select (optional)
    redirect:   # true/false (optional, default: true)

rss

The rss property can disable the generation of RSS feeds for a collection, it can be set to either true or false and defaults to true if it's not specified.

When set to true the collection will have rss URLs that are are also linked inside the <head> element. For example, a /blog/ collection would have the following URLs unless it had rss: false set:

https://example.com/blog/rss/ -> default RSS generator (no template)
https://example.com/blog/rss/2/ -> default RSS generator (no template)

content_type

You can use this to specify a route returns something other than HTML. For example to create a custom RSS feed:

routes:
   /podcast/rss/:
    template: podcast/rss
    content_type: rss

content_type can be a short version that matches a typical file extension for a mime-type (e.g. rss) or it can be set to the full mime-type (e.g. application/rss+xml).

index URL

The index URL property (or collection "key") will determine where the archive pages for the collection appear. For example, if your publications url config is equal to https://example.com/ and you have the following collection config:

collections:
  /blog/:
    permalink: /blog/{slug}/

Then the archive URLs for the collection would look like this:

The trailing slash is required, so /blog/: is valid but /blog: is not. If the trailing slash is missed you'll get an error when uploading the routes.yaml file in the admin, or if you've modified the file directly on the server Ghost will refuse to start.

home.hbs for root collections

If you have a root collection (/:) then it will render home.hbs for the first page if the template is available. For example:

collections:
  /:
    permalink: /posts/{slug}/

Would generate the following URLs and template fallbacks:

This behaviour is deprecated. It's available for backwards compatibility with Ghost 1.x, the recommended approach is to set the template explicitly, eg:

collections:
  /:
    permalink: /posts/{slug}/
    template: home

permalink

The permalink property determines the URL for any post that is matched by the collection's filter. Following the example given in "index URL" above, posts would have urls similar to this:

Available permalink variables:

  • {id} - unique series of characters, e.g. "5982d807bcf38100194efd67"
  • {slug} - value set in the URL field in post settings menu
  • {year} - 4-digit representation of publish date, e.g. "2018"
  • {month} - 2-digit representation of publish date, e.g. "08"
  • {day} - 2-digit representation of publish date, e.g. "14"
  • {primary_tag} - slug of the first tag in the post's tags list
  • {primary_author} - slug of the first author in the post's author list

template

The template property is optional but if specified can be a single value or an array. When not specified it will default to the name of the collection or home for the / route. The specified template(s) will be added to the beginning of the template lookup queue, with the first template in the queue that exists in theme being be used to render the collection.

collections:
  /podcast/:
    permalink: /podcast/{slug}/
  /vlog/:
    permalink: /vlog/{slug}/
    template: archives/video
  /:
    permalink: /{slug}/

Here we can see the following URLs generated and the associated template lookups:

Dynamic Routing

Define custom URLs, post collections, and feeds