PortForm

A form backend for static websites

A static site is a set of files, so it has nothing running to receive a form submission. A form backend is the service the form posts to instead: PortForm stores the submission and emails it to you, and your site stays static.

What lives on your site, and what lives in PortForm.

The split is what makes a static site enough. Your files hold markup; everything that needs a running server is on the other side of the form’s action.

On your site

  • The HTML form: its action, the access key, your fields and the honeypot.
  • A thank-you page, as a static file, if you want one.

In PortForm

  • The endpoint that receives the request, and the checks that run on it.
  • Storage of every accepted submission, and the dashboard and exports.
  • The notification email, and any integrations you connect.

What a form backend is, in general, is covered on the form backend page, and how the endpoint takes a request on the form endpoint page.

No server, no custom backend, no JavaScript.

A browser sends a form’s data to the URL in action as an HTTP request, and whatever is at that URL handles it (MDN: sending form data). PortForm’s endpoint accepts a plain form post, and it also answers cross-origin fetch requests, so JavaScript is optional.

Any static site that can submit a standard HTML form to an external HTTPS endpoint can use this pattern. This page does not claim testing against any particular static site generator or host, because the pattern does not depend on one. The full form markup is on form to email and in the basic HTML template.

A thank-you page on a static site.

HTML form, then PortForm, then a redirect to a static page of yours. Add one hidden field to the form:

in your form
<input type="hidden" name="redirect" value="https://example.com/thanks/" />

On success PortForm answers 303 to that URL, and a plain form post follows it. The value must be a full http:// or https:// URL. A relative path such as /thanks/ is ignored, and the visitor sees the JSON response instead.

When the request is refused, the redirect goes to the same URL with ?error=1 added. That covers a wrong access key, an oversized payload, a failed CAPTCHA, a blocked domain, a passed submission limit and a server error, as long as PortForm could read the redirect field. A submission filtered as spam gets the normal success redirect on purpose. Your thank-you page can tell the two apart:

thanks/index.html
<!-- /thanks/index.html -->
<h1>Thanks, we got your message.</h1>
<p id="failed" hidden>Something went wrong. Please try again.</p>
<script>
  if (new URLSearchParams(location.search).has('error')) {
    document.getElementById('failed').hidden = false
  }
</script>

All the responses are listed in the docs.

The access key is in your page source.

That is by design. An access key is a public routing ID: it tells PortForm which form and destination a submission belongs to. It is not a secret and it does not authenticate anyone, so it being visible is not a security incident.

What limits misuse is elsewhere: a per-IP rate limit, your submission quota, spam filtering (a honeypot, payload checks and an optional CAPTCHA), and on Pro a domain restriction. That last one is a convenience, not authentication: browsers send an Origin or Referer header and it checks them, but a raw request without either passes. Someone who copies your key can still submit to your form, and accepted submissions count against your quota.

Built-in host forms or an external endpoint.

Some hosts handle forms themselves. Netlify Forms is one: Netlify’s build system detects forms in your HTML at deploy time, and a form needs a data-netlify attribute (Netlify docs: Forms setup). An external endpoint has no build step, because the form is only HTML pointing at a URL.

The PortForm vs Netlify Forms page compares them in detail, and the comparison hub covers the other form backends.

Limits.

  • 300 submissions every 30 days on the free plan, as a hard cap on a rolling window. Pro allows 10,000 and Agency & Team 20,000.
  • Past the cap, submissions are rejected with a 429 until the window rolls over. They are not silently dropped.
  • Submissions are kept for 30 days on every plan and can be exported as CSV or JSON.
  • Unlimited forms. The quota is per account, not per form.

Questions

Can a static website have a working contact form?
Yes. The page holds only the HTML form; the submission is posted to an external endpoint that receives, stores and delivers it. Any static site that can submit a standard HTML form to an external HTTPS endpoint can use this pattern, with no server of your own.
Why can’t a static site process a form itself?
A static host serves files. A form submission is an HTTP request that something has to receive and act on: read the data, store it, send an email. MDN describes that as the server’s job. A static site has no code running to do it, so the form posts to an external service instead.
Do I need a server to handle form submissions?
Not one you run. The form posts to PortForm’s endpoint, which receives the request, stores the submission and emails you. Your site stays static files. You still need a PortForm access key from the dashboard, and a verified address to receive the email.
Is it safe to put the access key in static HTML?
Yes, by design. The access key is a public routing ID, not a secret, and it does not authenticate anyone. Anyone who reads your page source can see it. What limits misuse is a per-IP rate limit, your submission quota, spam filtering and, on Pro, an optional domain restriction that is a convenience rather than authentication.
How do I show a thank-you page after a form submit?
Add a hidden redirect field holding the full URL of your thank-you page, including https://. On success PortForm answers with a 303 redirect to that URL. On a failure it redirects to the same URL with ?error=1 added. A relative path is ignored and the visitor sees a JSON response instead.
Do I need JavaScript for a static site form?
No. A plain form with method="POST" and the endpoint as its action works with JavaScript disabled, and the redirect field covers the thank-you page. You can use fetch instead if you want to submit without a page load: the endpoint sends Access-Control-Allow-Origin: * so a browser will let the page read the response.
What is the difference between Netlify Forms and an external form backend?
Netlify Forms is part of the Netlify platform: per Netlify’s docs, its build system detects forms in your HTML at deploy time, and a form needs a data-netlify attribute. An external form backend like PortForm needs no build step: any form posts to its endpoint with an access key. See the Netlify comparison for detail.

Keep the site static. Get the form working.

Create an access key, point your form at the endpoint, and submissions start arriving. 300 every 30 days, free, no card.

We use optional analytics cookies to understand how the site is used. Necessary cookies for signing in and checkout always load. See our privacy policy.