Skip to content

Forminit + Gridsome Integration Guide

Gridsome is a Vue.js-powered static site generator that outputs fast, SEO-friendly websites. Since Gridsome generates static HTML files with no server-side processing, Forminit handles all form submissions through our client-side SDK.

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 Gridsome site (v0.7+)
  • Node.js 12.13+ or 14+ or 16+
  • 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., abc123xyz)

Store your Form ID in gridsome.config.js:

// gridsome.config.js

module.exports = {
  siteName: 'My Site',
  
  // Custom site metadata
  metadata: {
    forminitFormId: 'abc123xyz'
  }
}

Or use environment variables for better security:

// gridsome.config.js

module.exports = {
  siteName: 'My Site',
  
  metadata: {
    forminitFormId: process.env.GRIDSOME_FORMINIT_FORM_ID || 'abc123xyz'
  }
}

Create a .env file:

GRIDSOME_FORMINIT_FORM_ID=abc123xyz

Create a reusable form component:

<!-- src/components/ContactForm.vue -->

<template>
  <form @submit.prevent="handleSubmit" id="contact-form">
    <div class="form-group">
      <label for="firstName">First Name</label>
      <input 
        type="text" 
        id="firstName" 
        name="fi-sender-firstName" 
        v-model="form.firstName"
        placeholder="John" 
        required 
      />
    </div>

    <div class="form-group">
      <label for="lastName">Last Name</label>
      <input 
        type="text" 
        id="lastName" 
        name="fi-sender-lastName" 
        v-model="form.lastName"
        placeholder="Doe" 
        required 
      />
    </div>

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

    <div class="form-group">
      <label for="message">Message</label>
      <textarea 
        id="message" 
        name="fi-text-message" 
        v-model="form.message"
        placeholder="Your message..." 
        rows="5" 
        required
      ></textarea>
    </div>

    <p v-if="status === 'error'" class="status-error">{{ errorMessage }}</p>
    <p v-if="status === 'success'" class="status-success">Message sent successfully!</p>

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

<script>
export default {
  name: 'ContactForm',
  
  data() {
    return {
      form: {
        firstName: '',
        lastName: '',
        email: '',
        message: ''
      },
      status: 'idle',
      errorMessage: null,
      forminit: null
    }
  },
  
  mounted() {
    // Load Forminit SDK
    this.loadForminitSDK()
  },
  
  methods: {
    loadForminitSDK() {
      if (typeof window !== 'undefined' && !window.Forminit) {
        const script = document.createElement('script')
        script.src = 'https://forminit.com/sdk/v1/forminit.js'
        script.onload = () => {
          this.forminit = new window.Forminit()
        }
        document.body.appendChild(script)
      } else if (window.Forminit) {
        this.forminit = new window.Forminit()
      }
    },
    
    async handleSubmit() {
      if (!this.forminit) {
        this.status = 'error'
        this.errorMessage = 'Form not ready. Please try again.'
        return
      }
      
      this.status = 'loading'
      this.errorMessage = null
      
      const formData = new FormData()
      formData.append('fi-sender-firstName', this.form.firstName)
      formData.append('fi-sender-lastName', this.form.lastName)
      formData.append('fi-sender-email', this.form.email)
      formData.append('fi-text-message', this.form.message)
      
      const formId = this.$static?.metadata?.forminitFormId || process.env.GRIDSOME_FORMINIT_FORM_ID
      
      const { data, error } = await this.forminit.submit(formId, formData)
      
      if (error) {
        this.status = 'error'
        this.errorMessage = error.message
        return
      }
      
      this.status = 'success'
      this.form = { firstName: '', lastName: '', email: '', message: '' }
    }
  }
}
</script>

<static-query>
query {
  metadata {
    forminitFormId
  }
}
</static-query>

Create a contact page that uses the form component:

<!-- src/pages/Contact.vue -->

<template>
  <Layout>
    <div class="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 />
    </div>
  </Layout>
</template>

<script>
import ContactForm from '~/components/ContactForm.vue'

export default {
  components: {
    ContactForm
  },
  metaInfo: {
    title: 'Contact'
  }
}
</script>

For better SSR compatibility, wrap the form in Gridsome’s <ClientOnly> component:

<!-- src/pages/Contact.vue -->

<template>
  <Layout>
    <div class="contact-page">
      <h1>Contact Us</h1>
      
      <ClientOnly>
        <ContactForm />
      </ClientOnly>
    </div>
  </Layout>
</template>

<script>
import ContactForm from '~/components/ContactForm.vue'

export default {
  components: {
    ContactForm
  }
}
</script>

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.vue -->

<template>
  <form @submit.prevent="handleSubmit">
    <input 
      type="text" 
      name="fi-sender-firstName" 
      v-model="form.firstName"
      placeholder="First name" 
      required 
    />
    <input 
      type="text" 
      name="fi-sender-lastName" 
      v-model="form.lastName"
      placeholder="Last name" 
      required 
    />
    <input 
      type="email" 
      name="fi-sender-email" 
      v-model="form.email"
      placeholder="Email" 
      required 
    />
    
    <select name="fi-select-subject" v-model="form.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" 
      v-model="form.message"
      placeholder="Your message" 
      required
    ></textarea>
    
    <p v-if="status === 'error'" class="status-error">{{ errorMessage }}</p>
    <p v-if="status === 'success'" class="status-success">Message sent!</p>
    
    <button type="submit" :disabled="status === 'loading'">
      {{ status === 'loading' ? 'Sending...' : 'Send' }}
    </button>
  </form>
</template>

<script>
export default {
  data() {
    return {
      form: {
        firstName: '',
        lastName: '',
        email: '',
        subject: '',
        message: ''
      },
      status: 'idle',
      errorMessage: null,
      forminit: null
    }
  },
  
  mounted() {
    this.loadForminitSDK()
  },
  
  methods: {
    loadForminitSDK() {
      if (typeof window !== 'undefined' && !window.Forminit) {
        const script = document.createElement('script')
        script.src = 'https://forminit.com/sdk/v1/forminit.js'
        script.onload = () => {
          this.forminit = new window.Forminit()
        }
        document.body.appendChild(script)
      } else if (window.Forminit) {
        this.forminit = new window.Forminit()
      }
    },
    
    async handleSubmit() {
      if (!this.forminit) return
      
      this.status = 'loading'
      
      const formData = new FormData()
      formData.append('fi-sender-firstName', this.form.firstName)
      formData.append('fi-sender-lastName', this.form.lastName)
      formData.append('fi-sender-email', this.form.email)
      formData.append('fi-select-subject', this.form.subject)
      formData.append('fi-text-message', this.form.message)
      
      const formId = this.$static?.metadata?.forminitFormId
      const { data, error } = await this.forminit.submit(formId, formData)
      
      if (error) {
        this.status = 'error'
        this.errorMessage = error.message
        return
      }
      
      this.status = 'success'
      this.form = { firstName: '', lastName: '', email: '', subject: '', message: '' }
    }
  }
}
</script>

<static-query>
query {
  metadata {
    forminitFormId
  }
}
</static-query>
<!-- src/components/NewsletterForm.vue -->

<template>
  <form @submit.prevent="handleSubmit" class="newsletter-form">
    <input 
      type="email" 
      name="fi-sender-email" 
      v-model="email"
      placeholder="Enter your email" 
      required 
    />
    <button type="submit" :disabled="status === 'loading'">
      {{ status === 'loading' ? 'Subscribing...' : 'Subscribe' }}
    </button>
    
    <p v-if="status === 'error'" class="status-error">{{ errorMessage }}</p>
    <p v-if="status === 'success'" class="status-success">Thank you for subscribing!</p>
  </form>
</template>

<script>
export default {
  props: {
    formId: {
      type: String,
      required: true
    }
  },
  
  data() {
    return {
      email: '',
      status: 'idle',
      errorMessage: null,
      forminit: null
    }
  },
  
  mounted() {
    this.loadForminitSDK()
  },
  
  methods: {
    loadForminitSDK() {
      if (typeof window !== 'undefined' && !window.Forminit) {
        const script = document.createElement('script')
        script.src = 'https://forminit.com/sdk/v1/forminit.js'
        script.onload = () => {
          this.forminit = new window.Forminit()
        }
        document.body.appendChild(script)
      } else if (window.Forminit) {
        this.forminit = new window.Forminit()
      }
    },
    
    async handleSubmit() {
      if (!this.forminit) return
      
      this.status = 'loading'
      
      const formData = new FormData()
      formData.append('fi-sender-email', this.email)
      
      const { data, error } = await this.forminit.submit(this.formId, formData)
      
      if (error) {
        this.status = 'error'
        this.errorMessage = error.message
        return
      }
      
      this.status = 'success'
      this.email = ''
    }
  }
}
</script>

Usage:

<NewsletterForm formId="newsletter123" />
<!-- src/components/FeedbackForm.vue -->

<template>
  <form @submit.prevent="handleSubmit">
    <input 
      type="email" 
      name="fi-sender-email" 
      v-model="form.email"
      placeholder="Email (optional)" 
    />
    
    <fieldset>
      <legend>How would you rate your experience?</legend>
      <label v-for="n in 5" :key="n">
        <input 
          type="radio" 
          name="fi-rating-experience" 
          :value="n" 
          v-model="form.rating"
        />
        {{ n }}
      </label>
    </fieldset>
    
    <textarea 
      name="fi-text-feedback" 
      v-model="form.feedback"
      placeholder="Tell us more (optional)"
    ></textarea>
    
    <p v-if="status === 'error'" class="status-error">{{ errorMessage }}</p>
    <p v-if="status === 'success'" class="status-success">Thank you for your feedback!</p>
    
    <button type="submit" :disabled="status === 'loading'">
      {{ status === 'loading' ? 'Submitting...' : 'Submit Feedback' }}
    </button>
  </form>
</template>

<script>
export default {
  data() {
    return {
      form: {
        email: '',
        rating: null,
        feedback: ''
      },
      status: 'idle',
      errorMessage: null,
      forminit: null
    }
  },
  
  mounted() {
    this.loadForminitSDK()
  },
  
  methods: {
    loadForminitSDK() {
      if (typeof window !== 'undefined' && !window.Forminit) {
        const script = document.createElement('script')
        script.src = 'https://forminit.com/sdk/v1/forminit.js'
        script.onload = () => {
          this.forminit = new window.Forminit()
        }
        document.body.appendChild(script)
      } else if (window.Forminit) {
        this.forminit = new window.Forminit()
      }
    },
    
    async handleSubmit() {
      if (!this.forminit) return
      
      this.status = 'loading'
      
      const formData = new FormData()
      if (this.form.email) formData.append('fi-sender-email', this.form.email)
      if (this.form.rating) formData.append('fi-rating-experience', this.form.rating)
      if (this.form.feedback) formData.append('fi-text-feedback', this.form.feedback)
      
      const formId = this.$static?.metadata?.forminitFormId
      const { data, error } = await this.forminit.submit(formId, formData)
      
      if (error) {
        this.status = 'error'
        this.errorMessage = error.message
        return
      }
      
      this.status = 'success'
      this.form = { email: '', rating: null, feedback: '' }
    }
  }
}
</script>

<static-query>
query {
  metadata {
    forminitFormId
  }
}
</static-query>
<!-- src/components/ApplicationForm.vue -->

<template>
  <form @submit.prevent="handleSubmit">
    <input type="text" v-model="form.firstName" placeholder="First name" required />
    <input type="text" v-model="form.lastName" placeholder="Last name" required />
    <input type="email" v-model="form.email" placeholder="Email" required />
    <input type="tel" v-model="form.phone" placeholder="Phone (+1234567890)" />
    
    <input type="url" v-model="form.linkedin" placeholder="LinkedIn profile URL" />
    <input type="url" v-model="form.portfolio" placeholder="Portfolio URL" />
    
    <select v-model="form.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" ref="resume" accept=".pdf" required />
    </label>
    
    <label>
      Cover Letter (optional)
      <input type="file" ref="coverLetter" accept=".pdf,.doc,.docx" />
    </label>
    
    <textarea v-model="form.whyJoin" placeholder="Why do you want to join us?"></textarea>
    
    <p v-if="status === 'error'" class="status-error">{{ errorMessage }}</p>
    <p v-if="status === 'success'" class="status-success">Application submitted!</p>
    
    <button type="submit" :disabled="status === 'loading'">
      {{ status === 'loading' ? 'Submitting...' : 'Submit Application' }}
    </button>
  </form>
</template>

<script>
export default {
  data() {
    return {
      form: {
        firstName: '',
        lastName: '',
        email: '',
        phone: '',
        linkedin: '',
        portfolio: '',
        position: '',
        whyJoin: ''
      },
      status: 'idle',
      errorMessage: null,
      forminit: null
    }
  },
  
  mounted() {
    this.loadForminitSDK()
  },
  
  methods: {
    loadForminitSDK() {
      if (typeof window !== 'undefined' && !window.Forminit) {
        const script = document.createElement('script')
        script.src = 'https://forminit.com/sdk/v1/forminit.js'
        script.onload = () => {
          this.forminit = new window.Forminit()
        }
        document.body.appendChild(script)
      } else if (window.Forminit) {
        this.forminit = new window.Forminit()
      }
    },
    
    async handleSubmit() {
      if (!this.forminit) return
      
      this.status = 'loading'
      
      const formData = new FormData()
      formData.append('fi-sender-firstName', this.form.firstName)
      formData.append('fi-sender-lastName', this.form.lastName)
      formData.append('fi-sender-email', this.form.email)
      if (this.form.phone) formData.append('fi-sender-phone', this.form.phone)
      if (this.form.linkedin) formData.append('fi-url-linkedin', this.form.linkedin)
      if (this.form.portfolio) formData.append('fi-url-portfolio', this.form.portfolio)
      formData.append('fi-select-position', this.form.position)
      if (this.form.whyJoin) formData.append('fi-text-why-join', this.form.whyJoin)
      
      // File uploads
      if (this.$refs.resume.files[0]) {
        formData.append('fi-file-resume', this.$refs.resume.files[0])
      }
      if (this.$refs.coverLetter.files[0]) {
        formData.append('fi-file-cover-letter', this.$refs.coverLetter.files[0])
      }
      
      const formId = this.$static?.metadata?.forminitFormId
      const { data, error } = await this.forminit.submit(formId, formData)
      
      if (error) {
        this.status = 'error'
        this.errorMessage = error.message
        return
      }
      
      this.status = 'success'
      this.form = {
        firstName: '', lastName: '', email: '', phone: '',
        linkedin: '', portfolio: '', position: '', whyJoin: ''
      }
      this.$refs.resume.value = ''
      this.$refs.coverLetter.value = ''
    }
  }
}
</script>

<static-query>
query {
  metadata {
    forminitFormId
  }
}
</static-query>

Store multiple Form IDs in your configuration:

// gridsome.config.js

module.exports = {
  siteName: 'My Site',
  
  metadata: {
    forms: {
      contact: process.env.GRIDSOME_FORMINIT_CONTACT || 'abc123',
      newsletter: process.env.GRIDSOME_FORMINIT_NEWSLETTER || 'xyz456',
      application: process.env.GRIDSOME_FORMINIT_APPLICATION || 'sms55'
    }
  }
}

Access in your components:

<static-query>
query {
  metadata {
    forms {
      contact
      newsletter
      application
    }
  }
}
</static-query>

<script>
export default {
  computed: {
    contactFormId() {
      return this.$static.metadata.forms.contact
    }
  }
}
</script>

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 Gridsome!"
    }
  },
  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:

async handleSubmit() {
  // ... form submission code
  
  const { data, redirectUrl, error } = await this.forminit.submit(formId, formData)
  
  if (error) {
    this.status = 'error'
    this.errorMessage = error.message
    return
  }
  
  // Redirect to thank-you page
  this.$router.push('/thank-you/')
  // Or use Forminit's redirect URL:
  // window.location.href = redirectUrl
}

Create a thank-you page:

<!-- src/pages/ThankYou.vue -->

<template>
  <Layout>
    <h1>Thank You!</h1>
    <p>Thank you for your message! We'll get back to you soon.</p>
    <g-link to="/">← Back to Home</g-link>
  </Layout>
</template>

<script>
export default {
  metaInfo: {
    title: 'Thank You'
  }
}
</script>

Here’s a typical Gridsome project structure with Forminit forms:

my-gridsome-site/
├── gridsome.config.js         # Site config with Form IDs
├── gridsome.server.js         # Server-side hooks
├── src/
│   ├── components/
│   │   ├── ContactForm.vue    # Contact form component
│   │   ├── NewsletterForm.vue # Newsletter component
│   │   └── FeedbackForm.vue   # Feedback component
│   ├── layouts/
│   │   └── Default.vue        # Default layout
│   ├── pages/
│   │   ├── Index.vue          # Homepage
│   │   ├── Contact.vue        # Contact page
│   │   └── ThankYou.vue       # Thank you page
│   ├── main.js                # App entry point
│   └── favicon.png
├── static/                    # Static assets
├── .env                       # Environment variables
└── package.json

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