Formspree Logo
Guide Thumbnail
+

Adding a Form to Astro

In this guide we’ll show you how to add a contact form to your Astro app using Formspree.

Formspree is a form backend that’s an ideal companion for static site generators, letting you build and deploy your Jamstack website without worrying about backend code.

At the end of this guide, you’ll have a working contact form on your Astro site that sends submissions directly to your email via Formspree.

Prerequisites

To follow this guide, you’ll need:

If you don’t have an Astro project yet, you can create one by running:

npm create astro@latest
cd your-astro-project
npm install
npm run dev

If you’re new to Astro, check out the official docs to learn more about its static-first approach.

Step 1: Adding the Form Code

Let’s start with adding the code for the form. There are two ways you can do it: adding a static HTML form and using a React-based client-side island.

Option 1: Add a Static HTML Form

Since Astro supports plain HTML out of the box, integrating a Formspree-powered form is as simple as dropping in a <form> tag. Let’s see how to set up an Astro contact form:

Create a new page in your project at src/pages/contact.astro and paste the following code into it:

---
---
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Contact</title>
  </head>
  <body>
    <h1>Contact Us</h1>
    <form
      action="https://formspree.io/f/{FORM_ID}"
      class="fs-form"
      target="_top"
      method="POST"
    >
      <div class="fs-field">
        <label class="fs-label" for="email">Email</label>
        <input class="fs-input" id="email" name="email" required />
      </div>
      <div class="fs-field">
        <label class="fs-label" for="message">Message</label>
        <textarea class="fs-textarea" id="message" name="message"></textarea>
      </div>
      <div class="fs-button-group">
        <button class="fs-button" type="submit">Submit</button>
      </div>
    </form>
  </body>
</html>

This code comes from the Simple Contact Form example in the Formspree forms library. If you want to style this form, you will find some useful CSS on the form page.

A few things to note here:

  • This is fully static HTML—no JavaScript, no client-side hydration, no server-side code.
  • The action attribute should point to your unique Formspree form endpoint. We’ll set that up next.

Option 2: Creating an Interactive Client Island with React

Alternatively, you can set up a React-based client island to make the form interactive while keeping the rest of your Astro site static and performant.

To get started, install the React integration in your Astro project by running the following command:

npx astro add react

This allows you to embed a React component in your Astro page as a client-side island! This means that you can also use the @formspree/react library to better manage your Formspree forms!

To install it, run the following command:

npm install --save @formspree/react

Then, paste the following code snippet into the src/components/contact.jsx file:

import { useForm, ValidationError } from "@formspree/react";

export default function ContactForm() {
  const [state, handleSubmit] = useForm("{FORM_ID}");

  if (state.succeeded) {
    return <p>Thanks for your submission!</p>;
  }

  return (
    <form onSubmit={handleSubmit} method="POST">
      <label htmlFor="email">Email Address</label>
      <input id="email" type="email" name="email" />
      <ValidationError prefix="Email" field="email" errors={state.errors} />
      <textarea id="message" name="message" />
      <ValidationError prefix="Message" field="message" errors={state.errors} />
      <button disabled={state.submitting}>
        Submit
      </button>
      <ValidationError errors={state.errors} />
    </form>
  );
}

The useForm hook returns a state object and a handleSubmit function which you pass to the onSubmit form attribute. Combined, these provide a way to submit the form data via AJAX and update form state depending on the response received.

The @formspree/react library also offers a ValidationError component, which is a helper that display error messages for field errors, or general form errors (if no field attribute is provided). For the sake of clarity, this form doesn’t include any styling, but in this GitHub project you can see an example of how to apply styles to the form.

The component can now be imported in your Astro like so:

import ContactForm from "../components/contact";

And inserted into the page as a React component:

<ContactForm client:load />

Using client:load makes sure that Astro treats this component as an island and hydrates it with the client-side logic that you’ve supplied.

You can try adding it to the index page and test out the form! You should receive a similar output:

Form not found error

You receive this error because you still have the placeholder "{FORM_ID}" in the code. Let’s fix this by setting up a form endpoint to accept our form submissions

Step 2: Creating a Form Endpoint

To create a form endpoint using Formspree, sign up here for a free Formspree account.

Next, create a new form with ++ Add New > New Form, call it Contact form and update the recipient email to the email where you wish to receive your form submissions.

Then click Create Form.

Formspree new form modal

On the form details page, you will find the Form Endpoint:

Formspree form endpoint

Step 3: Set the Form action to the Formspree Form Endpoint

Now, you just need to replace the action in your form with this URL.

Now, when someone fills out your form and clicks submit, the data will be sent to Formspree and forwarded to your inbox.

Step 4: Testing Your Form

Once you’ve saved your contact.astro page, you can preview it locally by running npm run dev and visiting http://localhost:4321

Now your Astro form is connected to Formspree. No custom code, no integrations, no backend; just a simple, scalable way to handle contact forms, lead capture, feedback requests, and more.

Bonus Section: Using Environment Variables for Different Form IDs

To manage different form endpoints across development and production environments, you can use Astro’s built-in support for environment variables.

Create the following .env files at the root of your project:

.env (for production)

FORMSPREE_FORM_ID=abcd1234

.env.development (for local development)

FORMSPREE_FORM_ID=devtest99

In your Astro component, access this variable using import.meta.env:

---
const formId = import.meta.env.FORMSPREE_FORM_ID;
---
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Contact</title>
  </head>
  <body>
    <h1>Contact Us</h1>
    <form
      action={`https://formspree.io/f/${formId}`}
      class="fs-form"
      target="_top"
      method="POST"
    >
      <div class="fs-field">
        <label class="fs-label" for="email">Email</label>
        <input class="fs-input" id="email" name="email" required />
      </div>
      <div class="fs-field">
        <label class="fs-label" for="message">Message</label>
        <textarea class="fs-textarea" id="message" name="message"></textarea>
      </div>
      <div class="fs-button-group">
        <button class="fs-button" type="submit">Submit</button>
      </div>
    </form>
  </body>
</html>

Now, when you run npm run dev, Astro will load values from .env.development. When you build for production, it will load from .env.

🔐 Important: Astro only exposes environment variables that are explicitly prefixed with PUBLIC_ to client-side JavaScript. Since this form is fully static and not using any client-side code, you can safely access non-PUBLIC_ variables in .astro files on the server side.

Deployment

You can deploy your Astro project to any static host like:

Make sure to set the FORMSPREE_FORM_ID environment variable in your host’s dashboard for production builds.

That’s It!

You now have a working Astro contact form powered by Formspree, with environment-based configuration for development and production environments.

To explore additional features like redirecting after submission or connecting to APIs, check out Formspree’s documentation.


Got Feedback?