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:
- Add a backend (Node.js, Python, PHP)
- Use serverless functions (AWS Lambda, Vercel Functions)
- 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:
<form action="https://formspree.io/f/xwkgpqnj" method="POST"> <label for="email">Your Email:</label> <input type="email" name="email" id="email" required> <label for="message">Message:</label> <textarea name="message" id="message" required></textarea> <button type="submit">Send</button> </form>
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:
<input type="hidden" name="_next" value="https://yourdomain.com/thanks">
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:
<form action="https://formspree.io/f/xwkgpqnj" method="POST"> <input type="text" name="_gotcha" style="display:none"> <!-- Your regular form fields --> <input type="email" name="email" required> <textarea name="message" required></textarea> <button type="submit">Send</button> </form>
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:
<form action="https://formspree.io/f/xwkgpqnj" method="POST"> <input type="email" name="email" required> <textarea name="message" required></textarea> <div class="g-recaptcha" data-sitekey="your_site_key"></div> <button type="submit">Send</button> </form> <script src="https://www.google.com/recaptcha/api.js" async defer></script>
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:
<input type="hidden" name="_subject" value="New contact form submission from website">
You can even make it dynamic by including form data:
<input type="text" name="name" placeholder="Your name"> <input type="hidden" name="_subject" value="Contact from website">
File uploads
Need users to upload files? Formspree supports file attachments on paid plans. Just add a file input:
<form action="https://formspree.io/f/xwkgpqnj" method="POST" enctype="multipart/form-data"> <input type="email" name="email" required> <input type="file" name="attachment"> <button type="submit">Send</button> </form>
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 ( <form onSubmit={handleSubmit}> <input type="email" name="email" required /> <textarea name="message" required /> <button type="submit">Send</button> {status === 'SUCCESS' && <p>Thanks! We'll be in touch.</p>} {status === 'ERROR' && <p>Oops! Something went wrong.</p>} </form> ); }
This gives you full control over the submission process and lets you show loading states, error messages, and success messages without leaving the page.
Vue.js integration
The Vue approach is similar, using the framework's reactivity:
<template> <form @submit.prevent="handleSubmit"> <input v-model="email" type="email" name="email" required> <textarea v-model="message" name="message" required></textarea> <button type="submit">Send</button> <p v-if="status === 'success'">Message sent successfully!</p> <p v-if="status === 'error'">Something went wrong. Please try again.</p> </form> </template> <script> export default { data() { return { email: '', message: '', status: null } }, methods: { async handleSubmit() { const formData = new FormData(); formData.append('email', this.email); formData.append('message', this.message); try { const response = await fetch('https://formspree.io/f/xwkgpqnj', { method: 'POST', body: formData, headers: { 'Accept': 'application/json' } }); this.status = response.ok ? 'success' : 'error'; if (response.ok) { this.email = ''; this.message = ''; } } catch (error) { this.status = 'error'; } } } } </script>
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 <div>Thanks! We'll be in touch soon.</div>; } return ( <form onSubmit={handleSubmit}> {step === 1 && ( <div> <h2>Your information</h2> <input type="text" placeholder="Name" value={formData.name} onChange={(e) => updateField('name', e.target.value)} required /> <input type="email" placeholder="Email" value={formData.email} onChange={(e) => updateField('email', e.target.value)} required /> <button type="button" onClick={() => setStep(2)}>Next</button> </div> )} {step === 2 && ( <div> <h2>Company details</h2> <input type="text" placeholder="Company name" value={formData.company} onChange={(e) => updateField('company', e.target.value)} required /> <select value={formData.employees} onChange={(e) => updateField('employees', e.target.value)} required > <option value="">Team size</option> <option value="1-10">1-10</option> <option value="11-50">11-50</option> <option value="51+">51+</option> </select> <button type="button" onClick={() => setStep(1)}>Back</button> <button type="button" onClick={() => setStep(3)}>Next</button> </div> )} {step === 3 && ( <div> <h2>Your requirements</h2> <textarea placeholder="Tell us what you need" value={formData.requirements} onChange={(e) => updateField('requirements', e.target.value)} required /> <button type="button" onClick={() => setStep(2)}>Back</button> <button type="submit">Submit</button> </div> )} {status === 'ERROR' && ( <p>Something went wrong. Please try again.</p> )} </form> ); }
This example shows how you can build sophisticated form experiences while still using Formspree as your backend. The entire form state lives in React, giving you full control over the user experience, but the actual data processing and storage happens through Formspree.
Making the right choice for your project
Here's how I think about the decision when starting a new project:
Start with Formspree if:
- You need forms working today, not next week
- Form volume is low to moderate (under a few hundred submissions per month)
- You don't have specific compliance requirements
- The form data doesn't need to integrate with complex business logic
Consider alternatives if:
- You're already deep in a specific ecosystem (NoVPS.io, Netlify, Vercel) that has integrated solutions
- You need real-time processing or complex workflows triggered by submissions
- Volume is high enough that costs become a concern
- You're building something where the form handling is a core feature, not a supporting one
Build your own when:
- You have specific compliance requirements that third-party services can't meet
- The form submission triggers complex, immediate business logic
- You need complete control over the data pipeline
- You're at scale where the cost of a service exceeds the cost of maintaining your own solution
For most early-stage projects, especially if you're a solo founder trying to validate an idea quickly, Formspree hits the sweet spot. It's not the most flexible solution, but it gets forms working in minutes instead of days, and that time savings is often exactly what you need when runway is limited.
The key is being honest about where you are and what you actually need right now, not what you might need in some hypothetical future. You can always migrate later. What you can't do is get back the days spent over-engineering a form handler when you should have been talking to customers.


