<- Back to blog
How-to10 min readUpdated May 1, 2026

How to Track Form Submissions and Lead Conversions

Step-by-step guide to tracking form submissions and lead conversions with privacy-friendly analytics. HTML snippet, JavaScript handler, validation, and FAQs.

track form submissionslead conversion trackingform analyticsnewsletter signup trackingcontact form analyticscookieless conversion tracking

TL;DR

  • 1.Form submissions are the highest-intent event on a marketing site — set up tracking before you set up anything else.
  • 2.Listen on the `submit` event, not the click of the submit button. Submit fires after validation and once per real submission.
  • 3.Send the form ID and the source page; never send the field values.
  • 4.For multi-step forms, fire a separate event per step so you can see where users drop.
  • 5.Pair client-side `form_submit` with a server-side `lead_created` event to filter out failed submissions and bot spam.

Why form tracking is the first event you should set up

On a marketing site, the form is where the value happens. A signup form, a newsletter form, a contact form, a demo-request form — every other metric on your dashboard is a leading indicator for that one moment. If you can only track one event, it should be form submissions.

Form tracking also has the cleanest payoff. Pageviews give you direction; form events give you a number you can put in a goal report and tie to revenue. "Visitors → form submits → customers" is the funnel everyone needs.

This guide sets up privacy-friendly form tracking that works without cookies, captures the right data, and avoids the four most common bugs (counting clicks instead of submits, double-firing on validation errors, leaking PII, and missing server failures).

Step 1: install Sleek

You need the script loaded on every page that has a form. The easiest way is to put it in your root layout or master template.

layout.html
<script async src="https://getsleek.io/v1.js" data-site="YOUR_SITE_KEY"></script>

Step 2: listen on `submit`, not on the button click

A common bug is to track the click on the submit button. That fires whether the form actually submits or not — including when client-side validation rejects the input. You end up over-counting and your conversion rate looks better than it is.

The `submit` event on the `<form>` element fires only after the form passes browser-level validation and is genuinely being submitted. Use it.

form-tracking.js
// Tag your form so the listener can identify it.
// <form id="newsletter-form" data-track="newsletter">…</form>

document.addEventListener('submit', function (event) {
  const form = event.target
  if (!form || !form.matches('form[data-track]')) return

  window.sleek('track', 'form_submit', {
    form_id: form.dataset.track,
    page: window.location.pathname,
  })
})

Step 3: never send the field values

It is tempting to include the email address or the message body in the event so you can "see" the leads in analytics. Do not do this. The right place for lead data is your CRM. Putting it in analytics violates the principle of data minimization, can violate GDPR, and creates an attack surface — analytics dashboards get shared liberally and that email list will end up somewhere it should not.

The fields you should send: `form_id`, `page`, `referrer_type`, and any non-PII categorical data like `plan_choice` for a pricing form or `topic` for a contact form. Everything else lives in your CRM.

warning:Treat your analytics dashboard as a public document. If you would not feel comfortable seeing a property show up in a screenshot in a Slack channel, do not send it.

Step 4: handle multi-step forms

For longer forms — onboarding flows, contact wizards, application forms — a single `form_submit` event hides the dropoff story. You want a `form_step` event for each completed step plus the final `form_submit` event when the form actually goes through.

In your dashboard, group `form_step` events by `step` and you have an instant funnel. The biggest dropoff is your refactor candidate.

multi-step.js
function trackStep(formId, stepName, stepIndex) {
  window.sleek('track', 'form_step', {
    form_id: formId,
    step: stepName,
    step_index: stepIndex,
  })
}

// Call trackStep() in your "Next" button handlers.
// Then send a final form_submit event when the form completes.

document.querySelector('#onboarding-form').addEventListener('submit', () => {
  window.sleek('track', 'form_submit', {
    form_id: 'onboarding',
    completed: true,
  })
})

Step 5: pair client and server events

A form_submit event fired from the browser tells you the user pressed submit. It does not tell you whether the form actually saved on your backend. Real-world failure modes — bot submissions caught by your spam filter, validation errors only your server can detect, payment forms that fail at the card processor — break the symmetry between "submit clicked" and "lead created".

The fix is two events. The client fires `form_submit` on the browser. The server fires `lead_created` after the form genuinely saved (or `payment_succeeded` for a checkout form). The gap between the two is your error rate.

server-side lead event
curl -X POST https://api.getsleek.io/v1/event \
  -H "Content-Type: application/json" \
  -d '{
    "site": "YOUR_SITE_KEY",
    "name": "lead_created",
    "url": "/contact",
    "props": {
      "form_id": "contact",
      "source": "server"
    }
  }'

Step 6: deal with thank-you-page redirects

A common pattern: the form submits, the page redirects to `/thank-you`, and you want to count the conversion. The `submit` listener fires before the redirect, so the event sends correctly — but you might already have analytics on the thank-you page treating its pageview as the conversion.

Pick one source of truth. If you use the thank-you page as your goal, you do not need a `form_submit` event at all — just create a goal in Sleek tied to `/thank-you` pageviews. If you want richer event properties (form_id, plan, etc.), keep the `form_submit` event and treat the thank-you pageview as a sanity check.

  • Goal-from-pageview: simpler, no JS required, but limited properties.
  • Goal-from-event: richer data (form_id, step, plan), works for AJAX forms with no redirect.
  • Both: redundant, but safe during a migration.

Step 7: filter bots and spam

Public contact forms get bot submissions. If you do not filter them, your conversion rate is inflated and your CRM fills with junk. Sleek's default bot filter catches obvious crawlers, but determined spam bots run real headless browsers and look like real users.

The cleanest defense is a server-side honeypot field plus the paired `lead_created` event from step 5. The browser fires `form_submit` for every submission including bots; the server fires `lead_created` only after honeypot + spam filter clears. Your conversion rate from `lead_created` is the real number.

Reading the data

In Sleek, set up a Goal for `form_submit` (or `lead_created` if you have the server-side version). The dashboard now shows conversion rate per source: organic search, direct, referrals, social. That single number is the answer to most "how is the website doing" questions.

For the funnel view, group `form_step` events by `step_index`. The biggest drop between steps is the most actionable refactor target. Pair this with the AI chat — "for visitors who reached step 3 but did not submit, what page did they come from?" — and you get a hypothesis to test in 30 seconds.

Frequently asked questions

Can I track form submissions without a cookie banner?

With Sleek, yes. The `form_submit` event in this guide does not include personal data and does not set cookies, so it is anonymous and falls outside the scope of cookie consent under GDPR and ePrivacy. With Google Analytics you cannot — GA4 sets cookies regardless of the event payload, so consent is required in the EU.

How do I track form submissions in a single-page app?

The `submit` event listener pattern works the same way in a SPA. Attach the listener once on `document` (delegated) and it will fire for every form submission across all client-side routes. If your forms use `event.preventDefault()` and submit via fetch, fire the `form_submit` event manually inside your submit handler instead of relying on the native event.

Should I track form views and form starts in addition to submits?

For high-value forms — pricing, demo, signup — yes. `form_view` (when the form scrolls into the viewport) and `form_start` (when the user focuses the first field) bracket the submission and reveal where the dropoff is. For low-value forms (footer newsletter), just track submits.

How do I track failed form submissions?

Listen for the response from your fetch/AJAX call and fire a `form_error` event with a coarse error category — `validation`, `network`, `server`. Do not include the validation message text if it might contain user input. For non-AJAX forms, the failure happens server-side and you fire a `form_error` from your backend instead.

What is the difference between a form_submit and a lead_created event?

form_submit means the browser fired the submission. lead_created means the server actually saved the record after spam filtering and validation. The gap between the two is your noise rate — failed validations, bot traffic, and timeouts. For real conversion rates, use lead_created.

Can I track form submissions from embedded forms (HubSpot, Typeform, ConvertKit)?

Most embed providers expose a postMessage or callback API for the submit event. Listen for it and fire a Sleek event from inside the callback. Some providers also support tracking pixels — but pixels limit you to a single event with no properties, so the postMessage route is usually better.

How do I A/B test form copy with this setup?

Add a `variant` property to the form_submit event (`variant: "A"` or `variant: "B"`) and split your conversion-rate report by it. Make sure your A/B testing tool assigns the variant before the form renders so the property is available when the user submits.

Track your own growth loop

Sleek Analytics gives you visitors, sources, pages, devices, and real-time behavior with one lightweight script. No cookies, no GDPR banners.

Related reading