A Primer on Netlify Forms Pt. 1

We see a lot of questions on The Community about Netlify Forms. How to integrate it, why it’s not working, why it’s not at easy as it sounds like at the outset of marketing materials, and how to use it well are just a few sample topics. The reality is (in my opinion, unaffiliated with Netlify’s) that Netlify Forms was originally built to be a one-click solution for static sites. Like, really static sites. Like, you wrote the HTML yourself, there’s no javascript running on the front end, and CSS is a fun styling tool static. Like, Jekyll builds your site into images and HTML but the only javascript on the system is Google Analytics static. I don’t believe Netlify Forms was written for the modern JAMstack application. But that doesn’t mean it can’t be used for these applications - things like Gatsby, Nuxt, Gridsome, Next.js static, and even Webflow and its ilk.. it can. And it’s still wildly useful and powerful. Let’s talk about it.

How Netlify Forms Works

Any time any site is built and deployed on Netlify, the process loosely follows this outline:

  1. Run the Build Step for that site
  2. Run Build Plugins installed on that site
  3. Execute Netlify’s post-processing tooling
  4. Deploy all files to the Netlify APN

Let’s walk through that a little bit deeper. In Step 1, the build step for the site is run. This should always produce a heap of static files - nothing magical. Just some HTML, CSS, JS, and perhaps some images or other media. In Step 2, the Build Plugins for that site are run. These plugins essentially just have access to those produced files and can do what they’d like (make sure you trust your build plugins!) but nothing crazy here.

Netlify Forms take shape during Step 3. Netlify’s Forms framework scans all of the files produced by Step 1 to determine if there are any <form> elements in any HTML files that contain either the netlify or data-netlify="true" attributes per the docs. E.g. either:

<form name="contact" action="/somewhere" data-netlify="true">
<!-- or -->
<form name="contact" action="/somewhere" netlify>

If it finds any of those forms (including multiple within a single HTML file), it will go ahead and: A. Transform the HTML in place to prepare for browsers to render it as-is, and B. set up a Form Submission Listener for each particular form on your site.

A. Netlify Forms will transform your HTML in-place then deploy it to the APN as such. This is a simple transformation: it removes the netlify or data-netlify="true" attributes from the <form> element and it adds a hidden input immediately following the <form> element definition that looks like (for the form declaration above):

<input type="hidden" name="form-name" value="contact">

B. Netlify sets up a Form Submission Listener for your site. This might seem mysterious, but I promise it’s fairly simple. This Listener listens for any HTTP POST request to your site (on any path, even if there’s nothing there) that is sent with the header Content-Type: application/x-www-form-urlencoded and contains form-name=contact within the url-encoded body. That’s it. If those conditions are met, the Listener will attempt to parse out all of the named fields from your <form> into the Submissions interface of the Netlify Admin UI.

At face value, once a Form is registered on your site (that is, it’s found during post-processing / Step 3 and a listener is created), all you have to do to trigger a submission on that form is POST some data in the form-urlencoded format with the correct header and make sure that the form-name=<YOUR_FORM_NAME> field is present in the payload.

Now, that’s pretty clever. If you think about how a purely static site works, Netlify set up the ground work for ensuring those steps happen perfectly. Even with the simplest of <form>s:

<!-- Dev wrote this -->
<form name="contact" netlify>
  <input name="first-name" type="text"/>

Where Step 3 will transform the form to:

<!-- Netlify Forms transformed into this -->
  <input type="hidden" name="form-name" value="contact"/>
  <input name="first-name" type="text"/>

The beauty is in the default behavior of a browser for handling a very basic form like this. On the submit event the browser will POST the values to the current url, and automatically url-encode the values of the inputs within the form into the POST body. That exactly satisfies the requirements for the listener to register a new submission. 💥 Booyah.


The most notable gotcha I find at this point is folks omitting the name attribute from their <input>s. If your input doesn’t have a name, the listener can’t parse it out of the POST request (and the browser may not even send that data anyway).

This works:

<input type="text" name="favorite-place-to-eat">

This does not:

<input type="text" id="foo">

Okay? Great.

Unfortunately, smart / dynamic static generators tend to operate with a tad more complexity. To get those running correctly, we just need to adhere to what the Form Submission Listener expects.

Read Part 2 Here

Header Photo / Banner by: @dianamia

Join the Conversation

Latest Blog Posts