# Static Forms > Static Forms is a secure form backend for static websites. It lets teams collect form submissions without server-side code and deliver them by email, webhooks, Google Sheets, Zapier, and dashboard storage. Use this file to find concise, canonical documentation for integrating forms with Static Forms. The primary submission endpoint is `https://api.staticforms.dev/submit`, and forms authenticate by including an account `apiKey` field in a POST request. Prefer the documentation links for implementation details, examples, request fields, spam protection setup, and troubleshooting. Product and optional links provide broader context when needed. ## Product - [Home](https://www.staticforms.dev/): Product overview for the Static Forms form backend. - [Features](https://www.staticforms.dev/features): Overview of form delivery, spam protection, integrations, and dashboard features. - [Pricing](https://www.staticforms.dev/pricing): Plan limits, billing options, trials, and feature availability. - [Examples](https://www.staticforms.dev/examples): Example form patterns for common static-site use cases. - [Templates](https://www.staticforms.dev/templates): Ready-made form templates that can be adapted for different sites. - [Contact](https://www.staticforms.dev/contact): Support and sales contact page. ## Getting Started - [Introduction](https://www.staticforms.dev/docs): Welcome to Static Forms - [Quick Start](https://www.staticforms.dev/docs/quick-start): Create your first form in 5 minutes - [How It Works](https://www.staticforms.dev/docs/how-it-works): The form submission flow end-to-end - [App Tour](https://www.staticforms.dev/docs/app-tour): Inbox, Forms, Workspace, and Account - [Per-form vs Workspace Settings](https://www.staticforms.dev/docs/per-form-vs-workspace): Where each setting lives in v3 ## Forms - [Forms Overview](https://www.staticforms.dev/docs/forms/overview): Creating, organizing, and managing forms - [Form Basics](https://www.staticforms.dev/docs/forms/form-basics): HTML form fundamentals and field naming - [Create a Form](https://www.staticforms.dev/docs/forms/create-a-form): The new-form flow in the app - [Form Editor Tour](https://www.staticforms.dev/docs/forms/editor-tour): The tabs at a glance - [Form Builder](https://www.staticforms.dev/docs/forms/form-builder): Design a form visually and embed it with one script tag - [General](https://www.staticforms.dev/docs/forms/general): Name, status, API key, embed snippet, and data retention - [Delivery](https://www.staticforms.dev/docs/forms/delivery): Recipients, CC, webhooks, Sheets, Slack - [Rules](https://www.staticforms.dev/docs/forms/rules): Conditional routing, actions, and dry-run testing (Pro/Agency) - [Custom Domain](https://www.staticforms.dev/docs/forms/custom-domain): Send from your own domain (Pro) - [Branding](https://www.staticforms.dev/docs/forms/branding): Remove branding, submission & reply email templates (Pro) - [Auto-reply](https://www.staticforms.dev/docs/forms/auto-reply): Templated confirmation emails - [AI Reply](https://www.staticforms.dev/docs/forms/ai-reply): AI-generated responses (Pro) - [Security](https://www.staticforms.dev/docs/forms/security): Captchas, honeypot, and domain restriction - [Honeypot Field](https://www.staticforms.dev/docs/forms/security/honeypot): Invisible bot trap - [reCAPTCHA](https://www.staticforms.dev/docs/forms/security/recaptcha): Google reCAPTCHA v2 and v3 setup - [Cloudflare Turnstile](https://www.staticforms.dev/docs/forms/security/turnstile): Cloudflare Turnstile setup - [hCaptcha](https://www.staticforms.dev/docs/forms/security/hcaptcha): hCaptcha setup - [ALTCHA](https://www.staticforms.dev/docs/forms/security/altcha): Privacy-friendly captcha - [Domain Restriction](https://www.staticforms.dev/docs/forms/security/domain-restriction): Whitelist approved submission domains - [Spam Filter](https://www.staticforms.dev/docs/forms/spam-filter): Content-based spam routing - [Application Forms](https://www.staticforms.dev/docs/forms/application-forms): Job applications with file uploads - [File Uploads](https://www.staticforms.dev/docs/forms/file-uploads): Handle file attachments ## Inbox - [Inbox Overview](https://www.staticforms.dev/docs/inbox/overview): View, search, and manage submissions - [Managing Submissions](https://www.staticforms.dev/docs/inbox/managing-submissions): Reply, mark, delete, and inspect delivery - [Notifications](https://www.staticforms.dev/docs/inbox/notifications): How submission emails work ## Workspace - [Workspace Overview](https://www.staticforms.dev/docs/workspace/overview): Team, plan, billing, credits, and domain - [Team](https://www.staticforms.dev/docs/workspace/team): Invite collaborators with role-based access - [Plan & Usage](https://www.staticforms.dev/docs/workspace/plan): Tier limits and usage metrics - [Billing](https://www.staticforms.dev/docs/workspace/billing): Invoices, payment method, and overage billing - [AI Credits](https://www.staticforms.dev/docs/workspace/credits): Top up and track AI reply credits - [Custom Domain](https://www.staticforms.dev/docs/workspace/custom-domain): Register workspace-wide sender domains ## Account - [Profile](https://www.staticforms.dev/docs/account/profile): Name, email, and avatar - [Security](https://www.staticforms.dev/docs/account/security): Sign-in and connected accounts - [Notifications](https://www.staticforms.dev/docs/account/notifications): Email preferences - [API Key](https://www.staticforms.dev/docs/account/api-key): Where API keys live in v3 - [Delete Account](https://www.staticforms.dev/docs/account/delete): Permanently remove your account ## Integrations - [Overview](https://www.staticforms.dev/docs/integrations): Connect Static Forms to other services - [Slack](https://www.staticforms.dev/docs/integrations/slack): Send submissions to Slack channels - [Discord](https://www.staticforms.dev/docs/integrations/discord): Send submissions to Discord channels - [Telegram](https://www.staticforms.dev/docs/integrations/telegram): Post submissions to a Telegram chat - [Make](https://www.staticforms.dev/docs/integrations/make): Visual automation platform - [n8n](https://www.staticforms.dev/docs/integrations/n8n): Self-hosted workflow automation - [Google Sheets](https://www.staticforms.dev/docs/integrations/google-sheets): Save submissions to spreadsheets - [Notion](https://www.staticforms.dev/docs/integrations/notion): Save to Notion databases - [Airtable](https://www.staticforms.dev/docs/integrations/airtable): Store in Airtable bases - [Mailchimp](https://www.staticforms.dev/docs/integrations/mailchimp): Add subscribers to a Mailchimp audience ## Framework Guides - [Overview](https://www.staticforms.dev/docs/framework-examples): Framework integrations - [React](https://www.staticforms.dev/docs/react): React integration guide - [Next.js](https://www.staticforms.dev/docs/nextjs): Next.js integration guide - [Vue.js](https://www.staticforms.dev/docs/vue): Vue.js integration guide - [Angular](https://www.staticforms.dev/docs/angular): Angular integration guide - [Svelte](https://www.staticforms.dev/docs/svelte): Svelte integration guide - [Astro](https://www.staticforms.dev/docs/astro): Astro integration guide - [Plain HTML](https://www.staticforms.dev/docs/html): Vanilla HTML forms ## Platform Guides - [Netlify](https://www.staticforms.dev/docs/netlify): Deploy on Netlify - [Vercel](https://www.staticforms.dev/docs/vercel): Deploy on Vercel - [Cloudflare Pages](https://www.staticforms.dev/docs/cloudflare-pages): Deploy on Cloudflare - [GitHub Pages](https://www.staticforms.dev/docs/github-pages): Deploy on GitHub Pages - [WordPress](https://www.staticforms.dev/docs/wordpress): WordPress integration - [Webflow](https://www.staticforms.dev/docs/webflow): Webflow integration - [Wix](https://www.staticforms.dev/docs/wix): Wix integration ## API Reference - [Complete Reference](https://www.staticforms.dev/docs/api-reference): Endpoints, parameters, responses, and error codes ## Troubleshooting - [Common Issues](https://www.staticforms.dev/docs/troubleshooting): Solutions to common problems - [Debugging](https://www.staticforms.dev/docs/debugging): Debug form submissions - [CORS Issues](https://www.staticforms.dev/docs/cors-issues): Fixing CORS errors - [Email Delivery](https://www.staticforms.dev/docs/email-delivery): Email troubleshooting ## Help - [FAQ](https://www.staticforms.dev/docs/faq): Frequently asked questions - [Get Support](https://www.staticforms.dev/docs/support): Contact support - [Changelog](https://www.staticforms.dev/docs/changelog): Recent updates ## Optional - [Blog](https://www.staticforms.dev/blog): Tutorials, announcements, and implementation guides. - [Alternatives](https://www.staticforms.dev/alternatives): Comparisons with other form backend and static-site form services. - [Tools](https://www.staticforms.dev/tools): Free tools for building, checking, and debugging forms. - [Form Checker](https://www.staticforms.dev/tools/form-checker): Tool for validating Static Forms markup and configuration. - [Form Generator](https://www.staticforms.dev/tools/form-generator): Tool for generating starter form markup. - [Webhook Tester](https://www.staticforms.dev/tools/webhook-tester): Tool for testing webhook payload delivery. - [Privacy Policy](https://www.staticforms.dev/privacy-policy): Privacy and data handling policy. - [Terms](https://www.staticforms.dev/terms): Terms of service for Static Forms. - [Full Content](https://www.staticforms.dev/llms-full.txt): Same index plus inlined Markdown content for every published blog post. # Blog (Full Content) ## EU Data Residency for Submissions and File Uploads Source: https://www.staticforms.dev/blog/eu-data-residency-form-submissions-file-uploads Date: 2026-07-26 Author: Static Forms Team Every form backend makes a quiet decision on your behalf: where the data lands. For most of them the answer is "somewhere in the United States", and you only find out when a customer sends you a DPA questionnaire asking which country their applicants' CVs are stored in. Static Forms now lets you make that decision yourself. Pick **European Union** when you sign up and your form data — submissions, file attachments, webhook delivery logs and AI reply logs — is stored in AWS `eu-central-1` (Frankfurt) instead of `us-east-1`. Not replicated to the US, not cached in the US, not written to a US table first and copied over. The row and the uploaded file are created in Frankfurt and read back from Frankfurt. This post is the engineering detail behind that sentence: how a submission is routed, where an attachment physically sits, how the download link is signed, and — the part most residency announcements skip — the things that deliberately stay in the United States for every account. The exact legal scope is in our [DPA](https://www.staticforms.dev/dpa); this is the same scope, written for developers. --- ## The split: control plane vs data plane The architecture has one rule, and everything else follows from it. **Account state is global. Form data is regional.** | Stays global (US) | Regional (follows your pick) | |---|---| | Your user record, login, billing | Form submissions | | Form/workspace configuration and API keys | File attachments (S3 objects + file records) | | Rate-limit counters, storage quota counters | Webhook delivery logs | | Email delivery (SES) | AI reply logs | | Operational request logs | Submission retention and cleanup | Why keep the control plane global? Because it is what lets there be **one product**. There is no `eu.staticforms.dev`, no second dashboard, no separate login, no parallel release train, and no second API key to manage. You log in at [staticforms.dev](https://www.staticforms.dev), and your API key resolves the same way regardless of region. Only the tables and buckets your submissions live in change. > Your account record — owner email and anonymized consent IP — stays in the US `users` table for every account, including EU ones. We state that plainly in the DPA rather than engineering around it, because pretending otherwise would be the interesting kind of lie. --- ## What happens to a submission There is a single ingest endpoint for everyone: `https://api.staticforms.dev/submit`. You do not swap it for a regional URL, and your HTML does not change. ```html Contact Us
``` Behind that request: 1. **The API key is resolved in the global control plane.** One lookup tells us which account owns the form, what plan it is on, and which region it is pinned to. 2. **Validation runs before anything is stored** — spam checks, CAPTCHA, rate limits, plan limits. Nothing region-specific here. 3. **The submission row is written to the owning account's region.** For an EU account that is the Frankfurt `form_submissions` table. Not a US table with a region flag on it — a different table, in a different AWS region. 4. **Every later write follows the same route.** Delivery status, read/unread, spam and not-spam, timeline entries, webhook attempt logs, AI reply logs. They are all keyed to the region resolved in step 1, including the ones written asynchronously by queue consumers, because the region travels inside the queue payload. The consequence worth internalizing: **an EU account's submission never exists as a row in `us-east-1`.** We verified that the hard way before shipping — after a full end-to-end pass (signup, submission, attachment, inbox, mutations, export, webhook, AI reply, deletion) the US tables held **zero** rows for that account across submissions, file uploads, webhook logs and AI reply logs. --- ## What happens to a file attachment File uploads are the part where residency usually leaks, because a file has two lives: the bytes in object storage, and the metadata row that makes them findable. Both are regional. Adding uploads to the form above is one attribute and one input — again, identical for both regions: ```html
``` What the platform does with `document`: - ✅ **The bytes are uploaded to your region's private bucket.** Each region has its own S3 bucket — bucket names are globally unique, so this is a genuinely separate bucket in a separate AWS region, with Block Public Access on and `AES256` server-side encryption. - ✅ **The file record is written to your region's `file_uploads` table**, alongside the submission it belongs to. The record — original filename, MIME type, size, object key — is data-subject content, so it lives where the submission lives. - ✅ **Downloads are signed against the regional bucket.** Clicking a download in your [Inbox](https://www.staticforms.dev/app/inbox) mints a fresh presigned `GET` URL that expires in **5 minutes**, signed for the bucket the object is actually in. For an EU account the bytes travel from Frankfurt to your browser — there is no US proxy hop and no US copy to serve from. - ✅ **Storage quota accounting stays global.** The counter is a number of bytes on your account record, not form data, so it lives in the control plane with the rest of your account state. The bytes it counts never leave your region. - ✅ **Expiry and deletion happen in region.** Delete a submission and its file records and S3 objects are removed from your region's table and bucket. The retention sweep visits **every** region rather than assuming US, so an EU account's expired attachments are actually collected instead of lingering forever. One deliberate failure mode, since it affects what you see: if persistent storage is unavailable for a file, the submission is still accepted and the file is still delivered by email, but no download button appears for it in the inbox. A storage problem never costs you a lead — and it never quietly falls back to storing the file in another region either. File uploads are a paid-plan feature (4.5MB per file). See [file uploads in the docs](https://www.staticforms.dev/docs/forms/file-uploads) for limits and framework snippets, or the walkthrough in [Next.js contact form with document upload](/blog/nextjs-contact-form-document-upload). --- ## Teams that span regions An agency owner pinned to the EU can invite a teammate whose own account is US-region — or who signed up before regions existed at all. Routing that teammate's reads by *their* region would query an empty Frankfurt-less table and render the workspace as if it had no data. So routing follows **the account that owns the data**, never the person looking at it. A US-based teammate opening an EU workspace reads from Frankfurt, and when they mark a submission read, reply, or unmark spam, the write lands in Frankfurt too. Nothing about cross-region team access pulls a copy into the US. --- ## What EU residency does not move This is the section we would rather you read twice than discover later. Selecting the EU region relocates **storage of form data**. It does not relocate everything, and we will not market it as "your data stays in the EU". - **Email delivery is US, for every account.** Notification and auto-responder emails are sent through AWS SES in `us-east-1`, with Singapore (`ap-southeast-1`) as delivery failover. The email carries the submission, so this is a transmission out of the region — disclosed in our [DPA](https://www.staticforms.dev/dpa) §8 and in the [Transfer Impact Assessment](https://www.staticforms.dev/dpa/transfer-impact-assessment), and covered by SCCs / the EU-U.S. Data Privacy Framework. SES does not retain message content, but it does keep a per-region suppression list of recipient addresses. - **Operational request logs are US, for every account.** Request metadata — IP, user agent, referer, delivery status, and form field **names** — is logged in the US for 365 days. **Field values are never logged**, by design and enforced in code: the submit path logs the keys of your payload, never the contents. Keeping this pipeline single-region is what keeps our abuse tooling working; an attacker should not be able to buy log immunity by picking a region at signup. - **Account and billing data are US, for every account** (see the table above). The supportable claim is precise: *form data stored in the EU, email delivered from the US, operational logs in the US.* That is what the DPA, the sub-processor table, Annex III and the TIA all say, and it is what the product actually does. --- ## Choosing and changing your region - **At signup.** The region picker sits above both signup paths — email and Google — and defaults to United States. It applies to the account on its **first** write, so an OAuth signup gets the region you picked, not a US default it has to be rescued from later. - **After signup, while you are empty.** You can change the region from your [account page](https://www.staticforms.dev/app/account) for as long as the account holds **no** stored form data. The check runs server-side across every region — submissions and file uploads, not just the pinned region — and fails closed: if the check itself errors, the change is refused. Refusing a change is recoverable; splitting an inbox across two continents is not. - **Once your first submission arrives, the pin is final.** Not a policy choice, a physical one: the rows and objects are in that region, and there is no migration that keeps a live inbox coherent while it runs. The account page shows the pin as locked with the reason. Pick deliberately, then. If you are collecting personal data from EU residents — job applications, patient intake, member records — the EU region is the one that removes the third-country transfer for that data at rest. --- ## Adding regions from here The region registry is config, not code: a region is an entry with an AWS region, a label, an uploads-bucket environment variable and an email-send region. Every submission, file, log and cleanup path resolves through that registry, so adding a third region (APAC is the obvious next one) means provisioning infrastructure and adding a registry entry — not auditing hundreds of call sites again. If you need residency in a geography we do not serve yet, [tell us which one](https://www.staticforms.dev/contact). That request is now a provisioning ticket rather than a rewrite. --- ## Get started 1. **New account?** Choose your region on the [signup page](https://www.staticforms.dev/register) — United States (default) or European Union (Frankfurt). 2. **Existing account with no submissions yet?** Set it from your [account page](https://www.staticforms.dev/app/account) before your first submission lands. 3. **Need the exact wording for a customer questionnaire?** The [DPA](https://www.staticforms.dev/dpa), the [sub-processor list](https://www.staticforms.dev/dpa/sub-processors) and the [Transfer Impact Assessment](https://www.staticforms.dev/dpa/transfer-impact-assessment) all name the regions, the sub-processors and the transfer mechanisms line by line. 4. **Just getting started with forms?** Begin with [Getting started with Static Forms](/blog/getting-started-with-static-forms), then add [privacy-first spam protection with Altcha](/blog/implementing-altcha-captcha-html-website) — no third-party tracking, which pairs well with an EU-resident inbox. That's it. Same endpoint, same API key, same dashboard — your form data just lives where you need it to. --- ## Web3Forms vs Static Forms: Which Is Better in 2026? Source: https://www.staticforms.dev/blog/web3forms-vs-static-forms-comparison Date: 2026-07-05 Author: Static Forms Team Web3Forms and Static Forms solve the same problem: they let you add a working contact form to a static site without running a backend, a database, or any server code. You drop in an HTML form, add a key, and submissions arrive in your inbox. Both are simple to set up, so the real decision comes down to price, spam protection, and which features you get. This is a practical, side-by-side look at both. We make Static Forms, so treat this as our take, but every number below is real and we'll point out the few places Web3Forms does well. > **Bottom line up front:** Both services are easy to set up, so simplicity isn't the deciding factor. Static Forms gives you double the free submissions (500 vs 250), half the Pro price for 2.5x the volume ($9 for 25,000 vs $18 for 10,000), and features Web3Forms doesn't offer at all, like AI auto-replies, a privacy-first CAPTCHA, and a custom sending domain. For almost any form that sees real traffic, Static Forms is the better value and the more capable tool. ## Quick Comparison Overview | Feature | Web3Forms | Static Forms | |---------|-----------|--------------| | **Free Tier** | 250 submissions/month | 500 submissions/month | | **Entry Paid Plan** | $18/mo (Pro: 10,000) | $9/mo (Pro: 25,000) | | **Setup Difficulty** | Very easy | Very easy | | **Free Forms** | Unlimited | Unlimited | | **AJAX/JSON Support** | ✅ Yes | ✅ Yes | | **Free Spam Protection** | Honeypot + hCaptcha + server-side check | Honeypot + reCAPTCHA v2 + CleanTalk filtering | | **File Uploads** | ✅ Yes (Pro) | ✅ Yes (Pro) | | **Webhooks** | ✅ Yes (Pro) | ✅ Yes (Pro) | | **Integrations** | ~4 apps | 11+ apps | | **AI-Powered Auto-Reply** | ❌ No | ✅ Yes (Pro) | | **Privacy-First CAPTCHA (Altcha)** | ❌ No | ✅ Yes (Pro) | | **Custom Sending Domain** | ❌ No | ✅ Yes (Pro) | | **Submission History** | 30 days free / 1 year paid | 30 days free / up to 5 years paid | ## What Web3Forms Does Well To be fair, Web3Forms is a capable free tool, and it's worth naming what it gets right: - **Automatic server-side spam filtering** on the free plan with no configuration. - **A large, active community** with polished, framework-specific examples. - **Unlimited forms even on free**, so you can reuse one key across a whole site. Those are real strengths. The catch is that Static Forms matches Web3Forms on simplicity and free-tier form limits, then pulls ahead on almost everything else: price, volume, spam options, integrations, and features. Here's where that plays out. ## Pricing Breakdown ### Web3Forms Pricing **Free, $0:** - 250 submissions per month - Unlimited access keys (forms) - 30-day submission history - Honeypot spam field + server-side spam check - hCaptcha support **Starter, $5/month (billed yearly):** - 5,000 submissions per month - Aimed at students, startups, and non-profits **Pro, $18/month:** - 10,000 submissions per month - 1-year submission history - File attachments and advanced file uploader - Webhooks, autoresponder, CC emails, domain restriction - Advanced spam protection (reCAPTCHA v3, Cloudflare Turnstile) **Agency, higher tier:** - 20,000 submissions per month, team-oriented ### Static Forms Pricing **Free, $0:** - 500 submissions per month - Unlimited forms - 30-day submission history - Honeypot + Google reCAPTCHA v2 - CleanTalk server-side spam filtering (automatic) **Pro, $9/month ($90/year):** - 25,000 submissions per month - File uploads (up to 4.5MB per file) - Webhooks and 11+ app integrations - Auto-responder emails + AI-powered auto-reply - reCAPTCHA v3, Cloudflare Turnstile, hCaptcha, and privacy-first Altcha - Custom sending domain + sender display name - Remove Static Forms branding - Submission history up to 5 years **Agency, $19/month ($192/year):** - 30,000 submissions per month - Team members and child/agency account management - Larger file storage quota and higher rate/AI limits ### What It Costs You Here's the practical cost at different volumes: | Monthly Submissions | Web3Forms | Static Forms | |---------------------|-----------|--------------| | Up to 250 | Free | Free | | 251 to 500 | $5/mo (Starter, yearly) | Free | | 501 to 10,000 | $18/mo (Pro) | $9/mo (Pro) | | 10,001 to 25,000 | Agency tier | $9/mo (Pro) | | 25,001 to 30,000 | Agency tier | $19/mo (Agency) | **The headline:** Static Forms gives you **twice the free submissions** (500 vs 250) and, on the entry paid plan, **2.5x the volume for half the price**, which is $9 for 25,000 vs $18 for 10,000. On cost per submission, it isn't close. ## Setup and Implementation Both use the same "key in the HTML" model, so setup is quick and beginner-friendly either way. ### Web3Forms You request a public **access key** by entering your email, then paste it into your form: ```html
``` The trade-off: file uploads, webhooks, and autoresponders all require the $18/mo Pro plan. ### Static Forms You create a free account in about two minutes, then use your `apiKey` with a single submit endpoint: ```html
``` AJAX/JSON works on every plan: ```javascript fetch('https://api.staticforms.dev/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, message, apiKey: 'YOUR_API_KEY' }) }) ``` Both are about as simple as form backends get. The difference is what you unlock: with Static Forms, that quick signup gives you a full dashboard and twice the free quota from day one, plus every advanced feature waiting on a $9 plan instead of an $18 one. ## Spam Protection Both services run automatic server-side spam checks, which is one of the best reasons to use a form backend at all. The difference is what you can configure for free. **Web3Forms (Free):** honeypot field, a built-in server-side check, and hCaptcha. reCAPTCHA v3 and Cloudflare Turnstile are Pro-only. **Static Forms (Free):** honeypot, Google reCAPTCHA v2, CleanTalk server-side filtering, and rate limiting. On Pro you also get reCAPTCHA v3, Turnstile, hCaptcha, and Altcha. ```html
``` Static Forms gives you more spam-fighting options on the free plan, and one feature Web3Forms can't match at any price: [Altcha CAPTCHA](/blog/implementing-altcha-captcha-html-website), a privacy-first challenge that stops bots without cookies or third-party tracking. If GDPR or user privacy matters, that's a clear win for Static Forms. ## Feature Comparison ### Email Notifications Both send submissions to your inbox and offer autoresponders and CC emails on paid plans. Static Forms adds multiple recipients with custom reply-to, plus a **custom sending domain and sender display name** on Pro, so emails come from your own address instead of a shared one. That improves deliverability and looks more professional to the people filling out your form. ### File Uploads Both gate uploads behind their Pro plan. Web3Forms includes an advanced file uploader; Static Forms supports files up to 4.5MB each (PDF, DOC, DOCX, images, and more): ```html
``` Since both require Pro, the deciding factor is price and volume, where Static Forms is half the cost with 2.5x the submissions. ### Webhooks & Integrations Web3Forms covers the basics on Pro with webhooks and around four integrations (Google Sheets, Notion, Slack). Static Forms includes 11+ on Pro: - Slack, Discord, Telegram - Google Sheets, Notion, Airtable, Mailchimp - Zapier, Make, n8n - Custom webhooks If you want submissions flowing into your existing tools without wiring everything up yourself, Static Forms covers far more ground. ### AI-Powered Auto-Reply This is the clearest gap between the two: Static Forms has **AI Reply**, and Web3Forms has nothing like it. AI Reply generates a contextual, personalized response to each submission using Anthropic Claude models. You upload your own knowledge base so replies reflect your product and docs, and it runs on pay-as-you-go credits on the Pro plan. It's ideal for support triage, FAQ automation, and lead nurturing, turning "we'll get back to you" into an instant, relevant answer. Web3Forms offers standard autoresponder templates on Pro, but no AI-generated responses. Learn more about [AI Reply](/docs/ai-reply). ### Custom Redirects Both redirect the user after submission, just with a different field name: ```html ``` ### Form Building & Submission History Both do client- and server-side validation. Static Forms adds field-level custom error messages and a **visual form builder**, and it retains submissions far longer on paid plans, up to 5 years (configurable) versus Web3Forms' 1 year. Both keep 30 days on their free tiers. ## Framework Support Neither locks you into a framework. Both work anywhere you can submit an HTML form or make a `fetch` request. Static Forms uses the standard fetch API, so the same code works in React, Vue, Angular, Svelte, or plain JS: ```jsx const handleSubmit = async (e) => { e.preventDefault(); await fetch('https://api.staticforms.dev/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...formData, apiKey }) }); }; ``` Detailed guides: - [HTML form to email without a server](/blog/html-form-send-email-without-server) - [React contact form tutorial](/blog/react-contact-form-email-tutorial) - [Next.js integration](/blog/using-staticforms-with-nextjs) ## Privacy & GDPR Web3Forms' CAPTCHA options all rely on third-party services (hCaptcha, reCAPTCHA, Turnstile), with no cookie-free option. Static Forms is GDPR-focused: it offers [Altcha](/blog/implementing-altcha-captcha-html-website) (no tracking, no cookies), encrypts data at rest and in transit, and lets you send from your own domain. For privacy-sensitive projects, Static Forms is the safer choice. ## Migrating From Web3Forms to Static Forms If you're already on Web3Forms, switching takes minutes and your HTML structure stays the same: **1. Update the form action:** ```html
``` **2. Swap the key field:** ```html ``` **3. Rename fields if needed:** `redirect` becomes `redirectTo`. That's it. ## Which Should You Choose? **Web3Forms may be enough if** it's a hobby project or a low-traffic contact page, you never expect to need integrations or AI, and you're happy with hCaptcha for spam. **Static Forms is the better choice if you want any of these,** which covers most real projects: - Lower cost as you grow (500 free submissions, $9 for 25,000) - AI auto-replies and a privacy-first Altcha CAPTCHA - More integrations, a custom sending domain, and longer submission history - A full dashboard and a visual form builder ## The Bottom Line Both tools do the core job well and are equally quick to set up, so simplicity won't decide this for you. Value and features will, and on both counts Static Forms comes out ahead: you pay less at every paid volume, keep double the free headroom, and get capabilities Web3Forms doesn't offer at all, including AI auto-replies, privacy-first CAPTCHA, custom sending domains, and 11+ integrations. Ready to try it? [Sign up for Static Forms](https://www.staticforms.dev) and start free with 500 submissions a month. Upgrade to Pro ($9/mo) when you need file uploads, AI Reply, and advanced spam protection, or see the full [pricing details](/pricing). More guides to get you started: - [HTML form to email without a server](/blog/html-form-send-email-without-server) - [React contact form tutorial](/blog/react-contact-form-email-tutorial) - [Next.js tutorial](/blog/using-staticforms-with-nextjs) --- ## The Best Form Backends for Hugo and Jekyll in 2026 Source: https://www.staticforms.dev/blog/best-form-backends-hugo-jekyll Date: 2026-06-14 Author: Static Forms Team Hugo and Jekyll generate static sites with no server behind them. That's exactly why they're fast and cheap to host — and exactly why a plain `` has nowhere to send its data. To make contact forms work, you point them at a *form backend*: a hosted endpoint that receives the submission, blocks spam, and emails it to you (or forwards it onward). There are a handful of ways to do this, and they're not equal for static sites built with Hugo or Jekyll. This guide walks through the realistic options, what each is good and bad at, and how to pick. ## What a form backend actually has to do Before comparing options, it helps to be clear about the job. For a Hugo or Jekyll site, a good form backend needs to: - **Accept a submission from a purely static page** — no server-side code on your side. - **Filter spam** without forcing a clunky CAPTCHA on every visitor. - **Deliver reliably to email**, with a usable reply-to address. - **Optionally route data elsewhere** — a spreadsheet, Slack, a webhook. - **Not lock you in** — your form is just HTML posting to a URL, so switching should be trivial.
Which approach fits your static site?
🔌
Hosted form backend (recommended)
Post HTML to an endpoint. Zero server, spam filtering and email built in. Best fit for Hugo & Jekyll.
🏗️
Host-bundled forms
Convenient if you stay on one host — but ties your form to that platform.
⚙️
Roll your own serverless function
Maximum control, but you own deliverability, spam, and maintenance.
📧
mailto: link
No backend at all — but it's not really a form. Avoid for anything real.
## Option 1: A hosted form backend (best fit for most sites) This is the approach built for exactly this problem. You add a normal HTML form to your Hugo partial or Jekyll include, set the `action` to a hosted endpoint, and you're done. The service receives the POST, screens it for spam, and emails you the message. **Why it suits Hugo and Jekyll:** there's nothing to deploy or maintain on your end, it works identically on every host, and your form stays portable — it's just markup pointing at a URL. [Static Forms](https://www.staticforms.dev) is built around this model. A working form is a single HTML block: ```html
``` The `honeypot` field catches the bulk of spam bots without a CAPTCHA, `replyTo="@"` makes the submitter's address your reply-to, and there's a free tier to start. You can also forward submissions to a [Google Sheet](https://www.staticforms.dev/integrations/google-sheets), [Slack](https://www.staticforms.dev/integrations/slack), or any endpoint via [webhooks](https://www.staticforms.dev/integrations/webhooks). Other hosted backends in this category include Formspree, Getform, Basin, and Web3Forms — the model is similar across them, so the differentiators are spam handling, deliverability, integrations, and pricing. **Best for:** almost any Hugo or Jekyll site. It's the lowest-effort option that still does the job properly. ## Option 2: Forms bundled with your host Some static hosts bundle a form-handling feature — Netlify Forms is the best-known example, and Cloudflare and others have their own takes. You add an attribute or special markup, and the host intercepts submissions during the build or at the edge. **The upside:** it's convenient if you're already on that host and never plan to leave. **The catch:** your form is now coupled to the platform. Move your Hugo or Jekyll site to a different host and the form stops working until you rewire it. There are also usually tight limits on submissions before you hit a paid tier. **Best for:** sites firmly committed to one host, with light form volume. ## Option 3: Your own serverless function If you want total control, you can write a small serverless function — on Vercel, Cloudflare Workers, AWS Lambda, or similar — that receives the POST, sends an email through a provider, and screens for spam. Your Hugo or Jekyll form posts to that function's URL. **The upside:** you own the whole pipeline and can do anything. **The cost:** you now own *everything* — email deliverability (and the SPF/DKIM headaches that come with it), spam filtering, error handling, and ongoing maintenance. For a contact form, that's a lot of surface area to babysit. **Best for:** teams with specific custom logic and the appetite to maintain it. ## Option 4: A plain mailto: link (not really a form) The zero-backend "solution" is a `mailto:` link or a form with `action="mailto:..."`. It opens the visitor's email client instead of submitting anywhere. In practice it's a poor experience: it depends on the visitor having a configured desktop mail client, exposes your address to scrapers, and captures nothing structured. It's fine as a fallback link, not as your contact form. **Best for:** a quick "email me" link — not a real contact form. ## Feature comparison at a glance Here's how the four approaches stack up on the things that actually matter for a Hugo or Jekyll site: | | Hosted backend | Host-bundled | Own serverless | mailto: | |---|---|---|---|---| | **Server code to maintain** | None | None | Yes — you own it | None | | **Works on any host** | ✅ | ❌ tied to host | ✅ | ✅ | | **Built-in spam filtering** | ✅ | Partial | You build it | ❌ | | **Email delivery handled** | ✅ | ✅ | You configure SPF/DKIM | N/A | | **Route to Sheets / Slack / webhooks** | ✅ | Limited | You build it | ❌ | | **Captures structured data** | ✅ | ✅ | ✅ | ❌ | | **Setup time** | Minutes | Minutes | Hours+ | Minutes | | **Portable (just HTML)** | ✅ | ❌ | Mostly | ✅ | The pattern is clear: a hosted backend gives you the least to maintain while still ticking every box that matters. The host-bundled option trades portability for convenience, and rolling your own trades time and maintenance for control. ## What about spam? Spam is the number-one reason a naive contact form becomes unusable, so it deserves its own look. A static site can't run server-side filtering on its own, which leaves three layers worth understanding: - **Honeypot fields.** A hidden input that humans never fill but bots do. It's invisible to visitors, requires no extra clicks, and catches the bulk of automated spam. Every example in our Hugo and Jekyll tutorials includes one. - **Server-side screening.** A good hosted backend scores submissions and drops obvious junk before it ever reaches your inbox — something a `mailto:` link or a bare serverless function won't do unless you build it. - **CAPTCHA, only when needed.** If sophisticated spam slips through, you can add a challenge like reCAPTCHA, Cloudflare Turnstile, or Altcha. The trick is to treat it as a fallback rather than a default, since every CAPTCHA adds friction for real visitors. The right order is honeypot first, server-side screening always, and a CAPTCHA only if the first two aren't enough. ## How to choose For the large majority of Hugo and Jekyll sites, a **hosted form backend (Option 1)** is the right answer: it matches the static-site model exactly, requires no infrastructure, and keeps your form portable across hosts. Reach for a host-bundled option only if you're locked to one platform anyway, or a custom serverless function only when you genuinely need custom logic and will maintain it. If you want to see the hosted approach end to end, we have step-by-step tutorials for [Jekyll on GitHub Pages](https://www.staticforms.dev/blog/jekyll-contact-form-github-pages) and [Hugo](https://www.staticforms.dev/blog/hugo-contact-form-tutorial), plus a general primer on [sending form data without a server](https://www.staticforms.dev/blog/html-form-send-email-without-server). ## Switching is painless One underrated advantage of the hosted approach: there's almost nothing to migrate. Your form is just an HTML `
` with an `action` URL and a few hidden fields. Moving from one backend to another — or moving your whole Hugo or Jekyll site to a new host — means changing the endpoint URL and the key, and nothing else. Compare that to a host-bundled solution, where leaving the host means rebuilding form handling from scratch, or a custom serverless function, where the whole pipeline moves with you. Keeping the form as portable markup is the safest long-term bet for a static site. ## Frequently asked questions **Do I need to know any backend programming?** No. With a hosted backend your only job is to write the HTML form and point it at an endpoint. The server-side work is handled for you. **Will a form backend slow down my static site?** No. The form is static HTML that loads with the page. The backend only does work when someone actually submits, and that request goes directly from the visitor's browser to the API — your site stays as fast as ever. **Can I use the same backend for Hugo and Jekyll?** Yes. The approach is identical across static site generators — only the templating (a Hugo partial vs. a Jekyll include) differs. The form markup and endpoint are the same. **Is there a free option?** Yes — Static Forms has a free plan that's plenty for a typical contact form. You can see the limits and paid tiers on the [pricing page](https://www.staticforms.dev/pricing). ## Wrapping up A static site doesn't mean a static contact page. The cleanest way to make forms work on Hugo and Jekyll is to keep the form as plain HTML and let a hosted backend do the server-side work — so you keep the speed and simplicity that made you choose a static generator in the first place. [Start free with Static Forms](https://www.staticforms.dev) and have a working form on your site in a few minutes. --- ## How to Add a Contact Form to a Hugo Site (No Backend Required) Source: https://www.staticforms.dev/blog/hugo-contact-form-tutorial Date: 2026-06-14 Author: Static Forms Team Hugo builds blazing-fast static sites in seconds, which is exactly why it's a favorite for blogs, docs, and marketing sites. But that speed comes from generating plain HTML at build time — there's no running server to catch a form submission. So the first time a visitor wants to email you through a contact page, you hit a wall: Hugo has no built-in way to process forms. The clean solution is to post your form to a form backend that handles validation, spam filtering, and email delivery for you. In this tutorial you'll add a working contact form to a Hugo site using [Static Forms](https://www.staticforms.dev), package it as a reusable partial and shortcode, and wire it up so it works with any theme — including popular ones like PaperMod and Ananke. ## Why Hugo needs a form backend Hugo's whole model is "render everything ahead of time." When someone hits your site, they're downloading static files from a CDN — fast and secure, but with nothing on the other end to receive a `POST`. A form backend fills that gap by giving you a single endpoint to submit to. Hugo's job is just to render the ``; the backend does the rest.
One partial, reused everywhere
layouts/partials/
contact-form.html
Define the form once
Contact page
Footer
Any blog post (shortcode)
📬
Static Forms API
→ your inbox
## Prerequisites You'll need: - A Hugo site (any theme) that builds locally with `hugo server` - A Static Forms account — [sign up for free](https://www.staticforms.dev) - Your API key from the [Static Forms dashboard](https://www.staticforms.dev/app/forms) ## Step 1: Store your API key in site config Keep the key in one place so you never hard-code it across templates. Add it to `hugo.toml` (or `config.toml` on older Hugo versions): ```toml [params] staticFormsKey = "YOUR_API_KEY_HERE" ``` This is a public form key — it ships in your site's HTML like any client-side form field. It only authorizes submissions to your account, so it's safe to commit. ## Step 2: Create a reusable partial Hugo partials are the equivalent of includes. Create `layouts/partials/contact-form.html`: ```go-html-template
``` The key fields: - **`apiKey`** pulls your key from site config and routes the submission to your account. - **`replyTo`** set to `@` uses the visitor's email as the reply-to address, so you reply right from your inbox. - **`honeypot`** silently drops bot submissions — bots fill every field, humans never see this one. ## Step 3: Add a contact page Create `content/contact.md`. Hugo doesn't render raw HTML inside Markdown by default, so the cleanest approach is to call the partial from a template. If your theme has a single-page layout you can extend, add the partial there; the most portable route is a dedicated layout. Create `layouts/page/contact.html`: ```go-html-template {{ define "main" }}

{{ .Title }}

{{ .Content }} {{ partial "contact-form.html" . }}
{{ end }} ``` Then set the layout in the page's front matter: ```yaml --- title: "Contact" layout: "contact" --- Have a question? Drop me a line below. ``` ## Step 4: Use it anywhere with a shortcode To let yourself (or other authors) embed the form inside any Markdown post, wrap the partial in a shortcode. Create `layouts/shortcodes/contact.html`: ```go-html-template {{ partial "contact-form.html" . }} ``` Now any content file can include the form inline: ```markdown Want to work together? {{}} ``` ## Step 5: Theme-specific notes The partial works with any theme, but a couple of popular ones have a natural home for it: - **PaperMod** — PaperMod ships with a `single.html` layout. The simplest path is the dedicated `layouts/page/contact.html` above, which overrides the theme for just that page. Don't edit files inside `themes/` directly; put your overrides in your project's root `layouts/` so theme updates don't clobber them. - **Ananke** — Ananke renders page content through `.Content`. The dedicated layout approach works the same way. If you'd rather keep everything in Markdown, enable raw HTML by adding `[markup.goldmark.renderer]` with `unsafe = true` to your config — but the partial-and-shortcode approach keeps your content clean and avoids that flag. ## Step 6: Build, deploy, and test Run your normal build and deploy — Hugo sites work the same on Netlify, Cloudflare Pages, GitHub Pages, or any static host: ```bash hugo --minify ``` Open your contact page on the deployed site and send a test message. The first submission triggers a one-time verification email from Static Forms — click the link, and every submission afterward flows straight to your inbox. By default the API returns a plain success response. To send visitors to a thank-you page instead, add a redirect field to the partial: ```go-html-template ``` The full field reference lives in the [Static Forms documentation](https://www.staticforms.dev/docs/html). ## Step 7: Style the form to match your theme The partial inherits your theme's styles, but a contact form usually deserves a bit of intention. Add a stylesheet at `assets/css/contact-form.css` and pull it into your form layout, or drop the rules into your theme's main stylesheet: ```css .contact-form { display: flex; flex-direction: column; gap: 1rem; max-width: 32rem; } .contact-form input, .contact-form textarea { width: 100%; padding: 0.65rem 0.75rem; font: inherit; border: 1px solid #cbd5e1; border-radius: 6px; } .contact-form input:focus, .contact-form textarea:focus { outline: 2px solid #7c3aed; outline-offset: 1px; border-color: transparent; } .contact-form button { align-self: flex-start; padding: 0.65rem 1.25rem; font: inherit; font-weight: 600; color: #fff; background: #7c3aed; border: none; border-radius: 6px; cursor: pointer; } ``` If you're on a recent Hugo with the asset pipeline, fingerprint and link it from your form layout: ```go-html-template {{ with resources.Get "css/contact-form.css" | minify | fingerprint }} {{ end }} ``` ## Submit without leaving the page (AJAX) The plain form works with zero JavaScript. If you'd rather keep visitors on the page, submit with `fetch()`. Create a small script — for example `assets/js/contact-form.js` — and include it from your form layout: ```javascript const form = document.querySelector('.contact-form'); form?.addEventListener('submit', async (event) => { event.preventDefault(); const data = Object.fromEntries(new FormData(form).entries()); try { const response = await fetch('https://api.staticforms.dev/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...data, replyTo: '@' }), }); const result = await response.json(); if (result.success) { form.outerHTML = '

Thanks — your message is on its way!

'; } else { throw new Error(result.message); } } catch { form.insertAdjacentHTML( 'beforeend', '

Something went wrong. Please try again.

' ); } }); ``` Because the `apiKey` is already a hidden field in the partial, `new FormData(form)` picks it up automatically — no need to repeat it in the script. The honeypot rides along in the payload too, so spam protection still applies. ## Multilingual contact forms Hugo's first-class multilingual support is one of its biggest draws, and your contact form should follow suit. Move the user-facing strings into your i18n translation files (`i18n/en.toml`, `i18n/fr.toml`, and so on): ```toml # i18n/en.toml [contactName] other = "Name" [contactMessage] other = "Message" [contactSend] other = "Send message" ``` Then reference them in the partial with the `i18n` function: ```go-html-template ``` The same single endpoint handles every language — only the labels change. You can even set a per-language email subject so you know which site version a message came from. ## Going further - **Spreadsheets and tools.** Forward submissions to a sheet with the [Google Sheets integration](https://www.staticforms.dev/integrations/google-sheets), or send them anywhere via [webhooks](https://www.staticforms.dev/integrations/webhooks). - **AJAX submission.** Prefer an inline success message over a page navigation? Submit with `fetch()` — the pattern is the same one we use in the [JAMstack contact form guide](https://www.staticforms.dev/blog/jamstack-contact-form). - **Other generators.** Using more than one static site generator? We have matching tutorials for [Astro](https://www.staticforms.dev/blog/astro-contact-form-tutorial) and other frameworks. ## Troubleshooting The most common snags on a Hugo setup: - **`partial "contact-form.html" not found`.** The partial must live at `layouts/partials/contact-form.html` in your project root (not inside `themes/`). Hugo resolves project-level layouts first, which is also how you safely override a theme. - **Raw HTML in Markdown is being stripped.** Hugo's Goldmark renderer drops raw HTML by default for safety. That's why this guide uses a partial and a shortcode instead of pasting `
` into a `.md` file. If you must inline HTML, set `markup.goldmark.renderer.unsafe = true` in your config — but the partial approach is cleaner. - **The form submits but no email arrives.** Confirm the `action` is exactly `https://api.staticforms.dev/submit` and your key is correct, then check for the one-time verification email (including spam) and click its link. - **The shortcode prints literally instead of rendering.** Make sure you're using the shortcode syntax with percent or angle brackets — `{{}}` — and that the file is at `layouts/shortcodes/contact.html`. - **Styles aren't applying.** If you use the asset pipeline snippet, confirm the CSS file is under `assets/` (not `static/`) — `resources.Get` only reads from `assets/`. ## Frequently asked questions **Does this work with any Hugo theme?** Yes. The partial and shortcode live in your project root, so they work regardless of theme and survive theme updates. **Is the API key safe in the HTML?** Yes — it's a public form key that only authorizes submissions to your account. It ships in the page source like any client-side form field and can't be used to read your data. **Can I have more than one form on a site?** Absolutely. Reuse the same partial anywhere, and set a different `subject` value per form so you can tell submissions apart in your inbox. **What does it cost?** A free plan covers typical personal and small-business use. For higher volume, file uploads, or advanced spam controls, see the [pricing page](https://www.staticforms.dev/pricing). ## Wrapping up Hugo doesn't process forms, and it doesn't need to. Define the form once as a partial, expose it as a shortcode, and post to a form backend that handles validation, spam, and email. You keep Hugo's speed and simplicity, and your visitors get a contact form that just works — across every theme. [Create a free Static Forms account](https://www.staticforms.dev) and add a contact form to your Hugo site today. --- ## How to Add a Contact Form to a Jekyll Site on GitHub Pages Source: https://www.staticforms.dev/blog/jekyll-contact-form-github-pages Date: 2026-06-14 Author: Static Forms Team Jekyll is the static site generator that powers GitHub Pages, which is part of why it's so popular: push Markdown to a repo and your site is live. The catch shows up the moment you need a contact form. GitHub Pages serves static files only — there's no server to receive a POST request, no database to store a message, and no way to send an email. And because GitHub Pages runs Jekyll in `safe` mode, you can't install a Jekyll plugin to do it either. The fix is to hand form submissions off to a form backend. In this tutorial you'll add a fully working contact form to a Jekyll site on GitHub Pages using [Static Forms](https://www.staticforms.dev), wrap it in a reusable Jekyll include, and have submissions land in your inbox — without writing or hosting any server code. ## Why Jekyll on GitHub Pages can't process forms A traditional contact form posts to a script on your server that validates the data, stores or emails it, and returns a response. Jekyll produces only HTML, CSS, and JavaScript, and GitHub Pages serves those files as-is. There is no request handler at the other end of your ``. Here's what that submission path actually looks like once you route it through a form backend:
How a Jekyll form submission reaches your inbox
📝
Jekyll page
Static HTML built by GitHub Pages
🔌
Static Forms API
Validates, blocks spam, sends mail
📬
Your inbox
Message arrives as email
The browser POSTs the form directly to the API endpoint — GitHub Pages never has to run any code.
## Prerequisites Before you start, make sure you have: - A Jekyll site deployed to GitHub Pages (or one you can build locally) - A Static Forms account — [sign up for free](https://www.staticforms.dev) - Your API key from the [Static Forms dashboard](https://www.staticforms.dev/app/forms) ## Step 1: Build the contact form Jekyll renders Markdown, but it passes raw HTML straight through. So you can drop a standard form into any page or layout. Create a `contact.html` page at the root of your site: ```html --- layout: default title: Contact permalink: /contact/ ---

Get in touch

``` That top block between the `---` lines is Jekyll front matter — it tells Jekyll to wrap the page in your `default` layout and serve it at `/contact/`. Replace `YOUR_API_KEY_HERE` with your real key. A few fields are doing important work: - **`apiKey`** routes the submission to your account. - **`replyTo`** set to `@` tells Static Forms to use the submitter's own email as the reply-to address, so you can reply straight from your inbox. - **`honeypot`** is a hidden field. Real visitors never see it, but spam bots fill in every field they find — so any submission with a value here is silently rejected. ## Step 2: Make it a reusable include If you want the same form in your footer, your about page, and a dedicated contact page, copy-pasting the markup gets old fast. Jekyll includes solve this. Create `_includes/contact-form.html`: ```html
``` Store the key once in `_config.yml` so it isn't scattered across files: ```yaml staticforms_key: YOUR_API_KEY_HERE ``` Now drop the form anywhere with a single Liquid tag, optionally overriding the email subject per page: ```liquid {% include contact-form.html subject="Contact form — pricing page" %} ``` Because the API key is published in your site's HTML either way (that's true of any client-side form), treat it as a public form key rather than a secret. It only authorizes submissions to your account's endpoint. ## Step 3: Commit, push, and test GitHub Pages rebuilds automatically on every push to your publishing branch: ```bash git add contact.html _includes/contact-form.html _config.yml git commit -m "Add Static Forms contact form" git push origin main ``` Give the build a minute, then open `/contact/` on your live site and send yourself a test message. The first time you submit, Static Forms emails you a confirmation link to verify the form — click it once and every submission after that flows straight to your inbox. ## Step 4: Send visitors to a thank-you page By default the API returns a plain success response. For a Jekyll site you almost always want to send visitors to a friendly confirmation page instead. Add a `redirectTo` field pointing at a thank-you page you build in Jekyll: ```html ``` Then create `thank-you.html` with `permalink: /thank-you/` and whatever message you like. The full list of supported fields is in the [Static Forms documentation](https://www.staticforms.dev/docs/html). ## Step 5: Style the form to match your theme The default Jekyll theme (Minima) and most others give you sensible base styles, but a contact form usually wants a little polish. Add this to your `assets/css/style.scss` (or a `

Contact Us

``` Drop this into any static site — Next.js, Gatsby, Hugo, plain HTML, GitHub Pages — and you're done. Submissions land in Slack instantly. ## You're Done! You now have a fully working pipeline from your website to your Slack channel. To recap: - Connected Slack to your Static Forms account via native OAuth - Built a working HTML form pointing at the Static Forms API - Verified end-to-end delivery with a test submission - Set up routing for multiple forms to different channels - Layered in extra spam protection with a honeypot field If you want to take it further: - Add [Altcha CAPTCHA](/blog/implementing-altcha-captcha-html-website) for privacy-first spam protection - Mirror submissions to [Google Sheets](/blog/google-sheets-integration-guide) for long-term storage Have feedback or want a feature added? [Get in touch](https://www.staticforms.dev/contact) — we read every message. --- *Slack integration is available on the Pro plan. [View pricing](https://www.staticforms.dev/pricing) to learn more about Static Forms plans.* --- ## How to Send Form Submissions to Google Sheets Source: https://www.staticforms.dev/blog/google-sheets-integration-guide Date: 2026-04-21 Author: Static Forms Team Spreadsheets are the original data tool. Whether you're tracking leads, collecting RSVPs, or logging support requests, having every form submission land in a Google Sheet gives you instant access to filter, sort, chart, and share the data with your team — all without writing a line of code. Today we're launching a **native Google Sheets integration** for Static Forms. Connect your Google account with one click, and every submission is appended as a new row in a dedicated spreadsheet. No Zapier. No Make. No Google Apps Script. Just your forms and your sheet. ## What You'll Learn - How to connect Google Sheets to your Static Forms account - How form fields become spreadsheet columns - How to manage multiple sheets for different accounts - Troubleshooting common issues ## Prerequisites Before you begin, make sure you have: 1. A **Static Forms Pro** account (Google Sheets integration is a Pro feature). [Upgrade here](/pricing) if you're on the Free plan. 2. A **Google account** (any Gmail or Google Workspace account). 3. At least one **verified account** in your Static Forms dashboard. Already have all of these? Let's get started. ## Step 1: Open the Integrations Page Log into your Static Forms dashboard and navigate to **Settings**. Select the account you want to connect, then click the **Integrations** tab in the account navigation. You'll see three integrations listed: **Webhooks**, **Google Sheets**, and **Slack** (coming soon). Click on the Google Sheets card to expand it. ## Step 2: Connect Your Google Account Click the **Connect Google Sheets** button. You'll be redirected to Google's standard OAuth consent screen where you'll be asked to grant two permissions: - **Google Sheets** — so we can create a spreadsheet and append rows - **Email address** — so we can display which Google account is connected After you authorize, you'll be redirected back to your integrations page with a success confirmation. ### What Happens Behind the Scenes When you connect, Static Forms: 1. Creates a new spreadsheet in your Google Drive titled **"StaticForms — (your account name)"** 2. Stores an encrypted refresh token so we can write rows on your behalf 3. Enables the sync — the very next form submission will start populating your sheet You don't need to create the spreadsheet yourself, share it with a service account, or configure anything in the Google Cloud Console. ## Step 3: Verify the Connection Back on the integrations page, you'll see the Google Sheets card now shows: - **Connected as** — the Google email you authorized with - **Open spreadsheet** — a direct link to your sheet in Google Drive - **Last synced** — timestamp of the most recent row append - **Send test row** — a button to push a test submission immediately Click **Send test row** to confirm everything works. Open the spreadsheet — you should see a row with `test = true` and a timestamp. ## Step 4: Submit a Real Form Now submit a form using your account's API key (via your website, a `curl` command, or any HTTP client). Within seconds, a new row appears in the spreadsheet. Here's a simple HTML form you can drop into any page. Replace `YOUR_API_KEY` with your account's API key: ```html
``` Submit the form, then check your spreadsheet — you'll see a new row with columns for `submitted_at`, `submission_id`, `name`, `email`, and `message`. Every field in the form becomes a column. ## How Dynamic Columns Work One of the best things about our integration is that **you never need to configure column mapping**. Here's how it works: 1. **First submission**: Static Forms reads your form data keys (`name`, `email`, `message`, etc.) and writes them as the header row. Two meta-columns are always prepended: `submitted_at` and `submission_id`. 2. **Subsequent submissions**: Each new submission is appended as a row, aligned to the existing header. 3. **New fields appear**: If a later submission includes a field that didn't exist before (say `phone`), Static Forms appends it as a new column at the end of the header row. Earlier rows will have blank cells for that column — just like you'd expect. This means your spreadsheet schema evolves with your form. Add a field to your HTML form, and the column appears on the next submission. No reconfiguration needed. ## Managing the Integration ### Enable / Disable Sync Toggle the sync switch on the integration card to pause or resume row delivery. Your spreadsheet stays intact — we just stop writing to it while disabled. ### Change the Sheet Tab By default, rows go to a tab called **Submissions**. You can change this in the integration card. If the tab doesn't exist yet, Static Forms creates it on the next submission. This is useful if you want to organize data by month or campaign: - `Submissions` — default - `April-2026` — for date-based organization - `Landing-Page-Leads` — for campaign-specific tracking ### Disconnect Click **Disconnect** to revoke the connection. Your spreadsheet remains in your Google Drive with all existing data — only the live sync is removed. You can reconnect at any time, which creates a new spreadsheet. ## Per-Account Connections Each account in Static Forms has its own independent Google Sheets connection. This means you can: - Connect different Google accounts to different forms - Have separate spreadsheets for your contact form vs. your job application form - Give team members access to specific spreadsheets without sharing your entire dashboard Navigate to each account's **Integrations** tab to set up its connection independently. ## Common Use Cases ### Lead Generation Connect your landing page form to Google Sheets and share the spreadsheet with your sales team. They see leads the moment they come in, with no dashboard login required. ### Event RSVPs Create a simple RSVP form, connect it to Sheets, and use Google Sheets' built-in `COUNTA()` function to track attendance in real time. ### Support Requests Log every support request as a row. Use conditional formatting to highlight unresolved tickets, and add a "Status" column manually for your team to update. ### Data Export & Analysis Unlike email-based form submissions, spreadsheet data is instantly sortable, filterable, and chartable. Use Google Sheets' pivot tables, `QUERY()` function, or connect to Google Data Studio for reporting dashboards. ## Troubleshooting ### "Last error" appears on the integration card If a submission fails to sync, the error and timestamp are displayed on the card. Common causes: - **Token expired or revoked**: Your Google authorization was revoked (e.g., from Google's security settings). Click **Disconnect** and then **Connect Google Sheets** again. - **Spreadsheet deleted**: If you deleted the spreadsheet from Google Drive, disconnect and reconnect to create a new one. - **Sheet tab renamed or deleted**: If you renamed or deleted the tab in Google Sheets, update the tab name in the integration card to match, or let Static Forms auto-create it. Errors never block form submission delivery — your email notifications and webhooks continue working normally even if the Sheets sync fails. ### Form submissions arrive but no row appears 1. Check that the integration is **enabled** (the toggle should be on). 2. Verify you're using the correct account's API key — each account has its own connection. 3. Click **Send test row** to confirm the connection is still active. ### I want to use an existing spreadsheet The current integration auto-creates a new spreadsheet on connect. If you need to write to an existing sheet, you can use our [Webhook integration](/docs/webhooks) with [Zapier](/docs/integrations/zapier) or [Make](/docs/integrations/make) instead, which offer full control over the destination. ## Google Sheets vs. Webhooks vs. Zapier Not sure which integration to use? Here's a quick comparison: | | Google Sheets (Native) | Webhooks | Zapier/Make | |---|---|---|---| | **Setup time** | 30 seconds | 2 minutes | 5-10 minutes | | **Code required** | None | Endpoint needed | None | | **Cost** | Free (Pro plan) | Free (Pro plan) | Zapier/Make subscription | | **Destination** | Google Sheets only | Any HTTP endpoint | 5,000+ apps | | **Column mapping** | Automatic | You parse the JSON | Visual mapper | | **Best for** | Quick spreadsheet logging | Custom backends | Multi-step workflows | For most users who just want their form data in a spreadsheet, the native integration is the fastest path. ## What's Next We're continuing to expand our integrations. **Slack notifications** are next on the roadmap, followed by more native database connectors. If there's an integration you'd like to see, [let us know](/contact). Ready to get started? [Log in to your dashboard](/settings) and connect Google Sheets to your first account today. --- *Google Sheets integration is available on all Pro and Advanced plans. [View pricing](/pricing) to learn more about Static Forms plans.* --- ## hCaptcha Best Practices Source: https://www.staticforms.dev/blog/hcaptcha-best-practices Date: 2026-03-13 Author: Static Forms Team # hCaptcha Best Practices hCaptcha is a strong fit when you want privacy-friendly spam protection without depending on Google services. This guide focuses on the decisions and operational details that matter most when you use hCaptcha well. If you're using Static Forms and want the exact setup steps and code examples, use the [hCaptcha documentation](/docs/hcaptcha). That doc covers the provider-specific setup flow, HTML snippet, React example, expected token field, and troubleshooting. ## When hCaptcha is a good fit hCaptcha usually makes sense when you want: - A privacy-friendly alternative to reCAPTCHA - Support for checkbox and invisible modes - A familiar CAPTCHA flow with flexible rollout options - A provider that works well for both static HTML forms and frontend frameworks If your priority is the lowest possible friction, [Cloudflare Turnstile](/docs/cloudflare-turnstile) may be a better fit. If your priority is privacy-first protection without a third-party CAPTCHA provider, consider [ALTCHA](/docs/altcha). ## Best practices before you launch ### 1. Keep your keys separated correctly Your **site key** belongs in your frontend. Your **secret key** belongs in your server-side configuration or form backend settings only. That means: - Put the site key in your HTML or frontend app - Save the secret key only in secure backend settings - Never expose the secret key in client-side code ## 2. Register every domain you actually use Add all real environments in hCaptcha before you test: - Production domain - Staging domain - `localhost` if you test locally Most "invalid" or "domain not registered" errors come from incomplete domain configuration rather than a broken form. ### Use separate keys for production and non-production Do not treat all environments as one hCaptcha site unless you have a strong reason to do so. A better setup is: - One hCaptcha site and key pair for production domains only - A separate hCaptcha site and key pair for staging, preview, and local development This makes it easier to rotate keys, isolate testing, and avoid accidental cross-environment configuration mistakes. ### Do not add localhost to your production hCaptcha site `localhost` is useful for development, but it should live only in your non-production hCaptcha configuration. Avoid adding `localhost` to the production site registration. Keeping production domains separate reduces confusion and keeps your production configuration tighter. ## 3. Use invisible mode and execute the challenge at submit time This is the single biggest implementation detail people get wrong, and it causes intermittent "verification failed" errors that are hard to reproduce. hCaptcha tokens are valid for **only 120 seconds** from when the widget solves them. The default visible-checkbox integration auto-completes as soon as the user clicks. If they then take their time filling the form, attach a large file, or submit over a slow network, the token can expire mid-flight and hCaptcha rejects it as `expired-input-response` or `invalid-or-already-seen-response`. The fix is to render the widget in **invisible mode** and trigger the challenge from your submit handler: ```tsx import HCaptcha from '@hcaptcha/react-hcaptcha'; const captchaRef = useRef(null); async function handleSubmit(e) { e.preventDefault(); // Mint the token at submit time so it stays well within the 120s TTL const { response: token } = await captchaRef.current!.execute({ async: true }); // ...append token and POST } return (
{/* ...form fields */} ); ``` With this pattern the token is seconds old by the time your backend verifies it, regardless of how long the upload takes. Most users see no widget at all — the challenge UI only appears when hCaptcha's risk engine actually wants user interaction. Recommend this as your default integration pattern, **even when forms don't have file uploads** — it eliminates a class of intermittent failures with no real downside. Reserve the visible checkbox variant for high-risk endpoints where you want the friction. The docs cover the full HTML, React, and Next.js implementations: [View the hCaptcha setup guide](/docs/hcaptcha). ## 4. Verify the token field your backend expects When hCaptcha completes successfully, your form must send the token field your backend expects. That field name matters. If the widget renders correctly but the token is submitted under the wrong name, verification will fail even though the user completed the challenge. If you're using Static Forms, the required field name is documented here: [hCaptcha for Static Forms](/docs/hcaptcha). ## 5. Treat CAPTCHA as one layer, not the whole spam strategy hCaptcha works best alongside the rest of your form hardening: - Keep required fields tight - Use validation for email and message fields - Add honeypot protection when appropriate - Review abuse patterns instead of assuming one provider will stop everything Start with a layered setup, then tune based on real submissions. ## 6. Test the full submission path, not just widget rendering It is not enough to see the widget on the page. Before shipping, confirm the full flow: 1. The widget loads on the right domains 2. The user can complete the challenge 3. The token is included in the form submission 4. Your backend accepts the submission successfully That last step catches most configuration mistakes. ## Common mistakes to avoid Here are the issues teams hit most often: - Mixing up the site key and the secret key - Registering production but forgetting staging or localhost - Sending the wrong token field name - Using the visible checkbox and letting the token expire during long sessions or slow uploads (use invisible + execute-on-submit) - Assuming the frontend widget alone is enough without verifying the end-to-end submission ## Implementation checklist Use this as a quick handoff list: - Create or configure your hCaptcha site - Register your domains - Save the hCaptcha secret key in your backend or form handler - Add the widget with your site key - Confirm the form submits the token field your backend expects - Run a real test submission in each environment If you're using Static Forms, go straight to the docs for the exact code and setup walkthrough: - [hCaptcha with Static Forms](/docs/hcaptcha) - [Spam protection overview](/docs/spam-protection) - [CAPTCHA settings](/settings/captcha) ## Choosing between hCaptcha and the other options Use hCaptcha when you want a familiar CAPTCHA experience and a privacy-friendly alternative to Google. Use Turnstile when reducing visible challenge friction is the top priority. Use ALTCHA when you want a privacy-first approach without relying on a third-party CAPTCHA account. The provider comparison page is the best starting point if you are still deciding: [Compare spam protection options](/docs/spam-protection). --- ## Cloudflare Turnstile Best Practices Source: https://www.staticforms.dev/blog/cloudflare-turnstile-best-practices Date: 2026-03-11 Author: Static Forms Team # Cloudflare Turnstile Best Practices Cloudflare Turnstile is a strong choice when you want low-friction spam protection without traditional CAPTCHA puzzles. This guide focuses on rollout advice, implementation pitfalls, and the details that matter when you use Turnstile well. If you're using Static Forms and want the exact setup steps and code examples, use the [Cloudflare Turnstile documentation](/docs/cloudflare-turnstile). That doc covers the dashboard setup, HTML example, React flow, token field name, and troubleshooting. ## When Turnstile is a good fit Turnstile is a good choice when you want: - Minimal user interruption during form submission - A non-Google CAPTCHA option - A clean experience for marketing sites and contact forms - A provider that works well with static HTML and frontend frameworks If you want checkbox and invisible modes with a more familiar CAPTCHA pattern, [hCaptcha](/docs/hcaptcha) may be a better fit. If you want a privacy-first option without a third-party CAPTCHA provider, look at [ALTCHA](/docs/altcha). ## Best practices before you launch ### 1. Keep the site key and secret key in the right places Cloudflare gives you two different keys: - The **site key** is public and belongs in your frontend - The **secret key** is private and belongs in your backend or form service configuration Do not expose the secret key in your HTML or client-side JavaScript. ## 2. Register every environment you plan to use Add all relevant domains to your Turnstile widget configuration: - Production - Preview or staging - `localhost` for local testing Turnstile failures often come from domain configuration gaps rather than bad frontend code. ### Use separate widgets and keys for production and non-production Do not reuse the same Turnstile widget everywhere unless you intentionally want one shared configuration. A better setup is: - One production widget and key pair for live domains only - A separate non-production widget and key pair for staging, preview, and local development This keeps testing isolated, makes rotation safer, and reduces the chance of shipping a development configuration into production. ### Do not add localhost to the production widget `localhost` belongs in a non-production widget only. Keep your production Turnstile widget limited to real live domains, and use a separate widget for staging and local work. ## 3. Make token handling explicit in your implementation Make sure you know which token field name your backend expects and verify that your frontend sends that exact field. That is one of the most important implementation details to verify. If you render the widget but submit a different field name, verification will fail. If you're using Static Forms, the expected token field is documented here: [Cloudflare Turnstile with Static Forms](/docs/cloudflare-turnstile). ## 4. Test the complete submission flow A rendered widget is only the first half of the job. Before launch, verify that: 1. The Turnstile script loads 2. The widget renders on the page 3. The token is attached to the form submission 4. Your backend accepts the request successfully This is especially important in React or Next.js apps where token handling is often manual. ## 5. Mint the token at submit time, not at page load This is the single biggest implementation detail people get wrong, and it causes intermittent "verification failed" errors that are hard to reproduce. Turnstile tokens are valid for **300 seconds** from when the widget solves them. The default integration (`
`) auto-solves the challenge as soon as the page loads. If the user spends a few minutes filling out the form, picks a large file attachment, or submits over a slow network, the token can expire mid-flight and Cloudflare rejects it with `invalid-input-response` or `timeout-or-duplicate`. The fix is to render the widget in **execute mode** and trigger the challenge from your submit handler: ```javascript const widgetId = window.turnstile.render('#cf-widget', { sitekey: 'YOUR_SITE_KEY', execution: 'execute', // generate token on demand appearance: 'interaction-only', // widget stays invisible unless a challenge is needed callback: (token) => pendingResolve(token), }); form.addEventListener('submit', async (e) => { e.preventDefault(); const token = await new Promise((resolve) => { pendingResolve = resolve; window.turnstile.reset(widgetId); window.turnstile.execute(widgetId); }); // ...append token and POST }); ``` With this pattern: - The token is seconds old by the time your backend verifies it, regardless of how long the upload takes - Most users see no widget at all (silent verification) - The challenge UI only appears when Cloudflare's risk engine actually wants user interaction Recommend this as your default integration pattern, **even when forms don't have file uploads** — it eliminates a class of intermittent failures with no real downside. ## 6. Plan for failure states Even with execute-on-submit, you should still: - Disable submit until the widget script has loaded - Show a clear retry message when verification fails - Branch on Static Forms' stable error code (`captcha_expired_or_reused`) to retry automatically If you're using Static Forms, the implementation details for those flows live here: [Read the Turnstile setup guide](/docs/cloudflare-turnstile). ## 7. Combine Turnstile with broader spam controls Turnstile is one layer of protection. For better results: - Keep field validation strict - Use required fields intentionally - Add honeypot protection where it makes sense - Review submission patterns and adjust based on real abuse This usually performs better than relying on a single control. ## Common mistakes to avoid The most common Turnstile issues are: - Saving the site key where the secret key should go - Forgetting to allow the domain in Cloudflare - Using the wrong token field name - Rendering the widget but never appending the token in a custom React flow - Auto-solving on page load and letting the token expire during long sessions or slow uploads (use execute-on-submit) - Testing only the UI instead of a real end-to-end submission ## Implementation checklist Use this checklist before launch: - Create a Turnstile widget in Cloudflare - Register all production, staging, and local domains - Save the Turnstile secret key in your backend or form service settings - Add the widget to your form using the site key - Confirm the form sends the token field your backend expects - Run real test submissions in every environment If you're using Static Forms, use: - [Cloudflare Turnstile with Static Forms](/docs/cloudflare-turnstile) - [Spam protection overview](/docs/spam-protection) - [CAPTCHA settings](/settings/captcha) ## Choosing between Turnstile and the other options Use Turnstile when low friction is the priority. Use hCaptcha when you want a more traditional CAPTCHA experience with visible or invisible modes. Use ALTCHA when you want a privacy-first option without a third-party CAPTCHA account. If you are still choosing a provider, start here: [Compare spam protection options](/docs/spam-protection). --- ## How to Add a Contact Form to Google Sites Source: https://www.staticforms.dev/blog/google-sites-contact-form-tutorial Date: 2026-03-10 Author: Static Forms Team Google Sites is one of the easiest ways to build a website. It is free, works inside your browser, and integrates with the rest of Google Workspace. However, Google Sites does not include a built-in contact form that sends submissions to your email. The only native option is embedding a Google Form, which looks out of place and lacks features like email notifications, spam protection, and custom styling. In this tutorial, you will learn how to add a professional contact form to your Google Sites website using Static Forms. The form will send submissions directly to your inbox, protect against spam, and match the look and feel of your site — all without writing any backend code. ## Why Not Just Use Google Forms? Before we dive in, you might wonder why you would not just embed a Google Form. Here are some reasons: - **No email notifications by default** — Google Forms collects responses in a spreadsheet but does not email you when someone submits - **Limited styling** — Google Forms has its own design that cannot be customized to match your site - **No spam protection** — there is no honeypot or CAPTCHA support - **No reply-to address** — you cannot reply directly to the person who submitted the form Static Forms solves all of these problems. You get instant email notifications, built-in spam protection, custom styling, and the ability to reply directly to submissions. ## Prerequisites Before you begin, make sure you have: - A Google Sites website (new Google Sites, not the classic version) - A Static Forms account — [sign up for free](https://www.staticforms.dev) - Your API key from the [Static Forms dashboard](https://www.staticforms.dev/app/forms) ## How It Works Google Sites lets you embed custom HTML code into any page using the **Embed** feature. The embedded code runs inside an iframe on your page. We will use this feature to add an HTML form that submits data to the Static Forms API, which then delivers the submission to your email inbox. The flow looks like this: 1. Visitor fills out the form on your Google Sites page 2. The form sends data to Static Forms via the API 3. Static Forms delivers the submission to your email 4. The visitor sees a success confirmation message ## Step 1: Get Your Static Forms API Key 1. Go to [staticforms.dev](https://www.staticforms.dev) and create a free account 2. Verify your email address 3. Copy your API key from the [dashboard](https://www.staticforms.dev/app/forms) You will need this key in the next step. ## Step 2: Create the Contact Form HTML Copy the following HTML code. This is a complete, self-contained contact form with built-in styling that works inside Google Sites: ```html

Contact Us

Send us a message and we will get back to you shortly.

``` **Important**: Replace `YOUR_API_KEY_HERE` with your actual API key from the Static Forms dashboard. ## Step 3: Embed the Form in Google Sites Now that you have the HTML code, follow these steps to add it to your Google Sites page: 1. Open your site in the [Google Sites editor](https://sites.google.com) 2. Navigate to the page where you want the contact form 3. In the right sidebar, click **Insert** (the panel with text, images, and other elements) 4. Scroll down and click **Embed** (the `<>` icon) 5. Select the **Embed code** tab 6. Paste the entire HTML code from Step 2 into the code box 7. Click **Next** to see a preview of your form 8. Click **Insert** to add it to the page 9. Resize the embed block by dragging its edges — a height of around **500-600 pixels** works well for this form 10. Click **Publish** in the top right to make your changes live That's it! Your contact form is now live on your Google Sites page. ## Step 4: Test Your Form Before sharing your site, test the form: 1. Visit your published Google Sites page 2. Fill in the name, email, and message fields 3. Click **Send Message** 4. Check your inbox for the submission email If you do not receive the email, check the troubleshooting section below. ## Customizing Your Form ### Change the Color Scheme To match your site's branding, update the CSS colors in the embedded code. The main values to change are: ```css /* Primary button color */ button { background-color: #2563eb; /* Change to your brand color */ } button:hover { background-color: #1d4ed8; /* Slightly darker shade */ } /* Focus ring color */ input:focus, textarea:focus { border-color: #2563eb; /* Match your brand color */ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); } ``` ### Add More Fields You can add any standard form fields. For example, to add a phone number field: ```html
``` Prefix custom field names with `$` so Static Forms includes them in the email notification. ### Set a Custom Email Subject Add a hidden field to set a fixed subject line for all submissions: ```html ``` Or keep the visible subject field from the example above to let visitors choose their own subject. ### Redirect to a Thank-You Page If you prefer to redirect visitors after submission instead of showing an inline message, you can use a simple form POST approach instead. Replace the `
` tag and remove the JavaScript: ```html
``` Create a separate "Thank You" page on your Google Site and use its URL as the `redirectTo` value. ## Adding reCAPTCHA Spam Protection For additional spam protection beyond the honeypot field, you can add Google reCAPTCHA. First, get your site key from the [Google reCAPTCHA Admin Console](https://www.google.com/recaptcha/admin), then configure your secret key in your [Static Forms CAPTCHA settings](https://www.staticforms.dev/app/forms). Add the reCAPTCHA script and widget to your embedded HTML: ```html
``` Then include the reCAPTCHA response in your JavaScript submission: ```javascript var recaptchaResponse = document.querySelector('[name="g-recaptcha-response"]'); if (recaptchaResponse) { data['g-recaptcha-response'] = recaptchaResponse.value; } ``` For a deeper dive, see our [understanding reCAPTCHA guide](/blog/understanding-recaptcha). ## Minimal Form Version If you want the smallest possible form with no extra styling, here is a stripped-down version you can embed: ```html

``` This version uses a standard form POST, so the page will redirect to a Static Forms confirmation page after submission. It is a good choice if you want something quick and simple. ## Troubleshooting Common Issues ### Form Does Not Appear on the Page - ✅ Make sure you used the **Embed code** tab, not the **By URL** tab - ✅ Check that you pasted the complete HTML including the ``, ``, and `` tags - ✅ Resize the embed block by dragging its edges — the default size may be too small to display the full form ### Not Receiving Email Notifications - ✅ Verify your API key is correct — copy it again from the [dashboard](https://www.staticforms.dev/app/forms) - ✅ Check your spam or junk folder - ✅ Confirm your email address is verified in Static Forms - ✅ Test with a different email address ### Form Submits but Shows an Error - ✅ Open your browser's developer tools (F12) and check the Console tab for errors - ✅ Make sure the API endpoint is correct: `https://api.staticforms.dev/submit` - ✅ Verify the `Content-Type` header is set to `application/json` when using JavaScript submission ### Embed Block Is Too Small Google Sites embed blocks have a default size that may cut off your form. To fix this: 1. Click on the embed block in the editor 2. Drag the bottom edge to increase the height 3. A height of 500-600 pixels is usually enough for a standard contact form 4. Publish your site again to see the updated size ## Best Practices 1. **Keep it short** — only ask for the information you actually need. Name, email, and message are usually enough. 2. **Use the honeypot field** — it is a simple, invisible spam protection that does not inconvenience your visitors 3. **Test before publishing** — always submit a test message to confirm everything works 4. **Check mobile layout** — visit your published site on a phone to make sure the form is readable and usable 5. **Use a clear call to action** — tell visitors what will happen when they submit the form ## Conclusion Adding a contact form to Google Sites does not require any backend code or complex setup. With Static Forms, you can embed a fully functional, professional-looking contact form in just a few minutes. Your visitors get a smooth experience, and you receive submissions directly in your inbox with built-in spam protection. Ready to add a contact form to your Google Sites website? [Sign up for Static Forms](https://www.staticforms.dev) and get started with 500 free submissions per month. For more integration guides, check out our tutorials for [GitHub Pages](/blog/github-pages-contact-form-tutorial), [Astro](/blog/astro-contact-form-tutorial), and [Next.js](/blog/using-staticforms-with-nextjs). --- ## How to Add a Contact Form to Your Astro Website Source: https://www.staticforms.dev/blog/astro-contact-form-tutorial Date: 2026-02-28 Author: Static Forms Team Astro is a modern web framework that generates fast, static HTML by default. While this is great for performance, it means your site has no built-in way to process form submissions. You need a form backend service to handle the server-side work — receiving data, sending emails, and protecting against spam. In this tutorial, you will learn how to add a fully functional contact form to your Astro website using Static Forms. We will cover two approaches: a simple HTML form and a more interactive JavaScript-powered version. ## Prerequisites Before you begin, make sure you have: - An Astro project set up and running - A Static Forms account — [sign up for free](https://www.staticforms.dev) - Your API key from the [Static Forms dashboard](https://www.staticforms.dev/app/forms) ## Approach 1: Simple HTML Form The simplest way to add a contact form to Astro is with a plain HTML form. No JavaScript required — the form submits directly to the Static Forms endpoint. Create a new page at `src/pages/contact.astro`: ```astro --- // src/pages/contact.astro import Layout from '../layouts/Layout.astro'; ---

Contact Us

Send us a message and we will get back to you shortly.

``` Replace `YOUR_API_KEY_HERE` with your actual API key. The `replyTo` field set to `@` uses the submitter's email as the reply-to address. The hidden `honeypot` field provides basic bot protection. This approach works immediately with zero client-side JavaScript. When a user submits the form, they are redirected to a Static Forms confirmation page. You can customize this by adding a `redirectTo` hidden field with your own thank-you page URL. ## Approach 2: Async Fetch with Client-Side JavaScript For a smoother user experience without page redirects, you can use Astro's client-side scripting to submit the form via `fetch`: ```astro --- // src/pages/contact.astro import Layout from '../layouts/Layout.astro'; ---

Contact Us

``` ## Environment Variables Instead of hardcoding your API key, store it in an environment variable. Create a `.env` file in your project root: ``` PUBLIC_STATIC_FORMS_KEY=your-api-key-here ``` Astro exposes variables prefixed with `PUBLIC_` to client-side code via `import.meta.env`. This keeps your key out of source control while still making it available in the browser. Note that form API keys are safe to expose client-side since they are tied to your verified email address. ## Adding reCAPTCHA Spam Protection For stronger spam protection, add reCAPTCHA to your form. First, get your site key from the [Google reCAPTCHA Admin Console](https://www.google.com/recaptcha/admin), then configure your secret key in your [Static Forms CAPTCHA settings](https://www.staticforms.dev/app/forms). Add the reCAPTCHA script to your layout's `` and include the widget in your form: ```html
``` When submitting via JavaScript, include the reCAPTCHA response token in your request body: ```javascript const recaptchaResponse = document.querySelector('[name="g-recaptcha-response"]').value; // Add g-recaptcha-response: recaptchaResponse to your POST body ``` For a deeper dive into reCAPTCHA, see our [understanding reCAPTCHA guide](/blog/understanding-recaptcha). ## Astro-Specific Documentation For more detailed Astro integration instructions, including component examples and advanced configuration, check out our [Astro documentation page](/docs/astro). ## Next Steps You now have a working contact form on your Astro site. Static Forms handles email delivery, spam protection, and submission storage so you can focus on building your site. On the Pro plan, you also get access to file uploads, webhooks, auto-responders, and custom email templates. [Sign up for Static Forms](https://www.staticforms.dev) to get started with 500 free submissions per month. --- ## The Complete Guide to Contact Forms on JAMstack Sites Source: https://www.staticforms.dev/blog/jamstack-contact-form Date: 2026-02-28 Author: Static Forms Team JAMstack (JavaScript, APIs, Markup) is an architecture where websites are pre-built as static files and served from a CDN. Frameworks like Next.js, Gatsby, Hugo, Jekyll, Astro, and Nuxt all follow this pattern to deliver fast, secure websites. The tradeoff is that there is no server running to process form submissions, send emails, or store data. This means every JAMstack site that needs a contact form faces the same question: how do you handle form submissions without a backend? ## Your Options for JAMstack Forms ### Serverless Functions You can write your own serverless function (AWS Lambda, Vercel Functions, Netlify Functions) to process submissions. This gives you full control but requires: - Writing and maintaining backend code - Setting up an email service (SES, SendGrid, etc.) - Implementing spam protection yourself - Managing environment variables and deployment - Monitoring for failures For a simple contact form, this is a lot of infrastructure to build and maintain. ### Form Backend Services A form backend service provides an API endpoint that handles everything for you: receiving submissions, sending emails, filtering spam, and storing data. You point your HTML form at the service's URL and it takes care of the rest. This is the approach we recommend for most JAMstack sites. It works with any framework, any hosting provider, and requires zero backend code. ## Why Static Forms for JAMstack Static Forms is designed specifically for this use case. Here is why it works well with JAMstack: - **Zero backend code** — Add an action URL to your form and you are done - **Works with any host** — Vercel, Netlify, GitHub Pages, Cloudflare Pages, AWS S3, or any CDN - **Works with any framework** — HTML forms are universal across all JAMstack frameworks - **Free tier** — 500 submissions per month at no cost - **Spam protection** — reCAPTCHA v2, reCAPTCHA v3 (invisible), and Altcha (privacy-first CAPTCHA) - **Pro features** — File uploads, webhooks, auto-responders, submission dashboard, and CSV export ## Universal HTML Form This form works with every JAMstack framework because it uses standard HTML: ```html
``` Replace `YOUR_API_KEY_HERE` with your API key from the [Static Forms dashboard](https://www.staticforms.dev/app/forms). This form will work on any static site — just paste it into your HTML. For a JavaScript-powered version with inline success/error messages (no page redirect), submit via `fetch` instead: ```javascript const response = await fetch('https://api.staticforms.dev/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: 'YOUR_API_KEY_HERE', name: formData.name, email: formData.email, message: formData.message, replyTo: '@', }), }); const result = await response.json(); if (result.success) { // Show success message } ``` ## Framework-Specific Notes The HTML form above works everywhere, but each framework has its own conventions for components and environment variables. Here are quick pointers for the most popular JAMstack frameworks: ### Next.js Use a React component with `useState` for form state and `fetch` for submission. Store your API key in `NEXT_PUBLIC_STATIC_FORMS_KEY`. See our full [Next.js integration guide](/docs/nextjs). ### Gatsby Gatsby sites can use the same React component approach as Next.js. We have a dedicated tutorial: [Using Static Forms with Gatsby](/blog/using-staticforms-with-gatsby). ### Astro Astro generates static HTML by default, so the plain HTML form works perfectly. For client-side interactivity, use an Astro ` ``` ### How It Works 1. **Reactive form state** is managed with `reactive()`, which tracks all field values 2. **`handleSubmit`** sends a JSON POST request to the Static Forms endpoint with your API key and form data 3. **Loading state** disables the button and shows "Sending..." while the request is in flight 4. **Success state** hides the form and displays a confirmation message 5. **Error handling** catches network or API failures and shows an error message 6. **Honeypot field** is hidden from users but catches bots that auto-fill all fields ### Environment Variables Store your API key in a `.env` file: ``` VITE_STATIC_FORMS_KEY=your-api-key-here ``` Vite exposes variables prefixed with `VITE_` to the client. If you are using Nuxt, use `NUXT_PUBLIC_STATIC_FORMS_KEY` instead and access it via `useRuntimeConfig().public`. ## Vue 2 Options API Version If you are working with Vue 2, here is a condensed version using the Options API: ```vue ``` The template is the same as the Vue 3 version. The main difference is that data is returned from a `data()` function and the submit handler is defined in `methods`. ## Adding reCAPTCHA For stronger spam protection beyond the honeypot, you can add reCAPTCHA to your form. Install a Vue reCAPTCHA package, get your site key from [Google reCAPTCHA](https://www.google.com/recaptcha/admin), and configure your secret key in your [Static Forms CAPTCHA settings](https://www.staticforms.dev/app/forms). Include the `g-recaptcha-response` token in your submission body and Static Forms will verify it server-side. For a detailed reCAPTCHA walkthrough, see our [understanding reCAPTCHA guide](/blog/understanding-recaptcha). ## Using with Nuxt If you are building with Nuxt, check out our dedicated [Nuxt.js integration guide](/blog/using-staticforms-with-nuxtjs) for Nuxt-specific setup instructions and best practices. ## Get Started You now have everything you need to add a working contact form to your Vue application. Static Forms handles email delivery, spam protection, and submission storage so you do not need to build or maintain a backend. [Sign up for Static Forms](https://www.staticforms.dev) to get your API key and start receiving form submissions for free. --- ## Formspree vs Static Forms: Which Is Better in 2026? Source: https://www.staticforms.dev/blog/formspree-vs-static-forms-comparison Date: 2026-01-13 Author: Static Forms Team Choosing the right form backend service can significantly impact your website's functionality, user experience, and your budget. Formspree and Static Forms are two popular solutions for handling form submissions on static websites, but they have important differences in pricing, features, and implementation. In this comprehensive comparison, we'll examine both services across critical factors including ease of setup, features, spam protection, pricing, and platform compatibility. Whether you're building a personal portfolio, a business website, or a large-scale application, this guide will help you make an informed decision. We'll provide honest, unbiased insights into both services so you can choose the solution that best fits your specific needs and budget. ## Quick Comparison Overview | Feature | Formspree | Static Forms | |---------|-----------|--------------| | **Free Tier** | 50 submissions/month | 500 submissions/month | | **Entry Paid Plan** | $10/mo (Personal: 200) | $9/mo (Pro: 25,000) | | **Setup Difficulty** | Very easy | Very easy | | **Platform Support** | Any platform | Any platform | | **AJAX/JSON Support** | ✅ Yes | ✅ Yes | | **Spam Protection** | Honeypot, reCAPTCHA, Formshield + Akismet | Honeypot, reCAPTCHA, Altcha | | **File Uploads** | ✅ Yes (paid) | ✅ Yes (Pro) | | **Webhooks** | ✅ Yes (paid) | ✅ Yes (paid) | | **Form Rules / Conditional Routing** | ✅ Yes ($60/mo Business) | ✅ Yes ($9/mo Pro) | | **AI-Powered Auto-Reply** | ❌ No | ✅ Yes (Pro) | | **Custom Branding** | Paid plans only | All plans | | **Privacy-First CAPTCHA** | ❌ No | ✅ Yes (Altcha) | ## Pricing Breakdown ### Formspree Pricing **Free Plan:** - 50 submissions per month - 1 form - Email notifications - AJAX/JSON support - reCAPTCHA support - Formshield spam filtering (ML-based) **Personal Plan - $10/month:** - 200 submissions per month - Unlimited forms - File uploads (up to 25MB per file) - Webhook support - CSV export - Akismet as a secondary spam filter **Professional Plan - $20/month:** - 2,000 submissions per month - All Personal features - Higher storage quota **Business Plan - $60/month:** - 20,000 submissions per month - All Professional features - Form rules / workflows - Priority support ### Static Forms Pricing **Free Plan:** - 500 submissions per month - Unlimited forms - Email notifications - Custom branding included - Honeypot + reCAPTCHA v2 - AJAX/JSON support **Pro Plan - $9/month:** - 25,000 submissions per month - Unlimited forms - Everything in Free - AI-powered auto-reply (OpenAI, Anthropic, Google) - reCAPTCHA v3 (invisible) - Altcha CAPTCHA (privacy-first) - Webhooks - File uploads (up to 5MB per file) - Priority support - Form rules: conditional routing, subject/reply-to overrides, integration fan-out (10 per form) - Auto-responder emails ### Price Comparison for Common Use Cases | Use Case | Formspree | Static Forms | Winner | |----------|-----------|--------------|--------| | **Personal Portfolio** (Free) | $0 | $0 | Tie | | **Small Business** (1,000/mo) | $20/mo (Professional) | Free | Static Forms | | **Growing Business** (5,000/mo) | $60/mo (Business) | $9/mo | Static Forms | | **Cost per 1,000 submissions** | $3-$50 | $0.36 | Static Forms | **Verdict:** Static Forms offers significantly better value with 500 free submissions and 25,000 submissions for $9/month. ## Setup and Implementation ### Formspree Setup Formspree offers two setup methods: **Method 1: Simple HTML (No signup required)** ```html
``` **Method 2: AJAX/JavaScript (Requires account)** ```javascript fetch('https://formspree.io/f/YOUR_FORM_ID', { method: 'POST', body: JSON.stringify({ email, message }), headers: { 'Content-Type': 'application/json' } }) ``` **Pros:** - ✅ Can use without creating account initially - ✅ Email verification via first submission - ✅ Very simple for basic use cases **Cons:** - ❌ Must verify email via test submission - ❌ File uploads and webhooks require a paid plan - ❌ Only 1 form and 50 submissions/month on free tier ### Static Forms Setup Static Forms uses a consistent approach: **HTML Setup:** ```html
``` **AJAX/JavaScript (Available on all plans):** ```javascript fetch('https://api.staticforms.dev/submit', { method: 'POST', body: JSON.stringify({ email, message, apiKey: 'YOUR_API_KEY' }), headers: { 'Content-Type': 'application/json' } }) ``` **Pros:** - ✅ Unlimited forms on the free tier - ✅ Clear API documentation - ✅ Consistent endpoint for all forms - ✅ Immediate access after signup **Cons:** - Requires creating account first (takes 2 minutes) **Verdict:** Both are easy to set up. Formspree has a slight edge for the absolute simplest case, but Static Forms' free tier includes unlimited forms and 10x the submission quota. ## Spam Protection Comparison ### Formspree Spam Protection **Free Plan:** - Honeypot fields - Google reCAPTCHA support - Formshield (ML-based spam filter) - Basic rate limiting **Paid Plans ($10+):** - Akismet as a secondary spam filter - Advanced rate limiting - Spam blocking rules **Configuration:** ```html
``` ### Static Forms Spam Protection **Free Plan:** - Honeypot fields - Google reCAPTCHA v2 - Rate limiting - Basic filtering **Pro Plan ($9):** - Everything in Free - Google reCAPTCHA v3 (invisible) - Altcha CAPTCHA (privacy-first, GDPR-compliant) - Advanced filtering - Custom rules **Configuration:** ```html
``` For privacy-conscious projects, Static Forms offers [Altcha CAPTCHA](/blog/implementing-altcha-captcha-html-website) which doesn't track users or use cookies. **Verdict:** Static Forms wins with free reCAPTCHA and privacy-first Altcha option. ## Feature Comparison ### Email Notifications **Formspree:** - ✅ Customizable notification emails - ✅ Email template customization (paid) - ✅ Multiple recipient emails (paid) - ❌ No auto-responders on lower tiers **Static Forms:** - ✅ Customizable notification emails - ✅ Multiple recipient emails (free) - ✅ Custom reply-to addresses (free) - ✅ Auto-responder emails (Pro) - ✅ CC/BCC support ### File Uploads **Formspree:** - Available on paid plans ($10+) — not on the free tier - 25MB per file, up to 10 files per submission - 100MB total request size **Static Forms:** - Available on Pro plan ($9/mo) - 5MB limit per file - Multiple file support - Common file types supported (PDF, DOC, DOCX, images) **Example:** ```html
``` **Verdict:** Both services require paid plans for file uploads. Formspree Personal ($10/mo) offers a larger 25MB per-file limit but only 200 submissions, while Static Forms Pro ($9/mo) has a 5MB per-file limit with 25,000 submissions. ### Webhooks & Integrations **Formspree:** - Webhooks on paid plans ($10+) - Zapier integration - Native integrations limited - JSON payload to custom endpoints **Static Forms:** - Webhooks on Pro plan ($9) - Pre-built integrations: - Slack - Discord - Google Sheets - Notion - Airtable - Zapier - Make - n8n - Custom webhook endpoints - Flexible JSON payload **Verdict:** Static Forms offers more integrations at a lower price point. ### Form Rules & Conditional Routing Both services can act conditionally on a submission instead of sending every entry to the same inbox, but they gate it very differently. **Formspree:** - Form rules / workflows on the Business plan ($60/month) - Not available on Free, Personal, or Professional **Static Forms:** - [Form rules](/docs/forms/rules) on Pro ($9/month), up to 10 rules per form - Agency raises the cap to 25 rules per form and adds country, page URL, referrer, day-of-week, hour-of-day, attachment, UTM, and regex conditions - Each rule combines conditions (ALL or ANY) with actions: route to an additional verified recipient, set the subject or reply-to, redirect the visitor, post to Slack/Discord/Telegram, add a row to Google Sheets/Airtable/Notion, call a webhook, skip the default email, or mark as spam - Rules run top to bottom after spam filtering and before delivery, and a dry-run tester shows the per-condition trace before you save - Rule recipients must be already-verified addresses, so a rule can never blast an arbitrary inbox **Verdict:** Same capability, very different price — Static Forms includes conditional routing from the $9/mo Pro tier, while Formspree gates it to the $60/mo Business tier. ### AI-Powered Auto-Reply (Unique to Static Forms) One of the most significant differentiators in 2026 is Static Forms' **AI Reply** feature - something Formspree doesn't offer. **Static Forms AI Reply:** - Connect your own AI API key (OpenAI, Anthropic Claude, or Google Gemini) - Upload your knowledge base content - AI generates contextual, personalized responses to every form submission - Perfect for customer support, FAQ automation, and lead nurturing - Available on Pro plan ($9/mo) **Example Use Cases:** - Automatically answer common questions based on your documentation - Provide personalized responses to support inquiries - Send customized information based on form content - Reduce response time from hours to seconds **Formspree:** - ❌ No AI-powered responses - Manual responses only or basic template auto-responders For businesses looking to provide instant, intelligent responses to form submissions, Static Forms' AI Reply feature is a game-changer. Learn more about [AI Reply implementation](/docs/ai-reply). **Verdict:** Static Forms wins with exclusive AI-powered auto-reply feature. ### Custom Redirects **Both services** support custom redirects after form submission: **Formspree:** ```html ``` **Static Forms:** ```html ``` ### Form Validation **Formspree:** - Client-side validation via HTML5 - Server-side validation included - Custom validation rules (paid) **Static Forms:** - Client-side validation via HTML5 - Server-side validation included - Field-level validation - Custom error messages ## Framework Support ### React Integration **Formspree** provides an official React package: ```bash npm install @formspree/react ``` ```jsx import { useForm } from '@formspree/react'; function ContactForm() { const [state, handleSubmit] = useForm("YOUR_FORM_ID"); return (
); } ``` **Static Forms** works with standard fetch API (works in any framework): ```jsx // Works in React, Vue, Angular, Svelte, etc. const handleSubmit = async (e) => { e.preventDefault(); const response = await fetch('https://api.staticforms.dev/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...formData, apiKey }) }); }; ``` See our detailed guides: - [React contact form tutorial](/blog/react-contact-form-email-tutorial) - [Next.js integration](/blog/using-staticforms-with-nextjs) - [Gatsby integration](/blog/using-staticforms-with-gatsby) **Verdict:** Formspree's React package is convenient, but Static Forms' standard API works universally across all frameworks. ## Dashboard & Management ### Formspree Dashboard **Features:** - View all submissions - Form management - Export to CSV - Spam management - Analytics (basic) - Form archive/delete **User Experience:** - Clean, modern interface - Easy navigation - Quick form creation ### Static Forms Dashboard **Features:** - View all submissions - Multi-form management - Export functionality - Advanced filtering - Analytics and reporting - Per-form settings - API key management - Integration setup **User Experience:** - Intuitive interface - Comprehensive controls - Detailed submission data **Verdict:** Both dashboards are well-designed. Slight edge to Static Forms for more detailed analytics. ## Privacy & GDPR Compliance ### Formspree Privacy - Data stored in US - Standard privacy policy - reCAPTCHA and Akismet use third-party services - No specific GDPR features mentioned - Data retention policies in place ### Static Forms Privacy - GDPR-compliant by design - Altcha CAPTCHA available (no tracking, no cookies) - Transparent data processing - Data residency options - Privacy-first approach For privacy-conscious projects, read about [Altcha CAPTCHA implementation](/blog/implementing-altcha-captcha-html-website). **Verdict:** Static Forms wins for privacy-focused projects with Altcha CAPTCHA option. ## Performance & Reliability ### Response Times **Formspree:** - Average: 200-400ms - Generally reliable - Occasional slowdowns reported **Static Forms:** - Average: 150-300ms - Consistently fast - Globally distributed infrastructure ### Uptime Both services maintain excellent uptime: - **Formspree**: ~99.9% uptime - **Static Forms**: ~99.9% uptime **Verdict:** Both services are reliable. Performance is comparable. ## Support & Documentation ### Formspree Support **Documentation:** - Comprehensive guides - API documentation - Framework tutorials - Community forum **Support Channels:** - Email support (paid plans) - Community forum - GitHub discussions - Response time: 24-48 hours ### Static Forms Support **Documentation:** - Detailed guides for all features - Framework-specific tutorials - Code examples - Troubleshooting guides - Active blog with tutorials **Support Channels:** - Email support (all plans) - Priority support (Pro plans) - Comprehensive FAQ - Response time: 12-24 hours (Pro), 24-48 hours (Free) **Verdict:** Both offer good documentation. Static Forms provides support to free users. ## Migration Between Services ### From Formspree to Static Forms **Step 1:** Update form action ```html
``` **Step 2:** Add API key ```html ``` **Step 3:** Update field names if needed - `_next` → `redirectTo` - `_replyto` → `replyTo` - `_subject` → `subject` **Done!** Migration takes minutes. ### From Static Forms to Formspree Similarly straightforward - just reverse the process. ## Use Case Recommendations ### Choose Formspree If: ✅ You want the official React package for rapid development ✅ You need Formshield/Akismet spam filtering specifically ✅ You prefer the Formspree brand/ecosystem ✅ You're already familiar with Formspree ✅ You need larger per-file upload limits (25MB per file) ### Choose Static Forms If: ✅ Budget is important ($9 vs $10 for 25,000 vs 200 submissions) ✅ You need high submission volumes on paid tier (25,000 vs 200) ✅ You want unlimited forms on the free tier ✅ You want privacy-first CAPTCHA (Altcha) ✅ You need extensive webhook integrations ✅ You want conditional routing rules without paying for a $60/mo tier ✅ You want support even on free tier ✅ You prefer more detailed analytics ✅ GDPR compliance is critical ## Real User Scenarios ### Scenario 1: Personal Portfolio **Need:** Simple contact form, ~20 submissions/month **Formspree:** Free tier works great **Static Forms:** Free tier works great **Winner:** Tie - both excellent choices ### Scenario 2: Freelancer Website **Need:** Contact form + file uploads, ~100 submissions/month **Formspree:** Personal plan required ($10/mo) for file uploads **Static Forms:** Pro plan required ($9/mo) for file uploads **Winner:** Static Forms saves $12/year with far more submissions included ### Scenario 3: Small Business **Need:** Multiple forms, webhooks, 500 submissions/month **Formspree:** Professional plan - $20/mo (Personal only includes 200) **Static Forms:** Pro plan - $9/mo (includes 25,000 submissions) **Winner:** Static Forms offers dramatically better value ### Scenario 4: Agency/Multiple Sites **Need:** Many forms, high volume, integrations **Formspree:** Business plan - $60/mo (20,000 submissions) **Static Forms:** Pro plan - $9/mo (or Advanced $35/mo for 200,000) **Winner:** Static Forms - significant cost savings ## Pricing Calculator | Monthly Submissions | Formspree Cost | Static Forms Cost | Savings | |---------------------|----------------|-------------------|---------| | 0-50 | Free | Free | $0 | | 51-200 | $10/mo (Personal) | Free | Static Forms | | 201-2,000 | $20/mo (Professional) | Free (up to 500) / $9 Pro | Static Forms | | 2,001-20,000 | $60/mo (Business) | $9/mo (Pro) | Static Forms | | 20,001-25,000 | Custom / enterprise | $9/mo (Pro) | Static Forms | | 25,001-200,000 | Custom / enterprise | $35/mo (Advanced) | Static Forms | ## Final Verdict Both Formspree and Static Forms are excellent form backend services. Your choice depends on your priorities: ### Formspree Strengths: - Slightly simpler initial setup (no account needed for basic use) - Official React integration package - Established reputation - Formshield ML + Akismet spam filtering - Larger per-file upload limit (25MB) ### Static Forms Strengths: - **AI-powered auto-reply** (exclusive feature) - **Better value** paid plans ($9 vs $10 with 125x more submissions) - **Higher submission limits** (25,000 vs 200 on entry paid tier) - Unlimited forms on the free tier - Privacy-first Altcha CAPTCHA - More webhook integrations - **Form rules** on the $9/mo Pro tier (Formspree gates them to $60/mo Business) - Better overall value ## Conclusion For most users, **Static Forms offers better value**. The combination of lower pricing, far more submissions on paid plans (25,000 vs 200), unlimited forms on the free tier, and privacy-first options makes it the smarter choice for budget-conscious developers and businesses. **Choose Formspree** if you specifically need their React package or Formshield/Akismet spam filtering and don't mind the much lower submission quota. **Choose Static Forms** if you want: - **AI-powered auto-reply** (not available in Formspree) - Better value (500 free submissions, $9 for 25,000 vs $10 for 200) - **125x more submissions** on entry paid tier ($9 vs $10) - Privacy-first CAPTCHA (Altcha) - More webhook integrations - [Form rules](/docs/forms/rules) on Pro instead of Formspree's $60/mo Business tier - Unlimited forms on the free tier Ready to get started? [Sign up for Static Forms](https://www.staticforms.dev) and start with the generous free tier (500 submissions/month). Upgrade to Pro ($9/mo) when you need advanced features (25,000 submissions/month). For implementation guides, check out: - [HTML form tutorial](/blog/html-form-send-email-without-server) - [React integration](/blog/react-contact-form-email-tutorial) - [GitHub Pages guide](/blog/github-pages-contact-form-tutorial) - [Next.js tutorial](/blog/using-staticforms-with-nextjs) --- ## How to Build an Application Form with HTML & JavaScript (2026) Source: https://www.staticforms.dev/blog/how-to-build-application-form Date: 2026-01-13 Author: Static Forms Team Building an application form is essential for businesses looking to collect structured information from job applicants, program participants, or membership candidates. Unlike simple contact forms, application forms require careful planning for multiple fields, file uploads (like resumes or portfolios), validation, and professional data handling. In this comprehensive guide, you'll learn how to create production-ready application forms that handle file uploads, validate user input, and deliver submissions directly to your email—all without managing backend infrastructure or databases. Whether you're creating a job application form, event registration, membership signup, or program application, this tutorial provides everything you need to implement a professional solution in minutes. ## What You'll Build By the end of this tutorial, you'll have a complete application form with: - ✅ Professional multi-field layout (name, email, phone, position, etc.) - ✅ File upload capability for resumes, cover letters, and portfolios - ✅ Form validation with helpful error messages - ✅ Loading states and user feedback - ✅ Email notifications with file attachments - ✅ Spam protection (honeypot + optional reCAPTCHA) - ✅ Responsive, accessible design - ✅ React and Next.js versions included ## What is an Application Form? An application form is a structured web form that collects detailed information from individuals applying for jobs, programs, memberships, or services. Unlike simple contact forms that might only capture name and message, application forms typically include: ### Common Application Form Use Cases **Job Applications** - Collecting resumes, cover letters, and portfolios - Gathering work experience and qualifications - Receiving references and contact information **Program Registrations** - Educational program applications - Training course enrollments - Certification programs **Membership Applications** - Organization memberships - Club enrollments - Subscription registrations **Grant & Funding Applications** - Scholarship applications - Grant proposals - Funding requests ## Essential Fields for Application Forms A professional application form should include these core fields: ### Basic Information - **Full Name** - First and last name - **Email Address** - Primary contact method - **Phone Number** - Secondary contact option - **Location** - City, state, or country ### Position-Specific Fields - **Position/Program** - What they're applying for - **Cover Letter** - Why they're interested - **Resume/CV Upload** - File upload for credentials - **Portfolio** - Additional work samples (optional) ### Experience & Qualifications - **Work Experience** - Previous roles or relevant background - **Education** - Degrees or certifications - **Skills** - Relevant competencies - **References** - Contact information for references (optional) ### Additional Fields - **Start Date** - When they're available - **Additional Information** - Anything else they want to share - **How did you hear about us?** - Marketing attribution ## Step 1: Create a Basic HTML Application Form Let's start with a complete HTML application form with all essential fields: ```html Job Application Form

Job Application Form

We're excited to learn more about you! Please complete all required fields.

Accepted formats: PDF, DOC, DOCX (Max 5MB)
``` This complete HTML application form includes: - All essential fields for job applications - Built-in validation - File upload for resumes - Professional styling - Spam protection (honeypot field) - Success and error messages ## Step 2: Adding File Upload Handling File uploads are crucial for application forms. Here's what you need to know: ### Important File Upload Requirements 1. **Form enctype**: Must be set to `multipart/form-data` ```html
``` 2. **File input with restrictions**: ```html ``` 3. **FormData in JavaScript**: Don't use JSON for file uploads ```javascript const formData = new FormData(form); // Don't set Content-Type header manually ``` ### File Size Limits Static Forms paid plans (Pro/Advanced) support: - **Maximum file size**: 5MB per file - **Multiple files**: Supported - **File types**: Use the `accept` attribute for client-side filtering; the server enforces size only ## Step 3: Form Validation Best Practices Implement proper validation to improve user experience: ### HTML5 Validation Use built-in HTML5 validation attributes: ```html ``` ### Custom JavaScript Validation Add more sophisticated validation: ```javascript function validateForm(formData) { const email = formData.get('email'); const phone = formData.get('phone'); const file = formData.get('resume'); // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { alert('Please enter a valid email address'); return false; } // Validate file size (5MB = 5 * 1024 * 1024 bytes) if (file && file.size > 5 * 1024 * 1024) { alert('File size must be less than 5MB'); return false; } // Validate file type const allowedTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']; if (file && !allowedTypes.includes(file.type)) { alert('Only PDF, DOC, and DOCX files are allowed'); return false; } return true; } ``` ## Step 4: React Application Form Component For React applications, here's a complete component with hooks: ```jsx import React, { useState } from 'react'; import './ApplicationForm.css'; function ApplicationForm() { const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', phone: '', position: '', coverLetter: '', experience: '', startDate: '', linkedin: '', portfolio: '', additionalInfo: '', }); const [resume, setResume] = useState(null); const [status, setStatus] = useState({ submitting: false, submitted: false, error: false, }); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleFileChange = (e) => { const file = e.target.files[0]; // Validate file size (5MB) if (file && file.size > 5 * 1024 * 1024) { alert('File size must be less than 5MB'); e.target.value = ''; return; } setResume(file); }; const handleSubmit = async (e) => { e.preventDefault(); setStatus({ submitting: true, submitted: false, error: false }); try { const submitData = new FormData(); // Add all form fields Object.keys(formData).forEach(key => { submitData.append(key, formData[key]); }); // Add file if (resume) { submitData.append('resume', resume); } // Add Static Forms fields submitData.append('apiKey', 'YOUR_API_KEY_HERE'); submitData.append('replyTo', formData.email); submitData.append('subject', `Job Application: ${formData.position}`); const response = await fetch('https://api.staticforms.dev/submit', { method: 'POST', body: submitData, }); const result = await response.json(); if (result.success) { setStatus({ submitting: false, submitted: true, error: false }); // Reset form setFormData({ firstName: '', lastName: '', email: '', phone: '', position: '', coverLetter: '', experience: '', startDate: '', linkedin: '', portfolio: '', additionalInfo: '', }); setResume(null); // Reset file input document.getElementById('resume').value = ''; } else { throw new Error('Submission failed'); } } catch (error) { setStatus({ submitting: false, submitted: false, error: true }); console.error('Error:', error); } }; return (

Job Application Form

We're excited to learn more about you! Please complete all required fields.

{status.submitted && (
✅ Thank you for your application! We've received your information and will review it shortly.
)} {status.error && (
❌ Oops! Something went wrong. Please try again.
)}
Accepted formats: PDF, DOC, DOCX (Max 5MB)
)} {/* Step 3: Documents & Submit */} {step === 3 && (
)} ); ``` ### Webhook Integration for ATS Connect your application form to Applicant Tracking Systems: 1. Enable webhooks in your Static Forms dashboard 2. Add your ATS webhook URL 3. Map form fields to ATS fields 4. Receive real-time application notifications ### Auto-Responder Emails Send automatic confirmation emails to applicants: 1. Enable auto-responders in dashboard 2. Customize email template 3. Include next steps and timeline 4. Build trust with applicants ## Common Issues & Troubleshooting ### File Upload Not Working **Problem**: Files not being attached to emails **Solution**: 1. Ensure `enctype="multipart/form-data"` on form 2. Use FormData() in JavaScript, not JSON 3. Don't set Content-Type header manually 4. Upgrade to a paid plan (Pro or Advanced) (required for file uploads) ### Form Validation Failing **Problem**: Form submits with invalid data **Solution**: 1. Add HTML5 validation attributes (required, type="email", etc.) 2. Implement JavaScript validation before submit 3. Check file size limits (5MB max) 4. Validate file types ### Submissions Not Arriving **Problem**: Form submits successfully but no email received **Solution**: 1. Check spam folder 2. Verify API key is correct 3. Ensure email address in dashboard settings is correct 4. Check Static Forms dashboard for submission logs ## Best Practices Summary ✅ **Do:** - Use clear, descriptive field labels - Mark required fields with asterisks - Provide helpful placeholder text - Validate file sizes and types - Show loading states during submission - Display clear success/error messages - Implement spam protection - Make forms mobile-responsive - Include privacy policy link - Use auto-responders for better UX ❌ **Don't:** - Ask for unnecessary information - Use technical jargon in labels - Make all fields required - Skip error handling - Forget file size limits - Ignore accessibility - Use intrusive CAPTCHAs - Skip mobile testing ## Conclusion You now have everything needed to build professional application forms with file uploads, validation, and spam protection. Whether you're creating job application forms, program registrations, or membership applications, these patterns will help you collect structured data efficiently. Static Forms handles all the backend complexity—email delivery, file attachments, spam protection, and data storage—so you can focus on building great user experiences. ## Next Steps - [Sign up for Static Forms](https://www.staticforms.dev) (free tier available) - [Explore file upload documentation](/docs/file-uploads) - [Learn about webhook integrations](/docs/webhooks) - [Add AI-powered auto-replies](/docs/ai-reply) - [Implement spam protection](/docs/spam-protection) Ready to accept your first application? [Get started with Static Forms today](https://www.staticforms.dev) and start receiving applications in minutes. --- ## Netlify Forms vs Static Forms: Complete Comparison (2026) Source: https://www.staticforms.dev/blog/netlify-forms-vs-static-forms-comparison Date: 2026-01-13 Author: Static Forms Team Choosing the right form backend for your static website is crucial for user experience and long-term maintainability. Both Netlify Forms and Static Forms are popular solutions for handling form submissions without server-side code, but they serve different needs and use cases. In this comprehensive comparison, we'll examine both services across key factors: ease of use, features, pricing, platform flexibility, and spam protection. Whether you're building a personal portfolio, a business website, or a large-scale JAMstack application, this guide will help you make an informed decision. We'll provide an honest, unbiased comparison highlighting the strengths and limitations of each service so you can choose the best solution for your specific needs. ## Quick Overview | Feature | Netlify Forms | Static Forms | |---------|---------------|--------------| | **Hosting Required** | Netlify only | Any hosting platform | | **Free Tier** | 100 submissions/month | 500 submissions/month | | **Setup Complexity** | Very simple (1 attribute) | Simple (2 fields) | | **Spam Protection** | Honeypot, Akismet (paid) | Honeypot, reCAPTCHA, Altcha | | **Email Notifications** | Yes | Yes | | **AI-Powered Auto-Reply** | ❌ No | ✅ Yes (Pro) | | **File Uploads** | Yes | Yes | | **Webhooks** | Yes (paid) | Yes (paid) | | **Works Without Netlify** | ❌ No | ✅ Yes | | **Starting Price** | $19/mo (Level 1) | $9/mo (Pro) | ## Platform Flexibility ### Netlify Forms: Netlify Hosting Required Netlify Forms is tightly integrated with Netlify's hosting platform. This has both advantages and limitations: **Pros:** - ✅ Seamless integration if you're already on Netlify - ✅ Automatic form detection during builds - ✅ Zero additional configuration needed **Cons:** - ❌ Only works on Netlify hosting - ❌ Can't use on GitHub Pages, Vercel, or traditional hosting - ❌ Vendor lock-in - switching hosts means rebuilding forms **Example - Netlify Forms Setup:** ```html
``` ### Static Forms: Platform Agnostic Static Forms works with any hosting platform that serves HTML: **Pros:** - ✅ Works on Netlify, Vercel, GitHub Pages, Cloudflare Pages - ✅ Works on traditional hosting (cPanel, shared hosting) - ✅ Works on custom infrastructure - ✅ No vendor lock-in - switch hosts anytime **Cons:** - Requires adding API endpoint and API key (2 extra fields) **Example - Static Forms Setup:** ```html
``` **Verdict:** If you're committed to Netlify hosting, Netlify Forms offers simpler setup. If you want flexibility to host anywhere, Static Forms is the clear winner. ## Ease of Setup ### Netlify Forms: Slightly Simpler Setup Netlify Forms has a marginally simpler initial setup: 1. Add `data-netlify="true"` to your form 2. Deploy to Netlify 3. Done! The build process automatically detects and configures your forms. ### Static Forms: Simple and Explicit Static Forms requires two additional elements: 1. Set form action to `https://api.staticforms.dev/submit` 2. Add your API key as a hidden field 3. Works immediately on any hosting **Verdict:** Netlify Forms wins on simplicity *if* you're already using Netlify. Static Forms is nearly as simple and works everywhere. ## Spam Protection ### Netlify Forms: Basic + Paid Options **Free Spam Protection:** - Honeypot fields (add `netlify-honeypot="bot-field"`) - Basic submission throttling **Paid Spam Protection:** - Akismet integration ($19+/month) - More aggressive filtering **Example with Honeypot:** ```html
``` ### Static Forms: Multiple Free Options **Free Spam Protection:** - Honeypot fields (built-in) - Google reCAPTCHA v2 (free) - Custom validation rules **Pro Spam Protection:** - Google reCAPTCHA v3 (invisible) - Altcha CAPTCHA (privacy-first, GDPR compliant) - Advanced rate limiting **Example with reCAPTCHA:** ```html
``` For privacy-conscious projects, Static Forms offers [Altcha CAPTCHA](/blog/implementing-altcha-captcha-html-website) - a GDPR-compliant alternative that doesn't track users. **Verdict:** Static Forms offers more spam protection options, including free reCAPTCHA and privacy-first Altcha. ## Pricing Comparison ### Netlify Forms Pricing **Free Tier:** - 100 form submissions per month - Honeypot spam filtering only - Email notifications included **Level 1 - $19/month:** - 1,000 submissions per month - Akismet spam filtering - All features included **Overage Costs:** - Additional submissions: Not clearly published ### Static Forms Pricing **Free Tier:** - 500 submissions per month - Unlimited forms - Email notifications - Honeypot + reCAPTCHA v2 - File uploads (2MB limit) **Pro - $9/month:** - 25,000 submissions per month - Everything in Free - reCAPTCHA v3 (invisible) - Altcha CAPTCHA - Webhooks - Larger file uploads (10MB) - Priority support ### Pricing Comparison Table | Plan | Netlify Forms | Static Forms | |------|---------------|--------------| | **Free** | 100 submissions | 500 submissions | | **Entry Paid** | $19/mo (1,000) | $9/mo (25,000) | | **Cost per 1,000** | $19 | $0.36 | | **Spam Protection** | Basic (free), Akismet (paid) | Honeypot, reCAPTCHA (free), Altcha (paid) | **Verdict:** Static Forms offers significantly better value with 500 free submissions (vs 100) and 25,000 submissions for $9/month (vs $19 for 1,000). ## Features Deep Dive ### Email Notifications **Both services:** - ✅ Send submission notifications to your email - ✅ Include all form field data - ✅ Customizable email subjects **Static Forms extras:** - ✅ Auto-responder emails (Pro) - ✅ AI-powered auto-reply (Pro) - ✅ Multiple recipient emails - ✅ Custom reply-to addresses ### AI-Powered Auto-Reply (Unique to Static Forms) One of Static Forms' most powerful features in 2026 is **AI Reply** - something Netlify Forms doesn't offer. **Static Forms AI Reply:** - Connect your own AI API key (OpenAI, Anthropic Claude, or Google Gemini) - Upload your knowledge base, documentation, or FAQ content - AI generates contextual, personalized responses to every form submission - Perfect for customer support, FAQ automation, and lead qualification - Available on Pro plan ($9/mo) **Example Use Cases:** - Automatically answer common questions based on your documentation - Provide personalized responses to support inquiries - Send tailored information based on form submission content - Reduce response time from hours to seconds - Scale customer support without increasing headcount **Netlify Forms:** - ❌ No AI-powered responses - Manual responses only or simple notification emails For businesses looking to provide instant, intelligent responses to form submissions, Static Forms' AI Reply is a significant advantage. Learn more in our [AI Reply documentation](/docs/ai-reply). **Verdict:** Static Forms wins with exclusive AI-powered auto-reply capability. ### File Uploads **Netlify Forms:** - Supported on all plans - File size limits depend on plan tier - Files stored on Netlify infrastructure **Static Forms:** - Supported on Pro plan ($9/mo) - 5MB limit per file - Files delivered via email attachments - Multiple file uploads supported Learn more about implementing file uploads in our [file upload tutorial](/blog/nextjs-contact-form-document-upload). ### Webhooks & Integrations **Netlify Forms:** - Webhooks available on paid plans ($19+) - Send submissions to external services - Zapier integration available **Static Forms:** - Webhooks available on Pro ($9) - Send submissions to any HTTP endpoint - Pre-built integrations: - Slack - Discord - Google Sheets - Notion - Airtable - Zapier - Make (Integromat) - n8n ### Form Submissions Dashboard **Netlify Forms:** - Built into Netlify dashboard - View all submissions - Export to CSV - Spam filtering controls **Static Forms:** - Dedicated forms dashboard - View and manage submissions - Export functionality - Analytics and reporting - Form-specific settings ## JavaScript/AJAX Support ### Netlify Forms: Extra Configuration Needed For JavaScript-based submissions: ```javascript fetch('/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: encode({ 'form-name': 'contact', ...formData }) }) ``` You also need to include a hidden HTML form for detection during build time. ### Static Forms: Works Out of the Box ```javascript fetch('https://api.staticforms.dev/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }) ``` No hidden forms or special configuration needed. **Verdict:** Static Forms has simpler AJAX/JavaScript implementation. ## Framework Integration ### Netlify Forms Works well with: - ✅ Gatsby (excellent integration) - ✅ Next.js (requires special handling for static export) - ✅ Hugo, Jekyll, Eleventy - ❌ React SPAs (requires workarounds) - ❌ Vue/Nuxt SPAs (requires workarounds) ### Static Forms Works seamlessly with: - ✅ Gatsby ([full tutorial](/blog/using-staticforms-with-gatsby)) - ✅ Next.js ([full tutorial](/blog/using-staticforms-with-nextjs)) - ✅ Nuxt.js ([full tutorial](/blog/using-staticforms-with-nuxtjs)) - ✅ React, Vue, Angular, Svelte - ✅ Hugo, Jekyll, Eleventy, Astro - ✅ Plain HTML **Verdict:** Static Forms has better framework compatibility, especially for SPAs and modern frameworks. ## Privacy & GDPR Compliance ### Netlify Forms - Data stored on Netlify servers (US-based) - Subject to Netlify privacy policy - Akismet spam filtering sends data to third parties - No specific GDPR features ### Static Forms - Offers privacy-first Altcha CAPTCHA (no tracking, no cookies) - GDPR-compliant by default - Data processing transparent - No third-party tracking unless you add reCAPTCHA For privacy-conscious projects, read our guide on [GDPR-compliant contact forms](/blog/implementing-altcha-captcha-html-website). **Verdict:** Static Forms offers better privacy options with Altcha CAPTCHA. ## Use Case Recommendations ### Choose Netlify Forms If: ✅ You're already hosting on Netlify and happy with it ✅ You want the absolute simplest setup (one attribute) ✅ You need 100+ free submissions per month ✅ You're deeply integrated with Netlify's ecosystem ✅ Budget isn't a primary concern ### Choose Static Forms If: ✅ You want hosting flexibility (Vercel, GitHub Pages, etc.) ✅ You're building on multiple platforms ✅ You want affordable paid plans ($9 vs $19 with 25x more submissions) ✅ You need more spam protection options ✅ You want privacy-first CAPTCHA (Altcha) ✅ You're building SPAs or modern JavaScript frameworks ✅ You want simpler JavaScript/AJAX integration ✅ You need extensive webhook integrations ## Migration from Netlify Forms to Static Forms Switching is straightforward: **Step 1: Update your form** ```html
``` **Step 2: Add API key** ```html ``` **Step 3: Remove Netlify-specific attributes** ```html data-netlify="true" netlify-honeypot="bot-field" ``` **Step 4: Test thoroughly** before switching production traffic. That's it! Your forms will work on any hosting platform. ## Real-World Performance ### Response Times **Netlify Forms:** - Fast (integrated with hosting) - Variable depending on build process - ~200-400ms average response time **Static Forms:** - Consistently fast API endpoint - ~150-300ms average response time - Globally distributed infrastructure ### Reliability **Both services** offer excellent uptime (99.9%+). Both have proven track records with thousands of users. ## Developer Experience ### Documentation **Netlify Forms:** - ✅ Good documentation - ✅ Integrated with main Netlify docs - ❌ Limited code examples for advanced cases **Static Forms:** - ✅ Comprehensive documentation - ✅ Framework-specific tutorials - ✅ Working code examples for every use case - ✅ Active blog with tutorials ### Support **Netlify Forms:** - Community forum - Email support (paid plans) - Response times vary **Static Forms:** - Email support (all plans) - Priority support (Pro plans) - Comprehensive troubleshooting guides ## Final Verdict Both Netlify Forms and Static Forms are excellent solutions, but they serve different needs: ### Netlify Forms is Best For: - Netlify-exclusive projects - Teams already invested in Netlify ecosystem - Projects needing 100+ free submissions ### Static Forms is Best For: - Multi-platform projects - Budget-conscious developers - Privacy-focused applications - Modern JavaScript frameworks - Projects requiring hosting flexibility - Teams wanting extensive integrations ## Conclusion The choice between Netlify Forms and Static Forms depends on your specific requirements: **If you're locked into Netlify hosting** and want the absolute simplest setup, Netlify Forms is a solid choice. The one-attribute setup is hard to beat for pure simplicity. **If you value flexibility, affordability, and modern features**, Static Forms is the better option. The ability to work on any hosting platform, combined with better pricing ($9 vs $19 with 25x more submissions) and better spam protection options, makes it ideal for most projects. Both services will reliably handle your form submissions. Your decision should be based on: 1. Hosting platform flexibility needs 2. Budget constraints 3. Required features (especially spam protection) 4. Privacy requirements Ready to get started with Static Forms? [Sign up for free](https://www.staticforms.dev) and add forms to any website - whether you're on Netlify, GitHub Pages, Vercel, or anywhere else. For implementation guides, check out our tutorials for [GitHub Pages](/blog/github-pages-contact-form-tutorial), [Next.js](/blog/using-staticforms-with-nextjs), and [HTML forms](/blog/html-form-send-email-without-server). --- ## User Registration Form Tutorial: HTML, React & Vue (2026) Source: https://www.staticforms.dev/blog/user-registration-form-tutorial Date: 2026-01-13 Author: Static Forms Team Creating a user registration form is one of the most common requirements for web applications. Whether you're building a SaaS platform, membership site, online community, or any application requiring user accounts, a well-designed registration form is your first touchpoint with new users. In this comprehensive tutorial, you'll learn how to build secure, user-friendly registration forms with email verification, password validation, and GDPR compliance—all without managing backend infrastructure. ## What You'll Build By the end of this tutorial, you'll have production-ready registration forms with: - ✅ Email and password validation - ✅ Password strength indicators - ✅ Email verification workflow - ✅ Terms & conditions agreement - ✅ GDPR consent management - ✅ Secure data handling - ✅ Examples in HTML, React, and Vue.js - ✅ Spam protection built-in ## Why User Registration Forms Matter User registration forms serve as the gateway to your application. A well-designed registration form: ### Increases Conversion Rates - Simplified forms reduce friction - Clear error messages prevent abandonment - Progressive disclosure keeps users engaged ### Ensures Data Quality - Validation prevents fake or incorrect data - Email verification confirms real users - Strong passwords protect user accounts ### Builds Trust - GDPR compliance shows respect for privacy - Security measures demonstrate professionalism - Transparent data usage builds confidence ### Prevents Fraud - Email verification reduces fake accounts - CAPTCHA protection blocks bots - Rate limiting prevents abuse ## Essential Registration Form Fields A well-designed registration form balances security with usability. Here are the essential fields: ### Required Fields **Email Address** - Primary identifier for the account - Used for login and communication - Must be validated and verified **Password** - Minimum 8-12 characters - Mix of uppercase, lowercase, numbers, symbols - Password strength indicator helps users **Terms & Conditions Checkbox** - Legal requirement in most jurisdictions - Must be explicitly checked (not pre-checked) - Link to full terms and privacy policy ### Optional but Recommended **Full Name or Username** - Personalizes the user experience - Helps identify accounts - Can be collected during registration or onboarding **Password Confirmation** - Prevents typos in passwords - Improves user experience - Reduces support requests **Newsletter/Marketing Consent** - Separate from terms & conditions - Must be optional (GDPR requirement) - Clear opt-in language ## HTML Registration Form Example Let's start with a complete HTML registration form with validation: ```html User Registration Form

Create Your Account

Join thousands of users already using our platform

Please enter your full name
Please enter a valid email address
At least 8 characters with uppercase, lowercase, and numbers
Password is too weak
Passwords do not match
``` This complete HTML registration form includes: - Real-time password strength indicator - Form validation - Professional styling - Mobile-responsive design - Clear error messages ## Adding Password Strength Validation Strong password validation is critical for security. Here's a comprehensive validation function: ```javascript function validatePasswordStrength(password) { const requirements = { minLength: password.length >= 8, maxLength: password.length <= 128, hasUppercase: /[A-Z]/.test(password), hasLowercase: /[a-z]/.test(password), hasNumber: /[0-9]/.test(password), hasSpecial: /[!@#$%^&*(),.?":{}|<>]/.test(password), }; const score = Object.values(requirements).filter(Boolean).length; return { isValid: requirements.minLength && requirements.hasUppercase && requirements.hasLowercase && requirements.hasNumber, score, requirements, strength: score <= 2 ? 'weak' : score <= 4 ? 'medium' : 'strong', }; } // Usage const validation = validatePasswordStrength('MyP@ssw0rd'); console.log(validation.isValid); // true console.log(validation.strength); // 'strong' ``` ### Best Practices for Password Validation **Minimum Requirements:** - At least 8 characters (12+ recommended) - Mix of uppercase and lowercase letters - At least one number - At least one special character **Avoid:** - Maximum length restrictions (allow up to 128 characters) - Overly complex rules that frustrate users - Rejecting valid special characters - Requiring frequent password changes **Show Users:** - Real-time password strength feedback - Clear requirements before they start typing - Specific feedback on what's missing ## Email Confirmation (Auto-Responder) Static Forms can automatically send confirmation emails to users who register through your form. This provides immediate feedback and helps ensure they entered a valid email address. To set up email confirmation: 1. Go to your Static Forms dashboard 2. Select your registration form 3. Enable the "Auto-Responder" feature 4. Customize your confirmation email template: ```text Subject: Registration Received Hi {name}, Thank you for registering! We've received your information and will be in touch soon. Registration Details: - Email: {email} - Submitted: {timestamp} Best regards, The [Your App] Team ``` **Note:** Static Forms auto-responder sends a simple confirmation email. If you need advanced email verification workflows (with unique tokens, verification links, or backend validation), you'll need to implement a custom backend solution outside of Static Forms. ## React Registration Form Component For React applications, here's a complete registration form with hooks: ```jsx import React, { useState, useEffect } from 'react'; import './RegistrationForm.css'; function RegistrationForm() { const [formData, setFormData] = useState({ name: '', email: '', password: '', confirmPassword: '', terms: false, newsletter: false, }); const [errors, setErrors] = useState({}); const [passwordStrength, setPasswordStrength] = useState(0); const [status, setStatus] = useState({ submitting: false, submitted: false, error: false, }); // Calculate password strength useEffect(() => { if (formData.password) { const strength = calculatePasswordStrength(formData.password); setPasswordStrength(strength); } else { setPasswordStrength(0); } }, [formData.password]); const calculatePasswordStrength = (password) => { let strength = 0; if (password.length >= 8) strength++; if (password.length >= 12) strength++; if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++; if (/[0-9]/.test(password)) strength++; if (/[^a-zA-Z0-9]/.test(password)) strength++; return strength; }; const validateForm = () => { const newErrors = {}; if (formData.name.trim().length < 2) { newErrors.name = 'Please enter your full name'; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(formData.email)) { newErrors.email = 'Please enter a valid email address'; } if (passwordStrength < 2) { newErrors.password = 'Password is too weak'; } if (formData.password !== formData.confirmPassword) { newErrors.confirmPassword = 'Passwords do not match'; } if (!formData.terms) { newErrors.terms = 'You must accept the terms and conditions'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleChange = (e) => { const { name, value, type, checked } = e.target; setFormData(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); // Clear error when user starts typing if (errors[name]) { setErrors(prev => ({ ...prev, [name]: '' })); } }; const handleSubmit = async (e) => { e.preventDefault(); if (!validateForm()) return; setStatus({ submitting: true, submitted: false, error: false }); try { const submitData = new FormData(); Object.keys(formData).forEach(key => { if (key !== 'confirmPassword') { submitData.append(key, formData[key]); } }); submitData.append('apiKey', 'YOUR_API_KEY_HERE'); submitData.append('subject', 'New User Registration'); submitData.append('replyTo', formData.email); const response = await fetch('https://api.staticforms.dev/submit', { method: 'POST', body: submitData, }); const result = await response.json(); if (result.success) { setStatus({ submitting: false, submitted: true, error: false }); setFormData({ name: '', email: '', password: '', confirmPassword: '', terms: false, newsletter: false, }); } else { throw new Error('Registration failed'); } } catch (error) { setStatus({ submitting: false, submitted: false, error: true }); console.error('Error:', error); } }; const getStrengthClass = () => { if (passwordStrength === 0) return ''; if (passwordStrength <= 2) return 'weak'; if (passwordStrength <= 3) return 'medium'; return 'strong'; }; return (

Create Your Account

Join thousands of users already using our platform

{status.submitted && (
✅ Registration successful! Please check your email to verify your account.
)} {status.error && (
❌ Something went wrong. Please try again.
)}
{errors.name &&
{errors.name}
}
{errors.email &&
{errors.email}
}
At least 8 characters with uppercase, lowercase, and numbers
{errors.password &&
{errors.password}
}
{errors.confirmPassword &&
{errors.confirmPassword}
}
Already have an account? Sign in
); } export default RegistrationForm; ``` ## Vue.js Registration Form Example For Vue.js applications (using Composition API): ```vue ``` ## Security Best Practices ### 1. Never Store Passwords in Plain Text - Always hash passwords server-side - Use bcrypt, Argon2, or similar - Never send plain passwords via email ### 2. Implement Rate Limiting - Limit registration attempts per IP - Prevent automated bot registrations - Use CAPTCHA for suspicious activity ### 3. Validate on Both Client and Server - Client-side validation for UX - Server-side validation for security - Never trust client-side data ### 4. Use HTTPS Always - Encrypt all data in transit - Enable HSTS headers - Use secure cookies ### 5. Implement CAPTCHA Protection Add reCAPTCHA or Altcha to prevent bot registrations: ```html
``` ## GDPR Compliance Considerations When building registration forms for users in the EU, ensure GDPR compliance: ### 1. Clear Consent - Separate consent for terms vs. marketing emails - No pre-checked boxes for optional consents - Clear, plain language explanations ### 2. Data Minimization - Only collect necessary data - Explain why each field is required - Don't collect data "just in case" ### 3. Right to Access & Deletion - Provide data export functionality - Allow account deletion - Honor deletion requests promptly ### 4. Privacy Policy - Link to detailed privacy policy - Explain data usage clearly - List third parties with access ### 5. Secure Data Handling - Encrypt data at rest and in transit - Limit data access - Regular security audits ## Getting Your Static Forms API Key To use Static Forms with your registration form: 1. **Sign up for free** at [Static Forms](https://www.staticforms.dev) 2. **Create a new form** in your dashboard 3. **Copy your API key** 4. **Replace** `YOUR_API_KEY_HERE` in the code examples ### Using Environment Variables **React/Next.js (.env.local)**: ```bash NEXT_PUBLIC_STATICFORMS_KEY=your_api_key_here ``` **Vue.js (.env)**: ```bash VUE_APP_STATICFORMS_KEY=your_api_key_here ``` Then use: ```javascript process.env.NEXT_PUBLIC_STATICFORMS_KEY // Next.js process.env.VUE_APP_STATICFORMS_KEY // Vue.js ``` ## Common Issues & Solutions ### Password Validation Too Strict **Problem**: Users struggle to create valid passwords **Solution**: - Show requirements clearly before they start - Provide real-time feedback - Allow paste functionality - Consider passphrase support ### Email Verification Not Working **Problem**: Users don't receive verification emails **Solution**: - Check spam folders - Use recognizable sender name - Send email immediately after registration - Include resend option ### Terms Checkbox Not Visible **Problem**: Users miss the terms checkbox **Solution**: - Make checkbox and label clearly visible - Use adequate contrast - Position prominently - Block submission with clear error if unchecked ## Conclusion You now have everything needed to build secure, user-friendly registration forms with email verification, password validation, and GDPR compliance. Whether you're using plain HTML, React, or Vue.js, these patterns will help you create professional registration experiences. Static Forms handles all the backend complexity—email delivery, data storage, spam protection—so you can focus on building great user experiences. ## Next Steps - [Sign up for Static Forms](https://www.staticforms.dev) (free tier available) - [Learn about spam protection](/docs/spam-protection) - [Configure auto-responders](/docs/auto-responders) - [Build application forms](/blog/how-to-build-application-form) - [Add Altcha CAPTCHA](/docs/altcha) Ready to start collecting registrations? [Get started with Static Forms today](https://www.staticforms.dev) and have your registration form live in minutes. --- ## How to Add a Contact Form to GitHub Pages Source: https://www.staticforms.dev/blog/github-pages-contact-form-tutorial Date: 2026-01-06 Author: Static Forms Team GitHub Pages is an excellent platform for hosting static websites - it's free, fast, and integrates seamlessly with your Git workflow. However, one common challenge developers face is adding a functional contact form since GitHub Pages doesn't support server-side processing. The good news? You can easily add a professional contact form to your GitHub Pages site without writing any backend code. In this comprehensive guide, we'll show you how to implement a fully functional contact form on GitHub Pages using Static Forms, a form backend service that handles all the server-side logic for you. Your visitors will be able to send messages, and you'll receive them directly in your email inbox. ## Why GitHub Pages Needs a Form Solution GitHub Pages serves only static files - HTML, CSS, and JavaScript. While this makes it incredibly fast and secure, it means you can't process form submissions directly. Traditional form handling requires: - Server-side code to process submissions - A database to store form data - Email sending capabilities - Spam protection mechanisms Static Forms solves all of these challenges by providing a dedicated API endpoint that handles form submissions for you. ## Prerequisites Before we begin, make sure you have: - A GitHub Pages site (already deployed or ready to deploy) - A Static Forms account - [sign up for free](https://www.staticforms.dev) - Your API key from the [Static Forms dashboard](https://www.staticforms.dev/app/forms) ## Step 1: Create a Basic HTML Contact Form Let's start with a simple contact form. Create a new file called `contact.html` in your GitHub Pages repository, or add this form to an existing page: ```html Contact Us

Get in Touch

We'd love to hear from you. Send us a message and we'll respond as soon as possible.

``` **Important**: Replace `YOUR_API_KEY_HERE` with your actual API key from your Static Forms dashboard. ## Step 2: Understanding the Form Configuration Let's break down the key parts of the form: ### The Action Attribute ```html
``` This points to the Static Forms API endpoint that will handle your submissions. ### Required Hidden Fields ```html ``` Your API key authenticates your form with Static Forms. ### Optional Configuration Fields ```html ``` The `@` value tells Static Forms to use the email address from the form's email field as the reply-to address, making it easy to respond to messages. ### Honeypot Spam Protection ```html ``` This hidden field catches bots that automatically fill out all form fields. Legitimate users won't see or fill this field. ## Step 3: Add reCAPTCHA Protection (Optional) For additional spam protection, you can add Google reCAPTCHA. First, get your reCAPTCHA keys from the [Google reCAPTCHA Admin Console](https://www.google.com/recaptcha/admin). Add the reCAPTCHA script to your ``: ```html ``` Then add the reCAPTCHA widget to your form: ```html
``` Don't forget to configure your reCAPTCHA secret key in your [Static Forms CAPTCHA settings](https://www.staticforms.dev/app/forms). For more details on implementing reCAPTCHA, check out our [understanding reCAPTCHA guide](/blog/understanding-recaptcha). ## Step 4: Deploy to GitHub Pages Now let's deploy your contact form to GitHub Pages: 1. **Commit your changes**: ```bash git add contact.html git commit -m "Add contact form with Static Forms integration" ``` 2. **Push to GitHub**: ```bash git push origin main ``` 3. **Wait for deployment**: GitHub Pages automatically deploys your changes. This usually takes 1-2 minutes. 4. **Test your form**: Visit `https://yourusername.github.io/yourrepo/contact.html` and submit a test message. ## Step 5: Customize Your Form ### Custom Success Redirect Instead of showing a success message on the same page, you can redirect users to a thank-you page: ```html ``` Create a `thank-you.html` page: ```html Thank You

✅ Message Sent!

Thank you for contacting us. We'll get back to you as soon as possible.

Return to Home
``` ### Custom Email Subject Set a custom subject line for better organization: ```html ``` Or use a visible subject field (as shown in the main example) to let users set their own subject. ## Advanced Features ### File Attachments Allow users to upload files with their messages: ```html
``` Note: File uploads require encoding the form as `multipart/form-data`. See our [file uploads guide](/blog/nextjs-contact-form-document-upload) for implementation details. ### Custom Fields Add custom fields by prefixing the name with `$`: ```html
``` ### Webhook Integration For advanced workflows, you can connect your form to other services using webhooks (paid plans: Pro/Advanced). This allows you to: - Send submissions to Slack or Discord - Add contacts to your CRM - Trigger automation workflows - Store data in Google Sheets or Airtable Configure webhooks in your [Static Forms dashboard](https://www.staticforms.dev/app/forms). ## Complete Working Example Here's a complete, copy-paste-ready contact form for GitHub Pages: ```html Contact - My GitHub Pages Site

Contact Us

Have a question or want to work together? Drop us a message!

``` ## Troubleshooting Common Issues ### Not Receiving Email Notifications If you're not receiving form submissions: - ✅ Verify your API key is correct in the form - ✅ Check your spam/junk folder - ✅ Confirm your email is verified in Static Forms - ✅ Test with different email addresses ### Form Submission Fails If submissions aren't working: - ✅ Check the browser console for JavaScript errors - ✅ Ensure the API endpoint URL is correct: `https://api.staticforms.dev/submit` - ✅ Verify all required fields have the `required` attribute - ✅ Make sure the honeypot field is properly hidden ### CORS Errors If you see CORS-related errors: - ✅ Use HTTPS for your GitHub Pages site (enable in repository settings) - ✅ Ensure you're using JSON format when submitting via JavaScript - ✅ Include proper `Content-Type` header: `application/json` ## Best Practices for GitHub Pages Forms 1. **Keep it simple** - Only ask for information you actually need 2. **Validate inputs** - Use HTML5 validation and JavaScript for better UX 3. **Provide feedback** - Show loading states and confirmation messages 4. **Mobile-friendly** - Ensure your form works well on all devices 5. **Test thoroughly** - Submit test forms before going live ## Conclusion Adding a contact form to your GitHub Pages site doesn't have to be complicated. With Static Forms, you can implement a professional contact form in minutes without any backend code or complex infrastructure. Your form will handle submissions reliably, protect against spam with built-in security features, and deliver messages straight to your inbox - all while keeping your GitHub Pages site completely static. Ready to add a contact form to your GitHub Pages site? [Sign up for Static Forms](https://www.staticforms.dev) and get started today with our free tier! For more integration examples, check out our guides for [Next.js](/blog/using-staticforms-with-nextjs), [Gatsby](/blog/using-staticforms-with-gatsby), and [Nuxt.js](/blog/using-staticforms-with-nuxtjs). --- ## HTML Form That Sends Email Without Server (2026) Source: https://www.staticforms.dev/blog/html-form-send-email-without-server Date: 2026-01-06 Author: Static Forms Team One of the most common questions beginners ask is: "How can I create an HTML form that sends emails without a server?" The traditional answer involves PHP scripts, Node.js backends, or complex server configurations. But in 2026, there's a much simpler solution that doesn't require any server-side programming knowledge. In this comprehensive guide, you'll learn how to create a fully functional HTML contact form that sends emails directly to your inbox using Static Forms - a free form backend service. No PHP, no Node.js, no server setup required. Just pure HTML and a few lines of JavaScript for better user experience. Whether you're building your first website, creating a portfolio, or adding a contact form to a landing page, this guide will walk you through everything step by step. ## Why Traditional Methods Are Complicated Before we dive into the simple solution, let's understand why this used to be difficult: ### The Old Way: PHP Mail Scripts ```php // This requires a PHP-enabled server ``` **Problems**: Requires PHP hosting, vulnerable to spam, often blocked by servers, needs SMTP configuration. ### The Old Way: Node.js Backend ```javascript // This requires running a Node.js server const nodemailer = require('nodemailer'); // ...dozens of lines of configuration ``` **Problems**: Requires server hosting, complex setup, ongoing maintenance, security concerns. ### The Simple Modern Way ```html
``` **Benefits**: ✅ No server required, ✅ No coding knowledge needed, ✅ Works immediately, ✅ Built-in spam protection. ## What You'll Need Before we start, make sure you have: - A text editor (VS Code, Sublime Text, or even Notepad) - A Static Forms account - [sign up free here](https://www.staticforms.dev) - Your API key from the [dashboard](https://www.staticforms.dev/app/forms) - A web browser for testing That's it! No server, no hosting requirements, no complex installations. ## Step 1: Create Your Basic HTML Form Let's start with the simplest possible contact form. Create a new file called `contact.html`: ```html Contact Form

Contact Us







``` **That's it!** Open this file in your browser and test it. When someone submits the form, you'll receive the message in your email inbox. ### How It Works 1. The `action` attribute points to Static Forms' API endpoint 2. The `method="POST"` sends the data securely 3. The hidden `apiKey` field tells Static Forms where to send the email 4. Your form fields (`name`, `email`, `message`) are included in the email ## Step 2: Style Your Form Let's make it look professional with some CSS: ```html Contact Form - Send Email

Get in Touch

Fill out the form below and we'll get back to you shortly.

``` Now your form looks professional and modern! 🎨 ## Step 3: Add User Feedback with JavaScript Let's enhance the user experience by showing a success message without leaving the page: ```html Contact Form - Send Email

Get in Touch

Fill out the form below and we'll get back to you shortly.

``` **Perfect!** Now your form provides instant feedback when emails are sent. ✅ ## Step 4: Add Spam Protection Protect your form from spam with these built-in features: ### Honeypot Field (Already Included) ```html ``` This invisible field catches bots. Human users won't see it, but bots will fill it out, identifying themselves. ### Add reCAPTCHA for Extra Protection Include Google reCAPTCHA for stronger spam protection: 1. Get your reCAPTCHA keys from [Google reCAPTCHA Admin](https://www.google.com/recaptcha/admin) 2. Add the script to your ``: ```html ``` 3. Add the widget to your form: ```html
``` 4. Configure the secret key in your [Static Forms settings](https://www.staticforms.dev/app/forms) For a complete reCAPTCHA guide, see our [understanding reCAPTCHA tutorial](/blog/understanding-recaptcha). ## Advanced Features ### Custom Email Subject Line Set a custom subject for better email organization: ```html ``` Or let users set their own subject: ```html
``` ### Reply-To Configuration The `replyTo` field determines where replies go: ```html ``` ### Redirect After Submission Send users to a thank-you page instead of showing a message: ```html ``` ### File Attachments Allow users to upload files: ```html
``` Note: File uploads require additional setup. See our [file upload tutorial](/blog/nextjs-contact-form-document-upload) for details. ## Common Questions ### Do I need to install anything on my server? No! Static Forms handles everything. You just need to add HTML to your website. ### Will this work on any hosting provider? Yes! It works on: - ✅ GitHub Pages - ✅ Netlify - ✅ Vercel - ✅ Traditional hosting (cPanel, etc.) - ✅ Any hosting that serves HTML files ### Is it really free? Yes! Static Forms offers a generous free tier perfect for personal websites and small businesses. Premium features are available for larger projects. ### How do I prevent spam? Use the built-in honeypot field (included in our examples) and optionally add reCAPTCHA for extra protection. ### Can I customize the email content? The email will include all form fields. You can customize which fields appear by adding or removing form inputs. ### What if I need more advanced features? Static Forms Pro offers: - Webhook integrations (send data to other services) - Higher submission limits - File upload support - Altcha CAPTCHA (privacy-focused alternative to reCAPTCHA) - Priority support ## Troubleshooting ### Emails not arriving? - ✅ Check your spam/junk folder - ✅ Verify your API key is correct - ✅ Confirm your email is verified with Static Forms - ✅ Check the browser console for errors ### Form not submitting? - ✅ Make sure you replaced `YOUR_API_KEY_HERE` with your actual API key - ✅ Check that the form action URL is correct: `https://api.staticforms.dev/submit` - ✅ Verify required fields are filled out - ✅ Look for JavaScript errors in the browser console ### Getting a CORS error? - ✅ Use the JavaScript fetch method (as shown in Step 3) - ✅ Include the `Content-Type: application/json` header - ✅ Make sure you're sending JSON data, not FormData ## Complete Copy-Paste Solution Here's the complete, production-ready code you can copy and paste: ```html Contact Form

Contact Us

``` **Just replace `YOUR_API_KEY_HERE` and you're done!** ## Conclusion Creating an HTML form that sends emails without a server is now incredibly simple. With Static Forms, you can: - ✅ Set up a working contact form in under 5 minutes - ✅ Receive emails directly in your inbox - ✅ No PHP, Node.js, or server configuration needed - ✅ Built-in spam protection - ✅ Works on any hosting platform - ✅ Completely free for personal use Whether you're a beginner building your first website or an experienced developer looking for a simple solution, Static Forms eliminates the complexity of form handling. Ready to add email functionality to your HTML forms? [Sign up for Static Forms](https://www.staticforms.dev) and start receiving submissions in minutes! For more tutorials, check out our guides on [GitHub Pages integration](/blog/github-pages-contact-form-tutorial), [Next.js forms](/blog/using-staticforms-with-nextjs), and [Gatsby forms](/blog/using-staticforms-with-gatsby). --- ## React Contact Form with Email (No Backend Required) Source: https://www.staticforms.dev/blog/react-contact-form-email-tutorial Date: 2026-01-06 Author: Static Forms Team Building a contact form in React is straightforward, but sending those form submissions to your email typically requires backend infrastructure. What if you could handle form submissions and receive emails without setting up a Node.js server, configuring SMTP, or managing databases? In this comprehensive tutorial, you'll learn how to create a production-ready React contact form that sends emails directly to your inbox using Static Forms - no backend code required. We'll use modern React patterns including hooks, implement form validation, add loading states, and include spam protection. Whether you're building a portfolio, a business website, or any React application that needs a contact form, this guide provides everything you need to implement email functionality in minutes. ## What You'll Build By the end of this tutorial, you'll have a React contact form with: - ✅ Modern React hooks (useState, useCallback) - ✅ Form validation with error messages - ✅ Loading states and user feedback - ✅ Email notifications to your inbox - ✅ Spam protection (honeypot + optional reCAPTCHA) - ✅ Responsive design - ✅ Accessible markup ## Prerequisites Before starting, make sure you have: - Basic knowledge of React and JavaScript - Node.js and npm installed - A React app created (via Create React App, Vite, or similar) - A Static Forms account - [sign up free](https://www.staticforms.dev) - Your API key from the [dashboard](https://www.staticforms.dev/app/forms) ## Step 1: Create the Basic Contact Form Component Let's start by creating a new component called `ContactForm.js`: ```jsx import React, { useState } from 'react'; import './ContactForm.css'; function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', subject: '', message: '', }); const [status, setStatus] = useState({ submitting: false, submitted: false, error: false, }); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = async (e) => { e.preventDefault(); setStatus({ submitting: true, submitted: false, error: false }); try { const response = await fetch('https://api.staticforms.dev/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ...formData, apiKey: 'YOUR_API_KEY_HERE', // Replace with your API key replyTo: formData.email, }), }); const result = await response.json(); if (result.success) { setStatus({ submitting: false, submitted: true, error: false }); setFormData({ name: '', email: '', subject: '', message: '' }); } else { throw new Error('Form submission failed'); } } catch (error) { setStatus({ submitting: false, submitted: false, error: true }); } }; return (

Get in Touch

Have a question or want to work together? Send us a message!

{status.submitted && (
✅ Thank you! Your message has been sent successfully. We'll get back to you soon.
)} {status.error && (
❌ Oops! Something went wrong. Please try again.
)}
``` #### Next.js Example ```typescript 'use client'; import { useState, useEffect } from 'react'; declare global { interface Window { grecaptcha: { execute: (siteKey: string, options: { action: string }) => Promise; }; } } export default function ContactForm() { const [isLoading, setIsLoading] = useState(false); useEffect(() => { // Load reCAPTCHA v3 script const script = document.createElement('script'); script.src = `https://www.google.com/recaptcha/api.js?render=${process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}`; document.head.appendChild(script); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); try { // Get reCAPTCHA token const token = await window.grecaptcha.execute( process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!, { action: 'submit' } ); const formData = new FormData(e.currentTarget); formData.append('apiKey', process.env.NEXT_PUBLIC_STATIC_FORMS_API_KEY!); formData.append('recaptchaToken', token); const response = await fetch('https://api.staticforms.dev/submit', { method: 'POST', body: formData, }); if (response.ok) { alert('Form submitted successfully!'); e.currentTarget.reset(); } else { const error = await response.json(); throw new Error(error.error || 'Submission failed'); } } catch (error) { console.error('Error:', error); alert('Failed to submit form'); } finally { setIsLoading(false); } }; return (
``` **That's it!** Just add the script tag and the `` element. The widget handles everything automatically - no JavaScript needed! ## Step 3: Optional Styling Add some basic CSS to make it look better: ```css ``` ## Step 4: Common Options (Optional) You can customize the widget with these simple options: ```html ``` ### Simple Color Customization Change the widget colors to match your site: ```css altcha-widget { --altcha-color-border: #your-border-color; --altcha-color-border-focus: #your-focus-color; } ``` ## That's It! Your form now has Altcha CAPTCHA protection. The widget automatically: - ✅ Loads and verifies challenges - ✅ Handles all the complex cryptography - ✅ Sends the token with your form - ✅ Protects against spam and bots ## Quick Troubleshooting **Widget not showing?** - Make sure you included the script: `` - Replace `YOUR_API_KEY_HERE` with your actual API key **Form not working?** - Check you have a [paid plan](https://www.staticforms.dev/pricing) (Altcha requires Pro or Advanced) - Verify Altcha is enabled in your [account settings](https://www.staticforms.dev/app/forms) ## Complete Working Example Copy and paste this complete example: ```html Contact Form with Altcha

Contact Form

``` **Just replace `YOUR_API_KEY_HERE` with your actual API key and you're done!** ## Why Use Altcha? - ✅ **Privacy-first** - No tracking or cookies - ✅ **User-friendly** - No puzzles to solve - ✅ **GDPR compliant** - Built-in privacy protection - ✅ **Easy to implement** - Just 2 lines of code - ✅ **Works everywhere** - Modern browsers only ## Need Help? - [Get a paid plan (Pro or Advanced)](https://www.staticforms.dev/pricing) to use Altcha - [Configure Altcha](https://www.staticforms.dev/app/forms) in your account settings - [Read more form guides](/blog/getting-started-with-static-forms) ## Conclusion Altcha is the easiest way to add privacy-focused CAPTCHA to your forms. Just include the script, add the widget, and you're protected from spam while respecting your users' privacy. Ready to get started? [Upgrade to a paid plan](https://www.staticforms.dev/pricing) today! --- ## How to Add Document Upload to Your Next.js Contact Form Source: https://www.staticforms.dev/blog/nextjs-contact-form-document-upload Date: 2025-06-12 Author: Static Forms Team Building a contact form with document upload functionality can significantly enhance user experience by allowing visitors to share attachments, portfolios, or supporting documents. In this comprehensive guide, we'll walk through creating a robust Next.js contact form with secure file upload capabilities. ## What You'll Learn - How to implement file upload in React with proper validation - Secure file handling with size and type restrictions - Error handling for file uploads - Form submission with FormData - Integration with Static Forms API for seamless form processing > **Note**: File uploads are available on paid plans (Pro/Advanced, including trials). Free-tier submissions with files will be rejected. ## Project Setup First, let's set up our Next.js project with the necessary dependencies: ```bash npx create-next-app@latest contact-form-upload --typescript --tailwind --eslint cd contact-form-upload npm install ``` ## Building the Contact Form Component Let's create a comprehensive contact form component with document upload functionality: ```typescript 'use client'; import { useState, useEffect } from 'react'; export default function ContactForm() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [subject, setSubject] = useState(''); const [message, setMessage] = useState(''); const [attachment, setAttachment] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [status, setStatus] = useState<'idle' | 'success' | 'error'>('idle'); const [statusMessage, setStatusMessage] = useState(''); const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] || null; // File size validation (5MB limit) if (file && file.size > 5 * 1024 * 1024) { setStatus('error'); setStatusMessage('File size must be less than 5MB.'); e.target.value = ''; // Clear the input return; } // File type validation if (file) { const allowedTypes = [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain', 'application/rtf' ]; if (!allowedTypes.includes(file.type)) { setStatus('error'); setStatusMessage('Please upload a valid file type (images, PDF, Word documents, or text files).'); e.target.value = ''; return; } } setAttachment(file); if (status === 'error') { setStatus('idle'); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); try { const formData = new FormData(); formData.append('apiKey', 'your-static-forms-api-key'); formData.append('name', name); formData.append('email', email); formData.append('subject', subject); formData.append('message', message); formData.append('replyTo', email); // Add attachment if present if (attachment) { formData.append('attachment', attachment); } const response = await fetch('https://api.staticforms.dev/submit', { method: 'POST', body: formData, }); if (response.ok) { setStatus('success'); setStatusMessage('Thank you for your message! We will get back to you soon.'); // Reset form setName(''); setEmail(''); setSubject(''); setMessage(''); setAttachment(null); const fileInput = document.getElementById('attachment') as HTMLInputElement; if (fileInput) fileInput.value = ''; } else { setStatus('error'); setStatusMessage('Failed to send your message. Please try again.'); } } catch (error) { setStatus('error'); setStatusMessage('An unexpected error occurred. Please try again.'); } finally { setIsSubmitting(false); } }; return (

Contact Us

{status === 'success' && (

{statusMessage}

)} {status === 'error' && (

{statusMessage}

)}
setName(e.target.value)} required className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" />
setEmail(e.target.value)} required className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" />
setSubject(e.target.value)} required className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" />
``` Replace `YOUR_API_KEY_HERE` with the actual API key from your Static Forms dashboard. ### Step 3: Customize Your Form's Behavior Static Forms provides several hidden fields that allow you to customize the behavior of your form: ```html
``` These hidden fields allow you to: - Set a custom subject line for the notification email - Configure the reply-to address - Redirect users to a custom thank-you page after submission ### Step 4: Add Spam Protection To protect your form from spam submissions, it's recommended to add reCAPTCHA: ```html
``` For more information on implementing reCAPTCHA with Static Forms, check out our [understanding reCAPTCHA guide](/blog/understanding-recaptcha). ## Advanced Features for Better Contact Forms Static Forms offers several advanced features to enhance your contact forms: ### File Attachments Allow users to upload files with their form submissions: ```html ``` ### Form Validation While HTML5 provides basic validation, you might want to add custom JavaScript validation: ```javascript document.querySelector('form').addEventListener('submit', function(event) { const email = document.querySelector('input[name="email"]').value; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { event.preventDefault(); alert('Please enter a valid email address'); } }); ``` ## Integrating with Popular Static Site Generators Static Forms works seamlessly with all major static site generators: ### Next.js Integration For Next.js projects, check out our detailed [guide on using Static Forms with Next.js](/blog/using-staticforms-with-nextjs). ### Nuxt.js Integration If you're using Nuxt.js, we have a comprehensive [tutorial on integrating Static Forms with Nuxt.js](/blog/using-staticforms-with-nuxtjs). ### Gatsby, Hugo, and Jekyll The integration process is similar for other static site generators - simply include the HTML form with the appropriate action and hidden fields. ## Styling Your Contact Form A well-designed contact form improves user experience and increases submission rates. Here's a simple CSS example to style your form: ```css .contact-form { max-width: 600px; margin: 0 auto; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .form-group { margin-bottom: 1.5rem; } .form-control { display: block; width: 100%; padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .submit-btn { display: inline-block; padding: 0.75rem 1.5rem; background-color: #4a90e2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; transition: background-color 0.3s ease; } .submit-btn:hover { background-color: #3578ca; } ``` ## Best Practices for Contact Forms on Static Sites To ensure your contact form provides the best possible user experience: 1. **Keep it simple** - Only ask for information you actually need 2. **Validate inputs** - Provide clear error messages for invalid entries 3. **Show loading states** - Indicate when a form is being submitted 4. **Provide clear success messages** - Confirm when submissions are successful 5. **Test thoroughly** - Ensure your form works across different browsers and devices ## Troubleshooting Common Issues If you're experiencing problems with your contact form: - **Not receiving emails?** Check your spam folder and verify your API key is correct - **reCAPTCHA not working?** Ensure your site and secret keys are properly configured - **Styling issues?** Use browser developer tools to inspect and debug CSS problems - **Submission errors?** Check the browser console for JavaScript errors ## Conclusion Adding a contact form to your static website doesn't have to be complicated. With form endpoint services like Static Forms, you can implement professional contact forms without the need for server-side code or complex infrastructure. By following the steps outlined in this guide, you can create beautiful, functional contact forms that enhance your static website's interactivity while maintaining all the benefits of static hosting. Ready to get started? [Sign up for Static Forms](https://www.staticforms.dev) today and add a professional contact form to your static website in minutes. ## Complete Example Here's a complete example of a styled contact form with Static Forms integration: ```html Contact Us

Contact Us

``` This blog post is part of our ongoing series on web development best practices. For more insights on creating effective websites, check out our other guides on [getting started with Static Forms](/blog/getting-started-with-static-forms). --- ## Using StaticForms with Nuxt.js: A Complete Guide Source: https://www.staticforms.dev/blog/using-staticforms-with-nuxtjs Date: 2025-04-27 Author: Static Forms Team Are you building a Nuxt.js application and need a simple way to handle form submissions without setting up a backend server? Static Forms is the perfect solution for collecting form data from your static sites and receiving submissions directly in your inbox. In this guide, we'll explore how to integrate the StaticForms service with your Nuxt.js application, including both basic implementation and enhanced security with reCAPTCHA. If you're using Next.js instead, check out our [Next.js integration guide](/blog/using-staticforms-with-nextjs). ## What is StaticForms? StaticForms is a form-to-email service designed specifically for static websites. It allows you to receive form submissions via email without writing any server-side code. This is particularly useful for: - Contact forms - Feedback forms - Newsletter signups - Simple data collection The service is free to use (with some limitations) and provides a straightforward API for integrating forms into your static website. ## Getting Started with StaticForms Before implementing forms in your Nuxt.js app, you'll need to: 1. Visit [staticforms.dev](https://www.staticforms.dev) 2. Register for a free account 3. Obtain your unique API key This API key will be used to connect your forms to your StaticForms account, ensuring submissions are forwarded to your email address. ## Basic Integration in Nuxt.js Let's start by implementing a simple contact form without reCAPTCHA. This approach is quick to set up but offers less protection against spam. ### Create a Client-Side Component for Your Form ```vue ``` ### Use the Form Component in Your Page ```vue ``` ## Enhanced Integration with reCAPTCHA For better protection against spam and bot submissions, we can add Google reCAPTCHA to our form. First, we need to install the Vue reCAPTCHA package: ```bash npm install vue-recaptcha@latest ``` ### Contact Form with reCAPTCHA ```vue ``` ### Using the reCAPTCHA Form Component ```vue ``` ## Using Nuxt.js Composition API for Form Reusability For larger applications, you might want to extract the form logic into a composable function for better reusability: ```typescript // composables/useStaticForm.ts import { ref } from 'vue' export default function useStaticForm(initialData: any = {}) { const formData = ref({ name: '', email: '', message: '', subject: 'Contact Form Submission', honeypot: '', replyTo: '@', apiKey: 'YOUR_API_KEY', // Replace with your actual apiKey ...initialData }) const response = ref({ type: '', message: '' }) const isSubmitting = ref(false) const submitForm = async () => { isSubmitting.value = true try { const res = await fetch('https://api.staticforms.dev/submit', { method: 'POST', body: JSON.stringify(formData.value), headers: { 'Content-Type': 'application/json' } }) const json = await res.json() if (json.success) { response.value = { type: 'success', message: 'Thank you for your message! We\'ll get back to you soon.' } // Reset certain fields but keep configuration const { apiKey, subject, replyTo, honeypot } = formData.value formData.value = { name: '', email: '', message: '', subject, honeypot, replyTo, apiKey } return true } else { response.value = { type: 'error', message: json.message || 'An error occurred. Please try again.' } return false } } catch (error) { console.error('Error submitting form:', error) response.value = { type: 'error', message: 'An error occurred while submitting the form. Please try again.' } return false } finally { isSubmitting.value = false } } const resetForm = () => { const { apiKey, subject, replyTo, honeypot } = formData.value formData.value = { name: '', email: '', message: '', subject, honeypot, replyTo, apiKey } response.value = { type: '', message: '' } } return { formData, response, isSubmitting, submitForm, resetForm } } ``` You can then use this composable in any component: ```vue ``` ## Environment Variables in Nuxt.js For better security, you should store your StaticForms API key as an environment variable: 1. Add to your `nuxt.config.ts`: ```typescript export default defineNuxtConfig({ runtimeConfig: { public: { staticformsKey: process.env.STATICFORMS_API_KEY || '' } } }) ``` 2. Create a `.env` file in your project root: ``` STATICFORMS_API_KEY=your_api_key_here ``` 3. Access it in your components or composables using `useRuntimeConfig`: ```typescript const config = useRuntimeConfig() const apiKey = config.public.staticformsKey ``` ## Conclusion Integrating StaticForms with Nuxt.js provides a simple, effective solution for handling form submissions in static sites without setting up a backend server. Whether you use the basic implementation or enhance it with reCAPTCHA for extra security, StaticForms makes the process straightforward. For more information about StaticForms, check out our [getting started guide](/blog/getting-started-with-static-forms) or explore other integration examples for different frameworks, such as our [Next.js integration tutorial](/blog/using-staticforms-with-nextjs). Remember that the free tier has some limitations, but it's perfect for most personal projects and small business websites. For larger projects with higher submission volumes, consider upgrading to a paid plan. Happy coding with Nuxt.js and StaticForms! --- ## Introducing New Plans and Policies for Static Forms Source: https://www.staticforms.dev/blog/introducing-new-plans-and-policies Date: 2025-04-19 Author: Static Forms Team We're excited to announce significant updates to Static Forms plans and policies, designed to better serve our growing user base while maintaining the security and reliability you've come to expect. ## What's Changing? ### Free, Pro, and Advanced Plans We've introduced a structured approach to our service with three distinct plans: - **Free Plan**: Includes a generous usage allowance suitable for most personal projects and small to medium websites - **Pro Plan**: Designed for higher volume needs, offering increased limits and additional features - **Advanced Plan**: Built for high-volume teams that need significantly higher limits and priority support **Good news**: All existing accounts are automatically enrolled in the Free plan, which includes enough capacity for the majority of our users. ### Extended Grace Period We understand that transitioning to a new pricing model requires time and planning. To ensure a smooth transition, we're providing an extended grace period: - If your usage exceeds the Free plan threshold, your service will continue uninterrupted until **April 30, 2025** - Starting May 2025, if you exceed the Free plan limits, email sending will pause for the remainder of that month until you upgrade to a paid plan (Pro or Advanced) This gives you nearly a full year to evaluate your needs and make any necessary adjustments. ### reCAPTCHA Is Now Optional Based on your valuable feedback, we've made reCAPTCHA integration optional for all users: - You can now choose whether to implement reCAPTCHA on your forms - It will only become mandatory if we detect spam submissions from your form - If spam is detected, you'll have 14 days to configure reCAPTCHA This change provides more flexibility in how you design and implement your forms while still maintaining robust protection when needed. ## Why These Changes? These updates are the result of extensive user feedback and our commitment to providing a sustainable, high-quality service: 1. **Better resource allocation**: By introducing tiered plans, we can optimize resources for different usage patterns 2. **Improved spam prevention**: Our targeted approach to reCAPTCHA implementation focuses security measures where they're most needed 3. **Sustainable growth**: These changes help ensure we can continue to provide reliable service and develop new features ## What You Need to Do For most users, no immediate action is required. Here's what we recommend: 1. **Check your current usage**: Log in to your dashboard to see how your usage compares to the Free plan limits 2. **Review plan options**: Familiarize yourself with the different plans to determine which best fits your needs 3. **Consider your reCAPTCHA strategy**: Decide whether to implement reCAPTCHA proactively or wait until needed ## Plan Comparison | Feature | Free Plan | Pro Plan | Advanced Plan | |---------|-----------|----------|---------------| | Monthly submissions | 500 | 25,000 | 200,000 | | Priority support | ✗ | ✓ | ✓ | | Price | $0 | $9/month | $35/month | ## Looking Forward These changes represent an important step in our journey to provide the best form handling solution for static websites. We're committed to continuing to improve Static Forms based on your feedback and needs. We're grateful for your ongoing support and trust in our service. As always, our team is available to answer any questions or provide assistance during this transition. For more information or if you have any questions, please don't hesitate to reach out to our support team. --- We look forward to continuing to serve your form handling needs and are excited about the future of Static Forms! --- ## Using StaticForms with Next.js: A Complete Guide Source: https://www.staticforms.dev/blog/using-staticforms-with-nextjs Date: 2025-04-19 Author: Static Forms Team Are you building a Next.js application and need a simple way to handle form submissions without setting up a backend server? Static Forms is the perfect solution for collecting form data from your static sites and receiving submissions directly in your inbox. In this guide, we'll explore how to integrate the StaticForms service with your Next.js application, including both basic implementation and enhanced security with reCAPTCHA. If you're using Nuxt.js instead, check out our [Nuxt.js integration guide](/blog/using-staticforms-with-nuxtjs). ## What is StaticForms? StaticForms is a form-to-email service designed specifically for static websites. It allows you to receive form submissions via email without writing any server-side code. This is particularly useful for: - Contact forms - Feedback forms - Newsletter signups - Simple data collection The service is free to use (with some limitations) and provides a straightforward API for integrating forms into your static website. ## Getting Started with StaticForms Before implementing forms in your Next.js app, you'll need to: 1. Visit [staticforms.dev](https://www.staticforms.dev) 2. Register for a free account 3. Obtain your unique API key This API key will be used to connect your forms to your StaticForms account, ensuring submissions are forwarded to your email address. ## Basic Integration in Next.js Let's start by implementing a simple contact form without reCAPTCHA. This approach is quick to set up but offers less protection against spam. ### Create a Client Component for Your Form ```tsx // app/components/contact-form.tsx "use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; export default function ContactForm() { const [formData, setFormData] = useState({ name: "", email: "", message: "", subject: "Contact Form Submission", // Default subject honeypot: "", // Anti-spam field replyTo: "@", // This will set replyTo to the email provided apiKey: "YOUR_API_KEY" // Replace with your actual API key }); const [response, setResponse] = useState({ type: "", message: "" }); const [isSubmitting, setIsSubmitting] = useState(false); const handleChange = (e: React.ChangeEvent) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); try { const res = await fetch("https://api.staticforms.dev/submit", { method: "POST", body: JSON.stringify(formData), headers: { "Content-Type": "application/json" } }); const json = await res.json(); if (json.success) { setResponse({ type: "success", message: "Thank you for your message! We'll get back to you soon." }); // Reset form fields setFormData({ ...formData, name: "", email: "", message: "" }); } else { setResponse({ type: "error", message: json.message || "An error occurred. Please try again." }); } } catch (error) { console.error("Error submitting form:", error); setResponse({ type: "error", message: "An error occurred while submitting the form. Please try again." }); } finally { setIsSubmitting(false); } }; return (
{response.type === "success" ? (
{response.message}
) : response.type === "error" ? (
{response.message}
) : null}
{/* Honeypot field to prevent spam */}
``` Make sure to replace `YOUR_API_KEY_HERE` with your actual API key from the Static Forms dashboard. ## Step 2: Add reCAPTCHA Protection To protect your form from spam, we recommend adding reCAPTCHA validation: ```html
``` **reCAPTCHA v2 vs v3:** - **v2** (shown above): Requires user to check "I'm not a robot" box. Available for all users. - **v3**: Invisible verification, no user interaction. Available on paid plans (Pro/Advanced). See our [reCAPTCHA v3 guide](/blog/recaptcha-v3-for-pro-users) for details. ## Step 3: Customize the Form Behavior Static Forms allows you to customize the behavior of your form with additional hidden fields: ```html
``` ## Testing Your Form Once you've integrated your form, you should test it to make sure everything works as expected: 1. Fill out the form with valid information 2. Complete the reCAPTCHA challenge (if enabled) 3. Submit the form 4. Check your email for the form submission ## Troubleshooting If you're not receiving form submissions, check the following: - Verify your API key is correct - Make sure your reCAPTCHA keys are valid (if using reCAPTCHA) - Check that all required form fields are present - Look for any JavaScript errors in your browser console ## Next Steps Now that you have your basic form working, you might want to explore more advanced features of Static Forms: - **File Attachments**: Allow users to upload files with their submissions - **Custom Redirects**: Send users to different pages based on form data - **Advanced Spam Protection**: Additional layers of protection for your forms --- ## Code Example: Complete Contact Form Here's a complete example of a contact form with all the features mentioned above: ```html Contact Us

Contact Us

``` We hope this guide has been helpful in getting you started with Static Forms! If you have any questions or need assistance, don't hesitate to [contact our support team](/contact). ---