# How to make a Convertkit sign-up form with Next.js

> Want to grow your Newsletter list? Learn how to build a sign-up form with Convertkit and Next.js

*Published: 2022-07-24*
*Canonical: https://makerkit.dev/blog/tutorials/make-convertkit-signup-form-nextjs*

---

[Convertkit](https://converkit.com) is one of the most used services for creating and sending newsletters, or for selling digital products.

Sign-up forms are very important before launching your SaaS because it allows you to contact your prospective customers, get feedback, and generally create an audience for your product: this is why **the [Makerkit](https://makerkit.dev/ai-saas-starter)'s SaaS starter helps you during the pre-launch phase of your SaaS by giving you the option of using a [Convertkit](https://converkit.com) or a [EmailOctopus](https://emailoctopus.com/) sign-up form**, two among the favorite email marketing providers for developers and startups.

While Convertkit does offer various built-in sign-up forms, that means having to host one more script onto your website, **which also means a heavier page**. 

When I built Makerkit, I decided that adding yet another script wasn't worth it: fortunately, Convertkit does allow subscriptions using an API we can use for **building a lightweight form without any need for an external third-party script**.

## Building the Sign-Up Form with Makerkit's UI Components

### Retrieve the API Key
First, we have to retrieve the API key from your [Convertkit Dashboard](https://app.convertkit.com/account_settings/advanced_settings), click the link to visit it.

Your API key needs to be added to the Next.js environment variables of your application so that we can access it as `process.env.CONVERTKIT_API_KEY`.

### Retrieve the Form ID

Before being able to headlessly subscribe users to a form, we need to create one in Convertkit. In fact, to subscribe users with the Converkit API we need to specify a form ID.

To do so, [visit the forms page in Convertkit](https://app.convertkit.com/forms), create a form, and grab the ID of the form: this is available from the URL as the last segment, or by clicking on "Publish" where it will be available within the script coding block with the `data-ui` attribute.

Grab the form ID and store it somewhere in your environment or your configuration.

### Let's write the sign-up form's UI

It's time to write the UI of our sign-up form. The code below will look just like the one used in this website:

{% img src="/assets/images/posts/sign-up-form.webp" width="695" height="142" /%}

#### Writing the Component's UI

The Form's UI is very simple: a form that when submitted will call its handler `onSubmit`.

```tsx
<>
  <form
    target="_blank"
    className={`space-around flex w-full flex-grow justify-center`}
    onSubmit={onSubmit}
  >
    <input
      type="email"
      className="TextInput w-full !rounded-tr-none !rounded-br-none border-r-transparent py-1 text-sm hover:border-r-transparent md:w-80 md:text-base"
      name={name}
      aria-label="Your email address"
      placeholder="your@email.com"
      required
    />

    <button className="Button min-w-[6rem] rounded-tl-none rounded-bl-none text-sm md:text-base">
      {children ?? 'Sign up'}
    </button>
  </form>

  <p className={'mt-2 text-center text-sm md:text-xs'}>
    Subscribe to our newsletter to receive updates
  </p>
</>
```

#### Submitting an HTTP request to the API

When the user subscribes and the `onSubmit` is called, we have to make an HTTP request to our API endpoint at `/api/convertkit/subscribe`.

To do so, first we create a state property `success`: 
- if it's `false`, the request did not work
- if it's `true`, the request worked

Here is the handler's code:

```tsx
const [success, setSuccess] = useState<boolean | undefined>();

const onSubmit: FormEventHandler = useCallback(
  async (event) => {
    event.preventDefault();

    const target = event.target as HTMLFormElement;
    const data = new FormData(target);
    const email = data.get(name);

    const body = JSON.stringify({
      formId,
      email,
    });

    const headers = new Headers({
      'Content-Type': 'application/json; charset=utf-8',
    });

    try {
      await fetch(`/api/convertkit/subscribe`, {
        method: 'POST',
        mode: 'cors',
        cache: 'no-cache',
        headers,
        body,
      });

      setSuccess(true);
    } catch {
      setSuccess(false);
    }
  },
  [formId]
);
```

Depending on the result of the request, we display either a success message or an error.

### The full Form Component

Below, you can find the full component's code:

 ```tsx {% title="ConvertkitSignupForm.tsx" %}
import { FormEventHandler, useCallback, useState } from 'react';

const ConvertkitSignupForm: React.FC<
  React.PropsWithChildren<{
    formId: string;
  }>
> = ({ formId, children }) => {
  const name = 'email';
  const [success, setSuccess] = useState<boolean | undefined>();

  const onSubmit: FormEventHandler = useCallback(
    async (event) => {
      event.preventDefault();

      const target = event.target as HTMLFormElement;
      const data = new FormData(target);
      const email = data.get(name);

      const body = JSON.stringify({
        formId,
        email,
      });

      const headers = new Headers({
        'Content-Type': 'application/json; charset=utf-8',
      });

      try {
        await fetch(`/api/convertkit/subscribe`, {
          method: 'POST',
          mode: 'cors',
          cache: 'no-cache',
          headers,
          body,
        });

        setSuccess(true);
      } catch {
        setSuccess(false);
      }
    },
    [formId]
  );

  if (success === false) {
    return <p>Apologies, an error occurred</p>;
  }

  if (success) {
    return <p>You&apos;re in! Thank you for subscribing.</p>;
  }

  return (
    <>
      <form
        target="_blank"
        className={`space-around flex w-full flex-grow justify-center`}
        onSubmit={onSubmit}
      >
        <input
          type="email"
          className="TextInput w-full !rounded-tr-none !rounded-br-none border-r-transparent py-1 text-sm hover:border-r-transparent md:w-80 md:text-base"
          name={name}
          aria-label="Your email address"
          placeholder="your@email.com"
          required
        />

        <button className="Button min-w-[6rem] rounded-tl-none rounded-bl-none text-sm md:text-base">
          {children ?? 'Sign up'}
        </button>
      </form>

      <p className={'mt-2 text-center text-sm md:text-xs'}>
        Subscribe to our newsletter to receive updates
      </p>
    </>
  );
};

export default ConvertkitSignupForm;
```

## Building the API to subscribe the user to a sign-up form

Below is the API handler's code that takes care of calling the Convertkit API and subscribe the user:

```tsx
import { NextApiRequest, NextApiResponse } from 'next';

const API_KEY = process.env.CONVERTKIT_API_KEY;
const BASE_URL = 'https://api.convertkit.com/v3';

function subscribeToForm(
  params: { formId: string; email: string }
) {
  const url = [BASE_URL, `forms`, params.formId, 'subscribe'].join('/');

  const headers = new Headers({
    'Content-Type': 'application/json; charset=utf-8',
  });

  return fetch(url, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      api_key: API_KEY,
      email: params.email,
    }),
  });
}

async function subscribeToFormHandler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const body = req.body;

  // best to validate this with Zod...

  await subscribeToForm({
    formId: body.formId,
    email: body.email,
  });

  return res.send({ success: true });
}

export default subscribeToFormHandler;
```

And that's it! If you like me don&apos;t like the idea of adding yet another huge JS bundle to your client, you can use the script above and create a Convertkit Sign-up form quickly and easily.

[Of course, the code above is already included in the Makerkit's SaaS starter for Next.js. Take a look!](/)
