A Primer on Netlify Forms Pt. 1
10 November 2020
Any time any site is built and deployed on Netlify, the process loosely follows this outline:
- Run the Build Step for that site
- Run Build Plugins installed on that site
- Execute Netlify's post-processing tooling
- 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
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
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
<!-- Dev wrote this --> <form name="contact" netlify> <input name="first-name" type="text"/> </form>
Where Step 3 will transform the form to:
<!-- Netlify Forms transformed into this --> <form> <input type="hidden" name="form-name" value="contact"/> <input name="first-name" type="text"/> </form>
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).
<input type="text" name="favorite-place-to-eat">
This does not:
<input type="text" id="foo">
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.