Adding a contact form to an Eleventy site

July 24, 2026

Eleventy people are particular about shipping plain HTML. It's half the reason you picked 11ty in the first place. Turns out a contact form is the easiest place in your whole site to hold that line: no client JavaScript, no serverless function bolted onto your static site, no build plugin.

1. Grab an endpoint

Spin up a NormForms form. Free tier, no card required. You'll get a URL that looks like https://normforms.com/f/x7Kp2mQ9RtLw.

2. Drop the form into a template or include

It works the same in Nunjucks, Liquid, WebC, or plain HTML files. It's just markup. Here it is as an include (_includes/contact-form.njk):

<form action="https://normforms.com/f/YOUR_TOKEN" method="POST">
  <label for="nf-name">Name</label>
  <input id="nf-name" type="text" name="name" autocomplete="name" required>

  <label for="nf-email">Email</label>
  <input id="nf-email" type="email" name="email" autocomplete="email" required>

  <label for="nf-message">Message</label>
  <textarea id="nf-message" name="message" required></textarea>

  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
  <button type="submit">Send</button>
</form>
{% include "contact-form.njk" %}

Want more fields? Add inputs: <input name="budget">, a select, whatever you need. Any field name you send shows up in the notification email, no configuration required. The _gotcha honeypot stays hidden and empty. It's doing more anti-spam work than it looks like.

3. Test it from localhost

Submit from npx eleventy --serve and you're automatically in test mode. The email shows up with a [Test] prefix and doesn't touch your quota. Set your production domain in the form settings and submissions from any other origin get silently dropped.

Scorecard

Client JavaScript added: 0 bytes. Build time impact: none. Spam filtering, delivery, storage, and a dashboard: included. Your 11ty site stays a folder of honest HTML files. Now it has a working front door.

Ship your form →