FreeGoogle Tag Manager recipes

Track Fluent Forms Submissions in Google Tag Manager with this Free GTM Recipe

A free, tested Google Tag Manager recipe that captures Fluent Forms submissions and gives you a Trigger you can use to fire conversions to Google Ads, Google Analytics and more.

Aaron Beashel
Aaron BeashelUpdated July 20267 min read
The Fluent Forms recipe tag inside Google Tag Manager

Google Tag Manager is a common way to send conversions to Google Ads, Google Analytics, etc when someone submits a Fluent Forms form on your website.

The problem is that it's quite difficult to set up because Fluent Forms uses AJAX to save form submissions, which means Google Tag Manager's built-in form submission trigger doesn't work.

This means you ultimately have to write custom code to detect the form submission, send an event to the dataLayer, and build a trigger on top of that. And unless you're a developer or very experienced with Google Tag Manager, you're going to struggle.

Fortunately though, there is a simple solution. You can just install this Fluent Forms Conversion Tracking Recipe in your Google Tag Manager account and you'll have everything you need to get basic conversion tracking set up.

What Is a Google Tag Manager Recipe and What's in It?

A recipe is a pre-built bundle of Google Tag Manager assets (a tag, a trigger and a variable) that you import in one go instead of building each piece yourself. You can see all our recipes and how they work in the GTM recipe library.

This recipe detects successful Fluent Forms submissions, sends a 'Form Submitted' event to Google Tag Manager, and gives you a Trigger you can use to send conversions to Google Analytics, Google Ads, etc. There are three parts to it, and nothing inside is hidden. Here's exactly what each piece contains, so you can see what you're importing before you import it.

Custom HTML Tag

Listens for successful Fluent Forms submissions and pushes a 'fluent_forms_submitted' event to the dataLayer. It fires on All Pages, once per event, and this is the code inside it.

The Tag's CodeJavaScript · 140 lines
(function () {
  if (window.__converlyRecipeFluentForms) return;
  window.__converlyRecipeFluentForms = true;
  window.dataLayer = window.dataLayer || [];

  function isFluentForm(formEl) {
    if (!formEl) return false;
    if (formEl.className && formEl.className.indexOf('frm-fluent-form') !== -1) return true;
    try {
      if (formEl.closest && formEl.closest('.fluentform')) return true;
    } catch (e) {
      // .closest not available — fall back to ID check
    }
    var id = formEl.id || '';
    return /^fluentform_\d+$/.test(id);
  }

  // Accepts a jQuery object or a DOM element; returns the underlying DOM
  // element if it is a Fluent Forms <form>, else null.
  function asFluentForm(candidate) {
    if (!candidate) return null;
    var el = candidate;
    if (el.jquery && el.length) el = el[0]; // unwrap jQuery objects only —
    // never plain elements: form[0] on a raw <form> is its first control.
    if ((el.tagName || '').toLowerCase() === 'form' && isFluentForm(el)) return el;
    return null;
  }

  // Click-tracking fallback for form attribution: remember the Fluent form
  // whose submit button was most recently clicked, in case an old Fluent
  // Forms version fires the success event without a form reference.
  var lastActiveForm = null;

  function trackSubmitClick(event) {
    try {
      var target = event.target;
      if (!target || !target.closest) return;
      var btn = target.closest('.ff-btn-submit');
      if (!btn) return;
      var form = btn.closest('form');
      if (form && isFluentForm(form)) lastActiveForm = form;
    } catch (e) {
      // non-fatal
    }
  }

  // Resolution order (first valid Fluent form wins) — live-verified against
  // Fluent Forms 6.2.5, 2026-07-05:
  //   1. data.form from the handler's data argument — the <body> dispatch
  //      passes { form, config, response } where form is the jQuery-wrapped
  //      form (also accept data itself being the form, defensively)
  //   2. event.originalEvent.detail.form — the native CustomEvent dispatch
  //      carries the raw form element in detail
  //   3. event.target, if it IS a Fluent form (direct-on-form re-trigger)
  //   4. the form whose submit button was most recently clicked
  //   5. the only .frm-fluent-form on the page, if exactly one exists
  function resolveSuccessForm(event, data) {
    var el = asFluentForm(data && data.form) || asFluentForm(data);
    if (el) return el;

    try {
      var oe = event && event.originalEvent;
      if (oe && oe.detail) {
        el = asFluentForm(oe.detail.form);
        if (el) return el;
      }
    } catch (e) {
      // originalEvent/detail unreadable — keep falling back
    }

    el = asFluentForm(event && event.target);
    if (el) return el;

    if (lastActiveForm && document.contains(lastActiveForm) && isFluentForm(lastActiveForm)) {
      return lastActiveForm;
    }

    try {
      var forms = document.querySelectorAll('.frm-fluent-form');
      if (forms.length === 1) return forms[0];
    } catch (e) {
      // querySelectorAll unavailable — give up
    }

    return null;
  }

  // Dedup: one successful submission reaches a document-level jQuery
  // listener TWICE on current Fluent Forms (the jQuery <body> trigger and
  // the native CustomEvent) — verified live. Both duplicates resolve to the
  // same form within the same tick, so fire once per form per 2s window
  // (same window the production Converly module uses). Rapid legitimate
  // re-submissions of the same form take well over 2s (server round trip +
  // success render), so none are lost.
  var lastFiredForm = null;
  var lastFiredAt = 0;

  function fireConversion(formEl) {
    var now = new Date().getTime();
    if (lastFiredForm === formEl && now - lastFiredAt < 2000) return;
    lastFiredForm = formEl;
    lastFiredAt = now;
    window.dataLayer.push({
      event: 'fluent_forms_submitted',
      form_id: (formEl && formEl.id) || ''
    });
  }

  // Fire path — Fluent Forms' success signal, fired once an AJAX submission
  // passes server-side validation. This is the ONLY path this recipe
  // tracks; Fluent Forms always submits via AJAX (there is no non-AJAX
  // mode to miss). Client-side validation failures, native HTML5 blocks,
  // and server-side rejections never fire it. Hooked via jQuery because
  // the <body> dispatch is jQuery-only AND jQuery is guaranteed on any
  // working Fluent Forms page (Fluent Forms' own submission handler
  // requires it — without jQuery the form cannot submit at all).
  var successHooked = false;

  function hookFluentSuccess() {
    if (successHooked) return true;
    if (typeof window.jQuery === 'undefined') return false;

    window.jQuery(document).on('fluentform_submission_success', function (event, data) {
      fireConversion(resolveSuccessForm(event, data));
    });
    successHooked = true;
    return true;
  }

  document.addEventListener('click', trackSubmitClick, true);

  if (!hookFluentSuccess()) {
    document.addEventListener('DOMContentLoaded', function () {
      hookFluentSuccess();
    });
    window.addEventListener('load', function () {
      hookFluentSuccess();
    });
  }
})();

Variable

Captures the ID of the form that was submitted, ready to reference in any tag. These are its exact settings.

Name

DLV - Fluent Form ID

Variable Type

Data Layer Variable

Data Layer Variable Name

form_id

Trigger

Fires when the 'fluent_forms_submitted' event is received. Use it as the trigger for any conversion tag. These are its exact settings.

Name

CE - Fluent Forms Submitted

Trigger Type

Custom Event

Event Name

fluent_forms_submitted

Download This Recipe

Get the ready-to-import file and upload it into Google Tag Manager. Everything shown above (the tag, the variable and the trigger) comes pre-built inside.

Built from Converly's own Fluent Forms detection code

Why It's the Best Recipe Out There

It is built from the same detection code we use in our own conversion tracking product, Converly, just adapted to run inside Google Tag Manager.

Only fires on Fluent Forms' own success event, which the platform dispatches after the submission passes validation on the server. Failed submissions are never counted as conversions.
Works out which form actually submitted. Fluent Forms' success event doesn't say which form fired, so on a page with 2 forms most scripts just guess. This recipe ports the multi-step form resolution logic from our production code to report the right form ID.
It's been tested extensively on our dedicated testing infrastructure, and has been used to detect thousands of Fluent Forms submissions by our customers.

How to Use the Fluent Forms GTM Recipe

1

Install the Recipe

Download the recipe using the form above, then import it into your Google Tag Manager container. In GTM, head to Admin, choose Import Container, select the downloaded file, and pick the Merge option so it doesn't overwrite anything already in your container. The listener, variable and trigger are added for you automatically.

Importing the recipe container in Google Tag Manager
2

Configure Your Tags

Now that the recipe is installed, you can use it as a Trigger for your conversion tags. Here are two common examples.

Example 1: Google Analytics

Create a GA4 event tag and set the Trigger to be the one the recipe gives (called CE - Fluent Forms Submitted). Every Fluent Forms submission now sends an event to Google Analytics.

A GA4 event tag using the recipe's trigger

Example 2: Google Ads

Add your Google Ads conversion tag and set it on the Trigger the recipe gives you. Fluent Forms submissions will now be sent to Google Ads as conversions.

A Google Ads conversion tag using the recipe's trigger

What This Enables You to Do (and What It Doesn't)

This recipe fires an event in Google Tag Manager whenever a successful Fluent Forms submission is detected. You can use that as a trigger to send conversions to Google Ads, Google Analytics and other tags.

What it does

Sends an Event to GTM

Detects successful Fluent Forms submissions and pushes an event called 'fluent_forms_submitted' into your dataLayer automatically.

Tells You Which Form Fired

Captures the ID of the form that was submitted, so a site with several forms can fire different tags for different forms.

Creates a Trigger in GTM

Gives you a 'Trigger' in Google Tag Manager which you can use as the trigger for any of your conversion tags.

What it doesn't do

Doesn't send the conversion server-side

Everything happens in the visitor's browser, which means ad blockers and browser privacy restrictions can interfere. Expect to lose 30+% of your conversions this way.

No Click IDs or Identifiers

It does not capture the Google click ID, Facebook click ID, Meta pixel cookies, IP address, user agent, etc. You can expect to have a relatively low match rate using this approach (i.e. even though you're sending a conversion event, because the ad platform can't match it back to the real person that clicked your ad, it will basically just be discarded).

No Personal Details

The script only fires an event. It does not capture the lead's name, email or phone number, all of which ad platforms like Google and Meta require for Enhanced Conversions (Google) or a high match rate score (Meta & others).

No Logging or Notifications

You can test it with Tag Assistant when you first set it up, but unless you check every week, you have no way of knowing if it breaks.

The Proper Way to Send Conversions to Google Ads, Meta Ads & More

This recipe is fine if you only send events to Google Analytics, which does not accept personal information or IP addresses. But to get a good Event Match Quality (EMQ) score in Meta, or use Enhanced Conversions in Google, you need to send the data server-side, together with the lead's name and email. That is what Converly does.

A Converly workflow sending Fluent Forms conversions to ad platforms

Here's why Converly is the best way to send conversions to Google Ads, Meta Ads, Microsoft Ads, LinkedIn Ads, etc.

Easy to set up

Converly gives you a simple drag-and-drop builder you can use to build a Conversion Flow. You simply select a trigger (like a Fluent form being submitted) and then select your actions (like sending a conversion to Google Ads). No custom code, no complex configuration.

Sends Data Server-Side

Instead of sending conversions through the visitor's browser (where ad blockers and privacy restrictions get in the way), Converly sends conversions directly from its servers to Google, Meta, LinkedIn, etc. You can expect 99+% of your conversions to arrive this way.

Sends More Data

It captures the lead's name, email and phone plus the Google Click ID, Facebook click IDs, IP address, user agent, etc. and sends them with every conversion. That enables Enhanced Conversions in Google Ads, or gives you an Event Match Quality (EMQ) score of 9 or 10 in Meta.

Full Conversion Log & Email Notifications

See every conversion: what was captured, what was sent to each platform, and whether it was successfully received or not. Set up email alerts when things go wrong so your tracking can't break without you knowing.

Supports Multiple Tools & Destinations

Detects submissions across 100+ forms, scheduling, and chat tools and sends them to 20+ destinations, including Google Ads, Meta, ChatGPT, LinkedIn, Microsoft, and TikTok. So if you ever add a new tool or start advertising on a new platform, it's just a few clicks to set up conversion tracking for it.

Provides Free Support

Our team has 20 years of experience in conversion tracking and analytics and is here to help whenever you need it. With Google Tag Manager, you're on your own.

Start Your Free Trial

Start a 14-day free trial and track your conversions the proper way. No code, no complicated configuration.

About the author

Aaron Beashel
Aaron Beashel

Aaron is the founder of Converly. With over 15 years of experience in digital marketing and SaaS, he's passionate about helping businesses track and optimise their ad conversions.

Start your free trial

Easily send conversions to your ad platforms and analytics tools.
No code required.