Forminit + Astro Integration Guide
![]()
Astro is a modern web framework that ships zero JavaScript by default and supports partial hydration for interactive components. Forminit integrates seamlessly with Astro’s “Islands Architecture” — keeping your forms lightweight while adding powerful backend functionality.
What you’ll get:
- Accept form submissions without building a backend
- Email notifications for new submissions
- Spam protection (reCAPTCHA, hCaptcha, honeypot)
- File uploads support
- Submission dashboard to manage responses
Requirements:
- Astro 3.0+ (Astro 5.0+ recommended)
- Node.js 18+
- A Forminit account and Form ID
Quick Start
Section titled “Quick Start”Step 1: Create a Form on Forminit
Section titled “Step 1: Create a Form on Forminit”- Sign up or log in at forminit.com
- Create a new form
- Go to Form Settings → Set authentication mode to Public
- Copy your Form ID (e.g.,
YOUR-FORM-ID)
Step 2: Create an Astro Form Component
Section titled “Step 2: Create an Astro Form Component”Astro components are static by default. For form handling, you have two options:
Option A: Client-Side Script (No Framework)
Section titled “Option A: Client-Side Script (No Framework)”---
// src/components/ContactForm.astro
---
<form id="contact-form" class="contact-form">
<div class="field">
<label for="firstName">First Name</label>
<input type="text" id="firstName" name="fi-sender-firstName" required />
</div>
<div class="field">
<label for="lastName">Last Name</label>
<input type="text" id="lastName" name="fi-sender-lastName" required />
</div>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="fi-sender-email" required />
</div>
<div class="field">
<label for="message">Message</label>
<textarea id="message" name="fi-text-message" rows="5" required></textarea>
</div>
<button type="submit">Send Message</button>
<p id="form-status" class="status" aria-live="polite"></p>
</form>
<script>
const FORM_ID = 'YOUR-FORM-ID'; // ← Replace with your Form ID
const form = document.getElementById('contact-form');
const status = document.getElementById('form-status');
const button = form.querySelector('button[type="submit"]');
// Load Forminit SDK
const script = document.createElement('script');
script.src = 'https://forminit.com/sdk/v1/forminit.js';
script.onload = () => {
const forminit = new Forminit();
form.addEventListener('submit', async (e) => {
e.preventDefault();
status.textContent = 'Sending...';
status.className = 'status loading';
button.disabled = true;
const formData = new FormData(form);
const { data, error } = await forminit.submit(FORM_ID, formData);
button.disabled = false;
if (error) {
status.textContent = error.message;
status.className = 'status error';
return;
}
status.textContent = 'Message sent successfully!';
status.className = 'status success';
form.reset();
});
};
document.body.appendChild(script);
</script>
<style>
.contact-form {
max-width: 28rem;
}
.field {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.25rem;
font-weight: 500;
}
input, textarea {
width: 100%;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
}
button {
background: #2563eb;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.375rem;
cursor: pointer;
}
button:hover { background: #1d4ed8; }
button:disabled { background: #9ca3af; cursor: not-allowed; }
.status { margin-top: 0.5rem; }
.status.loading { color: #6b7280; }
.status.success { color: #059669; }
.status.error { color: #dc2626; }
</style>
Option B: React Island (Interactive Component)
Section titled “Option B: React Island (Interactive Component)”If you prefer React, first install the Forminit package:
npm install forminit
Then create an interactive island:
// src/components/ContactFormReact.tsx
import { useState, useRef } from 'react';
import { Forminit } from 'forminit';
const FORM_ID = 'YOUR-FORM-ID'; // ← Replace with your Form ID
export default function ContactFormReact() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const formRef = useRef<HTMLFormElement>(null);
const forminit = new Forminit();
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!formRef.current) return;
setStatus('loading');
setErrorMessage(null);
const formData = new FormData(formRef.current);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
setStatus('error');
setErrorMessage(error.message);
return;
}
setStatus('success');
formRef.current.reset();
}
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="text" name="fi-sender-firstName" placeholder="First name" required />
<input type="text" name="fi-sender-lastName" placeholder="Last name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<textarea name="fi-text-message" placeholder="Message" rows={5} required />
{status === 'error' && <p className="error">{errorMessage}</p>}
{status === 'success' && <p className="success">Message sent!</p>}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Sending...' : 'Send'}
</button>
</form>
);
}
Use with client:load directive:
---
// src/pages/contact.astro
import ContactFormReact from '../components/ContactFormReact';
---
<ContactFormReact client:load />
Use with client:load directive:
---
// src/pages/contact.astro
import ContactFormReact from '../components/ContactFormReact';
---
<ContactFormReact client:load />
Step 3: Add to Your Page
Section titled “Step 3: Add to Your Page”---
// src/pages/contact.astro
import Layout from '../layouts/Layout.astro';
import ContactForm from '../components/ContactForm.astro';
---
<Layout title="Contact Us">
<main>
<h1>Get in Touch</h1>
<p>Have a project in mind? Let's talk about it.</p>
<ContactForm />
</main>
</Layout>
Form Field Reference
Section titled “Form Field Reference”For a complete list of available form blocks (text, email, phone, file, rating, select, etc.) and field naming patterns, see the Form Blocks documentation.
Complete Examples
Section titled “Complete Examples”Quote Request Form
Section titled “Quote Request Form”Perfect for freelancers and agencies:
---
// src/components/QuoteRequestForm.astro
---
<form id="quote-form">
<div class="grid">
<div class="field">
<label for="name">Your Name</label>
<input type="text" id="name" name="fi-sender-fullName" required />
</div>
<div class="field">
<label for="email">Email Address</label>
<input type="email" id="email" name="fi-sender-email" required />
</div>
</div>
<div class="field">
<label for="company">Company (optional)</label>
<input type="text" id="company" name="fi-sender-company" />
</div>
<div class="field">
<label for="website">Current Website (if any)</label>
<input type="url" id="website" name="fi-url-current_website" placeholder="https://" />
</div>
<div class="field">
<label>Services Needed</label>
<div class="checkbox-group">
<label><input type="checkbox" name="fi-checkbox-services" value="web-design" /> Web Design</label>
<label><input type="checkbox" name="fi-checkbox-services" value="development" /> Development</label>
<label><input type="checkbox" name="fi-checkbox-services" value="seo" /> SEO</label>
<label><input type="checkbox" name="fi-checkbox-services" value="branding" /> Branding</label>
<label><input type="checkbox" name="fi-checkbox-services" value="maintenance" /> Maintenance</label>
</div>
</div>
<div class="field">
<label for="budget">Budget Range</label>
<select id="budget" name="fi-select-budget" required>
<option value="">Select your budget</option>
<option value="under-5k">Under $5,000</option>
<option value="5k-10k">$5,000 - $10,000</option>
<option value="10k-25k">$10,000 - $25,000</option>
<option value="25k-50k">$25,000 - $50,000</option>
<option value="50k-plus">$50,000+</option>
</select>
</div>
<div class="field">
<label for="timeline">Ideal Timeline</label>
<select id="timeline" name="fi-select-timeline">
<option value="">When do you need this?</option>
<option value="asap">ASAP</option>
<option value="1-month">Within 1 month</option>
<option value="1-3-months">1-3 months</option>
<option value="3-6-months">3-6 months</option>
<option value="flexible">Flexible</option>
</select>
</div>
<div class="field">
<label for="details">Project Details</label>
<textarea id="details" name="fi-text-project_details" rows="6" placeholder="Tell us about your project, goals, and any specific requirements..." required></textarea>
</div>
<button type="submit">Request Quote</button>
<p id="quote-status" aria-live="polite"></p>
</form>
<script>
const FORM_ID = 'YOUR-FORM-ID'; // ← Replace with your Form ID
// Same script pattern as above
</script>
Bug Report Form
Section titled “Bug Report Form”For software products and open-source projects:
---
// src/components/BugReportForm.astro
---
<form id="bug-form">
<div class="field">
<label for="email">Your Email</label>
<input type="email" id="email" name="fi-sender-email" required />
</div>
<div class="field">
<label for="title">Bug Title</label>
<input type="text" id="title" name="fi-text-bug_title" placeholder="Brief description of the issue" required />
</div>
<div class="field">
<label for="severity">Severity</label>
<select id="severity" name="fi-select-severity" required>
<option value="">Select severity</option>
<option value="critical">🔴 Critical - App crashes or data loss</option>
<option value="high">🟠 High - Major feature broken</option>
<option value="medium">🟡 Medium - Feature works but has issues</option>
<option value="low">🟢 Low - Minor issue or cosmetic</option>
</select>
</div>
<div class="field">
<label for="version">App Version</label>
<input type="text" id="version" name="fi-text-app_version" placeholder="e.g., 2.1.0" />
</div>
<div class="field">
<label for="browser">Browser / Environment</label>
<input type="text" id="browser" name="fi-text-browser" placeholder="e.g., Chrome 120, macOS Sonoma" />
</div>
<div class="field">
<label for="steps">Steps to Reproduce</label>
<textarea id="steps" name="fi-text-steps_to_reproduce" rows="4" placeholder="1. Go to... 2. Click on... 3. See error" required></textarea>
</div>
<div class="field">
<label for="expected">Expected Behavior</label>
<textarea id="expected" name="fi-text-expected_behavior" rows="2" placeholder="What should happen?" required></textarea>
</div>
<div class="field">
<label for="actual">Actual Behavior</label>
<textarea id="actual" name="fi-text-actual_behavior" rows="2" placeholder="What actually happens?" required></textarea>
</div>
<div class="field">
<label for="screenshot">Screenshot (optional)</label>
<input type="file" id="screenshot" name="fi-file-screenshot" accept="image/*" />
</div>
<div class="field">
<label for="logs">Console Logs / Error Messages</label>
<textarea id="logs" name="fi-text-console_logs" rows="3" placeholder="Paste any error messages here..."></textarea>
</div>
<button type="submit">Submit Bug Report</button>
<p id="bug-status" aria-live="polite"></p>
</form>
Waitlist / Early Access Form
Section titled “Waitlist / Early Access Form”For product launches:
---
// src/components/WaitlistForm.astro
---
<form id="waitlist-form" class="waitlist">
<div class="field">
<label for="email">Email Address</label>
<input type="email" id="email" name="fi-sender-email" placeholder="you@company.com" required />
</div>
<div class="field">
<label for="name">Name (optional)</label>
<input type="text" id="name" name="fi-sender-fullName" placeholder="Your name" />
</div>
<div class="field">
<label>What best describes you?</label>
<select name="fi-select-persona" required>
<option value="">Select one</option>
<option value="indie-developer">Indie Developer</option>
<option value="startup">Startup (1-10 employees)</option>
<option value="small-business">Small Business (11-50)</option>
<option value="mid-market">Mid-Market (51-500)</option>
<option value="enterprise">Enterprise (500+)</option>
<option value="agency">Agency / Freelancer</option>
<option value="other">Other</option>
</select>
</div>
<div class="field">
<label>How did you hear about us?</label>
<select name="fi-select-referral_source">
<option value="">Select one</option>
<option value="twitter">Twitter / X</option>
<option value="linkedin">LinkedIn</option>
<option value="hacker-news">Hacker News</option>
<option value="product-hunt">Product Hunt</option>
<option value="google">Google Search</option>
<option value="friend">Friend / Colleague</option>
<option value="blog">Blog Post</option>
<option value="other">Other</option>
</select>
</div>
<div class="field">
<label for="use-case">What would you use this for? (optional)</label>
<textarea id="use-case" name="fi-text-use_case" rows="2" placeholder="Help us understand your needs..."></textarea>
</div>
<button type="submit">Join Waitlist →</button>
<p id="waitlist-status" aria-live="polite"></p>
<p class="note">We'll notify you when early access is available. No spam, ever.</p>
</form>
<script>
const FORM_ID = 'YOUR-FORM-ID'; // ← Replace with your Form ID
const form = document.getElementById('waitlist-form');
const status = document.getElementById('waitlist-status');
const button = form.querySelector('button[type="submit"]');
const script = document.createElement('script');
script.src = 'https://forminit.com/sdk/v1/forminit.js';
script.onload = () => {
const forminit = new Forminit();
form.addEventListener('submit', async (e) => {
e.preventDefault();
status.textContent = 'Joining...';
button.disabled = true;
button.textContent = 'Joining...';
const formData = new FormData(form);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
status.textContent = error.message;
status.className = 'status error';
button.disabled = false;
button.textContent = 'Join Waitlist →';
return;
}
// Replace form with success message
form.innerHTML = `
<div class="success-state">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22 4 12 14.01 9 11.01"/>
</svg>
<h3>You're on the list!</h3>
<p>We'll be in touch when early access is ready.</p>
</div>
`;
});
};
document.body.appendChild(script);
</script>
Handling Form Submission Response
Section titled “Handling Form Submission Response”The SDK returns { data, redirectUrl, error }:
On success:
{
data: {
hashId: "7LMIBoYY74JOCp1k",
date: "2026-01-01 21:10:24",
blocks: {
sender: {
firstName: "John",
lastName: "Doe",
email: "john@example.com"
},
message: "Hello from Astro!"
}
},
redirectUrl: "https://forminit.com/thank-you"
}
On error:
{
error: {
error: "ERROR_CODE",
code: 400,
message: "Human-readable error message"
}
}
Redirect After Submission
Section titled “Redirect After Submission”const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);
if (error) {
status.textContent = error.message;
return;
}
// Redirect to thank-you page
window.location.href = '/thank-you';
Create a thank-you page:
---
// src/pages/thank-you.astro
import Layout from '../layouts/Layout.astro';
---
<Layout title="Thank You">
<main class="thank-you">
<h1>Thank You!</h1>
<p>Your message has been received. We'll get back to you shortly.</p>
<a href="/">← Back to Home</a>
</main>
</Layout>
Project Structure Example
Section titled “Project Structure Example”my-astro-site/
├── src/
│ ├── components/
│ │ ├── ContactForm.astro
│ │ ├── QuoteRequestForm.astro
│ │ ├── WaitlistForm.astro
│ │ └── ContactFormReact.tsx # Optional React island
│ ├── layouts/
│ │ └── Layout.astro
│ └── pages/
│ ├── index.astro
│ ├── contact.astro
│ └── thank-you.astro
├── public/
├── astro.config.mjs
└── package.json
Build and Deploy
Section titled “Build and Deploy”# Development
npm run dev
# Build
npm run build
# Preview production build
npm run preview
Deploy the dist/ folder to Netlify, Vercel, Cloudflare Pages, or any static host.
Next Steps
Section titled “Next Steps”- View your submissions in the Forminit dashboard
- Set up email notifications for new submissions
- Explore webhook integrations for advanced workflows