A Primer on Netlify Forms Pt. 2

Netlify Forms With Smart Static Site Generators

So, for starters, let me clarify what I mean by “smart” static site generators. I’m referring specifically to static site generators that generate static files which will fully render in a javascript-free browser and operate “purely statically” in that way, but which also build in a lot of plumbing and tooling to hydrate that site with a lot of runtime javascript in the browser once the page loads (if the browser doesn’t have javascript disabled). The biggest player in this space is Gatsby, but Gridsome, Nuxt (I think), Next.JS (in some cases), Webflow (in some cases), Sapper, etc, all operate similarly.

I call all of these static site generators “smart” because.. they are. It’s a great idea - to be able to have a single code-base or component tree that can render down to a purely static site (if the client loading the published files doesn’t have javascript enabled) but which also comes out-of-the-box with extremely complex tooling for loading the first page then running the rest of the site with Javascript thereafter. That’s why Gatsby sites feel so fast and load in place without the browser even flashing (typically). The idea is clever and executed well, but it doesn’t always play very nicely with Netlify Forms.


When you build a Gatsby site, it produces a batch of plain static files that ultimately get sent up to Netlify’s APN. If you have a form in your Gatsby site and added data-netlify="true" to it, that static file will have been transformed after Gatsby was done building but before it gets to the APN or runs in anyone’s browser. Let’s walk that out.

We can use my personal website, jonsully.net, as an example of this workflow. It’s a Gatsby site and the code in my Contact Form component contains the following <form> declaration:

<form data-netlify="true" name="contact" action="/thanks">

So when I run gatsby build and inspect the resulting plain HTML files, /contact/index.html contains the following snippet:

<form data-netlify="true" name="contact" action="/thanks">

It’s the exact same. And that’s what we should expect - Gatsby doesn’t know anything about Netlify; to Gatsby those are just some random attributes on an otherwise normal <form> element. It doesn’t do anything with them.

But let’s recall that in Step 3 of the Netlify build, this <form> will get transformed. The easiest way to see what that looks like after-the-fact is to request the URL your form is on from the command line. That will give you the raw, static contents of your page without the browser getting in the way or doing any magic faster than we can catch! So let’s run: http https://jonsully.net/contact (I use httpie as my command line web request tool - curl works just as well).

The result is a huge blob of HTML 😜 but if you search for the form inside, we can see that the form is now:

<form name='contact' action='/thanks' method='post'>
<input type='hidden' name='form-name' value='contact' />

Which, if you’re following from Part 1, means that Netlify indeed transformed the HTML and injected a hidden input preparing for default browser form submission process. Nice! So Netlify’s working great and presumably set up a Form Submission Listener for this particular form on my site too!

Here’s where the two problems of these smarter SSG’s come into play.

  1. These smart SSG’s generally don’t just allow the browser to handle the form and submit it with default process. They prefer to handle form submissions (and often even form inputs and value) in Javascript
  2. These smart SSG’s overwrite the static content sent from Netlify and revert it back to your original code - which didn’t have the hidden input for form-name

Let’s start with the latter. When I load my contact page in the browser (either by navigating directly to it, or by navigating to my home page then clicking “Contact”) and inspect the DOM for my <form>, I find that the form reverted to its original declaration and doesn’t have the hidden in put immediately after it. It reads:

<form data-netlify="true" name="contact" action="/thanks">

How’s that possible? We already verified that when getting the page content directly from CLI that it had the correct post-processing transformation done! It’s possible because these smart SSG’s constantly re-render the page and change the page contents to match the original code you wrote. They weren’t aware that Netlify was trying to change your HTML between when it built and when it runs in the browser - and they don’t care. Every time they re-render with Javascript, they re-render the original code you wrote.

But what’s the side-effect? Well, the hidden input with the form-name is no longer present. That means that even if you did submit the form, that submission would no longer meet the spec requirement of the Form Submission Listener and would never get recorded! We see this a lot - “I submit my form and nothing happens it just goes nowhere”. It’s getting submitted to Netlify, that’s true. There just aren’t any Form Submission Listeners on your site listening for the submission - all of the listeners on your site are seeking out a particular form name and your submission now has none. Uh oh!

Now, back to the two problems above, let’s talk about the first. Most of these smart SSG’s handle form submission via custom javascript rather than just letting the browser execute default form behavior. While that’s not technically any real issue, we need to be careful to make sure that the submission request is formatted and executed correct.

  1. It needs to be an HTTP POST request
  2. It needs to have the correct content type header
  3. It needs to have a payload that is formatted to the url-encoded spec
  4. It needs to contain the form-name within the payload

If you get those four bits correct, the Form Submission Listener will catch your submission and log all of the named input fields it saw during post-processing (meaning that if you only had one input with name="address" during post-processing but then during run-time attempt to also send last-name=xyz, it would not log the last-name field).

Wow. Okay So Now What.

Great question! It’s up to you!

If you’re reading this because you’re having issues getting your own form working, walk through the steps I did in this post with your own site and see where the breakage is. Make sure that the form-name is getting submitted in the POST request. Make sure that it’s formatted as url-encoded (with the Header) and not JSON.

If you’re using React and/or Gatsby, consider checking out a package I made that should make everything just work. https://github.com/jon-sully/react-ssg-netlify-forms

If none of that works, try running your POST request directly from command line and see if you can get a submission into Netlify that way (quick tip: httpie has the -f flag that does all the form prep for you; so you can just run http -f https://jonsully.net form-name=contact name=fred and it’ll work like a charm).

Beyond that, come join us on Netlify Community.

Header Photo / Banner by: @dianamia

Join the Conversation

Latest Blog Posts