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
Quick Start
Section titled “Quick Start”Step 1: Create a Form on Forminit
Section titled “Step 1: Create a Form on Forminit”- Sign up or log in at forminit.com
- Create a new form
- Go to Form Settings → Set authentication mode to Public
- Copy your Form ID (e.g.,
abc123xyz)
Step 2: Add Form ID to Configuration
Section titled “Step 2: Add Form ID to Configuration”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
Step 3: Create a Form Component
Section titled “Step 3: Create a Form Component”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>
Step 4: Create Your Contact Page
Section titled “Step 4: Create Your Contact Page”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>
Alternative: Using ClientOnly Wrapper
Section titled “Alternative: Using ClientOnly Wrapper”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>
Form Field Reference
Section titled “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.
Complete Examples
Section titled “Complete Examples”Contact Form with Subject Selection
Section titled “Contact Form with Subject Selection”<!-- 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>
Newsletter Signup
Section titled “Newsletter Signup”<!-- 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" />
Feedback Form with Rating
Section titled “Feedback Form with Rating”<!-- 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>
Job Application with File Upload
Section titled “Job Application with File Upload”<!-- 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>
Multiple Forms on One Site
Section titled “Multiple Forms on One Site”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>
Handling Form Submission Response
Section titled “Handling Form Submission Response”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"
}
}
Redirect After Submission
Section titled “Redirect After Submission”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>
Project Structure Example
Section titled “Project Structure Example”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
Next Steps
Section titled “Next Steps”- View your submissions in the Forminit dashboard
- Set up email notifications for new submissions
- Explore webhook integrations for advanced workflows