Forminit + Hexo Integration Guide
Hexo is a fast, Node.js-powered static site generator popular for blogs and documentation. Since Hexo outputs plain 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 Hexo site (v6.0+ or v7.0+ or v8.0+)
- Node.js 14+ (Node.js 18+ recommended)
- 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.,
YOUR-FORM-ID)
Step 2: Add Form ID to Configuration
Section titled “Step 2: Add Form ID to Configuration”Store your Form ID in _config.yml:
# _config.yml
title: My Site
url: https://example.com
# Forminit configuration
forminit:
form_id: "YOUR-FORM-ID"
Access it in your templates using config.forminit.form_id.
Step 3: Create a Form Partial
Section titled “Step 3: Create a Form Partial”Hexo supports multiple template engines. Here are examples for the most common ones:
EJS (default)
Section titled “EJS (default)”<!-- themes/your-theme/layout/_partial/contact-form.ejs -->
<form id="contact-form">
<div class="form-group">
<label for="firstName">First Name</label>
<input
type="text"
id="firstName"
name="fi-sender-firstName"
placeholder="John"
required
/>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input
type="text"
id="lastName"
name="fi-sender-lastName"
placeholder="Doe"
required
/>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
name="fi-sender-email"
placeholder="john@example.com"
required
/>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea
id="message"
name="fi-text-message"
placeholder="Your message..."
rows="5"
required
></textarea>
</div>
<button type="submit">Send Message</button>
</form>
<p id="form-status"></p>
Pug (Jade)
Section titled “Pug (Jade)”//- themes/your-theme/layout/_partial/contact-form.pug
form#contact-form
.form-group
label(for="firstName") First Name
input(type="text" id="firstName" name="fi-sender-firstName" placeholder="John" required)
.form-group
label(for="lastName") Last Name
input(type="text" id="lastName" name="fi-sender-lastName" placeholder="Doe" required)
.form-group
label(for="email") Email
input(type="email" id="email" name="fi-sender-email" placeholder="john@example.com" required)
.form-group
label(for="message") Message
textarea(id="message" name="fi-text-message" placeholder="Your message..." rows="5" required)
button(type="submit") Send Message
p#form-status
Nunjucks
Section titled “Nunjucks”{# themes/your-theme/layout/_partial/contact-form.njk #}
<form id="contact-form">
<div class="form-group">
<label for="firstName">First Name</label>
<input
type="text"
id="firstName"
name="fi-sender-firstName"
placeholder="John"
required
/>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input
type="text"
id="lastName"
name="fi-sender-lastName"
placeholder="Doe"
required
/>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
name="fi-sender-email"
placeholder="john@example.com"
required
/>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea
id="message"
name="fi-text-message"
placeholder="Your message..."
rows="5"
required
></textarea>
</div>
<button type="submit">Send Message</button>
</form>
<p id="form-status"></p>
Step 4: Add the Forminit SDK
Section titled “Step 4: Add the Forminit SDK”Create a partial for the form script:
<!-- themes/your-theme/layout/_partial/forminit-script.ejs -->
<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<script>
const forminit = new Forminit();
const FORM_ID = '<%= config.forminit.form_id %>';
const form = document.getElementById('contact-form');
const status = document.getElementById('form-status');
form.addEventListener('submit', async function(event) {
event.preventDefault();
// Show loading state
status.textContent = 'Sending...';
status.className = 'status-loading';
const formData = new FormData(form);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
status.textContent = error.message;
status.className = 'status-error';
return;
}
// Success
status.textContent = 'Message sent successfully!';
status.className = 'status-success';
form.reset();
});
</script>
//- themes/your-theme/layout/_partial/forminit-script.pug
script(src="https://forminit.com/sdk/v1/forminit.js")
script.
const forminit = new Forminit();
const FORM_ID = '#{config.forminit.form_id}';
const form = document.getElementById('contact-form');
const status = document.getElementById('form-status');
form.addEventListener('submit', async function(event) {
event.preventDefault();
status.textContent = 'Sending...';
status.className = 'status-loading';
const formData = new FormData(form);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
status.textContent = error.message;
status.className = 'status-error';
return;
}
status.textContent = 'Message sent successfully!';
status.className = 'status-success';
form.reset();
});
Step 5: Include in Your Layout
Section titled “Step 5: Include in Your Layout”Add the script partial to your layout before </body>:
<!-- themes/your-theme/layout/layout.ejs -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= page.title %> | <%= config.title %></title>
<%- css('css/style') %>
</head>
<body>
<%- partial('_partial/header') %>
<main>
<%- body %>
</main>
<%- partial('_partial/footer') %>
<% if (page.has_form) { %>
<%- partial('_partial/forminit-script') %>
<% } %>
</body>
</html>
//- themes/your-theme/layout/layout.pug
doctype html
html
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1.0")
title= page.title + ' | ' + config.title
!= css('css/style')
body
!= partial('_partial/header')
main
!= body
!= partial('_partial/footer')
if page.has_form
!= partial('_partial/forminit-script')
Step 6: Create Your Contact Page
Section titled “Step 6: Create Your Contact Page”Create a contact page with front matter:
---
title: Contact
has_form: true
---
# Contact Us
We'd love to hear from you. Fill out the form below and we'll get back to you soon.
<%- partial('_partial/contact-form') %>
Or create a dedicated page layout:
<!-- themes/your-theme/layout/contact.ejs -->
<%- partial('_partial/header') %>
<div class="contact-page">
<h1><%= page.title %></h1>
<p>We'd love to hear from you. Fill out the form below and we'll get back to you soon.</p>
<%- partial('_partial/contact-form') %>
</div>
<%- partial('_partial/footer') %>
<%- partial('_partial/forminit-script') %>
Then create the page:
---
title: Contact
layout: contact
---
Using Theme Configuration
Section titled “Using Theme Configuration”You can also store Form ID in your theme’s config:
# themes/your-theme/_config.yml
forminit:
contact_form_id: "YOUR-FORM-ID"
newsletter_form_id: "YOUR-NEWSLETTER-FORM-ID"
Access in templates:
const FORM_ID = '<%= theme.forminit.contact_form_id %>';
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”<!-- themes/your-theme/layout/_partial/contact-form.ejs -->
<form id="contact-form">
<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></textarea>
<button type="submit">Send</button>
</form>
<p id="form-status"></p>
Newsletter Signup
Section titled “Newsletter Signup”<!-- themes/your-theme/layout/_partial/newsletter-form.ejs -->
<form id="newsletter-form">
<input type="email" name="fi-sender-email" placeholder="Enter your email" required />
<button type="submit">Subscribe</button>
</form>
<p id="newsletter-status"></p>
<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<script>
const forminit = new Forminit();
const FORM_ID = '<%= config.forminit.newsletter_form_id || theme.forminit.newsletter_form_id %>';
const form = document.getElementById('newsletter-form');
const status = document.getElementById('newsletter-status');
form.addEventListener('submit', async function(event) {
event.preventDefault();
status.textContent = 'Subscribing...';
const formData = new FormData(form);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
status.textContent = error.message;
return;
}
status.textContent = 'Thank you for subscribing!';
form.reset();
});
</script>
Feedback Form with Rating
Section titled “Feedback Form with Rating”<!-- themes/your-theme/layout/_partial/feedback-form.ejs -->
<form id="feedback-form">
<input type="email" name="fi-sender-email" placeholder="Email (optional)" />
<fieldset>
<legend>How would you rate your experience?</legend>
<% for (let n = 1; n <= 5; n++) { %>
<label>
<input type="radio" name="fi-rating-experience" value="<%= n %>" />
<%= n %>
</label>
<% } %>
</fieldset>
<textarea name="fi-text-feedback" placeholder="Tell us more (optional)"></textarea>
<button type="submit">Submit Feedback</button>
</form>
<p id="feedback-status"></p>
Job Application with File Upload
Section titled “Job Application with File Upload”<!-- themes/your-theme/layout/_partial/application-form.ejs -->
<form id="application-form">
<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?"></textarea>
<button type="submit">Submit Application</button>
</form>
<p id="application-status"></p>
Multiple Forms on One Site
Section titled “Multiple Forms on One Site”Store multiple Form IDs in your config:
# _config.yml
forminit:
contact: "YOUR-CONTACT-FORM-ID"
newsletter: "YOUR-NEWSLETTER-FORM-ID"
application: "YOUR-APPLICATION-FORM-ID"
Reference them in your templates:
const FORM_ID = '<%= config.forminit.contact %>';
Using Hexo Helpers
Section titled “Using Hexo Helpers”Create a custom helper for Forminit scripts in your theme:
// themes/your-theme/scripts/forminit-helper.js
hexo.extend.helper.register('forminit_script', function(formElementId, formIdKey) {
const formId = this.config.forminit[formIdKey] || this.theme.forminit[formIdKey];
return `
<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<script>
(function() {
const forminit = new Forminit();
const form = document.getElementById('${formElementId}');
const status = document.getElementById('${formElementId}-status');
form.addEventListener('submit', async function(e) {
e.preventDefault();
status.textContent = 'Sending...';
const { data, error } = await forminit.submit('${formId}', new FormData(form));
if (error) {
status.textContent = error.message;
status.className = 'status-error';
return;
}
status.textContent = 'Message sent!';
status.className = 'status-success';
form.reset();
});
})();
</script>
`;
});
Use in your templates:
<form id="contact-form">
<!-- form fields -->
</form>
<p id="contact-form-status"></p>
<%- forminit_script('contact-form', 'contact') %>
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 Hexo!"
}
},
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:
form.addEventListener('submit', async function(event) {
event.preventDefault();
const formData = new FormData(form);
const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);
if (error) {
status.textContent = error.message;
return;
}
// Redirect to thank-you page
window.location.href = '/thank-you/';
// Or use Forminit's redirect URL:
// window.location.href = redirectUrl;
});
Create a thank-you page:
---
title: Thank You
---
# Thank You!
Thank you for your message! We'll get back to you soon.
[← Back to Home](/)
Project Structure Example
Section titled “Project Structure Example”Here’s a typical Hexo project structure with Forminit forms:
my-hexo-site/
├── _config.yml # Site config with Form IDs
├── source/
│ ├── _posts/ # Blog posts
│ ├── contact/
│ │ └── index.md # Contact page
│ └── thank-you/
│ └── index.md # Thank you page
├── themes/
│ └── your-theme/
│ ├── _config.yml # Theme config
│ ├── layout/
│ │ ├── _partial/
│ │ │ ├── contact-form.ejs
│ │ │ ├── forminit-script.ejs
│ │ │ ├── header.ejs
│ │ │ └── footer.ejs
│ │ ├── layout.ejs # Base layout
│ │ └── contact.ejs # Contact page layout
│ ├── scripts/
│ │ └── forminit-helper.js # Custom helper
│ └── source/
│ └── css/
│ └── style.css
├── package.json
└── node_modules/
Build and Deploy
Section titled “Build and Deploy”Generate your static site:
hexo generate
Or use the shorthand:
hexo g
The generated files will be in the public/ directory. Deploy to GitHub Pages, Netlify, or any static hosting:
hexo deploy
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