Subscribing Ghost Members From Your JAMStack Site
Tags: ghost, jamstack
Published: 2021-08-26 14:05:18

---
title: 'Subscribing Ghost Members From Your JAMStack Site'
publishedAt: '2021-08-26'
summary: This post will explain how JAMStack sites using Ghost as a headless CMS can subscribe readers to an email list using Ghost Memberships.
tags: ['Ghost', 'JAMStack']
published: true
canonical_url: https://www.jakewiesler.com/blog/ghost-jamstack-memberships
---

In a [previous post](https://jakewiesler.com/blog/ghost-nextjs-jamstack) we walked through a basic blog setup using [NextJS](https://nextjs.org/) and [Ghost](https://ghost.org/) as a headless CMS. In this post, we'll take things up a notch by adding support for [Ghost Memberships](https://ghost.org/docs/members/), a new feature in [v4.0](https://ghost.org/changelog/4/).

## 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](https://ghost.org/themes/) 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](https://ghost.org/docs/admin-api/).

## Ghost's Admin API

The Admin API exposes [a set of endpoints](https://ghost.org/docs/admin-api/#endpoints) that let you do things you would normally do in the Ghost Dashboard. Things like creating, editing and deleting [posts](https://ghost.org/docs/admin-api/#posts) and [static pages](https://ghost.org/docs/admin-api/#pages), uploading [images](https://ghost.org/docs/admin-api/#images) and [themes](https://ghost.org/docs/admin-api/#themes), or manipulating the [site object](https://ghost.org/docs/admin-api/#site).

You can also perform basic [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 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](https://ghost.org/docs), and the only reason I know about this is through hours of poking around a number of [Ghost's GitHub repositories](https://github.com/TryGhost), specifically [this one](https://github.com/TryGhost/Admin), [this one](https://github.com/TryGhost/Ghost) and [this one](https://github.com/TryGhost/Members). Oh and [this one](https://github.com/TryGhost/SDK/tree/main/packages/admin-api).

_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](https://github.com/TryGhost/SDK/blob/dc77bf8dcff320117a2c3d709494ac6cac035b2f/packages/admin-api/lib/index.js#L73), 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](https://ghost.org/docs/admin-api/javascript/):

```bash
yarn add @tryghost/admin-api
```

or with `npm`:

```bash
npm install @tryghost/admin-api
```

### Configuring the client

Configuring this client is the same as configuring [the Content API client from the previous post](https://jakewiesler.com/blog/ghost-nextjs-jamstack#querying-for-blog-posts). 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:

```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](https://jakewiesler.com/blog/ghost-nextjs-jamstack#installing-ghost) 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".

![integration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z138l5swukdjjw3oncwf.png) 

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:

```yaml
GHOST_API_URL=<YOUR_API_URL>
GHOST_ADMIN_API_KEY=<YOUR_ADMIN_API_KEY>
```

_If you are following along from [the previous
  post](https://jakewiesler.com/blog/ghost-nextjs-jamstack) 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:

```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](https://ghost.org/docs/admin-api/javascript/).

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:

```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:

```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](https://github.com/TryGhost/Ghost/blob/19fd16649a5a04cd19ccf802e1ad8f79db26d7f8/core/server/api/canary/members.js#L96), 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.

![email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q0zwfplurn5d6bwurlhr.png) 

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](https://demo.ghost.io/), 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](https://github.com/TryGhost/Ghost/blob/19fd16649a5a04cd19ccf802e1ad8f79db26d7f8/core/server/api/canary/members.js#L106):

1. `'signin'`
2. `'signup'`
3. `'subscribe'`

The value of this `email_type` is used to select [one of three different email templates](https://github.com/TryGhost/Ghost/tree/main/core/server/services/members/emails) to be sent to the member. Since we are using `'subscribe'`, you would think that the [`subscribe` template](https://github.com/TryGhost/Ghost/blob/main/core/server/services/members/emails/subscribe.js) 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](https://github.com/TryGhost/Ghost/issues/13222). 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](https://twitter.com/jakewies) if you have any further questions regarding this post.

Happy coding ⚡️