Skip to content
← All use cases
Use case · Static site forms

Contact forms for static sites, no functions required

A static site has no server to receive a form, and it does not need one. The visitor's browser posts straight to Formward, which filters spam, stores the message in the EU, emails you, and sends the visitor back to your thank-you page. Paste this into any template:

<form action="https://forms.formward.eu/f/YOUR_FORM_ID" method="POST">
  <input type="hidden" name="_redirect" value="/thanks/">
  <input type="text" name="name" required>
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
         style="position:absolute;left:-9999px;opacity:0">
  <button type="submit">Send</button>
</form>

No serverless function, no build step, no third-party script. The same markup works on GitHub Pages, Cloudflare Pages, an S3 bucket or your own nginx, and moving hosts does not touch the form.

Per-generator notes

The form is identical everywhere. Only where you keep it changes.

HugoKeep it as a shortcode in layouts/shortcodes/contact-form.html and read the endpoint from your site config.Hugo guide
JekyllAdd it to contact.html or a layout, with the endpoint stored in _config.yml.Jekyll guide
EleventyAdd it to a page template such as src/contact.njk. Eleventy passes the markup through unchanged.Eleventy guide
AstroPaste it into a .astro component. It needs no client-side JavaScript.Astro example
GatsbyRender it from a React component and submit with fetch() so the visitor stays on the page.Gatsby guide

Moving off Netlify Forms

Netlify detects forms at deploy time and ties them to the site. Switching is a small markup change: drop the Netlify attributes and the hidden form-name input, add the action, and rename the honeypot to _gotcha.

<!-- Before: Netlify Forms -->
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
  <input type="hidden" name="form-name" value="contact">
  <input name="bot-field" hidden>
  ...
</form>

<!-- After: Formward -->
<form action="https://forms.formward.eu/f/YOUR_FORM_ID" method="POST">
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
         style="position:absolute;left:-9999px;opacity:0">
  ...
</form>

To bring old submissions with you, export them from Netlify as CSV and import the file into the Formward form. The original created_at dates are kept.

Redirects and CORS

  • A normal POST gets a 302 redirect. The target is the _redirect field if it is a relative path or an allowed origin, then the redirect set in the dashboard, then Formward's default thank-you page.
  • A relative _redirect such as /thanks/ resolves against the site that sent the form, so one snippet works on staging and production.
  • With fetch() and Accept: application/json you get JSON instead of a redirect. Cross-origin requests are checked against the form's allowed origins, so add your domain there before going live.

The AJAX docs have a complete fetch() example.

Compared with the other options

  • A serverless function works, but you now maintain code, secrets and an email provider account for something an action attribute can do.
  • Host-bundled forms tie the form to one host and its pricing; leaving the host means redoing the form.
  • Another form backend plugs in the same way, through the action URL. If you are weighing Formspark, the Formspark comparison covers pricing model, hosting and uploads.
  • A mailto: link relies on every visitor having a mail app set up and gives you no record.

Where the data lives

Formward stores submissions, file attachments and backups in Sweden, on servers run by Hostup AB. Notification email goes out through Mailjet from an EU data centre. Submitter IP addresses are replaced with a keyed HMAC-SHA-256 hash on receipt, so the raw address is never written to disk. On Professional and Business, AI features run on Mistral AI in France and can be switched off per form.

Data leaves the EU only where you send it: a notification inbox hosted outside the EU, a webhook URL, a Slack channel or a Google Sheet. Cloudflare Turnstile is a US service, and it is off unless you switch it on for a form. The details are in the sub-processor register, the data residency page and the published DPA. For the wider picture, see GDPR form backend.

Questions

Does the form need JavaScript?
No. It is a standard HTML POST followed by a redirect. Add fetch() later if you want an in-page success message.
Will it break if I change hosts?
No. The form posts to Formward, not to your host, so moving from Netlify to GitHub Pages or anywhere else changes nothing.
Do I have to rebuild the site when settings change?
No. Recipients, spam settings, the default redirect and retention live in the Formward dashboard. Only the markup is in your repository.
What does it cost for a small site?
The Free plan covers 50 submissions a month, which is plenty for most personal and small business sites. Free submissions are deleted after 30 days.

Keep the site static. Add the form.

Start free with 50 submissions a month. No card needed.

Contact forms for static sites, no functions | Formward