FormsFort

Static Generators

FormsFort setup guide for Next.js, Astro, Nuxt, Hugo, Jekyll, Gatsby, Gridsome, and Eleventy.

Static Generators

Ship form handling without adding a backend. Static site generators work best with browser-first submissions. Backend rendering, server actions, and API routes are supported when a form has a paid/manual sender IP safelist.

Supported generators

Next.js

Prefer browser submissions for public forms. Server actions and route handlers need paid/manual server IP safelists.

Astro

Use static HTML forms for content pages, or client scripts for progressive success/error rendering.

Nuxt

Post from the client for public forms. Nitro server routes should be treated as server-side traffic.

Hugo

Drop the HTML form into a partial or shortcode and keep the hidden access key in the rendered page.

Jekyll

Use includes for shared contact forms and publish from GitHub Pages, Netlify, or another static host.

Gatsby

Submit from browser components with fetch or FormData so CORS and domain restrictions see the site origin.

Gridsome

Use a Vue component or static HTML block and post to the public endpoint from the browser.

Eleventy

Use Nunjucks/Liquid includes for reusable forms and test the generated HTML before enabling restrictions.

Reusable static form include

<form action="https://api.formsfort.dev/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
  <input type="hidden" name="subject" value="New static site submission" />
  <input name="name" required />
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <input type="checkbox" name="botcheck" style="display:none" />
  <button type="submit">Send</button>
</form>

Next.js client component

Keep public FormsFort submissions in a client component so domain restrictions, captcha checks, and browser-origin CORS work the same way they do for static HTML.

"use client";

import { useState } from "react";

export function ContactForm() {
  const [result, setResult] = useState("");

  async function handleSubmit(event) {
    event.preventDefault();
    const form = event.currentTarget;
    const formData = new FormData(form);

    setResult("Sending...");

    const response = await fetch("https://api.formsfort.dev/submit", {
      method: "POST",
      headers: {
        accept: "application/json",
        "content-type": "application/json",
      },
      body: JSON.stringify({
        access_key: "YOUR_ACCESS_KEY",
        name: formData.get("name"),
        email: formData.get("email"),
        message: formData.get("message"),
      }),
    });

    const body = await response.json();
    setResult(body.message || (response.ok ? "Submitted." : "Submission failed."));

    if (response.ok) {
      form.reset();
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" required />

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

      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required />

      <button type="submit">Submit Form</button>
      <p role="status">{result}</p>
    </form>
  );
}

Next.js file upload

Upload forms must send multipart FormData and skip the content-type header. FormsFort only accepts uploads when the form has the paid/manual upload entitlement and matching limits.

"use client";

import { useState } from "react";

export function UploadForm() {
  const [result, setResult] = useState("");

  async function handleSubmit(event) {
    event.preventDefault();
    const form = event.currentTarget;
    const formData = new FormData(form);

    formData.set("access_key", "YOUR_ACCESS_KEY");
    setResult("Uploading...");

    const response = await fetch("https://api.formsfort.dev/submit", {
      method: "POST",
      headers: { accept: "application/json" },
      body: formData,
    });

    const body = await response.json();
    setResult(body.message || (response.ok ? "Uploaded." : "Upload failed."));

    if (response.ok) {
      form.reset();
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" required />
      <input name="email" type="email" required />
      <input name="attachment" type="file" required />
      <button type="submit">Upload</button>
      <p role="status">{result}</p>
    </form>
  );
}

Astro inline validation

Astro can start with a normal static form and add progressive JavaScript for validation and inline API responses.

<form id="contact-form" class="needs-validation" novalidate>
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
  <input name="name" required />
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send Message</button>
  <p id="form-result" role="status"></p>
</form>

<script is:inline>
  document.addEventListener("astro:page-load", () => {
    const form = document.querySelector("#contact-form");
    const result = document.querySelector("#form-result");

    form.addEventListener("submit", async (event) => {
      event.preventDefault();
      form.classList.add("was-validated");

      if (!form.checkValidity()) {
        form.querySelector(":invalid")?.focus();
        return;
      }

      result.textContent = "Sending...";

      const response = await fetch("https://api.formsfort.dev/submit", {
        method: "POST",
        headers: {
          accept: "application/json",
          "content-type": "application/json",
        },
        body: JSON.stringify(Object.fromEntries(new FormData(form))),
      });

      const body = await response.json();
      result.textContent = body.message || (response.ok ? "Submitted." : "Submission failed.");

      if (response.ok) {
        form.reset();
        form.classList.remove("was-validated");
      }
    });
  });
</script>

Server-side submission checklist

  1. Use a paid/manual entitlement.
  2. Add the server egress IP to the form sender IP safelist.
  3. Keep access keys out of logs.
  4. Forward only expected user fields.
  5. Preserve X-Request-Id from the FormsFort response for support.

On this page