How to Add a Contact Form to an Eleventy (11ty) Site
Eleventy generates static HTML — there's no backend to receive form submissions. Here's how to add a working contact form to any 11ty site in minutes, no serverless functions needed.
How to Add a Contact Form to an Eleventy (11ty) Site
Eleventy (11ty) is a static site generator — it outputs pure HTML with no server-side runtime. That means no built-in way to handle form submissions. This guide shows how to add a contact form to any 11ty site that works on any host, without writing a serverless function.
Step 1: Create a Formboost Endpoint
Sign up at dashboard.formboost.app and create a new endpoint. You'll get a URL like:
https://formboost.app/f/YOUR_ENDPOINT_ID
Step 2: Create a Contact Page
Create contact.njk (or contact.html, contact.liquid) in your Eleventy source directory:
1---
2layout: base.njk
3title: Contact
4permalink: /contact/
5---
6
7<h1>Contact Us</h1>
8
9<form action="https://formboost.app/f/YOUR_ENDPOINT_ID" method="POST">
10 <!-- Honeypot: bots fill this in, humans don't see it -->
11 <input type="text" name="_honey" style="display:none" tabindex="-1" autocomplete="off" />
12
13 <div>
14 <label for="name">Name</label>
15 <input type="text" id="name" name="name" required />
16 </div>
17
18 <div>
19 <label for="email">Email</label>
20 <input type="email" id="email" name="email" required />
21 </div>
22
23 <div>
24 <label for="message">Message</label>
25 <textarea id="message" name="message" rows="6"></textarea>
26 </div>
27
28 <button type="submit">Send Message</button>
29</form>Replace YOUR_ENDPOINT_ID with your actual endpoint ID.
Step 3: Add a Thank-You Page (Optional)
By default, Formboost redirects to a hosted confirmation page after submission. To use your own 11ty page:
Add a hidden field in the form:
1<input type="hidden" name="_redirect" value="https://yoursite.com/thank-you/" />Create thank-you.njk:
1---
2layout: base.njk
3title: Message Sent
4permalink: /thank-you/
5---
6
7<h1>Message received!</h1>
8<p>Thanks for reaching out. We'll get back to you soon.</p>
9<a href="/">← Back to home</a>Step 4: Create a Reusable Shortcode
In Eleventy, you can create a reusable shortcode to embed the form on any page. Add this to your .eleventy.js config:
1// .eleventy.js
2module.exports = function(eleventyConfig) {
3 eleventyConfig.addShortcode("contactForm", function(endpointId) {
4 return `
5 <form action="https://formboost.app/f/${endpointId}" method="POST">
6 <input type="text" name="_honey" style="display:none" tabindex="-1" autocomplete="off" />
7 <div>
8 <label for="name">Name</label>
9 <input type="text" id="name" name="name" required />
10 </div>
11 <div>
12 <label for="email">Email</label>
13 <input type="email" id="email" name="email" required />
14 </div>
15 <div>
16 <label for="message">Message</label>
17 <textarea id="message" name="message" rows="5"></textarea>
18 </div>
19 <button type="submit">Send</button>
20 </form>
21 `;
22 });
23
24 return { dir: { input: "src", output: "_site" } };
25};Then use it in any template:
1{% contactForm "YOUR_ENDPOINT_ID" %}Step 5: Use Nunjucks Data for Dynamic Fields
Pass page context to the form using Eleventy's template data:
1<input type="hidden" name="source_page" value="{{ page.url }}" />This records which 11ty page each submission came from — useful if you embed the form on multiple pages.
AJAX Submit (Stay on Page)
To avoid a page redirect on submit, handle the form with JavaScript:
1<form id="contact-form">
2 <input type="text" name="name" required placeholder="Name" />
3 <input type="email" name="email" required placeholder="Email" />
4 <textarea name="message" rows="5"></textarea>
5 <button type="submit">Send</button>
6 <p id="status" hidden></p>
7</form>
8
9<script>
10 document.getElementById('contact-form').addEventListener('submit', async function(e) {
11 e.preventDefault();
12 const data = Object.fromEntries(new FormData(this));
13 const status = document.getElementById('status');
14
15 try {
16 const res = await fetch('https://formboost.app/f/YOUR_ENDPOINT_ID', {
17 method: 'POST',
18 headers: { 'Content-Type': 'application/json' },
19 body: JSON.stringify(data),
20 });
21
22 status.textContent = res.ok
23 ? 'Message sent! We\'ll be in touch.'
24 : 'Something went wrong. Please try again.';
25 status.style.color = res.ok ? 'green' : 'red';
26 if (res.ok) this.reset();
27 } catch {
28 status.textContent = 'Something went wrong.';
29 status.style.color = 'red';
30 }
31
32 status.hidden = false;
33 });
34</script>