Gatsby

Add a contact form to a Gatsby site with a React component and an environment variable for the access key.

Gatsby renders every page to static HTML and then hydrates it with React, so a contact form needs somewhere to send its data. You do not need Gatsby Functions, a plugin or anything in gatsby-browser.js for that: the form component posts to SilentForms from the browser and you get each submission by email. What is specific to Gatsby is how the access key gets into the bundle and how you move to a thank-you page.

The component here is deliberately short. For a fuller walkthrough of form state, accessibility and error handling in React, see the React guide.

1. Get an access key#

Enter your email on the access key page and click the confirmation link. The key only routes submissions to you, so it is safe in client-side JavaScript.

2. Add the key as an environment variable#

Gatsby only exposes variables whose names start with GATSBY_ to browser code, and it loads .env.development for gatsby develop and .env.production for gatsby build. Add the key to both:

# .env.development and .env.production
GATSBY_SILENTFORMS_KEY=YOUR_ACCESS_KEY_HERE

The value is compiled into the JavaScript when the development server starts or the site builds. Restart gatsby develop after changing it, and set the same variable in your host's build settings, since .env files are usually not committed.

3. Create the form component#

Create src/components/contact-form.js:

import * as React from 'react';
import { navigate } from 'gatsby';

export default function ContactForm() {
  const [sending, setSending] = React.useState(false);
  const [error, setError] = React.useState('');

  async function handleSubmit(event) {
    event.preventDefault();
    setSending(true);
    setError('');

    const fields = Object.fromEntries(new FormData(event.currentTarget));
    try {
      const response = await fetch('https://silentforms.com/api/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
        body: JSON.stringify({ ...fields, accessKey: process.env.GATSBY_SILENTFORMS_KEY }),
      });
      const result = await response.json();
      if (!response.ok) throw new Error(result.message);
      navigate('/thanks/');
    } catch (err) {
      setError(err.message || 'Something went wrong. Please try again.');
      setSending(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" type="text" autoComplete="name" required />

      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" autoComplete="email" required />

      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" rows={6} required />

      {/* Honeypot: hidden from people, filled in by bots */}
      <input type="checkbox" name="botcheck" style={{ display: 'none' }} tabIndex={-1} autoComplete="off" />

      <button type="submit" disabled={sending}>
        {sending ? 'Sending…' : 'Send message'}
      </button>
      {error && <p role="alert">{error}</p>}
    </form>
  );
}

navigate is Gatsby's client-side router, so the thank-you page loads without a full reload. Because the request is JSON, SilentForms answers with JSON instead of redirecting, and the redirect field is not needed.

4. Add the pages#

Every file in src/pages becomes a route. Create src/pages/contact.js:

import * as React from 'react';
import ContactForm from '../components/contact-form';

export default function ContactPage() {
  return (
    <main>
      <h1>Contact us</h1>
      <ContactForm />
    </main>
  );
}

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

And src/pages/thanks.js:

import * as React from 'react';
import { Link } from 'gatsby';

export default function ThanksPage() {
  return (
    <main>
      <h1>Thanks!</h1>
      <p>Your message is on its way. We will reply by email.</p>
      <Link to="/">Back to the homepage</Link>
    </main>
  );
}

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

Test it#

Run gatsby develop, open http://localhost:8000/contact/ and send yourself a message. You should land on /thanks/ and the email should arrive within a minute. Then run gatsby build && gatsby serve and test once more on http://localhost:9000: that is the static output you will deploy, built with .env.production.

Static hosting#

The build output in public/ is plain files, and the form needs nothing on the server, so it runs unchanged on Netlify, Cloudflare Pages, GitHub Pages or an S3 bucket. Set GATSBY_SILENTFORMS_KEY in the host's build environment variables.

Gotchas#

  • "Access key is required." process.env.GATSBY_SILENTFORMS_KEY was empty when the bundle was built. Check the prefix, the file name (.env.production for builds) and the host's build variables, then rebuild.
  • Reading the key in gatsby-config.js or gatsby-node.js. Node-side files do not get .env files automatically. The component above reads it in the browser, where Gatsby handles it for you.
  • A Netlify site captures the form. Do not add data-netlify or netlify-honeypot attributes. See Netlify.

For every error message, see Troubleshooting.

Next steps#