Routing is the system that maps URL patterns to data and templates within Ghost. It comes pre-configured by default, but it can also be customized extensively to build powerful custom site structures.
content/settings/routes.yaml
- which you can edit directly, but you can also upload/download this file from within your Ghost admin panel under Settings » Labs
.
If you edit the file manually, you’ll need to restart Ghost to see the changes, but if you upload the file in admin then your routes will automatically be updated right away.
routes.yaml
which comes with all new installs of Ghost sets things up with a traditional publication structure. The homepage of the site is a reverse-chronological list of the site’s posts, with each post living on its own URL defined by a {slug}
parameter, such as my-great-post
. There are also additional archives of posts sorted by tag and author.
key:value
pairs, commonly used for simple/readable configuration.
The most important thing to know when working with YAML is that it uses indentation to denote structure. That means the only type of nesting which works is 2 spaces.
The most common reason for YAML files not working is when you accidentally use the wrong type or quantity of spacing for indentation. So keep a close eye on that!
site.com/writing/
. Maybe you actually want to split your site into two main collections of content, like /blog/
and /podcast/
. Maybe you just want to change the URL of your archives from /tag/news/
to /archive/news/
.
If you’re looking to create an alternative site structure to the one described above, then dynamic routing is what you need in order to achieve all your hopes and dreams.
Okay, maybe not all your hopes and dreams but at least your URLs. We’ll start there.
Hopes and dreams come later.
/custom/
load custom.hbs
Using template routes is very minimal. There’s no default data associated with them, so there isn’t any content automatically loaded in from Ghost like there is with posts and pages. Instead, you can write all the custom code you like into a specific file, and then have that file load on the route of your choice.
Custom routes are handy for creating static pages outside of Ghost Admin, when you don’t want them to be editable, they use lots of custom code, or you need to create a specific custom URL with more than a basic slug.
Don’t worry, we’ll go through some examples of all of the above!
routes
, and this is where custom routes can be defined.
Let’s say you’ve got a big Features landing page with all sorts of animations and custom HTML. Rather than trying to cram all the code into the Ghost editor and hope for the best, you can instead store the code in a custom template called features.hbs
- and then point a custom route at it:
site.com/features/
- the second is the template which will be used: features.hbs
- you leave off the .hbs
because Ghost takes care of that part. Now you’ve created a new static page in Ghost, without using the admin!
You can also use custom routes to simulate subdirectories. For example, if you want a “Team” page to appear, for navigational purposes, as if it’s a subpage of your “About” page.
site.com/about/team/
is a dedicated URL for a static team.hbs
template within your theme. Routes can be just about anything you like using letters, numbers, slashes, hyphens, and underscores.
/about/team
route to point at a static team.hbs
template is that it’s… well: static.
Unlike the Features the team page needs to be updated fairly regularly with a list of team members; so it would be inconvenient to have to do that in code each time. What we really want is to keep the custom route, but have the page still use data stored in Ghost. This is where the data
property comes in.
team
to the new route, and it will also automatically redirect the original URL of the content to the new one.
Now, the data from site.com/team/
will be available inside the {{#page}}
block helper in a custom team.hbs
template on site.com/about/team/
and the old URL will redirect to the new one, to prevent the content being duplicated in two places.
content_type
property with a custom mime-type.
For example, you might want to build a custom RSS feed to get all posts tagged with podcast
and deliver them to iTunes. In fact, here’s a full tutorial for how to do that.
Or perhaps you’d like to build your own little public JSON API of breaking news, and provide it to other people to be able to consume your most important updates inside their websites and applications? That’s fine too, you’d just pass json
as the content_type
.
blog
and podcast
.
Collections serve two main purposes:
/
URL which defines the entire structure of the site.
site.com
will display all posts, using the index.hbs
template file, and render each post on a URL determined by the {slug}
created in the Ghost editor.
In short: This is exactly how and why Ghost works by default!
/
route is now pointing at a static template called home.hbs
— and the main collection has now been moved to load on site.com/blog/
. Each post URL is also prefixed with /blog/
.
blog
and podcast
.
site.com/blog/
site.com/blog/my-story/
primary_tag
of blog
site.com/podcast/
site.com/podcast/my-episode/
primary_tag
of podcast
primary_tag
property is simply the first tag that is entered in the tag list inside Ghost’s editor. It’s useful to filter against the primary tag because it will always be unique.
If posts match the filter property for multiple collections this can lead to problems with post rendering and collection pagination, so it’s important to try and always keep collection filters unique from one another.
portfolio
which lists all of your most recent work. But how do you set the title, description, and metadata for that collection index?
work.hbs
template will have access to all of the data (and metadata) from your work
tag. And don’t forget: site.com/tag/work/
will now also be redirected to site.com/portfolio/
— so no duplicate content!
site.com/de/
section for all posts in German, tagged with a private tag of #de
. Using Private tags means these tags wouldn’t be shown on the front end but can still be treated differently with Handlebars templating. The main collection excludes these same posts to avoid any overlap.
/tag/getting-started/
which will render a list of associated content.
Unlike collections, posts can appear in multiple taxonomies, and the post’s URL is not affected by which taxonomies are applied.
Taxonomies are structured like this:
Cameron
is tagged with News
then it will be included in archives appearing on:
site.com
– (The collection index)site.com/author/cameron
site.com/tag/news/
host
and topic
.
controller
property called channel
, and a filter to determine which posts to return.
iPhone
, iPad
or Mac
on a custom route of site.com/apple-news/
.
The second is a special Editor’s Column area, which will return any posts tagged with Column
, but only if they’re explicitly authored by Cameron
.
These are two small examples of how you can use channels to include and exclude groups of posts from appearing together on a custom paginated route, with full automatic RSS feeds included as standard. Just add /rss/
to any channel URL to get the feed.
/news/
and posts like /news/my-story/
news
AND featured
news
but NOT authored by steve
Property | Description |
---|---|
template | Determines which Handlebars template file will be used for this route. Defaults to index.hbs if not specified. |
permalink | The generated URL for any post within a collection. Can contain dynamic variables based on post data: • {id} - unique set of characters, eg. 5982d807bcf38100194efd67 • {slug} - the post slug, eg. my-post • {year} - publication year, eg. 2019 • {month} - publication month, eg. 04 • {day} - publication day, eg. 29 • {primary_tag} - slug of first tag listed in the post, eg. news • {primary_author} - slug of first author, eg. cameron |
filter | Extensively filter posts returned in collections and channels using the full power and syntax of the Ghost Content API For example author:cameron+tag:news will return all posts published by Cameron, tagged with ‘News’. Mix and match to suit. |
order | Choose any number of fields and sort orders for your content: • published_at desc - default, newest post first• published_at asc - chronological, oldest first• featured desc, published_at desc - featured posts, then normal posts, newest first |
data | Fetch & associate data from the Ghost API with a specified route. The source route of the data will be redirected to the new custom route. • post.slug - get data with => {{#post}} • page.slug - get data with => {{#page}} • tag.slug - get data with => {{#tag}} • author.slug - get data with => {{#author}} |
rss | Collections and channels come with automatically generated RSS feeds which can be disabled by setting the rss property to false |
content_type | Specify the mime-type for the current route, default: HTML |
controller | Add a custom controller to a route to perform additional functions. Currently the only supported value is channel |
redirects.yaml
file is located in content/data/redirects.yaml
and - like routes.yaml
- can also be downloaded/uploaded in the settings in Ghost Admin.
redirects.yaml
file in Ghost admin in the settings. This is the recommended method.
To replace the YAML file on the server, ensure it exists in content/data/redirects.yaml
and restart Ghost for your changes to take effect.
redirects.yaml
redirects.yaml
file:
routes.yaml
file. (However, you may still need to redirect existing content using redirects.yaml
)./about/
and a page in Ghost called about
then one of them is going to work, but not both. You’ll need to manage this manually.
Collections must be unique
If you have a collection filtering for posts tagged with camera
and another filtering for posts tagged with news
- then you will run into problems if a post is tagged with both camera
and news
. You should either trust your authors to use the correct tags, or base collections on properties that are always unique, like primary_tag
.
Trailing slashes are required
You probably noticed that all the examples here use trailing slashes on routes, which is because these are required for dynamic routing to function correctly.