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
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(),
],
});
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
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>
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."
>
Provide your own error message:
<form
data-onsubmit="YOUR_FORM_ID"
data-onsubmit-error="Something went wrong. Please try again."
>
Customize the button while the request is in progress:
<button
type="submit"
data-onsubmit-loading="Sending..."
>
Send
</button>
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>
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"
/>
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);
});
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>
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)