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:

lib/ghost.js
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".

Ghost Custom Integration

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:

.env.local
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:

lib/ghost.js
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:

lib/ghost.js
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:

lib/ghost.js
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.

Signup Email

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:

  1. 'signin'
  2. 'signup'
  3. '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 ⚡️

Jake Wiesler

Hey! 👋 I'm Jake

Thanks for reading! I write about software and building on the Web. Learn more about me here.

Subscribe To Original Copy

A weekly email for makers on the Web.

Learn More