
A newsletter signup is one email field and one button. Drop it in with Gravity Forms and the page also loads jQuery, Gravity Forms’ front-end scripts and its theme CSS, all to collect a single address.
You don’t need any of that. Gravity Forms has a REST API with a public submissions endpoint, so a plain HTML form and a few lines of fetch can submit straight to it. The entry still lands in Gravity Forms, notifications still go out, and add-on feeds like Mailchimp still run. The page just stops loading jQuery for it.
What it does to your Lighthouse score
The savings show up in more than one metric:
- Total Blocking Time drops. jQuery and Gravity Forms’ scripts no longer download, parse and run on the main thread. A plain
fetchhandler only runs when someone clicks Subscribe. - First paint gets faster. Gravity Forms’ theme CSS loads in the
<head>and blocks rendering until it arrives. Without it, there’s one less render-blocking file, which helps First Contentful Paint and LCP. - The page gets lighter. Fewer requests and fewer kilobytes, on every page that shows the signup.
The form
Write the form yourself. The only thing it needs from Gravity Forms is the field name: each field is input_ plus its field ID, which you’ll find in the form editor. None of the hidden fields Gravity Forms prints (gform_ajax, state_1, gform_currency and the rest) are needed.
<form class="grind-form" action="/wp-json/gf/v2/forms/1/submissions" method="post" novalidate>
<label for="grind-email">Email</label>
<input id="grind-email" type="email" name="input_1" placeholder="youremail@website.com" autocomplete="email" required>
<button type="submit" class="btn btn-1"><span>Subscribe</span></button>
<p class="grind-msg" role="status" aria-live="polite"></p>
</form>
The 1 in the URL is the form ID. If WordPress isn’t installed at the domain root, print the URL from PHP instead: <?php echo esc_url( rest_url( 'gf/v2/forms/1/submissions' ) ); ?>.
The script
The script stops the normal submit, sends the email as JSON, and reads Gravity Forms’ answer. A successful submission comes back with is_valid: true. A failed one comes back with validation_messages, keyed by field ID, which are the same messages Gravity Forms shows on its own forms.
document.querySelectorAll('.grind-form').forEach(function (form) {
form.addEventListener('submit', async function (e) {
e.preventDefault();
var msg = form.querySelector('.grind-msg');
var btn = form.querySelector('button');
var email = form.querySelector('input[type="email"]');
msg.textContent = '';
if (!email.value || !email.checkValidity()) {
msg.textContent = 'Please enter a valid email address.';
email.setAttribute('aria-invalid', 'true');
email.focus();
return;
}
email.removeAttribute('aria-invalid');
btn.disabled = true;
try {
var res = await fetch(form.action, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input_1: email.value })
});
var json = await res.json();
if (json.is_valid) {
var done = document.createElement('p');
done.setAttribute('role', 'status');
done.textContent = "You're on the list. Your first recipe is on its way.";
form.replaceWith(done);
return;
}
var errors = Object.values(json.validation_messages || {});
msg.textContent = errors[0] || json.message || 'Please check your email and try again.';
} catch (err) {
msg.textContent = 'Something went wrong. Please try again.';
}
btn.disabled = false;
});
});
The json.message fallback matters. When something other than validation goes wrong, such as a blocked or missing route, the REST API puts the reason there, and without it every failure looks the same.
The gotcha: rest_no_route
The first test will probably fail. Log the response and you’ll see this:
{ "code": "rest_no_route", "message": "No route was found matching the URL and request method." }
It means WordPress’s REST API is fine, but the Gravity Forms route doesn’t exist. Gravity Forms only registers its gf/v2 routes when its REST API is switched on, and it’s off by default.
Go to Forms → Settings → REST API, check Enable access to the API and save. You don’t need an API key: public form submissions don’t require one. To confirm it worked, open /wp-json/gf/v2 in the browser. You should get JSON listing the routes.
What you keep, and what you give up
Gravity Forms still handles the submission on the server, so most of what it does keeps working. What goes is anything that depends on its front-end script or markup.
What still works
| Feature | Still works? | What to do |
|---|---|---|
| Entries | Yes | Nothing, they save under Forms → Entries as usual |
| Notifications | Yes | Nothing, admin alerts and welcome emails still go out |
| Add-on feeds | Yes | Nothing, Mailchimp and other feeds still run |
| Field validation | Yes | The script shows validation_messages |
| Confirmation message | No | Keep the script’s message in sync with it |
| Spam protection | No | Add a honeypot field or a server-side anti-spam check |
| Orbital theme styles | No | Style the form yourself |
For a one-field signup, that’s a good trade. For a long form with conditional logic or multiple pages, keep Gravity Forms’ scripts and load jQuery in the footer instead.
Before you ship
/wp-json/gf/v2returns JSON, so the REST API is on.- Each field’s
namematches itsinput_ID in the form editor. - A test submission shows up in Forms → Entries and its notification arrives.
- The script’s success message matches the Gravity Forms confirmation.
- A honeypot or server-side spam check is in place.
- The Network tab shows no jQuery or Gravity Forms files on the page.