Eleventy
Add a contact form to an Eleventy site with a Nunjucks include and the access key in global data.
Eleventy writes static HTML to _site, so there is nothing on your host that can accept a form post. Point the form at SilentForms instead and each submission is emailed to you. In Eleventy terms that means a Nunjucks include for the form, a global data file for the access key and site URL, and a thank-you page with its own permalink. The examples below use Eleventy v3 and the default folders (_includes and _data inside the input directory).
1. Get an access key#
Enter your email on the access key page and click the confirmation link. The key only routes submissions to you, so it is safe in the generated HTML.
2. Add a global data file#
Every file in _data becomes a global variable named after the file. The simplest version is _data/site.json:
{
"title": "My site",
"url": "https://example.com",
"silentformsKey": "YOUR_ACCESS_KEY_HERE"
}Templates can now read site.silentformsKey and site.url.
If you would rather keep the values out of the repository, or want the thank-you redirect to point at localhost while developing, use _data/site.js instead. JavaScript data files can read process.env:
export default {
title: 'My site',
url: process.env.SITE_URL || 'http://localhost:8080',
silentformsKey: process.env.SILENTFORMS_KEY || 'YOUR_ACCESS_KEY_HERE',
};export default needs ESM, which Eleventy supports from v3: either set "type": "module" in package.json or name the file site.mjs. In a CommonJS project, write module.exports = { ... } instead. Keep only one of site.json and site.js.
3. Create the include#
Create _includes/contact-form.njk:
<form action="https://silentforms.com/api/submit" method="POST" class="contact-form">
<input type="hidden" name="accessKey" value="{{ site.silentformsKey }}">
<input type="hidden" name="subject" value="New message from {{ site.title }}">
<input type="hidden" name="redirect" value="{{ site.url }}/thanks/">
<label for="cf-name">Name</label>
<input id="cf-name" type="text" name="name" autocomplete="name" required>
<label for="cf-email">Email</label>
<input id="cf-email" type="email" name="email" autocomplete="email" required>
<label for="cf-message">Message</label>
<textarea id="cf-message" name="message" rows="6" required></textarea>
<!-- Honeypot: hidden from people, filled in by bots -->
<input type="checkbox" name="botcheck" style="display: none;" tabindex="-1" autocomplete="off">
<button type="submit">Send message</button>
</form>The redirect value is built from site.url because it must be an absolute URL. A relative path like /thanks/ is rejected with "redirect must be an absolute http(s) URL." Nunjucks escapes output by default, so a site title with quotes cannot break the attribute.
4. Add the contact page#
Create contact.njk in your input directory:
---
layout: base.njk
title: Contact
permalink: /contact/
---
<h1>Contact us</h1>
<p>Questions or feedback? We reply to every message.</p>
{% include "contact-form.njk" %}Swap base.njk for whatever layout your site uses. Using a .njk page rather than a .md one matters: Eleventy pre-processes Markdown files with Liquid by default, not Nunjucks, so a Nunjucks include in Markdown depends on your markdownTemplateEngine setting.
5. Add the thank-you page#
Create thanks.njk:
---
layout: base.njk
title: Thanks!
permalink: /thanks/
eleventyExcludeFromCollections: true
---
<h1>Thanks!</h1>
<p>Your message is on its way. We will get back to you soon.</p>
<p><a href="/">Back to the homepage</a></p>permalink puts the page at /thanks/index.html, which matches the redirect in the include. eleventyExcludeFromCollections keeps it out of collections.all, so it does not show up in a sitemap or navigation built from collections. Without the redirect field, visitors see the default SilentForms success page instead. See Redirect after submit.
Test it#
Run the development server:
npx @11ty/eleventy --serveOpen http://localhost:8080/contact/, send yourself a message and check that you land on /thanks/ and the email arrives. If you used site.json, the redirect goes to the production URL even locally; that is expected.
For the production build, set the variables your data file reads:
SITE_URL=https://example.com npx @11ty/eleventyThen check _site/contact/index.html: the hidden fields should hold your key and https://example.com/thanks/.
Gotchas#
- The access key field is empty. The data file name and the variable do not match (
_data/site.jsonissite), or bothsite.jsonandsite.jsexist. SyntaxError: Unexpected token 'export'. The data file uses ESM in a CommonJS project. Rename it tosite.mjsor usemodule.exports.- The redirect URL has a double slash. Leave the trailing slash off
site.url. - The include is not found. Includes are resolved from
_includesinside the input directory. If you changeddir.inputordir.includesin your Eleventy config file, move the file accordingly.
For every error message, see Troubleshooting.
Next steps#
- Lock the key to your domain: Restrict to your domain.
- Show the result inline without leaving the page: AJAX form with JavaScript.
- Add a CAPTCHA: Cloudflare Turnstile.