Tuan Phung
August 17, 2022
React subscribe form with Tailwind using ConvertKit API
How to make a subscription form with Tailwind and using Convertkit
article cover

API subscribe

Here is a video of what we will be building today.

import isValidEmail from 'utils/isValidEmail';

const subscribeConvertkit = async (req, res) => {
  const { email } = JSON.parse(req.body);

  const isValid = isValidEmail(email);

  if (!email) {
    return res.status(400).json({ error: 'Email is required.' });

  if (!isValid) {
    return res.status(400).json({ error: 'Email is invalid.' });

  try {
    const FORM_ID = process.env.CONVERTKIT_FORM_ID;
    const API_KEY = process.env.CONVERTKIT_API_KEY;
    const API_URL = process.env.CONVERTKIT_API_URL;

    const response = await fetch(`${API_URL}forms/${FORM_ID}/subscribe`, {
      body: JSON.stringify({ email, api_key: API_KEY }),
      headers: { 'Content-Type': 'application/json' },
      method: 'POST'

    // something went wrong on the convertkit server
    if (response.status >= 400) {
      return res
        .json({ error: 'There was an error subscribing to the list.' });

    // Success
    return res.status(201).json({ error: null });
  } catch (error) {
    return res.status(500).json({ error: error.message || error.toString() });

export default subscribeConvertkit;

AddSubscriber fetcher

For submitting the subscription, let's create a simple handler that returns the result from the API call

export default async function addSubscriber(email) {
  // The location of your API route
  const url = '/api/subscribe-convertkit';

  const response = await fetch(url, {
    method: 'POST',
    body: JSON.stringify({ email })

  const result = await response.json();

  return result;

React Subscribe component

Now we have the API ready and the fetcher ready, so we can get into creating the Subscribe component

import Container from 'components/Container';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import addSubscriber from 'utils/addSubscriber';
import Confetti from './Confetti';

export default function Subscribe() {
  const [formState, setFormState] = useState({ state: 'initial', message: '' });
  const { register, handleSubmit } = useForm();

  const onSubmit = async data => {
    setFormState({ state: 'loading', message: null });
    const { error } = await addSubscriber(;

    if (error) {
      return setFormState({ state: 'error', message: error });

    return setFormState({
      state: 'success',
      message: 'Check your email to confirm your subscription'

  return (
    <div className="py-24 bg-gray-100">
        <div className="sm:text-center">
          <div className="text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl">
            Subscribe to the newsletter
          <p className="max-w-2xl mx-auto mt-4 text-lg text-gray-500">
            Get emails from me about web development, tech, and early access to new
          className="mt-8 sm:mx-auto sm:max-w-lg sm:flex"
          <div className="flex-1 min-w-0">
            <label htmlFor="cta-email" className="sr-only">
              Email address

              className="block w-full px-5 py-3 text-base text-gray-900 placeholder-gray-500 border border-transparent rounded-md shadow-sm"
              placeholder="Enter your email"

            {formState.state === 'success' && (
              <div className="my-2 mb-4 ml-2 text-sm font-semibold text-green-700 dark:text-green-500">
            {formState.state === 'error' && (
              <div className="my-2 mb-4 ml-2 text-sm font-semibold text-red-700 dark:text-red-500">
              className="block w-full px-5 py-3 mt-4 text-base font-medium text-white bg-gray-600 border border-transparent rounded-md shadow hover:bg-gray-400 focus:outline-none sm:px-10"
Powered by