Freeframe

Forms and backends

Connect template forms to Formspree, Netlify Forms, a Next.js Route Handler, Resend or a WordPress form plugin.

Every form in every template validates in the browser: required fields, email formats, minimum lengths and template-specific rules, with an error message under each field and focus moved to the first problem. After a valid submit it shows a success message.

Out of the box, nothing is sent. The success message says so, and the submit code is marked with a comment such as Demo only: nothing is sent. Connect each form to a backend before you launch. The template's own Documentation page names the exact function to change.

Formspree (HTML or Next.js)#

Formspree receives form posts and emails them to you, with no server of your own.

  1. Create a form on Formspree and copy its endpoint, for example https://formspree.io/f/abcdwxyz.

  2. Give the form that action and make sure every field has a name:

    html
    <form action="https://formspree.io/f/abcdwxyz" method="POST" data-validate>
      <input name="email" type="email" required>
    </form>
  3. Replace the demo code in the success handler with a real request:

    js
    async function sendForm(form) {
      const response = await fetch(form.action, {
        method: "POST",
        body: new FormData(form),
        headers: { Accept: "application/json" },
      });
      if (!response.ok) throw new Error("Could not send the form");
    }

    Show the success message only after sendForm resolves, and show an error message if it throws.

Netlify Forms (HTML, hosted on Netlify)#

If you deploy to Netlify, add data-netlify="true" and a name to the form. Netlify detects it at deploy time and collects submissions in its dashboard:

html
<form name="contact" method="POST" data-netlify="true" data-validate>

Then post the form with fetch("/", { method: "POST", body: new FormData(form) }) from the success handler, including a hidden form-name field with the same value.

Next.js Route Handler or Server Action#

The Next.js templates use output: "export" so they build to static files, and static exports can't run server code. To handle forms on your own server:

  1. Remove output: "export" from next.config.ts.

  2. Add a Route Handler such as src/app/api/contact/route.ts:

    ts
    export async function POST(request: Request) {
      const data = await request.formData();
      const email = String(data.get("email") ?? "");
      if (!email.includes("@")) return Response.json({ error: "Invalid email" }, { status: 400 });
      // Save it, or send an email (see Resend below).
      return Response.json({ ok: true });
    }
  3. Post to /api/contact from the form's submit handler, and deploy to a host that runs Node.js, such as Vercel. A Server Action works too if you prefer calling a function directly from the form.

Always validate again on the server: browser validation is for the visitor's convenience, not security.

Sending email with Resend#

Resend sends email from your own domain. In a Route Handler or Server Action:

ts
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

await resend.emails.send({
  from: "Website <hello@your-domain.com>",
  to: "you@your-domain.com",
  replyTo: email,
  subject: "New message from the website",
  text: message,
});

Keep the API key in an environment variable on your host, never in browser code.

WordPress themes#

The WordPress themes keep the template's forms, with the same validation, in Custom HTML blocks. To receive submissions, either point the form at a service such as Formspree (the steps above apply unchanged), or replace the block with a form plugin such as Contact Form 7, WPForms or Jetpack Forms and style it with the theme's classes. The theme's Documentation page lists every form and the code that handles it.

Spam protection#

  • Add a hidden honeypot field and reject submissions that fill it in.
  • Formspree and Netlify Forms include spam filtering; turn on their reCAPTCHA or Turnstile options if needed.
  • On your own server, rate-limit by IP address and validate every field.

Search documentation

Search the guides and every template's documentation