Skip to content

Forms on Static Sites — Formspree, Netlify Forms & Alternatives

DodaTech Updated 2026-06-22 8 min read

In this tutorial, you'll learn about Forms on Static Sites. We cover key concepts, practical examples, and best practices.

Static sites cannot process form submissions natively since there is no backend server — third-party form services bridge this gap by receiving form data and forwarding it via email, webhooks, or API storage without requiring a single line of server-side code.

What You'll Learn

Why It Matters

Contact forms, newsletter signups, and user surveys are essential for engagement, but static sites lack server-side processing. Integrating a form backend without maintaining a server keeps your site secure, fast, and free from database vulnerabilities. Each form service offers different trade-offs in pricing, privacy, customization, and data ownership.

Real-World Use

A portfolio site uses Formspree's free tier for a contact form that emails submissions directly. A SaaS landing page uses Netlify Forms with spam filtering and webhook integration to push leads into a CRM. A documentation site uses a custom API Gateway endpoint with Lambda for enterprise-grade form handling.

Form Architecture

flowchart LR
  A[Static HTML Form] --> B[Submit Action]
  B --> C{Form Service}
  C -->|Formspree| D[Email Forwarding]
  C -->|Netlify Forms| E[Netlify Dashboard]
  C -->|Custom API| F[Lambda Function]
  C -->|Google Forms| G[Google Sheets]
  D --> H[Your Inbox]
  E --> I[Webhook]
  I --> J[CRM/API]
  style C fill:#f90,color:#fff

Form Service Comparison

Feature Formspree Netlify Forms Google Forms Custom API
Free tier 50 submissions/mo 100 submissions/mo Unlimited Varies
Spam protection Honeypot + CAPTCHA Honeypot + reCAPTCHA Built-in Custom
Email notifications Yes Yes Yes Custom
File uploads Paid plan Paid plan Yes Yes
Webhook integration Paid plan Yes No Yes
Data export CSV export CSV + API Google Sheets Direct DB
Self-hosted No No No Yes
Setup time 5 minutes 10 minutes 2 minutes 1-2 hours

Formspree Integration

Formspree provides a public endpoint that accepts form submissions and forwards them as email.

Basic Contact Form

<form action="https://formspree.io/f/xyzabcde" method="POST">
  <label for="name">Name</label>
  <input type="text" id="name" name="name" required>

  <label for="email">Email</label>
  <input type="email" id="email" name="email" required>

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="5" required></textarea>

  <!-- Honeypot spam prevention (hidden from users) -->
  <input type="text" name="_gotcha" style="display:none">

  <button type="submit">Send Message</button>
</form>

Expected behavior: When the user submits the form, Formspree validates the submission, checks the honeypot field (if filled, silently discards as spam), and forwards the data to the email address configured in your Formspree dashboard.

Netlify Forms Integration

Netlify Forms detects HTML forms in your static site during build and processes submissions automatically.

Form with Netlify Attributes

<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
  <!-- Required: hidden form name for Netlify detection -->
  <input type="hidden" name="form-name" value="contact">

  <!-- Honeypot field -->
  <p class="hidden" style="display:none">
    <label>Don't fill this out: <input name="bot-field"></label>
  </p>

  <label>Name <input type="text" name="name" required></label>
  <label>Email <input type="email" name="email" required></label>
  <label>Subject
    <select name="subject">
      <option value="general">General Inquiry</option>
      <option value="support">Technical Support</option>
      <option value="billing">Billing</option>
    </select>
  </label>
  <label>Message <textarea name="message" required></textarea></label>
  <button type="submit">Submit</button>
</form>

Custom Success Page with AJAX

<form name="contact" method="POST" data-netlify="true" id="contact-form">
  <input type="hidden" name="form-name" value="contact">
  <label>Email <input type="email" name="email" required></label>
  <label>Message <textarea name="message" required></textarea></label>
  <button type="submit">Send</button>
</form>

<div id="form-success" style="display:none">
  <p>Thank you. We will respond within 24 hours.</p>
</div>

<script>
  const form = document.getElementById('contact-form');
  form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const data = new FormData(form);
    try {
      const response = await fetch('/', { method: 'POST', body: data });
      if (response.ok) {
        form.style.display = 'none';
        document.getElementById('form-success').style.display = 'block';
      }
    } catch (error) {
      alert('Submission failed. Please try again.');
    }
  });
</script>

Expected behavior: Netlify detects the data-<a href="/web-servers-hosting/netlify/">netlify</a>="true" attribute during build, creates the form endpoint automatically, and processes submissions. The AJAX version submits without page reload and shows a custom success message.

Spam Protection Strategies

Method How It Works Effectiveness
Honeypot Hidden field that bots fill but humans skip Good against simple bots
reCAPTCHA Google's visual or invisible challenge Very good, but privacy concerns
Time-based Reject submissions made in under 3 seconds Good against automated tools
Rate limiting Max N submissions per IP per hour Essential for public forms
Keyword filtering Block submissions with spam patterns Secondary defense

Serverless Form Backend

For complete control, deploy a custom form handler using Cloudflare Workers or AWS Lambda:

// Cloudflare Workers — Form handler example
export default {
  async fetch(request) {
    if (request.method === 'POST') {
      const formData = await request.formData();
      const name = formData.get('name');
      const email = formData.get('email');
      const message = formData.get('message');

      // Validate inputs
      if (!email || !message) {
        return new Response('Missing required fields', { status: 400 });
      }

      // Send email via SendGrid or similar API
      const emailBody = {
        to: 'contact@example.com',
        from: 'forms@example.com',
        subject: `New contact from ${name}`,
        text: `From: ${name} (${email})\n\n${message}`
      };

      await fetch('https://api.sendgrid.com/v3/mail/send', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${SENDGRID_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(emailBody)
      });

      return Response.redirect('https://example.com/thank-you', 302);
    }

    return new Response('Method not allowed', { status: 405 });
  }
}

Expected behavior: The Worker intercepts POST requests, validates input, sends an email via the SendGrid API, and redirects the user to a thank-you page. The API key is stored as a Worker secret, never exposed to the client.

Common Errors

1. Missing form-name Hidden Field in Netlify Forms

Netlify Forms requires a hidden form-name input matching the form's name attribute. Without it, submissions fail silently. Always include <input type="hidden" name="form-name" value="contact">.

2. CORS Errors with Formspree

Submitting to Formspree from a different domain than the one registered in your Formspree dashboard causes CORS errors. Register all domains that will use the form endpoint in the Formspree dashboard settings.

3. Form Not Detected During Build

Netlify scans HTML during build to detect forms. If your form is rendered by JavaScript (e.g., a React component), Netlify misses it. Use static HTML forms with data-<a href="/web-servers-hosting/netlify/">netlify</a>="true" or the Netlify Forms JS client.

4. Spam Bypassing Honeypot Fields

Sophisticated bots can detect and skip honeypot fields. Combine honeypot with rate limiting and hidden timestamps for stronger protection. Add a server-side timestamp check to reject forms submitted in under 3 seconds.

5. Unsubmitted Forms After Redirect

If the user navigates away before the form submission completes, the data is lost. Use AJAX submission with loading state and prevent navigation with beforeunload events. Show a clear success message after submission completes.

Practice Questions

1. How does a honeypot field prevent spam?

A honeypot is a hidden form field that bots automatically fill but humans cannot see. If the field has a value on submission, the form service silently discards it as spam.

2. What is the difference between Formspree and Netlify Forms?

Formspree provides a standalone endpoint URL that receives submissions and forwards them as email. Netlify Forms integrates into the Netlify platform, detecting forms during build and providing a dashboard, webhooks, and spam filtering.

3. Why might a form not be detected during a Netlify build?

Netlify scans static HTML during build. If the form is injected by JavaScript or rendered by a client-side framework, Netlify cannot detect it. Use static HTML forms with the data-<a href="/web-servers-hosting/netlify/">netlify</a>="true" attribute.

4. What are the privacy concerns with reCAPTCHA?

reCAPTCHA sends user behavior data to Google servers, which may violate privacy regulations like GDPR. Alternatives include honeypot fields, time-based validation, and Cloudflare Turnstile.

5. Challenge: Build a static site contact form that submits to a Cloudflare Worker. The Worker should validate the input, send an email notification, store the submission in KV storage (via Cloudflare KV), and return a JSON response with a success message. Add rate limiting to allow a maximum of 3 submissions per IP per hour.

Mini Project: Multi-Form Lead Capture System

Build a lead capture system for a static marketing site:

  1. Create a "Contact Us" form using Formspree with honeypot spam protection
  2. Create a "Newsletter Signup" form using Netlify Forms with webhook integration
  3. Create a "Report Bug" form using a custom Cloudflare Worker endpoint
  4. Add AJAX submission for all forms with inline validation
  5. Implement a rate-limiting layer that prevents abuse across all forms
  6. Test each form by submitting sample data and verifying delivery

Start with the HTML structure:

<form action="https://formspree.io/f/your-id" method="POST">
  <input type="text" name="_gotcha" style="display:none">
  <input type="email" name="email" placeholder="Your email" required>
  <textarea name="message" placeholder="Your message" required></textarea>
  <button type="submit" class="btn btn-primary">Send</button>
</form>

Verify each submission arrives correctly and check that spam prevention blocks automated submissions. Monitor the form service dashboard for submission analytics.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro