Skip to content
FormSubmit

Gatsby

How to add a contact form to Gatsby

Add a contact form to a Gatsby site without Gatsby Functions by posting directly to a form backend.

1 min readUpdated Sep 28, 2026

Gatsby sites are static, so point the contact form at a FormSubmit endpoint and submit it with fetch. No Gatsby Functions, no serverless email code.

Component

src/components/ContactForm.tsx:

src/components/ContactForm.tsx
import { useState, type FormEvent } from "react";

const ENDPOINT = "https://formsubmit.app/f/YOUR_FORM_ID";

export default function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle");
  const [error, setError] = useState("");
  const [renderedAt] = useState(() => Date.now());

  async function onSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus("sending");
    const data = new FormData(e.currentTarget);
    data.set("_ts", String(renderedAt)); // helps FormSubmit's time-based spam check
    try {
      const res = await fetch(ENDPOINT, {
        method: "POST",
        body: data,
        headers: { Accept: "application/json" },
      });
      const json = await res.json();
      if (!res.ok || !json.ok) throw new Error(json.error?.message ?? "Something went wrong");
      setStatus("success");
    } catch (err) {
      setError((err as Error).message);
      setStatus("error");
    }
  }

  if (status === "success") {
    return <p className="text-green-700">Thanks! We'll be in touch soon.</p>;
  }

  return (
    <form onSubmit={onSubmit} className="mx-auto max-w-lg space-y-5">
      <div className="space-y-1.5">
        <label htmlFor="fs-name" className="block text-sm font-medium text-gray-900">Name</label>
        <input id="fs-name" type="text" name="name" placeholder="Jane Doe" required className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20" />
      </div>
      <div className="space-y-1.5">
        <label htmlFor="fs-email" className="block text-sm font-medium text-gray-900">Email</label>
        <input id="fs-email" type="email" name="email" placeholder="jane@example.com" required className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20" />
      </div>
      <div className="space-y-1.5">
        <label htmlFor="fs-message" className="block text-sm font-medium text-gray-900">Message</label>
        <textarea id="fs-message" name="message" rows={5} placeholder="How can we help?" required className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"></textarea>
      </div>
      {/* Honeypot: bots fill this in, humans never see it */}
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: "none" }} />
      {status === "error" && <p className="text-sm text-red-600">{error}</p>}
      <button type="submit" disabled={status === "sending"} className="inline-flex w-full items-center justify-center rounded-lg bg-gray-900 px-4 py-2.5 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-50">
        {status === "sending" ? "Sending…" : "Send"}
      </button>
    </form>
  );
}

Page

src/pages/contact.tsx
import * as React from "react";
import ContactForm from "../components/ContactForm";

export default function ContactPage() {
  return (
    <main style={{ maxWidth: 560, margin: "0 auto", padding: "4rem 1rem" }}>
      <h1>Contact</h1>
      <ContactForm />
    </main>
  );
}

export const Head = () => <title>Contact</title>;

No-JavaScript fallback

Add action and method="POST" to the form element so it still submits during server-side rendering or if hydration fails.

Next steps

Add your domain to the allowlist and connect an integration such as Slack.

Frequently asked questions

Do I need Gatsby Functions?

No. The browser posts directly to FormSubmit, so the site stays fully static.

Keep reading

Your form backend is 60 seconds away

Sign up with Google or email, create a form, paste the endpoint. Free forever for small sites — no credit card.