---
title: "Add forms to Gatsby"
description: "Add a form backend to your Gatsby (React) site. Handle submissions, file uploads, and email notifications, with no backend code required."
url: https://forminit.com/docs/gatsby/
---

# Add forms to Gatsby

Gatsby is a React-based static site generator that creates fast, SEO-friendly websites. Forminit handles all form submissions through our client-side SDK, making it easy to add forms without building a backend.

**What you'll get:**
- Accept form submissions without building a backend
- Email notifications for new submissions
- Spam protection (Cloudflare Turnstile, reCAPTCHA, hCaptcha, honeypot)
- File uploads support
- Submission dashboard to manage responses

**Requirements:**
- A Gatsby site (v4.0+ or v5.0+)
- Node.js 18+ (for Gatsby 5) or Node.js 14+ (for Gatsby 4)
- A Forminit account and Form ID

---

## Quick Start

### Step 1: Create a Form on Forminit

1. Sign up or log in at [forminit.com](https://forminit.com)
2. Create a new form
3. Go to **Form Settings** → Set authentication mode to **Public**
4. Copy your **Form ID** (e.g., `YOUR-FORM-ID`)

> **Caution**
>
> Make sure authentication mode is set to Public in Form Settings.

---

### Step 2: Add Form ID to Configuration

Store your Form ID in `gatsby-config.js`:

```javascript
// gatsby-config.js

module.exports = {
  siteMetadata: {
    title: 'My Site',
    forminitFormId: 'YOUR-FORM-ID',
  },
  plugins: [
    // your plugins
  ],
}
```

Or use environment variables (recommended):

```javascript
// gatsby-config.js

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`,
})

module.exports = {
  siteMetadata: {
    title: 'My Site',
    forminitFormId: process.env.GATSBY_FORMINIT_FORM_ID,
  },
  plugins: [],
}
```

Create a `.env.development` file:

```
GATSBY_FORMINIT_FORM_ID=YOUR-FORM-ID
```

> **Note**
>
> Environment variables in Gatsby must be prefixed with `GATSBY_` to be available in the browser.

---

### Step 3: Create a Form Component

Create a reusable form component:

```jsx
// src/components/ContactForm.js

import React, { useState, useEffect, useRef } from 'react'

const ContactForm = ({ formId }) => {
  const [status, setStatus] = useState('idle')
  const [errorMessage, setErrorMessage] = useState(null)
  const forminitRef = useRef(null)
  const formRef = useRef(null)

  useEffect(() => {
    // Load Forminit SDK
    if (typeof window !== 'undefined' && !window.Forminit) {
      const script = document.createElement('script')
      script.src = 'https://forminit.com/sdk/v1/forminit.js'
      script.onload = () => {
        forminitRef.current = new window.Forminit()
      }
      document.body.appendChild(script)
    } else if (window.Forminit) {
      forminitRef.current = new window.Forminit()
    }
  }, [])

  const handleSubmit = async (e) => {
    e.preventDefault()
    
    if (!forminitRef.current) {
      setStatus('error')
      setErrorMessage('Form not ready. Please try again.')
      return
    }

    setStatus('loading')
    setErrorMessage(null)

    const formData = new FormData(formRef.current)
    const { data, error } = await forminitRef.current.submit(formId, formData)

    if (error) {
      setStatus('error')
      setErrorMessage(error.message)
      return
    }

    setStatus('success')
    formRef.current.reset()
  }

  return (
    <form ref={formRef} onSubmit={handleSubmit} id="contact-form">
      <div className="form-group">
        <label htmlFor="firstName">First Name</label>
        <input
          type="text"
          id="firstName"
          name="fi-sender-firstName"
          placeholder="John"
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="lastName">Last Name</label>
        <input
          type="text"
          id="lastName"
          name="fi-sender-lastName"
          placeholder="Doe"
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="email">Email</label>
        <input
          type="email"
          id="email"
          name="fi-sender-email"
          placeholder="john@example.com"
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="message">Message</label>
        <textarea
          id="message"
          name="fi-text-message"
          placeholder="Your message..."
          rows={5}
          required
        />
      </div>

      {status === 'error' && (
        <p className="status-error">{errorMessage}</p>
      )}
      {status === 'success' && (
        <p className="status-success">Message sent successfully!</p>
      )}

      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Sending...' : 'Send Message'}
      </button>
    </form>
  )
}

export default ContactForm
```

---

### Step 4: Create Your Contact Page

Create a contact page that uses the form component:

```jsx
// src/pages/contact.js

import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import ContactForm from '../components/ContactForm'

const ContactPage = ({ data }) => {
  const { forminitFormId } = data.site.siteMetadata

  return (
    <Layout>
      <div className="contact-page">
        <h1>Contact Us</h1>
        <p>We'd love to hear from you. Fill out the form below and we'll get back to you soon.</p>
        
        <ContactForm formId={forminitFormId} />
      </div>
    </Layout>
  )
}

export const query = graphql`
  query {
    site {
      siteMetadata {
        forminitFormId
      }
    }
  }
`

export default ContactPage
```

---

## Using React Hooks for Form State

Here's a cleaner version using custom hooks:

```jsx
// src/hooks/useForminit.js

import { useState, useEffect, useRef } from 'react'

export const useForminit = () => {
  const forminitRef = useRef(null)
  const [isReady, setIsReady] = useState(false)

  useEffect(() => {
    if (typeof window === 'undefined') return

    if (window.Forminit) {
      forminitRef.current = new window.Forminit()
      setIsReady(true)
      return
    }

    const script = document.createElement('script')
    script.src = 'https://forminit.com/sdk/v1/forminit.js'
    script.onload = () => {
      forminitRef.current = new window.Forminit()
      setIsReady(true)
    }
    document.body.appendChild(script)
  }, [])

  const submit = async (formId, data) => {
    if (!forminitRef.current) {
      return { error: { message: 'Forminit not initialized' } }
    }
    return forminitRef.current.submit(formId, data)
  }

  return { submit, isReady }
}
```

Usage:

```jsx
// src/components/ContactForm.js

import React, { useState, useRef } from 'react'
import { useForminit } from '../hooks/useForminit'

const ContactForm = ({ formId }) => {
  const [status, setStatus] = useState('idle')
  const [errorMessage, setErrorMessage] = useState(null)
  const { submit, isReady } = useForminit()
  const formRef = useRef(null)

  const handleSubmit = async (e) => {
    e.preventDefault()
    
    if (!isReady) {
      setErrorMessage('Form not ready. Please try again.')
      return
    }

    setStatus('loading')
    setErrorMessage(null)

    const formData = new FormData(formRef.current)
    const { data, error } = await submit(formId, formData)

    if (error) {
      setStatus('error')
      setErrorMessage(error.message)
      return
    }

    setStatus('success')
    formRef.current.reset()
  }

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      {/* form fields */}
    </form>
  )
}
```

---

## Form Field Reference

For a complete list of available form blocks (text, email, phone, file, rating, select, etc.) and field naming patterns, see the [Form Blocks documentation](https://forminit.com/docs/form-blocks/).

---

## Complete Examples

### Contact Form with Subject Selection

```jsx
// src/components/ContactFormWithSubject.js

import React, { useState, useRef } from 'react'
import { useForminit } from '../hooks/useForminit'

const ContactFormWithSubject = ({ formId }) => {
  const [status, setStatus] = useState('idle')
  const [errorMessage, setErrorMessage] = useState(null)
  const { submit, isReady } = useForminit()
  const formRef = useRef(null)

  const handleSubmit = async (e) => {
    e.preventDefault()
    if (!isReady) return

    setStatus('loading')
    const formData = new FormData(formRef.current)
    const { error } = await submit(formId, formData)

    if (error) {
      setStatus('error')
      setErrorMessage(error.message)
      return
    }

    setStatus('success')
    formRef.current.reset()
  }

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="text" name="fi-sender-firstName" placeholder="First name" required />
      <input type="text" name="fi-sender-lastName" placeholder="Last name" required />
      <input type="email" name="fi-sender-email" placeholder="Email" required />
      
      <select name="fi-select-subject" required>
        <option value="">Select a subject</option>
        <option value="general">General Inquiry</option>
        <option value="support">Support</option>
        <option value="sales">Sales</option>
        <option value="partnership">Partnership</option>
      </select>
      
      <textarea name="fi-text-message" placeholder="Your message" required />
      
      {status === 'error' && <p className="status-error">{errorMessage}</p>}
      {status === 'success' && <p className="status-success">Message sent!</p>}
      
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Sending...' : 'Send'}
      </button>
    </form>
  )
}

export default ContactFormWithSubject
```

### Newsletter Signup

```jsx
// src/components/NewsletterForm.js

import React, { useState, useRef } from 'react'
import { useForminit } from '../hooks/useForminit'

const NewsletterForm = ({ formId }) => {
  const [email, setEmail] = useState('')
  const [status, setStatus] = useState('idle')
  const [errorMessage, setErrorMessage] = useState(null)
  const { submit, isReady } = useForminit()

  const handleSubmit = async (e) => {
    e.preventDefault()
    if (!isReady) return

    setStatus('loading')

    const formData = new FormData()
    formData.append('fi-sender-email', email)

    const { error } = await submit(formId, formData)

    if (error) {
      setStatus('error')
      setErrorMessage(error.message)
      return
    }

    setStatus('success')
    setEmail('')
  }

  return (
    <form onSubmit={handleSubmit} className="newsletter-form">
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter your email"
        required
      />
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Subscribing...' : 'Subscribe'}
      </button>
      
      {status === 'error' && <p className="status-error">{errorMessage}</p>}
      {status === 'success' && <p className="status-success">Thank you for subscribing!</p>}
    </form>
  )
}

export default NewsletterForm
```

### Feedback Form with Rating

```jsx
// src/components/FeedbackForm.js

import React, { useState, useRef } from 'react'
import { useForminit } from '../hooks/useForminit'

const FeedbackForm = ({ formId }) => {
  const [rating, setRating] = useState(null)
  const [status, setStatus] = useState('idle')
  const [errorMessage, setErrorMessage] = useState(null)
  const { submit, isReady } = useForminit()
  const formRef = useRef(null)

  const handleSubmit = async (e) => {
    e.preventDefault()
    if (!isReady) return

    setStatus('loading')
    const formData = new FormData(formRef.current)
    const { error } = await submit(formId, formData)

    if (error) {
      setStatus('error')
      setErrorMessage(error.message)
      return
    }

    setStatus('success')
    formRef.current.reset()
    setRating(null)
  }

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="email" name="fi-sender-email" placeholder="Email (optional)" />
      
      <fieldset>
        <legend>How would you rate your experience?</legend>
        {[1, 2, 3, 4, 5].map((n) => (
          <label key={n}>
            <input
              type="radio"
              name="fi-rating-experience"
              value={n}
              checked={rating === n}
              onChange={() => setRating(n)}
            />
            {n}
          </label>
        ))}
      </fieldset>
      
      <textarea name="fi-text-feedback" placeholder="Tell us more (optional)" />
      
      {status === 'error' && <p className="status-error">{errorMessage}</p>}
      {status === 'success' && <p className="status-success">Thank you for your feedback!</p>}
      
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Submitting...' : 'Submit Feedback'}
      </button>
    </form>
  )
}

export default FeedbackForm
```

### Job Application with File Upload

```jsx
// src/components/ApplicationForm.js

import React, { useState, useRef } from 'react'
import { useForminit } from '../hooks/useForminit'

const ApplicationForm = ({ formId }) => {
  const [status, setStatus] = useState('idle')
  const [errorMessage, setErrorMessage] = useState(null)
  const { submit, isReady } = useForminit()
  const formRef = useRef(null)

  const handleSubmit = async (e) => {
    e.preventDefault()
    if (!isReady) return

    setStatus('loading')
    const formData = new FormData(formRef.current)
    const { error } = await submit(formId, formData)

    if (error) {
      setStatus('error')
      setErrorMessage(error.message)
      return
    }

    setStatus('success')
    formRef.current.reset()
  }

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="text" name="fi-sender-firstName" placeholder="First name" required />
      <input type="text" name="fi-sender-lastName" placeholder="Last name" required />
      <input type="email" name="fi-sender-email" placeholder="Email" required />
      <input type="tel" name="fi-sender-phone" placeholder="Phone (+1234567890)" />
      
      <input type="url" name="fi-url-linkedin" placeholder="LinkedIn profile URL" />
      <input type="url" name="fi-url-portfolio" placeholder="Portfolio URL" />
      
      <select name="fi-select-position" required>
        <option value="">Select position</option>
        <option value="frontend">Frontend Developer</option>
        <option value="backend">Backend Developer</option>
        <option value="fullstack">Full Stack Developer</option>
        <option value="designer">UI/UX Designer</option>
      </select>
      
      <label>
        Resume (PDF)
        <input type="file" name="fi-file-resume" accept=".pdf" required />
      </label>
      
      <label>
        Cover Letter (optional)
        <input type="file" name="fi-file-cover_letter" accept=".pdf,.doc,.docx" />
      </label>
      
      <textarea name="fi-text-why_join" placeholder="Why do you want to join us?" />
      
      {status === 'error' && <p className="status-error">{errorMessage}</p>}
      {status === 'success' && <p className="status-success">Application submitted!</p>}
      
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Submitting...' : 'Submit Application'}
      </button>
    </form>
  )
}

export default ApplicationForm
```

---

## Multiple Forms on One Site

Store multiple Form IDs in your site metadata:

```javascript
// gatsby-config.js

module.exports = {
  siteMetadata: {
    title: 'My Site',
    forms: {
      contact: process.env.GATSBY_FORMINIT_CONTACT || 'YOUR-CONTACT-FORM-ID',
      newsletter: process.env.GATSBY_FORMINIT_NEWSLETTER || 'YOUR-NEWSLETTER-FORM-ID',
      application: process.env.GATSBY_FORMINIT_APPLICATION || 'YOUR-APPLICATION-FORM-ID',
    },
  },
}
```

Query in your pages:

```jsx
export const query = graphql`
  query {
    site {
      siteMetadata {
        forms {
          contact
          newsletter
          application
        }
      }
    }
  }
`
```

---

## Handling Form Submission Response

The SDK returns `{ data, redirectUrl, error }`:

**On success:**

```javascript
{
  data: {
    hashId: "7LMIBoYY74JOCp1k",      // Unique submission ID
    date: "2026-01-01 21:10:24",      // Timestamp
    blocks: {                          // Submitted values
      sender: {
        firstName: "John",
        lastName: "Doe",
        email: "john@example.com"
      },
      message: "Hello from Gatsby!"
    }
  },
  redirectUrl: "https://forminit.com/thank-you"
}
```

**On error:**

```javascript
{
  error: {
    error: "ERROR_CODE",
    code: 400,
    message: "Human-readable error message"
  }
}
```

---

## Redirect After Submission

If you prefer to redirect users to a thank-you page:

```jsx
import { navigate } from 'gatsby'

const handleSubmit = async (e) => {
  e.preventDefault()
  
  const formData = new FormData(formRef.current)
  const { data, redirectUrl, error } = await submit(formId, formData)

  if (error) {
    setStatus('error')
    setErrorMessage(error.message)
    return
  }

  // Redirect to thank-you page
  navigate('/thank-you/')
  // Or use Forminit's redirect URL:
  // window.location.href = redirectUrl
}
```

Create a thank-you page:

```jsx
// src/pages/thank-you.js

import React from 'react'
import { Link } from 'gatsby'
import Layout from '../components/Layout'

const ThankYouPage = () => (
  <Layout>
    <h1>Thank You!</h1>
    <p>Thank you for your message! We'll get back to you soon.</p>
    <Link to="/">← Back to Home</Link>
  </Layout>
)

export default ThankYouPage
```

---

## Project Structure Example

Here's a typical Gatsby project structure with Forminit forms:

```
my-gatsby-site/
├── gatsby-config.js              # Site config with Form IDs
├── gatsby-browser.js             # Browser APIs
├── gatsby-ssr.js                 # SSR APIs
├── src/
│   ├── components/
│   │   ├── ContactForm.js        # Contact form component
│   │   ├── NewsletterForm.js     # Newsletter component
│   │   ├── FeedbackForm.js       # Feedback component
│   │   └── Layout.js             # Layout wrapper
│   ├── hooks/
│   │   └── useForminit.js        # Forminit custom hook
│   ├── pages/
│   │   ├── index.js              # Homepage
│   │   ├── contact.js            # Contact page
│   │   └── thank-you.js          # Thank you page
│   └── styles/
│       └── global.css            # Global styles
├── .env.development              # Dev environment variables
├── .env.production               # Prod environment variables
└── package.json
```

---

## Build and Deploy

Build your static site:

```bash
gatsby build
```

The generated files will be in the `public/` directory, ready to deploy to Netlify, Vercel, or any static hosting service.

---

## Next Steps

1. [View your submissions](https://app.forminit.com) in the Forminit dashboard
2. Set up [email notifications](https://forminit.com/docs/email-notifications/) for new submissions
3. Explore [webhook integrations](https://forminit.com/docs/webhooks/) for advanced workflows
