Skip to content

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

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


Hexo supports multiple template engines. Here are examples for the most common ones:

<!-- 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>
//- 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
{# 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>

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();
  });

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')

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

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 %>';

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


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

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 %>';

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') %>

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"
  }
}

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](/)

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/

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

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