How-to Guides
Step-by-step guides for the most common W3Forms use cases. Each guide assumes you have already created a form and copied your access key from the dashboard.
Add a contact form to a static HTML site
The simplest integration — no JavaScript, no build step. Works with any hosting provider (GitHub Pages, Netlify, Vercel, Cloudflare Pages, S3, or a plain web server).
- In the dashboard, create a form and copy the access key (shown once).
- In your HTML, set the form's
actiontohttps://api.w3forms.com/submitwithmethod="POST". - Add a hidden input:
<input type="hidden" name="access_key" value="YOUR_KEY" />. - In Form → Settings → Allowed Domains, add your domain.
- Optional: set a Success redirect URL for a custom thank-you page. Without it, W3Forms shows a default success page.
That's it. When a visitor submits the form, the data is stored in your dashboard and you receive an email notification.
React / Next.js contact form
For single-page applications, use fetch with Accept: application/json to get a JSON response instead of a redirect. This lets you control the UI (loading states, inline messages) without a full page reload.
import { useState, FormEvent } from "react";
export function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const res = await fetch("https://api.w3forms.com/submit", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(e.currentTarget),
});
setStatus(res.ok ? "sent" : "error");
}
if (status === "sent") return <p>Thank you! We'll be in touch.</p>;
return (
<form onSubmit={handleSubmit}>
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="text" name="name" required placeholder="Name" />
<input type="email" name="email" required placeholder="Email" />
<textarea name="message" required placeholder="Message" />
<button type="submit" disabled={status === "sending"}>
{status === "sending" ? "Sending…" : "Send"}
</button>
{status === "error" && <p>Something went wrong. Please try again.</p>}
</form>
);
}Key points: use new FormData(e.currentTarget) to capture all fields including the hidden access key. Do not set Content-Type manually — the browser sets it correctly for FormData.
Astro contact form
Astro renders static HTML by default, so a plain form works perfectly. Set a redirect hidden field to redirect users to a thank-you page after submission.
---
// src/pages/contact.astro
---
<form action="https://api.w3forms.com/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="hidden" name="redirect" value="https://yoursite.com/thanks" />
<label>Name <input type="text" name="name" required /></label>
<label>Email <input type="email" name="email" required /></label>
<label>Message <textarea name="message" required></textarea></label>
<button type="submit">Send</button>
</form>Set up webhooks
Webhooks forward each submission to your own server — useful for CRM sync, Slack notifications, or custom workflows.
- Go to Form → Integrations and add a webhook URL (must be HTTPS in production).
- Copy the webhook secret and store it as an environment variable.
- On your server, verify the
X-W3Forms-Signatureheader using HMAC-SHA256 before processing the payload. - Respond with a 200 status within 10 seconds to acknowledge receipt.
See Webhooks for signature verification code examples in Node.js and Python.
File uploads
Available on Pro and Business plans. Add a file input to your form and set the enctype to multipart.
- Add
enctype="multipart/form-data"to your<form>tag. - Add one or more
<input type="file">fields with any name attribute. - Files are stored securely and linked to the submission in your dashboard. Download them anytime.
<form action="https://api.w3forms.com/submit" method="POST" enctype="multipart/form-data">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="file" name="attachment" />
<button type="submit">Upload</button>
</form>When using fetch in JavaScript, pass new FormData(form) as the body. Do not set the Content-Type header — the browser adds the correct multipart boundary automatically.
Send JSON with curl (debugging)
When debugging errors from the command line, JSON requests give you a clear error message. Always include Accept: application/json to prevent redirects.
curl -X POST "https://api.w3forms.com/submit" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"access_key": "YOUR_ACCESS_KEY",
"name": "Ada Lovelace",
"email": "ada@example.com",
"message": "Hello!"
}'See Troubleshooting for common error codes and fixes.
Migrate from another form service
Switching to W3Forms from Netlify Forms, Formspree, or another provider takes about two minutes:
- Create a form in the W3Forms dashboard and copy your access key.
- Change your form's
actionURL tohttps://api.w3forms.com/submit. - Add the
access_keyhidden field. - Remove any provider-specific attributes (e.g., Netlify's
netlifyattribute, Formspree's action URL). - Add your domain to Allowed Domains in Form → Settings.
Your existing form fields work as-is — no schema changes needed. W3Forms stores all fields you send.