Gatsby contact form
Formgong team ·
A Gatsby contact form can post from the browser to Formgong. Gatsby ships static files, so it has no server to receive the POST. You do not need a Gatsby Function or an email secret. Paste the form and replace the access key. On the free plan, email is a daily digest and Telegram is instant.
React component with fetch
Gatsby is React. A contact form is a component with state and fetch, not only a static HTML tag.
Save the component as src/components/ContactForm.js and render it from src/pages/contact.js.
The handler calls preventDefault, posts FormData, and checks response.ok and result.success. The button stays disabled while the request runs.
On the free plan, email is a daily digest to one address. It goes out the next morning at 08:00 in your time zone and covers the previous day.
Telegram is instant on every plan, including Free. Pro and Business email each lead.
// Save as src/components/ContactForm.js
import { useState } from "react";
// Paste your key: replace fk_your_access_key with the key from your Formgong form.
const ACCESS_KEY = "fk_your_access_key";
export default function ContactForm() {
const [status, setStatus] = useState("idle");
const [notice, setNotice] = useState("");
async function onSubmit(event) {
event.preventDefault();
const form = event.currentTarget;
if (!(form instanceof HTMLFormElement) || !form.reportValidity()) return;
const body = new FormData(form);
body.set("access_key", ACCESS_KEY);
body.set("_lang", "en");
setStatus("sending");
setNotice("");
try {
const response = await fetch("https://formgong.com/submit", {
method: "POST",
headers: { Accept: "application/json" },
body,
});
const result = await response.json();
if (!response.ok || !result.success) {
setStatus("error");
setNotice(result.message || "Could not send. Please try again.");
return;
}
form.reset();
setStatus("success");
setNotice(result.message || "Sent. Thank you!");
} catch {
setStatus("error");
setNotice("Connection failed. Please try again.");
}
}
const sending = status === "sending";
return (
<form onSubmit={onSubmit}>
<label>{"Name"} <input name="name" autoComplete="name" placeholder={"Your name"} required /></label>
<label>{"Email"} <input name="email" type="email" autoComplete="email" placeholder={"you@example.com"} required /></label>
<label>{"Message"} <textarea name="message" placeholder={"How can we help?"} required /></label>
<div aria-hidden="true" style={{ position: "absolute", insetInlineStart: 0, top: 0, width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }}>
<input name="botcheck" tabIndex={-1} autoComplete="off" />
</div>
<button type="submit" disabled={sending}>{sending ? "Sending…" : "Send"}</button>
<p role={status === "error" ? "alert" : "status"}>{notice}</p>
</form>
);
}src/pages/contact.js and the Gatsby Head API
// src/pages/contact.js
import React from "react";
import ContactForm from "../components/ContactForm";
export default function ContactPage() {
return (
<main>
<h1>Contact</h1>
<ContactForm />
</main>
);
}
export function Head() {
return <title>Contact</title>;
}Plain HTML form, if JavaScript is off
<form action="https://formgong.com/submit" method="POST"> <input type="hidden" name="access_key" value="fk_your_access_key"> <input type="hidden" name="_lang" value="en"> <label>Name <input type="text" name="name" autocomplete="name" placeholder="Your name" required></label> <label>Email <input type="email" name="email" autocomplete="email" placeholder="you@example.com" required></label> <label>Message <textarea name="message" placeholder="How can we help?" required></textarea></label> <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)"> <label>Leave this field empty<input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label> </div> <button type="submit">Send</button> </form>
Thanks page
// src/pages/thanks.js
import React from "react";
export default function ThanksPage() {
return (
<main>
<h1>Thanks</h1>
<p>Your message is in. We will reply by email.</p>
</main>
);
}Moving off Netlify Forms
Netlify reads HTML at the end of the build. A form that exists only after React runs is missed unless you also ship a hidden copy.
That hidden copy uses data-netlify, a honeypot attribute and a form-name field. An AJAX submit posts URL-encoded fields to the site root.
To leave, delete those Netlify attributes and the hidden copy. Point the action, or the fetch URL, at Formgong.
Formgong accepts FormData and JSON. You do not post to "/" and you do not set a form-name field.
Download the submissions you already have from the Netlify forms screen before you switch. Formgong starts with an empty inbox.
Remove data-netlify and point the form at Formgong
// Before: Netlify Forms. Remove data-netlify, netlify-honeypot and form-name.
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
<input type="hidden" name="form-name" value="contact" />
<p hidden><label>Do not fill this<input name="bot-field" /></label></p>
<label>Email <input type="email" name="email" required /></label>
<button type="submit">Send</button>
</form>
// After: the React component above posts FormData to Formgong.
// A no-JavaScript form can post here instead. Do not add data-netlify.
<form action="https://formgong.com/submit" method="POST">
<input type="hidden" name="access_key" value="fk_your_access_key">
<input type="hidden" name="_lang" value="en">
<label>Email <input type="email" name="email" required></label>
<div aria-hidden="true" style="position:absolute;left:-9999px">
<input name="botcheck" tabindex="-1" autocomplete="off">
</div>
<button type="submit">Send</button>
</form>Gatsby gotchas
These two trips catch the mistakes that show up only after a production build.
SSR and hydration
Gatsby renders the page to HTML at build time, then React hydrates it in the browser.
The first render must match that HTML. Do not read window, and do not render a different tree when the component mounts.
useState is fine when the initial state is the same on the server and in the browser. An empty notice is the right start.
A form that returns null until the client mounts is missing from the built HTML. Visitors then see a flash, and Netlify's scan sees nothing.
gatsby develop and gatsby build
gatsby develop serves the app from a dev server. gatsby build writes static files and the POST still leaves the browser.
Formgong behaves the same in both, because the host never receives the form.
Netlify's form scan runs on the production HTML, not on the develop server. A form that looks fine in develop can be invisible after deploy.
Send one test with gatsby develop, then run gatsby build and open the built page. Both should land in the Formgong inbox.
GATSBY_ names are public
Only environment names that start with GATSBY_ are inlined into the browser bundle. The access key is public, so that prefix is fine.
Never put an SMTP password or a private API key in a GATSBY_ name. It ships in the JavaScript anyone can read.
How forms work in Gatsby
Gatsby turns React pages into static HTML. Its contact-form guide offers a form service, Netlify Forms, or a server you run. Gatsby Functions are the documented server path. That is a function you deploy and keep.
A file in src/pages can hold a plain HTML form. It posts to https://formgong.com/submit with access_key, _lang and botcheck. Only names that start with GATSBY_ reach the browser, and this key is public, so that prefix is fine.
Set it up in Gatsby
- Create a form in Formgong and copy the access key. It starts with
fk_. - Add
src/pages/contact.jsand paste the form below. Set the action tohttps://formgong.com/submit. - Replace
fk_your_access_key. An env name such asGATSBY_FORMGONG_KEYis optional. Never put an SMTP password in aGATSBY_name. - Run
gatsby developand send a test. Then rungatsby build. The POST runs in the browser, so the live build behaves the same. - If the site is on Netlify, do not add
data-netlify. A React-only form is invisible to the HTML scan unless you also ship a hidden form withform-name.
Formgong or a Gatsby Function?
Use a Gatsby Function when the submit must change your own database or call a private API.
Use Formgong for contact and lead forms. You skip SMTP, a function to host, and the hidden HTML Netlify needs for React.
Questions and answers
Does Gatsby include a contact form?
No. The official guide lists a form backend, Netlify Forms if you host there, or your own server. The page itself stays static.
Why does Netlify miss my Gatsby form?
Netlify reads the HTML after the build. A form that appears only after React runs is missed. Post to Formgong, or add a hidden static copy.
Can I use fetch and stay on the page?
Yes. Call fetch on https://formgong.com/submit and send Accept: application/json. The plain HTML form still works if JavaScript is off.
Is the access key safe in the Gatsby bundle?
Yes. It only accepts submissions for your form. On Pro you can limit the allowed domains. Do not put private API keys in a GATSBY_ name.
Articles on this topic
Related pages
Guides
- Formgong for agencies
- Static website contact form
- Netlify Forms alternative
- GDPR-compliant contact form
- Formspree alternatives
- Web3Forms alternatives
- EmailJS alternatives
- Formspree vs Web3Forms
- Formspree vs EmailJS
- Best free form backends
- Free website templates
- Testimonial and rating widget
- Craftline: plumber website template
- Orrery: consulting website template
- Lattice: portfolio website template
- Lattice Estate: home builder website template
- Shoal: real estate agent website template
- Lattice Atelier: windows and doors website template
- Soglia: designer windows website template
- Halftone: architecture studio website template
- Ocra: shop and maker website template
- Arcwell: interior design studio website template
- AI builder prompts
- Form checker
- Agent rules files
- Tools