Skip to content

Forminit + Gatsby Integration Guide

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 (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

  1. Sign up or log in at 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)

Store your Form ID in gatsby-config.js:

// gatsby-config.js

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

Or use environment variables (recommended):

// 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

Create a reusable form component:

// 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

Create a contact page that uses the form component:

// 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

Here’s a cleaner version using custom hooks:

// 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:

// 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>
  )
}

For a complete list of available form blocks (text, email, phone, file, rating, select, etc.) and field naming patterns, see the Form Blocks documentation.


// 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
// 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
// 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
// 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

Store multiple Form IDs in your site metadata:

// 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:

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

The SDK returns { data, redirectUrl, error }:

On success:

{
  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:

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

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

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:

// 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

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 your static site:

gatsby build

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


  1. View your submissions in the Forminit dashboard
  2. Set up email notifications for new submissions
  3. Explore webhook integrations for advanced workflows