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).

  1. In the dashboard, create a form and copy the access key (shown once).
  2. In your HTML, set the form's action to https://api.w3forms.com/submit with method="POST".
  3. Add a hidden input: <input type="hidden" name="access_key" value="YOUR_KEY" />.
  4. In Form → Settings → Allowed Domains, add your domain.
  5. 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.

ContactForm.tsx
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.

contact.astro
---
// 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.

  1. Go to Form → Integrations and add a webhook URL (must be HTTPS in production).
  2. Copy the webhook secret and store it as an environment variable.
  3. On your server, verify the X-W3Forms-Signature header using HMAC-SHA256 before processing the payload.
  4. 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.

  1. Add enctype="multipart/form-data" to your <form> tag.
  2. Add one or more <input type="file"> fields with any name attribute.
  3. Files are stored securely and linked to the submission in your dashboard. Download them anytime.
multipart.html
<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.json
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:

  1. Create a form in the W3Forms dashboard and copy your access key.
  2. Change your form's action URL to https://api.w3forms.com/submit.
  3. Add the access_key hidden field.
  4. Remove any provider-specific attributes (e.g., Netlify's netlify attribute, Formspree's action URL).
  5. 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.