How to Add a Contact Form to Any Static Website (Without a Backend)
Source: Dev.to
Introduction
Static sites are fast, cheap, and easy to deploy, but they lack a built‑in way to handle form submissions. Without a backend (PHP, Node, database, etc.) you need a third‑party service to collect data, send email notifications, and protect against spam.
This guide shows three approaches—from the simplest two‑minute setup to fully custom solutions—using SnapForm as the example service.
Option 1: Plain HTML Form with SnapForm Backend
The quickest way is to point your HTML form to a SnapForm endpoint. SnapForm stores submissions, sends email notifications, and provides spam protection (honeypot) out of the box.
Basic Form
<form action="https://snapform.cc/api/f/YOUR_FORM_ID" method="POST">
<label>
Name
<input type="text" name="name" required />
</label>
<label>
Email
<input type="email" name="email" required />
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<button type="submit">Send Message</button>
</form>
No JavaScript, no build step, no dependencies. Every submission appears in your SnapForm dashboard and triggers email notifications.
Free Features
- Email notifications
- File uploads (most services charge for this)
- Spam protection (honeypot, no CAPTCHA)
- CSV export
Adding File Uploads
<form action="https://snapform.cc/api/f/YOUR_FORM_ID" method="POST" enctype="multipart/form-data">
<label>
Attach your resume
<input type="file" name="resume" />
</label>
<button type="submit">Submit</button>
</form>
Supported file types: JPEG, PNG, GIF, PDF, DOC/DOCX, XLS/XLSX, TXT, CSV, ZIP.
Uploading Files with JavaScript
const form = document.querySelector('form');
const formData = new FormData(form);
// Do NOT set Content-Type; the browser adds the multipart boundary automatically
const res = await fetch('https://snapform.cc/api/f/YOUR_FORM_ID', {
method: 'POST',
body: formData,
});
Tip: When uploading files via
fetch, omit theContent-Typeheader so the browser can set the correct multipart boundary.
Option 2: Handling Submissions with JavaScript (No Page Redirect)
If you prefer a single‑page experience (e.g., in React, Vue, Astro), intercept the form submit and send the data via fetch.
const form = document.querySelector('#contact-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
try {
const res = await fetch('https://snapform.cc/api/f/YOUR_FORM_ID', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (res.ok) {
form.reset();
alert('Message sent!');
} else {
alert('Something went wrong. Please try again.');
}
} catch (err) {
alert('Network error. Please try again.');
}
});
This works from any domain because SnapForm supports CORS.
React Example
import { useState } from 'react';
function ContactForm() {
const [status, setStatus] = useState('idle');
async function handleSubmit(e) {
e.preventDefault();
setStatus('sending');
const data = Object.fromEntries(new FormData(e.target));
const res = await fetch('https://snapform.cc/api/f/YOUR_FORM_ID', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
setStatus(res.ok ? 'sent' : 'error');
}
if (status === 'sent')
return <p>Thanks! Message received.</p>;
return (
<form id="contact-form" onSubmit={handleSubmit}>
{/* form fields go here */}
<button type="submit" disabled={status === 'sending'}>
{status === 'sending' ? 'Sending...' : 'Send'}
</button>
</form>
);
}
Option 3: Advanced Integrations (Webhooks, API)
Sending Submissions to Slack, Zapier, or Your Own API
Add a webhook URL in the SnapForm dashboard. Each submission triggers a POST request with this payload:
{
"formId": "your-form-id",
"formName": "Contact Form",
"submissionId": "abc123",
"data": {
"name": "Jane Doe",
"email": "jane@example.com",
"message": "Hello!"
},
"createdAt": "2026-02-28T10:30:00.000Z"
}
Programmatic Access (Pro/Business Plans)
You can query forms and submissions via the API.
# List all forms
curl -H "Authorization: Bearer sk_live_xxx" \
https://snapform.cc/api/v1/forms
# Get submissions with pagination
curl -H "Authorization: Bearer sk_live_xxx" \
"https://snapform.cc/api/v1/forms/FORM_ID/submissions?page=1&limit=10"
Useful for building custom dashboards, syncing to a CRM, or scheduled exports.
Pricing Comparison
| Service | Free Tier (submissions/mo) | Paid Tier (price) | File Uploads |
|---|---|---|---|
| SnapForm | 50 | $9/mo for 2,000 | Yes |
| Formspree | 50 | $24/mo for 1,000 | No |
| Getform | 50 | $16/mo for 1,000 | No |
| FormSubmit | Unlimited | Free | No |
Conclusion
For most static sites, Option 1 (plain HTML + SnapForm backend) is sufficient. It works everywhere—GitHub Pages, Vercel, Netlify, Cloudflare Pages, or a local HTML file—without any JavaScript, backend, or CAPTCHA.
If you need a smoother user experience, use Option 2 to handle submissions via JavaScript. For complex workflows, integrate Option 3 with webhooks or the SnapForm API.