How to Enable Preview for Member-Only Content in Ghost
Automatically add preview / teaser content for Member-Only posts in Ghost.
Wanna see the result first? Check out the demo here 👇
Ghost recently added memberships to all themes through Portal. With this, you can set some posts to be accessible by Members or Paid Members only and monetize your writing. For people who haven't subscribed yet, by default Ghost will not show any member-only content. On member-only posts Ghost will only show a call-to-action (CTA) box at the bottom.
This is a sub-optimal user experience and a lost opportunity to convince readers why these member-only posts are valuable enough for them to subscribe. I recently came across Rediverge (the blog of the founder of Ghost, John, O'Nolan) and realized it is feasible to add a preview (see the image below). In this case, the "preview" is a piece static text (i.e. the same for all posts) mostly as a sales pitch to get more people to sign up. Here what John wrote
This is probably one of the best posts I've ever written, so it's limited to members-only access. In 2019, after over a decade of writing a blog, I decided to set up Rediverge as place where I can write more openly and freely for a smaller, more specific audience....
After some tinkering and learning how Ghost themes work, I was able to achieve a similar effect, see the image below.
Moreover, it is actually possible to extend this a bit and make an actual preview of the actual content. Here is the demo of the final result if you want to take a peek. In this post, I'll show you how to do this.✌️
Part 1: Static "Preview"
This turned out to be really simple as long as you are willing to modify the theme. If you are not familiar with editing Ghost theme, check this out
Note: I'm using Capser, but the following should be pretty similar if you are using other themes.
First, go to post.hbs
and add a conditional block {{#unless access}} ...{{/unless}}
before {{content}}
. This checks if the reader does not have access to the post, it would insert the <p>
paragraphs which is the static preview text.
<div class="post-content">
{{#unless access}}
<div class='membersonly-excerpt'>
<p>
This is probably one of the best posts I've ever written, so it's limited to
members-only access.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
</p>
<p>
Urna et pharetra pharetra massa massa ultricies mi quis. Iaculis eu non diam
phasellus vestibulum lorem sed risus ultricies. Nullam ac tortor vitae purus.
</p>
</div>
{{/unless}}
{{content}}
</div>
(See here on GitHub)
Next, we can apply some styling similar to Rediverge to make the text fade vertically. To achieve this, add the following in to screen.css
and that's it! Now you just need to yarn zip
your theme (which will build the css into /built
) and deploy to your site.
/* membersonly excerpt */
.membersonly-excerpt {
position: relative;
}
.membersonly-excerpt:after {
content: "";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 70%;
background: linear-gradient(
to top,
rgba(255, 255, 255, 1) 0%,
rgba(255, 255, 255, 0.9) 30%,
rgba(255, 255, 255, 0) 100%
);
}
(See here on GitHub)
Part 2: Real Preview of Actual Content
What we have achieved so far is not bad - we at least have a bit of text to make a sales pitch and might be able to convince more people to sign up. But wouldn't it be better if we can show the reader what the actual post looks like so they can make a better decision on whether to subscribe?
To do this, we can
- put a bit of Javascript on the client side in the
post.hbs
, it will get a portion of the post somewhere (i.e. from a server), and render them in place of those static text - we build a small server app that serves that portion of the post, to achieve this, the web app need to be able to access member only / paid content
Let's look at the server side first. There is only one job the app needs to do, which is to return a portion of the post (identified by slug for example) upon request from the client.
In order to access the posts that are behind the membership access control, we need to use Ghost Admin API. So let's get the API key first. Open Ghost Admin, and go to Settings > Integrations > Add custom integration. Given the integration any name, and get the Admin API key from a the UI that looks like the following.
With this you can use your favorite language/framework to implement a service endpoint that use the Ghost Admin API to read a post (based on slug from the request), and returns a truncated version of it in the response.
Here is an example snippet in Javascript/Next that implements it:
const GhostAdminAPI = require('@tryghost/admin-api');
const api = new GhostAdminAPI({
url: process.env.GHOST_URL,
key: process.env.GHOST_ADMIN_KEY,
version: 'v3',
});
export default async (req, res) => {
const slug = req.query.slug;
if (slug) {
const length = TRUNCATION_LENGTH;
try {
const response = await api.posts.read({ slug: slug, formats: 'html' });
response.html = truncate(response.html, length);
res.statusCode = 200;
res.json({ response });
} catch (error) {
console.log(error);
res.statusCode = 500;
res.json({ error });
}
} else {
res.statusCode = 400;
res.json({ error: 'must provide slug in request query parameter' });
}
};
(Note this is not runnable code, you probably need to adjust it to your framework and at least deal with CORS).
With the server ready, on the client side, we can insert some Javascript into the handlebar templates. Since this is only relevant to posts, let's modify post.hbs
. We can add the following code block into the {{#contentFor "scripts"}
block.
var hasPreview = $(".membersonly-excerpt");
if (hasPreview) {
// strip trailing slash then split by slash
var pathArray = window.location.pathname.replace(/\/$/, "").split("/");
var slug = pathArray[pathArray.length - 1];
var domainUrl = "https://ghost-preview.dingran.me";
var previewEndpoint = `${domainUrl}/api/ghost?slug=${slug}`;
fetch(previewEndpoint)
.then((response) => response.json())
.then((data) => {
$(".membersonly-excerpt").html(data.response.html);
});
}
(See here on GitHub)
What this block of code does is that:
- it first gets the post
slug
from the page url - it then contacts the server (in my case, it is hosted at
https://ghost-preview.dingran.me/api/ghost
- it then uses the returned partial post html
data.response.html
to replace the content of thediv
marked by classmembersonly-excerpt
(it is safe to delete those static text or keep them as placeholder in case this fails)
And that's it 🎉, here is a live example with this preview enabled!
Too Much Work? Want a No-Code Solution? Join the Waiting List
I realized the above, especially the Real Preview might be a bit of hassle for many people. And even the Static "Preview" solution can be simplified into something that doesn't really need to require modifying the theme.
So conceivable, I can make something that makes things a lot easier for people who don't want to edit their theme:
- You input your Ghost Admin API key
- You get a one liner script, and paste it into Ghost UI (Code Injection)
- Done!
If you are interested in this, please join the waiting list below, so I can better gauge whether it's worth spending a bit more time and make this work for everyone. In the meantime, feel free to reach out if you have any questions/suggestions! 👋