Subscribing Ghost Members From Your JAMStack Site
In a previous post we walked through a basic blog setup using NextJS and Ghost as a headless CMS. In this post, we'll take things up a notch by adding support for Ghost Memberships, a new feature in v4.0.
Ghost Memberships
Memberships is a native feature to Ghost. It is essentially an email list that readers of your site can subscribe to. They can choose to subscribe as free or paid members, and the Ghost Dashboard displays analytics like Total Members, Email Open Rate and Monthly Recurring Revenue.
Ghost's official themes support memberships out-of-the-box, meaning you get form inputs and subscribe buttons throughout. No extra work needed. And most custom themes purchasable through the Themes Directory also support memberships.
As a writer this means you can publish posts and choose to send them to any and all of your members with one click of a button. It's a powerful model for content creators who want to capture some of the value they create via monetization.
However, if you use Ghost as a headless CMS, you're opting out of this native functionality and can only take advantage of it through Ghost's APIs. Specifically the Admin API.
Ghost's Admin API
The Admin API exposes a set of endpoints that let you do things you would normally do in the Ghost Dashboard. Things like creating, editing and deleting posts and static pages, uploading images and themes, or manipulating the site object.
You can also perform basic CRUD operations on your list of members.
In this post we are only concerned with the creation of a member, i.e subscribing.
Installing the client
At the time of writing this post, interacting with member data via the Admin API is an undocumented feature. I was not able to find any reference to it within Ghost's developer documentation, and the only reason I know about this is through hours of poking around a number of Ghost's GitHub repositories, specifically this one, this one and this one. Oh and this one.
I will keep my eye out for any changes on Ghost's end and update this post when applicable.
Even though it is undocumented, Ghost still considers the members endpoints stable, so we shouldn't have much to worry about. The only obstacle is figuring out how to use it.
First, we need to install the Admin API client:
yarn add @tryghost/admin-api
or with npm
:
npm install @tryghost/admin-api
Configuring the client
Configuring this client is the same as configuring the Content API client from the previous post. If you are confused how we got here, I recommend reading that one first.
Let's add on to our lib/ghost.js
file by importing the Admin API client and
initiating it:
import GhostAdminAPI from '@tryghost/admin-api'
const admin = new GhostAdminAPI({
url: '',
key: '',
version: ''
})
As with the Content API, the Admin API requires a url
and a key
value. They
can be found in the Integrations section of your Ghost Dashboard as well. In
fact, the url
value will be the same for the Content API client and the Admin
API client. The only difference is the key
.
To find them, navigate to your locally hosted Ghost instance, which is usually
deployed at http://localhost:2368/ghost
.
If you do not have Ghost running locally, see the previous post for more details.
Once there, navigate to the Integrations section on the left sidebar. If you have yet to create a custom integration for Ghost, you will need to do so by clicking "Add Custom Integration". You can name the integration whatever you want.
If you are continuing on with the previous post, your custom integration should already exist, "NextJS Front-end".
Once you've confirmed that your custom integration exists, open it to see more
information. This is where you will find your url
and key
values. The value
of url
will correspond to the API URL
value, and the value of key
will
correspond to ADMIN API key
.
Copy these values into a .env.local
file:
GHOST_API_URL=<YOUR_API_URL>
GHOST_ADMIN_API_KEY=<YOUR_ADMIN_API_KEY>
If you are following along from the previous
post in this series, you may already have the
GHOST_API_URL
in your .env.local
file.
Now that we have that sorted, lets update lib/ghost.js
with these values:
import GhostAdminAPI from '@tryghost/admin-api'
const admin = new GhostAdminAPI({
url: process.env.GHOST_API_URL,
key: process.env.GHOST_ADMIN_API_KEY,
version: 'v3'
})
We also updated the version
by using "v3"
. This is the value Ghost currently
uses in their documentation.
It's important to note that the url
and key
values will be different in your
production instance of Ghost. Since we are injecting these values as environment
variables, it should be trivial to update them for production. Wherever you
deploy your JAMStack site, just make sure to include the production values of
GHOST_API_URL
and GHOST_ADMIN_API_KEY
in your environment variable
configuration.
Subscribing members
Now that we have the client setup, we'll need to write a function that adds a member to our list. This was the tricky part about the Admin API. There is no documentation on how to do so. After some digging around, I came up with this:
const admin = new GhostAdminAPI({ /* ... */ })
export function addMember(email, name) {
return admin.members.add({ email, name });
}
The addMember
function above accepts an email
and a name
. Only email
is
required by the API, though the name
will also be set if you include it.
The form to capture these values can be built in any way you choose. I won't
include that in this post. Just make sure that when the form submits, you call
addMember
and pass the right values.
In development, you should be able to call this function and see the newly added member show up in the Members section of your local instance of Ghost. This is how you know it is working properly.
Sending a confirmation email
One of the great things about Ghost's native Members feature is that a reader will receive a special email when they subscribe for the first time. It is a feature that I want to support on my site, but again, there's no documentation on how to do this.
After some more digging I was able to find a few options that can be passed
along with the email
and name
that make this possible:
const admin = new GhostAdminAPI({ /* ... */ })
export function addMember(email, name) {
return admin.members.add(
{ email, name },
{ send_email: true, email_type: 'subscribe' });
}
These options are not required, but the Members API will look for them, and if they are passed, a special email will be sent to your member. Pretty neat!
Finding a bug in Ghost's email logic
During this process of discovering how to send subscription emails to new members, I realized that the content of the email being sent had nothing to do with confirming a subscription. Instead it contained a magic link to "Sign In" to my website.
The image above is a snapshot of the output from the command ghost log
, which
can be run in development to inspect the logs of your running Ghost instance.
Why would this be?
If you use an official Ghost theme like Casper, your members can sign in to view paywalled content, and this email enables that. But I'm not requesting that my members sign in, I'm requesting that they confirm their subscription.
If we take a step back to the options passed to the addMember
function, you'll
notice there is an email_type
option being used. This option
can have one of three values:
'signin'
'signup'
'subscribe'
The value of this email_type
is used to select
one of three different email templates
to be sent to the member. Since we are using 'subscribe'
, you would think that
the
subscribe
template
would be sent, but that's not the case.
I did some debugging through the various repositories in Ghost's GitHub
organization, and have found what I believe to be a bug in the way that email
templates are selected when adding a member from the @tryghost/admin-api
client.
If you're curious to know more, I've laid out my thesis in
a new issue in the TryGhost/Ghost
repository on GitHub.
I'll make sure to keep this post updated if the issue gets resolved.
Conclusion
So there you have it. It's not a perfect solution by any means, but if you are using Ghost as a headless CMS, this is currently the way to add members to your Ghost dashboard.
Hopefully, with continued growth in the JAMStack ecosystem, some of these issues will get ironed out over time. That's the beauty of open source!
Feel free to reach out to me on Twitter if you have any further questions regarding this post.
Happy coding ⚡️