DEV Community

Cover image for How to add a contact form to your Astro site in 60 seconds (no backend)

How to add a contact form to your Astro site in 60 seconds (no backend)

One of the reasons I love Astro is how easy it is to build fast, static websites.

But there's one feature that always seems to complicate things: contact forms.

Suddenly you need API routes, serverless functions, email providers, spam protection, and somewhere to store submissions.

For a simple contact page, that's a lot of infrastructure.

So I built a small Astro integration that lets you keep your site completely static while handling form submissions automatically.

Let's build a working contact form in about a minute.


Step 1 โ€” Install the integration

Install the package:

npm install astro-onsubmit
Enter fullscreen mode Exit fullscreen mode

Step 2 โ€” Register the integration

Open your astro.config.mjs and register the integration:

import { defineConfig } from 'astro/config';
import onsubmit from 'astro-onsubmit';

export default defineConfig({
  integrations: [
    onsubmit(),
  ],
});
Enter fullscreen mode Exit fullscreen mode

That's the only configuration required.


Step 3 โ€” Create a form

Create a form in your https://onsubmit.dev/integrations/astro dashboard.

You'll receive a unique Form ID like:

abc123xyz
Enter fullscreen mode Exit fullscreen mode

Step 4 โ€” Add your contact form

Now create a normal Astro form.

The only thing you need is the data-onsubmit attribute.

<form data-onsubmit="YOUR_FORM_ID">
  <input
    name="name"
    placeholder="Your name"
    required
  />

  <input
    name="email"
    type="email"
    placeholder="Email"
    required
  />

  <textarea
    name="message"
    placeholder="Tell us about your project"
    required
  ></textarea>

  <button
    type="submit"
    data-onsubmit-loading="Sending..."
  >
    Send message
  </button>
</form>
Enter fullscreen mode Exit fullscreen mode

That's it.

No fetch().

No API routes.

No backend code.

The integration intercepts the form submission and sends it directly to your onsubmit.dev endpoint.


What happens behind the scenes?

When a visitor submits the form, the integration:

  • Collects the form data
  • Sends it using fetch()
  • Displays loading states
  • Handles success and error responses
  • Automatically supports file uploads
  • Works with Astro View Transitions

Your website remains completely static.


Customize the user experience

The integration provides a few handy attributes.

Show a success message after submission:

<form
  data-onsubmit="YOUR_FORM_ID"
  data-onsubmit-success="Thanks! We'll get back to you soon."
>
Enter fullscreen mode Exit fullscreen mode

Provide your own error message:

<form
  data-onsubmit="YOUR_FORM_ID"
  data-onsubmit-error="Something went wrong. Please try again."
>
Enter fullscreen mode Exit fullscreen mode

Customize the button while the request is in progress:

<button
  type="submit"
  data-onsubmit-loading="Sending..."
>
  Send
</button>
Enter fullscreen mode Exit fullscreen mode

No JavaScript required.


Handling errors

If you'd like to display API errors inside the form, simply add an empty paragraph.

<form data-onsubmit="YOUR_FORM_ID">
  <input
    name="email"
    type="email"
    required
  />

  <button type="submit">
    Subscribe
  </button>

  <p class="onsubmit-error-message" hidden></p>
</form>
Enter fullscreen mode Exit fullscreen mode

The integration automatically reveals it whenever an error occurs.


File uploads? They just work.

Need users to attach a rรฉsumรฉ, screenshot, or document?

Simply add a file input.

<input
  type="file"
  name="attachment"
/>
Enter fullscreen mode Exit fullscreen mode

If the form contains files, the integration automatically switches to multipart/form-data.

Otherwise, it sends lightweight JSON requests.

No extra configuration is needed.


Listening for events

Need to trigger analytics, show a toast notification, or redirect users after submission?

The integration dispatches custom events.

document
  .querySelector('form[data-onsubmit]')
  .addEventListener('onsubmit:success', (event) => {
    console.log('Submission ID:', event.detail.id);
  });

document
  .querySelector('form[data-onsubmit]')
  .addEventListener('onsubmit:error', (event) => {
    console.error(event.detail.error);
  });
Enter fullscreen mode Exit fullscreen mode

Don't want to modify astro.config.mjs?

You can use the included component instead.

---
import OnSubmitForm from 'astro-onsubmit/OnSubmitForm.astro';
---

<OnSubmitForm
  formId="YOUR_FORM_ID"
  successMessage="Thanks! We'll be in touch."
>
  <input
    name="name"
    required
  />

  <input
    name="email"
    type="email"
    required
  />

  <button
    type="submit"
    data-onsubmit-loading="Sending..."
  >
    Send
  </button>
</OnSubmitForm>
Enter fullscreen mode Exit fullscreen mode

This is useful if you're integrating forms into only one or two pages and don't want to register the global integration.


Why not build your own backend?

There's absolutely nothing wrong with using Astro API routes or serverless functions.

In fact, they're the right choice when your application already has backend logic.

But for many websitesโ€”landing pages, portfolios, documentation sites, agency websites, or blogsโ€”a contact form is the only dynamic feature.

Installing a lightweight integration is often much simpler than maintaining backend code just to receive a few form submissions.


Final thoughts

Astro makes static websites incredibly enjoyable to build.

Your contact forms should feel just as simple.

With astro-onsubmit, you can go from zero to a fully working contact form in about a minute, while keeping your project completely static.

No backend.

No API routes.

No serverless functions.

Just install the package, add data-onsubmit to your form, and you're ready to receive submissions.

Top comments (0)