How to Add a Contact Form to Any Static Website (Without a Backend)

Published: (February 28, 2026 at 10:57 AM EST)
4 min read
Source: Dev.to

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 the Content-Type header 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

ServiceFree Tier (submissions/mo)Paid Tier (price)File Uploads
SnapForm50$9/mo for 2,000Yes
Formspree50$24/mo for 1,000No
Getform50$16/mo for 1,000No
FormSubmitUnlimitedFreeNo

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.

0 views
Back to Blog

Related posts

Read more »

Did Your Project Really Need Next.js?

Introduction Recently, I’ve been seeing more and more teams migrating projects from Next.js to TanStack. Cases like Inngest, which reduced local dev time by 83...