/ astro-integrations / How to Integrate ConvertKit with Astro: Complete Guide
astro-integrations 6 min read

How to Integrate ConvertKit with Astro: Complete Guide

Step-by-step guide to integrating ConvertKit with your Astro website. Setup, configuration, and best practices.

How to Integrate ConvertKit with Astro: Complete Guide

ConvertKit (now Kit) is an email marketing platform designed specifically for creators, bloggers, and online businesses. It focuses on subscriber management, automated email sequences, and landing pages. Integrating ConvertKit with your Astro site lets you add newsletter signups, deliver lead magnets, and build email sequences that engage your audience automatically. The API-first approach works particularly well with Astro's server-side rendering capabilities.

This guide covers connecting ConvertKit to Astro using the REST API for custom signup forms and the embedded script approach for simpler setups.

Prerequisites

You will need:

  • Node.js 18+ installed
  • An existing Astro project (any version for embeds, SSR for API integration)
  • A ConvertKit account (free plan for up to 10,000 subscribers)
  • Your ConvertKit API key (found in Settings > Advanced > API)
  • A Form ID or Tag ID for subscriber targeting

Installation

ConvertKit does not have an official Node.js SDK, so you will use the REST API directly with fetch. No additional packages are required.

For the API-based approach, add an SSR adapter:

npx astro add node

Configuration

Environment Variables

Add your ConvertKit credentials to .env:

CONVERTKIT_API_KEY=your_api_key_here
CONVERTKIT_API_SECRET=your_api_secret_here
CONVERTKIT_FORM_ID=your_default_form_id

The API key is used for subscriber management. The API secret is needed for some advanced operations.

Creating a ConvertKit Utility

// src/lib/convertkit.ts
const API_KEY = import.meta.env.CONVERTKIT_API_KEY;
const BASE_URL = 'https://api.convertkit.com/v3';

export async function subscribeToForm(formId: string, email: string, firstName?: string) {
  const response = await fetch(`${BASE_URL}/forms/${formId}/subscribe`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      api_key: API_KEY,
      email,
      first_name: firstName || '',
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message || 'Subscription failed');
  }

  return response.json();
}

export async function addTagToSubscriber(tagId: string, email: string) {
  const response = await fetch(`${BASE_URL}/tags/${tagId}/subscribe`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      api_key: API_KEY,
      email,
    }),
  });

  return response.json();
}

Astro Config

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'hybrid',
  adapter: node({ mode: 'standalone' }),
});

Common Patterns

Server-Side Signup API Route

Create an API endpoint that subscribes users to a ConvertKit form:

// src/pages/api/subscribe.ts
import type { APIRoute } from 'astro';
import { subscribeToForm } from '../../lib/convertkit';

export const POST: APIRoute = async ({ request }) => {
  const formData = await request.formData();
  const email = formData.get('email') as string;
  const firstName = formData.get('firstName') as string;
  const formId = import.meta.env.CONVERTKIT_FORM_ID;

  if (!email) {
    return new Response(JSON.stringify({ error: 'Email is required' }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  try {
    await subscribeToForm(formId, email, firstName);

    return new Response(JSON.stringify({ success: true }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
    });
  } catch (error: any) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    });
  }
};

Custom Newsletter Signup Component

---
// src/components/Newsletter.astro
interface Props {
  heading?: string;
  description?: string;
}

const { heading = 'Join the newsletter', description = 'Get weekly tips delivered to your inbox.' } = Astro.props;
---

<div class="newsletter-wrapper">
  <h3>{heading}</h3>
  <p>{description}</p>

  <form id="ck-signup" class="newsletter-form">
    <input type="text" name="firstName" placeholder="First name" />
    <input type="email" name="email" placeholder="your@email.com" required />
    <button type="submit">Subscribe</button>
  </form>
  <p id="ck-message" style="display: none;"></p>
</div>

<script>
  const form = document.getElementById('ck-signup') as HTMLFormElement;
  const msg = document.getElementById('ck-message') as HTMLParagraphElement;

  form?.addEventListener('submit', async (e) => {
    e.preventDefault();
    const data = new FormData(form);

    try {
      const res = await fetch('/api/subscribe', { method: 'POST', body: data });
      const result = await res.json();

      msg.style.display = 'block';
      if (res.ok) {
        msg.textContent = 'Check your email to confirm your subscription!';
        msg.style.color = 'green';
        form.reset();
      } else {
        msg.textContent = result.error || 'Something went wrong.';
        msg.style.color = 'red';
      }
    } catch {
      msg.style.display = 'block';
      msg.textContent = 'Network error. Please try again.';
      msg.style.color = 'red';
    }
  });
</script>

Embedded ConvertKit Form (No SSR Required)

For static sites, use ConvertKit's embed script. Get the code from ConvertKit: Forms > Select form > Embed:

---
// src/components/ConvertKitEmbed.astro
interface Props {
  formId: string;
}
const { formId } = Astro.props;
---

<script async data-uid={formId} src={`https://your-subdomain.ck.page/${formId}/index.js`}></script>

Use it in any page:

<ConvertKitEmbed formId="abc123def" />

Tagging Subscribers Based on Content

Add tags to subscribers based on where they signed up. This is useful for segmentation:

// src/pages/api/subscribe-tagged.ts
import type { APIRoute } from 'astro';
import { subscribeToForm, addTagToSubscriber } from '../../lib/convertkit';

export const POST: APIRoute = async ({ request }) => {
  const formData = await request.formData();
  const email = formData.get('email') as string;
  const firstName = formData.get('firstName') as string;
  const tag = formData.get('tag') as string;

  const formId = import.meta.env.CONVERTKIT_FORM_ID;

  try {
    await subscribeToForm(formId, email, firstName);

    if (tag) {
      await addTagToSubscriber(tag, email);
    }

    return new Response(JSON.stringify({ success: true }), { status: 200 });
  } catch (error: any) {
    return new Response(JSON.stringify({ error: error.message }), { status: 500 });
  }
};

Lead Magnet Delivery

ConvertKit can automatically send a lead magnet when someone subscribes to a specific form. Set up the incentive email in ConvertKit's form settings (Form > Settings > Incentive email). When you subscribe a user to that form via the API, ConvertKit handles the delivery automatically.

Troubleshooting

"Unprocessable Entity" (422) error: This usually means the email format is invalid or a required field is missing. Validate the email on the client side before submitting. ConvertKit rejects obviously malformed addresses.

Subscriber added but no confirmation email: ConvertKit sends a confirmation email by default for new subscribers. Check the subscriber's spam folder. If you want to skip double opt-in (not recommended), you need to request this from ConvertKit support.

Form ID not found: Make sure the form is published and active in ConvertKit. Archived or draft forms return a 404 error. You can find active form IDs in the ConvertKit dashboard URL when editing a form.

Duplicate subscriber handling: ConvertKit handles duplicates gracefully. If someone subscribes with an email that already exists, the API returns success and updates the subscriber's information rather than creating a duplicate. This means you do not need to check for existing subscribers before calling the API.

API rate limits: ConvertKit allows 120 requests per minute per API key. For high-traffic sites, implement client-side rate limiting or add a delay between rapid submissions.

Embedded form styling conflicts: ConvertKit's embedded forms come with their own CSS that may conflict with your site's styles. Use the "Naked" embed option for minimal styling, or use the API approach for full control over the form design.

Conclusion

ConvertKit pairs well with Astro for creator-focused websites and blogs. The REST API gives you full control over form design and subscriber management without depending on embedded scripts. Use the API approach for custom-designed forms that match your site's look, and leverage ConvertKit's tagging system to segment subscribers based on their interests and behavior. For static Astro sites, the embedded form script is a quick solution that requires no server-side code.