Home / Guides

Add a contact form to an Eleventy (11ty) site

Published 2026-07-15

Eleventy's pitch is that it doesn't force a framework on you — write templates in Nunjucks, Liquid, Markdown, or whatever mix you like, and it compiles to plain HTML. That flexibility is great until you need a contact form, at which point Eleventy is honest about what it is: a static site generator. It builds files. It doesn't run a server, and it doesn't process form submissions after the build.

That means a <form> in your Eleventy site needs somewhere real to POST to. You could reach for an Eleventy Serverless function or a separate API project, but for something as self-contained as "receive a contact form, store it, email me," that's a lot of infrastructure for three fields and a submit button. Pointing the form at a hosted endpoint keeps your build 100% static and skips the backend entirely.

The form: a Nunjucks template

Create _includes/contact-form.njk:

<form action="{{ formEndpoint }}" method="POST">
  <label>Name
    <input type="text" name="name" required>
  </label>
  <label>Email
    <input type="email" name="email" required>
  </label>
  <label>Message
    <textarea name="message" required></textarea>
  </label>

  <!-- honeypot: bots fill it, humans never see it -->
  <input type="text" name="_honeypot" style="position:absolute;left:-9999px" tabindex="-1" autocomplete="off">

  <!-- redirect target after a successful submission -->
  <input type="hidden" name="_redirect" value="/thanks/">

  <button type="submit">Send</button>
</form>

Include it in any page or layout:

{% include "contact-form.njk" %}

(Prefer Liquid? The same markup works — swap {% include %} for Eleventy's Liquid include tag and the {{ formEndpoint }} interpolation stays identical since it's resolved from global data either way.)

This is a plain HTML form with no JS attached, which is worth calling out explicitly: it works the same whether the visitor has a fast connection and a modern browser, or JavaScript disabled entirely. The browser submits it natively, SubmissionBuddy replies with a 303 to _redirect, and the visitor lands on the thanks page.

Wiring the endpoint through global data

Eleventy's data cascade is the natural place for a value like this — set it once, reference it from every template. Create _data/site.js (or add to an existing global data file):

module.exports = {
  formEndpoint: "https://api.submissionbuddy.io/f/your-form",
};

Now {{ formEndpoint }} (via site.formEndpoint if you namespace it, or the flat key if site.js exports data at that name — adjust to match how you reference other global data in your project) resolves everywhere the template runs, and switching endpoints for a preview deploy is a one-line change.

Sign up free at SubmissionBuddy and create a form to get the URL — it's a plain https://api.submissionbuddy.io/f/your-form link, nothing to configure beyond pasting it in.

Because the endpoint is schema-less, your Nunjucks template can add, drop, or rename input fields freely. There's no form definition to keep in sync on the SubmissionBuddy side — whatever field names arrive in the POST body are what gets stored.

Spam protection

That _honeypot field is functional, not cosmetic: it's a text input positioned off-screen so real visitors never see or fill it, but form-filling bots do. When it arrives populated, SubmissionBuddy drops the submission silently — no storage write, no notification — while still returning a normal-looking success response, so scripted bots have no signal to react to.

Rate limiting, per IP and per form, is active by default on every plan. If you're dealing with more determined bot traffic — the kind that skips honeypots — paid plans add an invisible proof-of-work CAPTCHA. It verifies entirely server-side, makes no third-party requests, and sets no cookies, so it doesn't add a privacy tradeoff to an otherwise cookie-free static site.

The thank-you page

_redirect needs a page waiting for it. In Eleventy, that's just another template — thanks.njk or thanks.md at the root, or wherever your permalink structure lives:

---
permalink: /thanks/
---

Thanks for reaching out — we'll get back to you shortly.

Eleventy builds this to /thanks/index.html, matching the _redirect value in the form above.

What happens after the visitor hits submit

SubmissionBuddy writes the raw submission to durable storage before it returns any response — nothing else touches the data first. From there it moves through independent, retryable stages: a database write that makes the submission searchable in your dashboard, then an email notification to your team. A delay in the email step retries on its own and doesn't affect the record that's already stored.

The honest pitch

Free plan: 100 submissions a month, 2 forms, no credit card. That's plenty for the kind of side projects, docs sites, and blogs the Eleventy community tends to build. Paid plans start at $8/month if you need more headroom. And there's no client library to add to your build — the integration is an action attribute and one line of data.

Stop losing form submissions

SubmissionBuddy stores every submission durably before anything else touches it — 100 free submissions/month, no credit card, live in five minutes.

Start free