How to Create a Read-Next Section in Ghost
Posted on January 20th, 2025
Whether it’s getting lost in a book, binge-watching a show, or eagerly waiting for the next part — when something is great, waiting can be tough. Your readers feel the same way about your content, so why not help them out by suggesting what to read next?
In this guide, you’ll learn how to create and customize a read-next section for your Ghost theme. This feature can take different forms, and you’ll discover how to make four useful versions:
- A section that suggests the latest three posts
- A section that recommends the last three posts by the same author
- A section that shows the latest posts related by tag
- A section that suggests content based on member status
No matter which version you choose, a read-next section ensures your audience will always have something to dive into next!
Show the Latest Posts
The aim of a read-next section is to give your audience new content.
Now, let’s go through the code that creates this section, step by step:
<code> {{#get "posts" filter="id:-{{id}}" limit="3" as |more_posts|}} {{#if more_posts}} <aside class="read-more-wrap outer"> <div class="read-more inner"> {{#foreach more_posts}} {{> "post-card"}} {{/foreach}} </div> </aside> {{/if}} {{/get}} </code>
Fetch Data with the Get Helper
The code starts with the key part of creating a read-next section: the get helper.
<code> {{#get "posts" filter="id:-{{id}}" limit="3" as |more_posts|}} </code>
In the example above, the get helper retrieves posts (it can also get tags, authors, and tiers). By default, it pulls the most recently published posts.
However, this can lead to an issue. If the current post is newly published, it will show up in the read-next section. This isn’t ideal, as it means you’d be suggesting the same article as something to read next.
Avoid Duplicates with the Filter Attribute
The get helper’s filter attribute is useful here. The line filter="id:-{{id}}"
means to exclude the current post from the results. Here’s how it works:
<code> id is the property you want to filter. </code>
The colon (:) separates the property from the value you’re filtering for. For example, if you were filtering fruit, type:banana
would only show bananas.
The minus sign (-) negates the value. So, type:-bananas
would return fruits that are not bananas, like apples, grapes, and limes.
{{id}}
is the value you’re filtering. The curly braces indicate that this value is dynamic, meaning it will take the id of the current post.
In simple terms, when you use this filter with the limit attribute, it means: Get the three most recent posts that don’t include the current post.
The last part of the get helper, written as |more_posts|
, is a block parameter that gives a name to the collection of posts returned, making it easier to work with and understand.
Conditional Rendering with the If Helper
Next in Casper’s read-next section is this line of code:
<code> {{#if more_posts}} </code>
The block parameter we talked about earlier is important here. The code after the if helper will only run if the condition is true, meaning if there are more posts to show.
Using conditional rendering helps prevent your page from displaying empty sections. Without the if helper, the HTML below would still show up even if there’s no content, which could mess up your site’s layout.
<code> <aside class="read-more-wrap outer"> <div class="read-more inner"> </div> </aside> </code>
Looping with Foreach
The last part of Casper’s read-next section displays the actual post content.
<code> {{#foreach more_posts}} {{> "post-card"}} {{/foreach}} </code>
The foreach helper goes through the three posts in the more_posts collection. Then, each post is shown using the post-card partial. (A partial is a small template that allows you to reuse code throughout your Ghost theme.)
All this code works together in the read-next section at the end of every post, helping your audience easily find something else to read!
Show the Latest Posts by the Same Author
Building on what you’ve just learned, let’s customize Casper’s read-next section to include extra content. This time, we’ll only show recent posts by the same author.
In the previous read-next section, you might have noticed that not all recent posts were by the same author.
Now, update the filter attribute so it only fetches posts from that author.
<code> {{#get "posts" filter="id:-{{id}}+authors:{{primary_author.slug}}" limit="3" as |more_posts|}} {{#if more_posts}} <aside class="read-more-wrap outer"> <div class="read-more inner"> {{#foreach more_posts}} {{> "post-card"}} {{/foreach}} </div> </aside> {{/if}} {{/get}} </code>
Just like before, the current post is left out of the results to prevent showing duplicate content. The part authors:{{primary_author.slug}}
tells Ghost to only include posts by the same author as the current one.
When you refresh the page, you’ll see that posts from different authors are no longer suggested in the read-next section. Instead, another post by the same author, titled “Selling Premium Memberships with Recurring Revenue,” is displayed.
Show Related Posts
The read-next section in Ghost’s Headline theme works differently than Casper’s. Instead of pulling the most recent posts, Headline gets posts that share the same tags as the current one. For example, if a post has technology and business tags, it will suggest posts that have either the business or technology tag, or both. This way, the posts are likely to be related to the topic of the current post, which can be more interesting for your readers.
Now, let’s take a look at the code used by Headline:
<code> {{#get "posts" include="authors" filter="tags:[{{post.tags}}]+id:-{{post.id}}" limit="3" as |next|}} {{#if next}} <div class="gh-read-next gh-canvas"> <section class="gh-pagehead"> <h4 class="gh-pagehead-title">Read next</h4> </section> <div class="gh-topic gh-topic-grid"> <div class="gh-topic-content"> {{#foreach next}} {{> "loop-grid" has_large_post=false}} {{/foreach}} </div> </div> </div> {{/if}} {{/get}} </code>
This code block is quite similar to the one from Casper. It uses the same basic structure, with the get, if, and foreach helpers. However, it also adds the include attribute. By default, the get helper doesn’t return tag or author information to improve performance. If you want that data included, you need to use the include attribute (for example, include=”authors, tags”).
The filter attribute here is also different from Casper’s:
<code> filter="tags:[{{post.tags}}]+id:-{{post.id}}" </code>
This time, the filter works with the tags property. It brings in all the tags from the current post using {{post.tags}}
. Since this is outside of the post context — not between the {{#post}} tags in the template — you need to add “post” before the tags property: post.tags
.
In simple words, the filter looks for the three most recent posts that share any tags with the current post. Here’s a table that compares the important data for the current post with the posts that the get helper returns.
As before, make sure to exclude the current post to avoid duplicates by using id:-{{post.id}}
. The plus sign (+) is used to combine multiple filters. The rest of Headline’s related-posts section follows the same method as Casper’s.
This strategy for related posts helps keep readers interested by showing them content on topics they already like. The next strategy will build on this by suggesting different posts based on member status.
Show Related Posts Based on Member Status
In this last example, let’s change the read-next section to suggest a paid post when the reader isn’t a member. The aim is to encourage them to become a paying subscriber. This code is a bit longer than before, so let’s split it into two parts. Here’s the first part of the code:
<code> {{#if @member.paid}} {{#get "posts" include="authors" filter="tags:[{{post.tags}}]+id:-{{post.id}}" limit="3" as |next|}} {{#if next}} <div class="gh-read-next gh-canvas"> <section class="gh-pagehead"> <h4 class="gh-pagehead-title">Read next</h4> </section> <div class="gh-topic gh-topic-grid"> <div class="gh-topic-content"> {{#foreach next}} {{> "loop-grid" has_large_post=false}} {{/foreach}} </div> </div> </div> {{/if}} {{/get}} </code>
Start by checking if the current reader is a paying member using {{#if @member.paid}}
. If they are a paying member, display the read-next section with related posts, just like in the earlier example.
The {{else}}
tag shows what to do when the reader isn’t a paying member. Here’s the second part of the code:
<code> {{#get "posts" include="authors" filter="visibility:public+id:-{{post.id}}+tags:[{{post.tags}}]" limit="2" as |public|}} {{#get "posts" include="authors" filter="visibility:paid+id:-../post.id" limit="1" as |paid|}} <div class="gh-read-next gh-canvas"> <section class="gh-pagehead"> <h4 class="gh-pagehead-title">Read next</h4> </section> <div class="gh-topic gh-topic-grid"> <div class="gh-topic-content"> {{#foreach paid}} {{> "loop-grid" has_large_post=false}} {{/foreach}} {{#foreach public}} {{> "loop-grid" has_large_post=false}} {{/foreach}} </div> </div> </div> {{/get}} {{/get}} </code>
This section starts with a variation of the get helper you’ve seen before, but now it includes a new filter property — visibility — which shows the post’s access level. In the first get helper, the property is set to “paid,” meaning that the posts returned are only available to paying members.
The next get helper pulls in two public posts by setting the visibility to “public.” Since the two get helpers filter by different visibility settings, you won’t end up showing duplicate content in your read-next section.
One thing to note about this second get helper: because it’s nested inside the first one, you need to go up a context (using ../) to access the current post’s id. For more information on navigating contexts in your Ghost theme, check out our debugging tutorial.
The HTML markup remains the same as before. The final difference is that there are two foreach loops to display the posts fetched by the two get helpers. {{#foreach paid}}
shows the paid post, and {{#foreach public}}
shows the two public posts.
Putting all these parts together, here’s how the final version of this read-next section works: If the reader is a paying member, the read-next section suggests related posts just like before. If the reader isn’t a paying member, the section first suggests a premium post to encourage them to become a subscriber. As in the earlier examples, the following two suggested posts are related to the current post.
Complete Code for the Read-Next Section
<code> {{#if @member.paid}} {{#get "posts" include="authors" filter="tags:[{{post.tags}}]+id:-{{post.id}}" limit="3" as |next|}} {{#if next}} <div class="gh-read-next gh-canvas"> <section class="gh-pagehead"> <h4 class="gh-pagehead-title">Read next</h4> </section> <div class="gh-topic gh-topic-grid"> <div class="gh-topic-content"> {{#foreach next}} {{> "loop-grid" has_large_post=false}} {{/foreach}} </div> </div> </div> {{/if}} {{/get}} {{else}} {{#get "posts" include="authors" filter="visibility:public+id:-{{post.id}}+tags:[{{post.tags}}]" limit="2" as |public|}} {{#get "posts" include="authors" filter="visibility:paid+id:-../post.id" limit="1" as |paid|}} <div class="gh-read-next gh-canvas"> <section class="gh-pagehead"> <h4 class="gh-pagehead-title">Read next</h4> </section> <div class="gh-topic gh-topic-grid"> <div class="gh-topic-content"> {{#foreach paid}} {{> "loop-grid" has_large_post=false}} {{/foreach}} {{#foreach public}} {{> "loop-grid" has_large_post=false}} {{/foreach}} </div> </div> </div> {{/get}} {{/get}} {{/if}} </code>
Conclusion
Starting with a read-next section that suggests the most recent posts and moving to one that customizes those posts based on the reader’s membership, you’ve explored four different ways to create this section to keep your readers interested.
The examples in this tutorial are some of the most common methods for building a read-next section in Ghost. However, thanks to the flexibility of the get helper and its filter attribute, there are many more possibilities.
Feel free to share how you created your read-next section or ask for help from the community on the official Ghost Forum. If you enjoyed this tutorial and want to find more to read, check out our “Keep on Learning” section below or subscribe to the Build with Ghost newsletter.