Forma

Formspree forms without a plugin

Keep contact forms off the PHP path. Drop a Formspree endpoint into a Forma snippet and ship HTML — no mailer, no extra tables, no plugin.

Forma stores pages. It does not run a mailer. That is not a missing feature. It is the point.

A contact form that POSTs into PHP on cheap hosting is how you inherit SMTP failures, open relays, spam floods, and one more table to back up. Formspree keeps the form as HTML. Submissions leave the page and land in their inbox. Your SQLite file stays a CMS, not a ticket queue.

Why Formspree fits

Forma already wants three things from a page: HTML for structure, snippets for reuse, Twig only when data pays. A form is structure. It belongs in markup.

Formspree is an endpoint, not a theme, not a block library, not a WordPress plugin that rewrites wp_mail(). You set action and method="post". Named fields go through. That is the whole integration.

It also leaves the HTML cache alone. A cached page is a file. A form in that file is still a form. PHP does not need to wake up to accept a name and an email.

Spam filtering, autoresponders, and Slack or Sheets hooks live on their side. Your attack surface does not grow by one mail() call and a half-configured SMTP plugin.

Put the form in a snippet

Do not paste the same <form> into five pages. Make a snippet. Edit it once.

In Admin → Snippets, add something like contact-form. Shortcode: contact-form. Body:

<form action="https://formspree.io/f/YOUR_FORM_ID" method="POST">
  <label for="name">Name</label>
  <input id="name" name="name" type="text" required>

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

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="6" required></textarea>

  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">

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

Replace YOUR_FORM_ID with the id from the Formspree dashboard. Every input that should arrive needs a name. No name, no field.

The _gotcha input is a honeypot. Humans never see it. Bots often fill it. Formspree can drop those.

Drop the snippet on a page:

[[contact-form]]

Code fences keep that literal here. On a live page, write the shortcode so Forma expands it. If you need the characters themselves in prose, escape it as [[!contact-form]].

Style it with the same CSS already in [[site-head]]. You brought the design. The form inherits it.

Stay on the page

A plain POST sends the browser to Formspree's thank-you screen. Fine for a first pass. If you want the visitor to stay on your URL, submit with fetch and ask for JSON:

<form id="contact-form" action="https://formspree.io/f/YOUR_FORM_ID" method="POST">
  <!-- same fields -->
  <p id="contact-status" hidden></p>
  <button type="submit">Send</button>
</form>
<script>
  const form = document.getElementById('contact-form');
  const status = document.getElementById('contact-status');
  form.addEventListener('submit', async (event) => {
    event.preventDefault();
    const response = await fetch(form.action, {
      method: form.method,
      body: new FormData(form),
      headers: { Accept: 'application/json' }
    });
    status.hidden = false;
    if (response.ok) {
      status.textContent = 'Sent. Thanks.';
      form.reset();
    } else {
      status.textContent = 'Did not send. Try again.';
    }
  });
</script>

Accept: application/json matters. Without it, Formspree answers with HTML and you are back on their thank-you page.

Keep the script in the snippet if every form should behave the same. Do not invent a second copy on the contact page.

What not to add

Do not stand up PHP mail on the same box "just in case." Do not store submissions in SQLite unless you actually want a CRM. Do not paste a Formspree embed that brings someone else's CSS into site-head and then fight it for a week.

Create the form in Formspree. Confirm the notification email. Put the endpoint in one snippet. Hit the public page. Send a test. Check the inbox.

Fifteen minutes. Then leave the mail path off your server.