NoVPS
PricingFAQDocumentationBlog
Sign InSign Up
Tutorials

How to add Formspree to static sites without backend code

Mark Hayes

Wed, Feb 11, 2026

Main picture

When you're building a static site—whether it's a landing page, portfolio, or documentation site—you eventually hit the same wall: forms. You need contact forms, newsletter signups, or feedback forms, but you don't want to spin up a backend server just to handle form submissions.

That's exactly where Formspree comes in. It's a form backend service that handles form submissions for you, sending the data to your email or integrating with other tools. No server setup, no PHP scripts, no Node.js backend—just HTML forms that actually work.

In this guide, I'll walk you through everything you need to know about adding Formspree to your static sites, from the basics to advanced configurations that can handle real production scenarios.

Why Formspree makes sense for static sites

Static sites have made a huge comeback. They're fast, secure, cheap to host, and easy to deploy. Tools like Next.js, Hugo, Jekyll, and simple HTML/CSS sites are everywhere. But the moment you need to process form data, you're forced to either:

  1. Add a backend (Node.js, Python, PHP)
  2. Use serverless functions (AWS Lambda, Vercel Functions)
  3. Use a form service like Formspree

For many projects, especially in the early stages, option 3 is the smartest choice. Here's why:

  • No infrastructure management — you don't need to maintain servers, worry about uptime, or handle scaling
  • Instant setup — literally 5 minutes from signup to working form
  • Built-in spam protection — reCAPTCHA and honeypot fields included
  • Email notifications — instant alerts when forms are submitted
  • Form data storage — submissions are stored in your Formspree dashboard
  • Integrations — connect to Slack, Google Sheets, webhooks, and more

The trade-off? You're depending on a third-party service. For a solo founder with limited runway, that's usually a much better trade-off than spending days building and maintaining your own form backend.

Getting started with Formspree: the basics

Let's start with the simplest possible implementation. You've got a contact form on your static site, and you want submissions sent to your email.

Step 1: Create a Formspree account

Head to formspree.io and sign up for a free account. The free tier gives you 50 submissions per month, which is plenty for most early-stage projects.

Step 2: Create your first form

After logging in, click "New Form" and give it a name like "Contact Form". Formspree will generate a unique form endpoint—something like https://formspree.io/f/xwkgpqnj.

Step 3: Update your HTML form

Here's where the magic happens. Take your existing HTML form and point its action attribute to your Formspree endpoint:

That's it. When someone submits this form, Formspree receives the data and emails it to you. The form submission includes all the field names and values you defined in your HTML.

Step 4: Customize the thank you page

By default, Formspree redirects users to a generic thank you page. You probably want to redirect them to your own page instead. Add a hidden input:

Now users will be redirected to your custom thank you page after submitting.

Advanced Formspree configurations

Once you've got the basics working, there are several powerful features you should know about to make your forms production-ready.

Spam protection with honeypots

Spam is inevitable once your site gets any traffic. Formspree includes honeypot spam protection—a hidden field that bots fill out but humans don't see:

Any submission with the _gotcha field filled out gets automatically rejected. This catches most basic bots without bothering your users with CAPTCHAs.

Adding reCAPTCHA for extra protection

For forms that get hit hard by spam, enable Google reCAPTCHA in your Formspree dashboard. Once enabled, add the reCAPTCHA site key to your form:

This adds the familiar "I'm not a robot" checkbox. It's more friction for users, but sometimes necessary.

Customizing email subjects

By default, emails from Formspree use a generic subject line. Make them more useful with the _subject field:

You can even make it dynamic by including form data:


File uploads

Need users to upload files? Formspree supports file attachments on paid plans. Just add a file input:

The file gets attached to the email notification and stored in your Formspree dashboard.

Using Formspree with JavaScript frameworks

Most modern sites use JavaScript frameworks. Here's how to integrate Formspree with popular frameworks while maintaining a good user experience.

React/Next.js integration

Instead of a basic HTML form, you'll want to handle submissions with JavaScript to provide instant feedback without page reloads:

import { useState } from 'react';

export default function ContactForm() {
  const [status, setStatus] = useState('');
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    const form = e.target;
    const data = new FormData(form);
    
    try {
      const response = await fetch('https://formspree.io/f/xwkgpqnj', {
        method: 'POST',
        body: data,
        headers: {
          'Accept': 'application/json'
        }
      });
      
      if (response.ok) {
        setStatus('SUCCESS');
        form.reset();
      } else {
        setStatus('ERROR');
      }
    } catch (error) {
      setStatus('ERROR');
    }
  };
  
  return (
    

Message sent successfully!

Something went wrong. Please try again.

Integrating Formspree with other tools

Form submissions are just data. The real power comes from connecting that data to your workflow.

Slack notifications

In your Formspree dashboard, you can add Slack integration. Every form submission triggers a Slack message to your chosen channel. This is incredibly useful for support forms or lead capture—you get instant notifications without constantly checking email.

Google Sheets integration

For forms that need data analysis or sharing with non-technical team members, connect Formspree to Google Sheets. Each submission becomes a new row in your spreadsheet, making it easy to track trends, export data, or share with stakeholders.

Webhook forwarding

For more complex workflows, use Formspree's webhook feature. Every form submission sends a POST request to your webhook URL with the form data as JSON:

{
  "email": "user@example.com",
  "message": "This is a test message",
  "_gotcha": "",
  "_subject": "New contact form submission"
}

This lets you trigger custom automation, update your CRM, or kick off other processes based on form submissions.

When Formspree might not be enough

Formspree is excellent for straightforward form handling, but there are scenarios where you'll need something more:

Complex validation logic — If you need sophisticated server-side validation beyond basic required fields and email formatting, you'll need custom backend code.

Immediate data processing — When form submissions need to trigger real-time actions (creating user accounts, processing payments, complex business logic), you need a proper backend.

Very high volume — If you're expecting thousands of form submissions per month, the costs can add up. At that scale, it might be more cost-effective to build your own solution.

Custom data storage — If you need form data stored in your own database with full control over the schema and queries, you'll need your own backend.

PII and compliance requirements — For highly sensitive data with strict compliance requirements (HIPAA, for example), you might need more control over how and where data is stored.

Alternative approaches for different scenarios

Depending on your specific needs, here are some alternatives to consider:

Netlify Forms — If you're already hosting on Netlify, their built-in form handling is seamless and included in most plans. It works similarly to Formspree but with tighter integration to your deployment pipeline.

Serverless functions — If you need more control but don't want to manage a full backend, serverless functions (Vercel Functions, Netlify Functions, AWS Lambda) let you write custom form handling code that only runs when needed. This is a middle ground between third-party services and full backends.

Managed infrastructure platforms — For projects that have outgrown simple form services but aren't ready for the complexity of managing their own infrastructure, platforms like NoVPS.io provide a middle ground. You get the flexibility of running your own backend code (including custom form processing) without the operational overhead of managing servers, databases, and scaling. This makes sense when you need custom business logic, database integration, or processing that goes beyond what form services can handle, but you want to stay focused on building features rather than managing infrastructure.

Real-world example: implementing a multi-step form

Let's tie everything together with a practical example. You're building a landing page for your SaaS product and need a multi-step form that collects user information, company details, and specific requirements.

Here's how you'd structure it with Formspree:

import { useState } from 'react';

export default function MultiStepForm() {
  const [step, setStep] = useState(1);
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    company: '',
    employees: '',
    requirements: ''
  });
  const [status, setStatus] = useState('');
  
  const updateField = (field, value) => {
    setFormData(prev => ({ ...prev, [field]: value }));
  };
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const submitData = new FormData();
    Object.keys(formData).forEach(key => {
      submitData.append(key, formData[key]);
    });
    
    try {
      const response = await fetch('https://formspree.io/f/xwkgpqnj', {
        method: 'POST',
        body: submitData,
        headers: { 'Accept': 'application/json' }
      });
      
      if (response.ok) {
        setStatus('SUCCESS');
      } else {
        setStatus('ERROR');
      }
    } catch (error) {
      setStatus('ERROR');
    }
  };
  
  if (status === 'SUCCESS') {
    return 
Thanks! We'll be in touch soon.
; } return (
{step === 1 && (

Your information

updateField('name', e.target.value)} required /> updateField('email', e.target.value)} required />
)} {step === 2 && (

Company details

updateField('company', e.target.value)} required />
)} {step === 3 && (

Your requirements