Add a working contact form to your Gatsby site with React components and FormsList. No serverless functions or Gatsby Functions needed.
TL;DR
To add a contact form to a Gatsby site, create a React component that submits to `https://formslist.com/f/your-form-id`. Since Gatsby generates static HTML, a form backend like FormsList handles submissions without needing serverless functions.
Sign up for a free FormsList account and create a new form. Copy the endpoint URL from your dashboard.
// Your endpoint will look like this:
// https://formslist.com/f/YOUR_FORM_HASHCreate a React component in your Gatsby project. Gatsby uses React, so the pattern is the same as a standard React form: useState for status management and fetch for submission.
// src/components/ContactForm.jsx
import React, { useState } from "react"
export default function ContactForm() {
const [status, setStatus] = useState("idle")
async function handleSubmit(e) {
e.preventDefault()
setStatus("sending")
const form = e.currentTarget
const data = new FormData(form)
try {
const res = await fetch("https://formslist.com/f/YOUR_FORM_HASH", {
method: "POST",
body: data,
})
if (res.ok) {
setStatus("sent")
form.reset()
} else {
setStatus("error")
}
} catch {
setStatus("error")
}
}
return (
<form onSubmit={handleSubmit} style={{ maxWidth: 480 }}>
<div style={{ marginBottom: "1rem" }}>
<label htmlFor="name" style={{ display: "block", fontWeight: 600 }}>Name</label>
<input type="text" id="name" name="name" required style={{ width: "100%", padding: "0.5rem" }} />
</div>
<div style={{ marginBottom: "1rem" }}>
<label htmlFor="email" style={{ display: "block", fontWeight: 600 }}>Email</label>
<input type="email" id="email" name="email" required style={{ width: "100%", padding: "0.5rem" }} />
</div>
<div style={{ marginBottom: "1rem" }}>
<label htmlFor="message" style={{ display: "block", fontWeight: 600 }}>Message</label>
<textarea id="message" name="message" rows={4} required style={{ width: "100%", padding: "0.5rem" }} />
</div>
<button type="submit" disabled={status === "sending"}
style={{ padding: "0.6rem 1.5rem", background: "#2563eb", color: "#fff", border: "none", borderRadius: 4, cursor: "pointer" }}>
{status === "sending" ? "Sending..." : "Send Message"}
</button>
{status === "sent" && <p style={{ color: "green" }}>Message sent successfully!</p>}
{status === "error" && <p style={{ color: "red" }}>Something went wrong. Please try again.</p>}
</form>
)
}Import the ContactForm component into a Gatsby page. Gatsby pages live in the src/pages/ directory and are automatically routed based on filename.
// src/pages/contact.jsx
import React from "react"
import Layout from "../components/Layout"
import ContactForm from "../components/ContactForm"
export default function ContactPage() {
return (
<Layout>
<h1>Contact Us</h1>
<p>Fill out the form below and we will get back to you within 24 hours.</p>
<ContactForm />
</Layout>
)
}
export function Head() {
return <title>Contact Us</title>
}Build your Gatsby site with `gatsby build` and deploy to your hosting provider (Netlify, Vercel, Gatsby Cloud, etc.). Submit a test message and verify it appears in your FormsList dashboard. Enable email notifications and integrations.
No. Gatsby Functions run server-side code, but with FormsList handling the backend, you can keep your site fully static. No serverless functions needed.
Yes. The form component renders as HTML during the build, and the client-side JavaScript handles submission via fetch. FormsList processes the data externally.
Plugins like gatsby-plugin-netlify add form handling tied to specific hosts. FormsList works on any hosting provider and gives you a dashboard for managing submissions, notifications, and integrations.
200 free submissions a month and a live endpoint in 30 seconds. No server, no credit card, just point your form at it.