# Connect Eleventy to SendBeam

How to connect Eleventy to SendBeam: what you need, the setup steps, and the code.

Via API

Eleventy builds plain HTML, so form handling has to live somewhere else. With SendBeam it lives at a public endpoint per form: an include renders it, a small script posts it, nothing is hard-coded.

## Before you start

- An account with Eleventy
- A SendBeam API key, or a form ID
- Somewhere to paste a snippet

## Set it up

1. **Create the forms in SendBeam.** Forms → New form. One Signup form (pick the list it feeds) and one Contact form (set the address messages go to). Copy each form ID from the form page.
2. **Put the IDs in your environment.** The snippets read them from environment variables so the same code serves every site. Form IDs are meant to be public; protection is on the form, not in the ID.
3. **Add the snippets and deploy.** Paste the components below, deploy to your host, then submit each form once with an address you control and check Contacts and your inbox.

## Code

### Global data

`src/_data/sendbeam.js`

```
// src/_data/sendbeam.js — read at build time, so the IDs are baked into the HTML.
export default function () {
  return {
    signupFormId: process.env.SENDBEAM_SIGNUP_FORM_ID || '',
    contactFormId: process.env.SENDBEAM_CONTACT_FORM_ID || '',
    turnstileSiteKey: process.env.TURNSTILE_SITE_KEY || '',
  };
}
```

### Includes

`src/_includes/*.njk`

```
{# src/_includes/newsletter-form.njk #}
<form data-sendbeam data-endpoint="https://sendbeam.io/api/forms/{{ sendbeam.signupFormId }}">
  <label for="nl-email">Email</label>
  <input id="nl-email" name="email" type="email" required autocomplete="email">
  <div style="position:absolute;left:-9999px" aria-hidden="true">
    <input name="YOUR_FORM_CHECK_FIELD" type="text" tabindex="-1" autocomplete="off">
  </div>
  <button type="submit">Subscribe</button>
</form>
<p data-status role="status" aria-live="polite" hidden></p>

{# src/_includes/contact-form.njk #}
<form data-sendbeam data-endpoint="https://sendbeam.io/api/forms/{{ sendbeam.contactFormId }}">
  <label for="c-name">Name</label>
  <input id="c-name" name="name" type="text" required>
  <label for="c-email">Email</label>
  <input id="c-email" name="email" type="email" required>
  <label for="c-message">Message</label>
  <textarea id="c-message" name="message" rows="5" required></textarea>
  <div style="position:absolute;left:-9999px" aria-hidden="true">
    <input name="YOUR_FORM_CHECK_FIELD" type="text" tabindex="-1" autocomplete="off">
  </div>
  {% if sendbeam.turnstileSiteKey %}
  <div class="cf-turnstile" data-sitekey="{{ sendbeam.turnstileSiteKey }}" data-size="flexible"></div>
  {% endif %}
  <button type="submit">Send message</button>
</form>
<p data-status role="status" aria-live="polite" hidden></p>

{# In your layout, before </body> (copy src/js/sendbeam-forms.js through with addPassthroughCopy): #}
<script src="/js/sendbeam-forms.js" defer></script>
{% if sendbeam.turnstileSiteKey %}<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>{% endif %}
```

Use {% include “newsletter-form.njk” %} wherever the form should appear. Liquid or WebC templates work the same way with their own syntax.

### The shared script

`src/js/sendbeam-forms.js`

```
// sendbeam-forms.js — posts any <form data-sendbeam> as JSON to SendBeam.
document.querySelectorAll('form[data-sendbeam]').forEach((form) => {
  const endpoint = form.dataset.endpoint; // https://sendbeam.io/api/forms/<form-id>
  const status = form.querySelector('[data-status]');
  const button = form.querySelector('button[type="submit"]');

  form.addEventListener('submit', async (event) => {
    event.preventDefault();
    const data = Object.fromEntries(new FormData(form).entries());
    const token = form.querySelector('[name="cf-turnstile-response"]');
    if (token) data.turnstile_token = token.value;

    button.disabled = true;
    try {
      const res = await fetch(endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      const json = await res.json();
      if (res.ok && json.success) {
        form.hidden = true;
        status.hidden = false;
        status.textContent = json.message;
        if (json.redirect_url) setTimeout(() => location.assign(json.redirect_url), 1500);
      } else {
        status.hidden = false;
        status.textContent = json.error || 'Something went wrong. Please try again.';
        if (window.turnstile) window.turnstile.reset();
      }
    } catch {
      status.hidden = false;
      status.textContent = 'Could not reach the server. Please try again.';
    } finally {
      button.disabled = false;
    }
  });
});
```

Register it with eleventyConfig.addPassthroughCopy({ “src/js”: “js” }).

## Things to know

- Set SENDBEAM_SIGNUP_FORM_ID and SENDBEAM_CONTACT_FORM_ID in your host’s build environment (Netlify: Site configuration → Environment variables). They are read once, during the build.
- Eleventy 3 uses ESM by default; for a CommonJS project write module.exports = function () { … } in the data file instead.
- The starter below is set up for Netlify with a netlify.toml that also sends a Content-Security-Policy header.

[← Eleventy email marketing integration](https://sendbeam.io/integrations/eleventy)

---
Source: https://sendbeam.io/docs/integrations/eleventy
