Plugin Directory

Changeset 3753857


Ignore:
Timestamp:
10/19/2026 03:06:05 AM (2 weeks ago)
Author:
jaffray
Message:

readme: one line per paragraph and list item so the listing stops breaking lines mid-sentence

Location:
choice-universal-form-tracker
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • choice-universal-form-tracker/tags/3.30.1/readme.txt

    r3752395 r3753857  
    1313== Description ==
    1414
    15 Five form plugins, one event shape. Each form plugin fires its submissions
    16 differently, so a site running more than one ends up with a different tag, a
    17 different trigger, and a different set of field names for each. This plugin
    18 detects whichever form plugins are active and emits a single, consistent
    19 `form_submit` event for all of them, so one tag in Google Tag Manager covers
    20 the whole site.
    21 
    22 Campaign attribution rides along. UTM parameters and advertising click IDs are
    23 captured on the visitor's first landing and stored for 30 days, so a form
    24 submitted a week after the ad click still reports the campaign that produced
    25 it.
     15Five form plugins, one event shape. Each form plugin fires its submissions differently, so a site running more than one ends up with a different tag, a different trigger, and a different set of field names for each. This plugin detects whichever form plugins are active and emits a single, consistent `form_submit` event for all of them, so one tag in Google Tag Manager covers the whole site.
     16
     17Campaign attribution rides along. UTM parameters and advertising click IDs are captured on the visitor's first landing and stored for 30 days, so a form submitted a week after the ad click still reports the campaign that produced it.
    2618
    2719= Supported form plugins =
    2820
    29 Detection is automatic. No configuration is needed beyond activating the
    30 plugin, and nothing is loaded for a form plugin that is not installed.
     21Detection is automatic. No configuration is needed beyond activating the plugin, and nothing is loaded for a form plugin that is not installed.
    3122
    3223* Contact Form 7
    … …  
    3627* Avada / Fusion Builder forms
    3728
    38 These are third-party products, listed here to describe compatibility. This
    39 plugin is not affiliated with, endorsed by, or supported by their makers.
     29These are third-party products, listed here to describe compatibility. This plugin is not affiliated with, endorsed by, or supported by their makers.
    4030
    4131= What gets tracked =
    4232
    43 * `form_submit` on every detected submission, carrying the form type, form ID,
    44   and the email address and phone number the visitor entered
    45 * `generate_lead` when a submission includes a valid email address and the
    46   Generate Lead Events setting is on (off by default on new installs)
    47 * `qualify_lead` when a submission carries an email address, a phone number,
    48   and an advertising click ID
    49 * `phone_click` when a visitor clicks a `tel:` link, and `email_click` for a
    50   `mailto:` link, except links and numbers you exclude (see the FAQ)
     33* `form_submit` on every detected submission, carrying the form type, form ID, and the email address and phone number the visitor entered
     34* `generate_lead` when a submission includes a valid email address and the Generate Lead Events setting is on (off by default on new installs)
     35* `qualify_lead` when a submission carries an email address, a phone number, and an advertising click ID
     36* `phone_click` when a visitor clicks a `tel:` link, and `email_click` for a `mailto:` link, except links and numbers you exclude (see the FAQ)
    5137* UTM parameters (source, medium, campaign, term, content) on all of the above
    52 * Advertising click IDs: `gclid`, `gbraid`, `wbraid`, `fbclid`, `msclkid`,
    53   `ttclid`, `twclid`, `rdt_cid`, `li_fat_id`, `snap_click_id`, and `pclid`
    54 
    55 Every event carries `cuft_tracked: true`, which gives you one reliable
    56 condition to build triggers on.
     38* Advertising click IDs: `gclid`, `gbraid`, `wbraid`, `fbclid`, `msclkid`, `ttclid`, `twclid`, `rdt_cid`, `li_fat_id`, `snap_click_id`, and `pclid`
     39
     40Every event carries `cuft_tracked: true`, which gives you one reliable condition to build triggers on.
    5741
    5842= Send it to GA4, Google Ads and Meta =
    5943
    60 One trigger in Google Tag Manager covers every form on the site, and a form
    61 plugin added later needs no new tag:
    62 
    63 1. Add Data Layer Variables for `cuft_tracked` and for the fields you need,
    64    such as `form_type`, `form_id`, `utm_campaign`, `gclid` and `lead_id`.
    65 2. Create a Custom Event trigger for `form_submit` with the condition
    66    `cuft_tracked` equals `true`.
    67 3. Fire your GA4 event tag, Google Ads conversion tag or Meta Pixel `Lead`
    68    event on that trigger. For Google Ads enhanced conversions, map
    69    `user_email` into a User-Provided Data variable. `user_phone` holds the
    70    number as the visitor typed it, so convert it to E.164 form (such as
    71    `+17805550173`) before mapping it. For GA4, send `lead_id` and leave the
    72    contact details out.
     44One trigger in Google Tag Manager covers every form on the site, and a form plugin added later needs no new tag:
     45
     461. Add Data Layer Variables for `cuft_tracked` and for the fields you need, such as `form_type`, `form_id`, `utm_campaign`, `gclid` and `lead_id`.
     472. Create a Custom Event trigger for `form_submit` with the condition `cuft_tracked` equals `true`.
     483. Fire your GA4 event tag, Google Ads conversion tag or Meta Pixel `Lead` event on that trigger. For Google Ads enhanced conversions, map `user_email` into a User-Provided Data variable. `user_phone` holds the number as the visitor typed it, so convert it to E.164 form (such as `+17805550173`) before mapping it. For GA4, send `lead_id` and leave the contact details out.
    7349
    7450= Shared lead identity =
    7551
    76 Each event can carry a `lead_id`: the SHA-256 hash of the visitor's email
    77 address, lowercased and trimmed, or of their phone number in E.164 form when
    78 no email was given. The same hash is computed server-side, so an analytics
    79 event and a downstream CRM record derived from the same person produce the
    80 same identifier.
    81 
    82 The `form_submit` event also carries the raw email address and phone number.
    83 Send `lead_id` instead of those fields wherever raw personal data must not
    84 go; Google Analytics, for one, forbids sending it email addresses or phone
    85 numbers.
    86 
    87 The normalisation matches what Google Enhanced Conversions and Meta CAPI
    88 expect, so the value doubles as a match key for offline conversion imports.
     52Each event can carry a `lead_id`: the SHA-256 hash of the visitor's email address, lowercased and trimmed, or of their phone number in E.164 form when no email was given. The same hash is computed server-side, so an analytics event and a downstream CRM record derived from the same person produce the same identifier.
     53
     54The `form_submit` event also carries the raw email address and phone number. Send `lead_id` instead of those fields wherever raw personal data must not go; Google Analytics, for one, forbids sending it email addresses or phone numbers.
     55
     56The normalisation matches what Google Enhanced Conversions and Meta CAPI expect, so the value doubles as a match key for offline conversion imports.
    8957
    9058= Built to stay out of the way =
    … …  
    9765= Who maintains this =
    9866
    99 [Choice OMG](https://choice.marketing/) maintains this plugin. We are a
    100 digital marketing and web agency in Edmonton, Alberta, and we built it because
    101 our clients' sites run several form plugins at once and each one needed its own
    102 analytics plumbing. The plugin's overview page is
    103 [Choice Universal Form Tracker for WordPress](https://choice.marketing/tools/choice-uft/).
    104 If you would rather have GTM, GA4 and Google Ads conversion tracking set up for
    105 you, [Choice OMG does that for clients](https://choice.marketing/services/conversion-optimization/).
     67[Choice OMG](https://choice.marketing/) maintains this plugin. We are a digital marketing and web agency in Edmonton, Alberta, and we built it because our clients' sites run several form plugins at once and each one needed its own analytics plumbing. The plugin's overview page is [Choice Universal Form Tracker for WordPress](https://choice.marketing/tools/choice-uft/). If you would rather have GTM, GA4 and Google Ads conversion tracking set up for you, [Choice OMG does that for clients](https://choice.marketing/services/conversion-optimization/).
    10668
    10769== External services ==
    10870
    109 This plugin can connect to the external services below. Every one of them is
    110 off on a new install and stays off until an administrator configures it on
    111 the settings screen. With none configured, the plugin only pushes events to
    112 the `window.dataLayer` array in the visitor's own browser and sends nothing
    113 anywhere.
     71This plugin can connect to the external services below. Every one of them is off on a new install and stays off until an administrator configures it on the settings screen. With none configured, the plugin only pushes events to the `window.dataLayer` array in the visitor's own browser and sends nothing anywhere.
    11472
    11573= Google Tag Manager =
    11674
    117 Used when you enter a GTM container ID on the settings screen. Leave that
    118 field empty and no request is made; the plugin then pushes events to a
    119 dataLayer supplied by your own GTM installation instead.
    120 
    121 *What is sent:* the visitor's browser requests the container script from
    122 `https://www.googletagmanager.com/gtm.js` (and, for visitors without
    123 JavaScript, the `ns.html` frame). As with any GTM installation, Google
    124 receives the request, the visitor's IP address and browser details, and
    125 whatever your own container tags choose to send, which will include the form
    126 and campaign data described above if you configure tags to forward it.
     75Used when you enter a GTM container ID on the settings screen. Leave that field empty and no request is made; the plugin then pushes events to a dataLayer supplied by your own GTM installation instead.
     76
     77*What is sent:* the visitor's browser requests the container script from `https://www.googletagmanager.com/gtm.js` (and, for visitors without JavaScript, the `ns.html` frame). As with any GTM installation, Google receives the request, the visitor's IP address and browser details, and whatever your own container tags choose to send, which will include the form and campaign data described above if you configure tags to forward it.
    12778
    12879*When:* on every front-end page load, once a container ID is set.
    … …  
    13485= Server-side Google Tag Manager (optional custom host) =
    13586
    136 Used only when you enable server-side GTM and enter your own tagging server
    137 hostname. Off by default. The setting accepts a bare hostname only (for
    138 example `gtm.example.com`); the plugin builds the script address itself as
    139 `https://<host>/gtm.js?id=<container ID>`.
    140 
    141 *What is sent:* the container script is requested from the host you entered
    142 instead of from Google, so that host receives the same page-load request
    143 described above. To decide whether the host is healthy, the plugin also
    144 requests `gtm.js` and `ns.html` from it, server to server, when you press
    145 the test button and on a six-hour schedule, with a recheck 15 minutes after a
    146 failed check. Those checks carry the container ID and no visitor data. When
    147 the custom host fails two checks in a row, the plugin falls back to
    148 `www.googletagmanager.com`, and returns to the host after three passes in a
    149 row.
    150 
    151 *When:* on every front-end page load, and on the six-hour health check, while
    152 server-side GTM is enabled.
    153 
    154 You choose the host, so you decide who receives the request and which terms
    155 apply.
     87Used only when you enable server-side GTM and enter your own tagging server hostname. Off by default. The setting accepts a bare hostname only (for example `gtm.example.com`); the plugin builds the script address itself as `https://<host>/gtm.js?id=<container ID>`.
     88
     89*What is sent:* the container script is requested from the host you entered instead of from Google, so that host receives the same page-load request described above. To decide whether the host is healthy, the plugin also requests `gtm.js` and `ns.html` from it, server to server, when you press the test button and on a six-hour schedule, with a recheck 15 minutes after a failed check. Those checks carry the container ID and no visitor data. When the custom host fails two checks in a row, the plugin falls back to `www.googletagmanager.com`, and returns to the host after three passes in a row.
     90
     91*When:* on every front-end page load, and on the six-hour health check, while server-side GTM is enabled.
     92
     93You choose the host, so you decide who receives the request and which terms apply.
    15694
    15795= Google Analytics Measurement Protocol =
    15896
    159 Used only when you supply both a GA4 Measurement ID and an API secret on the
    160 settings screen. Without both, no request is made.
    161 
    162 *What is sent:* a server-side event to
    163 `https://www.google-analytics.com/mp/collect`, containing the GA client ID
    164 read from the visitor's `_ga` cookie, the event name, the configured lead
    165 value and currency, and the event parameters for that submission.
    166 
    167 *When:* when a lead's status is updated through the plugin's webhook (for
    168 example, a lead marked qualified).
     97Used only when you supply both a GA4 Measurement ID and an API secret on the settings screen. Without both, no request is made.
     98
     99*What is sent:* a server-side event to `https://www.google-analytics.com/mp/collect`, containing the GA client ID read from the visitor's `_ga` cookie, the event name, the configured lead value and currency, and the event parameters for that submission.
     100
     101*When:* when a lead's status is updated through the plugin's webhook (for example, a lead marked qualified).
    169102
    170103* Service: [Google Analytics](https://marketingplatform.google.com/about/analytics/)
    … …  
    174107= Click collector (optional custom host) =
    175108
    176 Used only when you enter a Click Collector Host on the settings screen. Empty
    177 by default, in which case nothing is sent.
    178 
    179 *What is sent:* the visitor's browser sends a POST (`navigator.sendBeacon`)
    180 to `https://<the host you entered>/p` carrying the advertising click ID and
    181 its platform, the five UTM parameters, the referring URL, the landing page
    182 URL, and the GA client ID from the `_ga` cookie.
    183 
    184 *When:* on page load, only when a click ID is present in the URL or in the
    185 plugin's own click cookie.
    186 
    187 You choose the host. Point it at your own collector, or at the one Choice OMG
    188 operates for its clients, in which case Choice OMG receives the data above
    189 under the terms and privacy policy linked in the next section.
     109Used only when you enter a Click Collector Host on the settings screen. Empty by default, in which case nothing is sent.
     110
     111*What is sent:* the visitor's browser sends a POST (`navigator.sendBeacon`) to `https://<the host you entered>/p` carrying the advertising click ID and its platform, the five UTM parameters, the referring URL, the landing page URL, and the GA client ID from the `_ga` cookie.
     112
     113*When:* on page load, only when a click ID is present in the URL or in the plugin's own click cookie.
     114
     115You choose the host. Point it at your own collector, or at the one Choice OMG operates for its clients, in which case Choice OMG receives the data above under the terms and privacy policy linked in the next section.
    190116
    191117= Choice OMG phone validation service =
    192118
    193 Used only when an administrator switches phone validation on and registers
    194 the site with a registration secret that Choice OMG issues to its clients.
    195 Both are off and empty by default, so this service is never contacted on a
    196 standard install.
    197 
    198 *What is sent:* registering the site (the Register Site button) sends the
    199 site's domain name and the registration secret to
    200 `https://phone-validator.choice.zone`. After that, each phone number to be
    201 checked is sent from your server to
    202 `https://phone-validator.choice.zone/validate-phone` together with the
    203 site's registration token. Choice OMG's service looks the number up with its
    204 own providers and returns whether the number is valid, its line type, and a
    205 quality score. The plugin itself never contacts those providers.
    206 
    207 *When:* when a submitted phone number is validated. Results are cached on your
    208 site for 24 hours per number, so a repeated number is not sent again within
    209 that window.
     119Used only when an administrator switches phone validation on and registers the site with a registration secret that Choice OMG issues to its clients. Both are off and empty by default, so this service is never contacted on a standard install.
     120
     121*What is sent:* registering the site (the Register Site button) sends the site's domain name and the registration secret to `https://phone-validator.choice.zone`. After that, each phone number to be checked is sent from your server to `https://phone-validator.choice.zone/validate-phone` together with the site's registration token. Choice OMG's service looks the number up with its own providers and returns whether the number is valid, its line type, and a quality score. The plugin itself never contacts those providers.
     122
     123*When:* when a submitted phone number is validated. Results are cached on your site for 24 hours per number, so a repeated number is not sent again within that window.
    210124
    211125* Service: [Choice OMG](https://choice.marketing/tools/choice-uft/)
    … …  
    213127* Privacy policy: [https://choice.marketing/privacy-policy/](https://choice.marketing/privacy-policy/)
    214128
    215 Because form submissions can carry personal data, review your own privacy
    216 policy and any applicable data-protection obligations (GDPR, CCPA, PIPEDA and
    217 similar) before enabling any of these services.
     129Because form submissions can carry personal data, review your own privacy policy and any applicable data-protection obligations (GDPR, CCPA, PIPEDA and similar) before enabling any of these services.
    218130
    219131== Installation ==
    … …  
    231143= Do I need Google Tag Manager? =
    232144
    233 No. The plugin pushes its events to `window.dataLayer` whether or not it
    234 injects a container for you. If you already load GTM through a theme or
    235 another plugin, leave the container ID empty and the events will reach your
    236 existing container.
     145No. The plugin pushes its events to `window.dataLayer` whether or not it injects a container for you. If you already load GTM through a theme or another plugin, leave the container ID empty and the events will reach your existing container.
    237146
    238147= Can I use it alongside GTM4WP, GTM Kit or Site Kit? =
    239148
    240 Yes. When another plugin already loads your container, leave this plugin's
    241 container ID empty. Its events go into the same `window.dataLayer`, so your
    242 existing container receives them, provided that plugin keeps the default
    243 `dataLayer` name. Those plugins are separate products, and this one is not
    244 affiliated with their makers.
     149Yes. When another plugin already loads your container, leave this plugin's container ID empty. Its events go into the same `window.dataLayer`, so your existing container receives them, provided that plugin keeps the default `dataLayer` name. Those plugins are separate products, and this one is not affiliated with their makers.
    245150
    246151= Is there a paid version? =
    247152
    248 No. There is no paid tier and no upgrade prompt. Two optional features can
    249 use services Choice OMG runs for its agency clients: phone validation, which
    250 needs a registration secret Choice OMG issues to those clients, and the click
    251 collector, which can point at a collector of your own instead. Both are off by
    252 default and are described under External services.
     153No. There is no paid tier and no upgrade prompt. Two optional features can use services Choice OMG runs for its agency clients: phone validation, which needs a registration secret Choice OMG issues to those clients, and the click collector, which can point at a collector of your own instead. Both are off by default and are described under External services.
    253154
    254155= Which form plugins does it support? =
    255156
    256 Contact Form 7, Gravity Forms, Ninja Forms, Elementor Pro forms, and Avada /
    257 Fusion Builder forms. Detection is automatic; scripts for a form plugin that
    258 is not installed are never loaded.
     157Contact Form 7, Gravity Forms, Ninja Forms, Elementor Pro forms, and Avada / Fusion Builder forms. Detection is automatic; scripts for a form plugin that is not installed are never loaded.
    259158
    260159= What data does it collect, and where does it go? =
    261160
    262 Form submissions produce dataLayer events containing the email address and
    263 phone number entered, the form's identity, and the campaign parameters
    264 captured when the visitor arrived. Those events go to your own analytics
    265 setup. Nothing leaves your site unless you configure one of the services
    266 listed under External services.
     161Form submissions produce dataLayer events containing the email address and phone number entered, the form's identity, and the campaign parameters captured when the visitor arrived. Those events go to your own analytics setup. Nothing leaves your site unless you configure one of the services listed under External services.
    267162
    268163= How long is campaign attribution kept? =
    269164
    270 UTM parameters and click IDs are stored in the visitor's browser for 30 days
    271 from their first visit carrying them, so attribution survives a gap between
    272 the ad click and the form submission. First-touch attribution is kept for a
    273 year in a separate cookie.
    274 
    275 On the server, each tracked click is a row in the plugin's click tracking
    276 table: the click ID and its ad platform, the campaign parameters, a keyed
    277 hash of the visitor's IP address, the browser's user agent, the referring
    278 page, the landing page address, the Google Analytics client ID when the
    279 visitor has one, and the tracked events with their times. A click on a phone
    280 or email link by a visitor who did not arrive from an ad also creates a row,
    281 under a temporary ID. Rows older than the Click Data Retention setting (365
    282 days by default) are deleted once a day. The IP address itself is never
    283 stored, and the hash is keyed with the authentication keys in wp-config.php,
    284 so a copy of the database alone cannot reverse it.
     165UTM parameters and click IDs are stored in the visitor's browser for 30 days from their first visit carrying them, so attribution survives a gap between the ad click and the form submission. First-touch attribution is kept for a year in a separate cookie.
     166
     167On the server, each tracked click is a row in the plugin's click tracking table: the click ID and its ad platform, the campaign parameters, a keyed hash of the visitor's IP address, the browser's user agent, the referring page, the landing page address, the Google Analytics client ID when the visitor has one, and the tracked events with their times. A click on a phone or email link by a visitor who did not arrive from an ad also creates a row, under a temporary ID. Rows older than the Click Data Retention setting (365 days by default) are deleted once a day. The IP address itself is never stored, and the hash is keyed with the authentication keys in wp-config.php, so a copy of the database alone cannot reverse it.
    285168
    286169= Is it GDPR compliant? =
    287170
    288 That depends on how you configure it. The plugin writes campaign parameters to
    289 the visitor's browser and puts submitted contact details into your dataLayer,
    290 so it processes personal data and belongs in your privacy policy and your
    291 consent flow. Review the External services section for anything that leaves
    292 your site.
     171That depends on how you configure it. The plugin writes campaign parameters to the visitor's browser and puts submitted contact details into your dataLayer, so it processes personal data and belongs in your privacy policy and your consent flow. Review the External services section for anything that leaves your site.
    293172
    294173= Does it slow the site down? =
    295174
    296 The tracking scripts are plain JavaScript with no jQuery dependency, and each
    297 form plugin's handler loads only when that form plugin is present. Tracking
    298 errors are caught so a failure cannot block a form submission.
     175The tracking scripts are plain JavaScript with no jQuery dependency, and each form plugin's handler loads only when that form plugin is present. Tracking errors are caught so a failure cannot block a form submission.
    299176
    300177= Can my CRM or email platform update a lead's status? =
    301178
    302 Yes. The Click Tracking screen shows a webhook URL that marks a tracked click
    303 as qualified, scores it, or records a lifecycle status such as `qualify_lead`.
    304 Every request must carry the site's secret webhook key, which the same screen
    305 displays and can regenerate. Requests without the right key are rejected with
    306 HTTP 403 and change nothing.
     179Yes. The Click Tracking screen shows a webhook URL that marks a tracked click as qualified, scores it, or records a lifecycle status such as `qualify_lead`. Every request must carry the site's secret webhook key, which the same screen displays and can regenerate. Requests without the right key are rejected with HTTP 403 and change nothing.
    307180
    308181= Can I change what is tracked? =
    309182
    310 Yes. Filters cover the computed `lead_id` (`cuft_lead_id`), the phone country
    311 code used to normalise it (`cuft_lead_id_phone_country`), the attribution
    312 payload (`cuft_form_attribution_payload`), and whether the plugin runs at all
    313 (`cuft_enabled`). Since 3.30.0, `cuft_ignore_phone_numbers` adds numbers that
    314 are never tracked, `cuft_record_event` decides whether an event reported by
    315 the browser is recorded on its click, and `cuft_click_retention_days` sets how
    316 long click rows are kept.
     183Yes. Filters cover the computed `lead_id` (`cuft_lead_id`), the phone country code used to normalise it (`cuft_lead_id_phone_country`), the attribution payload (`cuft_form_attribution_payload`), and whether the plugin runs at all (`cuft_enabled`). Since 3.30.0, `cuft_ignore_phone_numbers` adds numbers that are never tracked, `cuft_record_event` decides whether an event reported by the browser is recorded on its click, and `cuft_click_retention_days` sets how long click rows are kept.
    317184
    318185= Can I keep one phone number or link out of tracking? =
    319186
    320 Yes. List the number under Settings > Ignored Phone Numbers, and clicks on any
    321 `tel:` link to it produce no `phone_click` in the dataLayer and nothing on the
    322 click record. Numbers match on their last 10 digits, so every formatting of the
    323 same number is covered. To exclude a single link of any kind, add the attribute
    324 `data-cuft-ignore` or the CSS class `cuft-ignore` to the link or to an element
    325 around it; in Elementor the class goes under Advanced > CSS Classes.
     187Yes. List the number under Settings > Ignored Phone Numbers, and clicks on any `tel:` link to it produce no `phone_click` in the dataLayer and nothing on the click record. Numbers match on their last 10 digits, so every formatting of the same number is covered. To exclude a single link of any kind, add the attribute `data-cuft-ignore` or the CSS class `cuft-ignore` to the link or to an element around it; in Elementor the class goes under Advanced > CSS Classes.
    326188
    327189== Screenshots ==
  • choice-universal-form-tracker/trunk/readme.txt

    r3752395 r3753857  
    1313== Description ==
    1414
    15 Five form plugins, one event shape. Each form plugin fires its submissions
    16 differently, so a site running more than one ends up with a different tag, a
    17 different trigger, and a different set of field names for each. This plugin
    18 detects whichever form plugins are active and emits a single, consistent
    19 `form_submit` event for all of them, so one tag in Google Tag Manager covers
    20 the whole site.
    21 
    22 Campaign attribution rides along. UTM parameters and advertising click IDs are
    23 captured on the visitor's first landing and stored for 30 days, so a form
    24 submitted a week after the ad click still reports the campaign that produced
    25 it.
     15Five form plugins, one event shape. Each form plugin fires its submissions differently, so a site running more than one ends up with a different tag, a different trigger, and a different set of field names for each. This plugin detects whichever form plugins are active and emits a single, consistent `form_submit` event for all of them, so one tag in Google Tag Manager covers the whole site.
     16
     17Campaign attribution rides along. UTM parameters and advertising click IDs are captured on the visitor's first landing and stored for 30 days, so a form submitted a week after the ad click still reports the campaign that produced it.
    2618
    2719= Supported form plugins =
    2820
    29 Detection is automatic. No configuration is needed beyond activating the
    30 plugin, and nothing is loaded for a form plugin that is not installed.
     21Detection is automatic. No configuration is needed beyond activating the plugin, and nothing is loaded for a form plugin that is not installed.
    3122
    3223* Contact Form 7
    … …  
    3627* Avada / Fusion Builder forms
    3728
    38 These are third-party products, listed here to describe compatibility. This
    39 plugin is not affiliated with, endorsed by, or supported by their makers.
     29These are third-party products, listed here to describe compatibility. This plugin is not affiliated with, endorsed by, or supported by their makers.
    4030
    4131= What gets tracked =
    4232
    43 * `form_submit` on every detected submission, carrying the form type, form ID,
    44   and the email address and phone number the visitor entered
    45 * `generate_lead` when a submission includes a valid email address and the
    46   Generate Lead Events setting is on (off by default on new installs)
    47 * `qualify_lead` when a submission carries an email address, a phone number,
    48   and an advertising click ID
    49 * `phone_click` when a visitor clicks a `tel:` link, and `email_click` for a
    50   `mailto:` link, except links and numbers you exclude (see the FAQ)
     33* `form_submit` on every detected submission, carrying the form type, form ID, and the email address and phone number the visitor entered
     34* `generate_lead` when a submission includes a valid email address and the Generate Lead Events setting is on (off by default on new installs)
     35* `qualify_lead` when a submission carries an email address, a phone number, and an advertising click ID
     36* `phone_click` when a visitor clicks a `tel:` link, and `email_click` for a `mailto:` link, except links and numbers you exclude (see the FAQ)
    5137* UTM parameters (source, medium, campaign, term, content) on all of the above
    52 * Advertising click IDs: `gclid`, `gbraid`, `wbraid`, `fbclid`, `msclkid`,
    53   `ttclid`, `twclid`, `rdt_cid`, `li_fat_id`, `snap_click_id`, and `pclid`
    54 
    55 Every event carries `cuft_tracked: true`, which gives you one reliable
    56 condition to build triggers on.
     38* Advertising click IDs: `gclid`, `gbraid`, `wbraid`, `fbclid`, `msclkid`, `ttclid`, `twclid`, `rdt_cid`, `li_fat_id`, `snap_click_id`, and `pclid`
     39
     40Every event carries `cuft_tracked: true`, which gives you one reliable condition to build triggers on.
    5741
    5842= Send it to GA4, Google Ads and Meta =
    5943
    60 One trigger in Google Tag Manager covers every form on the site, and a form
    61 plugin added later needs no new tag:
    62 
    63 1. Add Data Layer Variables for `cuft_tracked` and for the fields you need,
    64    such as `form_type`, `form_id`, `utm_campaign`, `gclid` and `lead_id`.
    65 2. Create a Custom Event trigger for `form_submit` with the condition
    66    `cuft_tracked` equals `true`.
    67 3. Fire your GA4 event tag, Google Ads conversion tag or Meta Pixel `Lead`
    68    event on that trigger. For Google Ads enhanced conversions, map
    69    `user_email` into a User-Provided Data variable. `user_phone` holds the
    70    number as the visitor typed it, so convert it to E.164 form (such as
    71    `+17805550173`) before mapping it. For GA4, send `lead_id` and leave the
    72    contact details out.
     44One trigger in Google Tag Manager covers every form on the site, and a form plugin added later needs no new tag:
     45
     461. Add Data Layer Variables for `cuft_tracked` and for the fields you need, such as `form_type`, `form_id`, `utm_campaign`, `gclid` and `lead_id`.
     472. Create a Custom Event trigger for `form_submit` with the condition `cuft_tracked` equals `true`.
     483. Fire your GA4 event tag, Google Ads conversion tag or Meta Pixel `Lead` event on that trigger. For Google Ads enhanced conversions, map `user_email` into a User-Provided Data variable. `user_phone` holds the number as the visitor typed it, so convert it to E.164 form (such as `+17805550173`) before mapping it. For GA4, send `lead_id` and leave the contact details out.
    7349
    7450= Shared lead identity =
    7551
    76 Each event can carry a `lead_id`: the SHA-256 hash of the visitor's email
    77 address, lowercased and trimmed, or of their phone number in E.164 form when
    78 no email was given. The same hash is computed server-side, so an analytics
    79 event and a downstream CRM record derived from the same person produce the
    80 same identifier.
    81 
    82 The `form_submit` event also carries the raw email address and phone number.
    83 Send `lead_id` instead of those fields wherever raw personal data must not
    84 go; Google Analytics, for one, forbids sending it email addresses or phone
    85 numbers.
    86 
    87 The normalisation matches what Google Enhanced Conversions and Meta CAPI
    88 expect, so the value doubles as a match key for offline conversion imports.
     52Each event can carry a `lead_id`: the SHA-256 hash of the visitor's email address, lowercased and trimmed, or of their phone number in E.164 form when no email was given. The same hash is computed server-side, so an analytics event and a downstream CRM record derived from the same person produce the same identifier.
     53
     54The `form_submit` event also carries the raw email address and phone number. Send `lead_id` instead of those fields wherever raw personal data must not go; Google Analytics, for one, forbids sending it email addresses or phone numbers.
     55
     56The normalisation matches what Google Enhanced Conversions and Meta CAPI expect, so the value doubles as a match key for offline conversion imports.
    8957
    9058= Built to stay out of the way =
    … …  
    9765= Who maintains this =
    9866
    99 [Choice OMG](https://choice.marketing/) maintains this plugin. We are a
    100 digital marketing and web agency in Edmonton, Alberta, and we built it because
    101 our clients' sites run several form plugins at once and each one needed its own
    102 analytics plumbing. The plugin's overview page is
    103 [Choice Universal Form Tracker for WordPress](https://choice.marketing/tools/choice-uft/).
    104 If you would rather have GTM, GA4 and Google Ads conversion tracking set up for
    105 you, [Choice OMG does that for clients](https://choice.marketing/services/conversion-optimization/).
     67[Choice OMG](https://choice.marketing/) maintains this plugin. We are a digital marketing and web agency in Edmonton, Alberta, and we built it because our clients' sites run several form plugins at once and each one needed its own analytics plumbing. The plugin's overview page is [Choice Universal Form Tracker for WordPress](https://choice.marketing/tools/choice-uft/). If you would rather have GTM, GA4 and Google Ads conversion tracking set up for you, [Choice OMG does that for clients](https://choice.marketing/services/conversion-optimization/).
    10668
    10769== External services ==
    10870
    109 This plugin can connect to the external services below. Every one of them is
    110 off on a new install and stays off until an administrator configures it on
    111 the settings screen. With none configured, the plugin only pushes events to
    112 the `window.dataLayer` array in the visitor's own browser and sends nothing
    113 anywhere.
     71This plugin can connect to the external services below. Every one of them is off on a new install and stays off until an administrator configures it on the settings screen. With none configured, the plugin only pushes events to the `window.dataLayer` array in the visitor's own browser and sends nothing anywhere.
    11472
    11573= Google Tag Manager =
    11674
    117 Used when you enter a GTM container ID on the settings screen. Leave that
    118 field empty and no request is made; the plugin then pushes events to a
    119 dataLayer supplied by your own GTM installation instead.
    120 
    121 *What is sent:* the visitor's browser requests the container script from
    122 `https://www.googletagmanager.com/gtm.js` (and, for visitors without
    123 JavaScript, the `ns.html` frame). As with any GTM installation, Google
    124 receives the request, the visitor's IP address and browser details, and
    125 whatever your own container tags choose to send, which will include the form
    126 and campaign data described above if you configure tags to forward it.
     75Used when you enter a GTM container ID on the settings screen. Leave that field empty and no request is made; the plugin then pushes events to a dataLayer supplied by your own GTM installation instead.
     76
     77*What is sent:* the visitor's browser requests the container script from `https://www.googletagmanager.com/gtm.js` (and, for visitors without JavaScript, the `ns.html` frame). As with any GTM installation, Google receives the request, the visitor's IP address and browser details, and whatever your own container tags choose to send, which will include the form and campaign data described above if you configure tags to forward it.
    12778
    12879*When:* on every front-end page load, once a container ID is set.
    … …  
    13485= Server-side Google Tag Manager (optional custom host) =
    13586
    136 Used only when you enable server-side GTM and enter your own tagging server
    137 hostname. Off by default. The setting accepts a bare hostname only (for
    138 example `gtm.example.com`); the plugin builds the script address itself as
    139 `https://<host>/gtm.js?id=<container ID>`.
    140 
    141 *What is sent:* the container script is requested from the host you entered
    142 instead of from Google, so that host receives the same page-load request
    143 described above. To decide whether the host is healthy, the plugin also
    144 requests `gtm.js` and `ns.html` from it, server to server, when you press
    145 the test button and on a six-hour schedule, with a recheck 15 minutes after a
    146 failed check. Those checks carry the container ID and no visitor data. When
    147 the custom host fails two checks in a row, the plugin falls back to
    148 `www.googletagmanager.com`, and returns to the host after three passes in a
    149 row.
    150 
    151 *When:* on every front-end page load, and on the six-hour health check, while
    152 server-side GTM is enabled.
    153 
    154 You choose the host, so you decide who receives the request and which terms
    155 apply.
     87Used only when you enable server-side GTM and enter your own tagging server hostname. Off by default. The setting accepts a bare hostname only (for example `gtm.example.com`); the plugin builds the script address itself as `https://<host>/gtm.js?id=<container ID>`.
     88
     89*What is sent:* the container script is requested from the host you entered instead of from Google, so that host receives the same page-load request described above. To decide whether the host is healthy, the plugin also requests `gtm.js` and `ns.html` from it, server to server, when you press the test button and on a six-hour schedule, with a recheck 15 minutes after a failed check. Those checks carry the container ID and no visitor data. When the custom host fails two checks in a row, the plugin falls back to `www.googletagmanager.com`, and returns to the host after three passes in a row.
     90
     91*When:* on every front-end page load, and on the six-hour health check, while server-side GTM is enabled.
     92
     93You choose the host, so you decide who receives the request and which terms apply.
    15694
    15795= Google Analytics Measurement Protocol =
    15896
    159 Used only when you supply both a GA4 Measurement ID and an API secret on the
    160 settings screen. Without both, no request is made.
    161 
    162 *What is sent:* a server-side event to
    163 `https://www.google-analytics.com/mp/collect`, containing the GA client ID
    164 read from the visitor's `_ga` cookie, the event name, the configured lead
    165 value and currency, and the event parameters for that submission.
    166 
    167 *When:* when a lead's status is updated through the plugin's webhook (for
    168 example, a lead marked qualified).
     97Used only when you supply both a GA4 Measurement ID and an API secret on the settings screen. Without both, no request is made.
     98
     99*What is sent:* a server-side event to `https://www.google-analytics.com/mp/collect`, containing the GA client ID read from the visitor's `_ga` cookie, the event name, the configured lead value and currency, and the event parameters for that submission.
     100
     101*When:* when a lead's status is updated through the plugin's webhook (for example, a lead marked qualified).
    169102
    170103* Service: [Google Analytics](https://marketingplatform.google.com/about/analytics/)
    … …  
    174107= Click collector (optional custom host) =
    175108
    176 Used only when you enter a Click Collector Host on the settings screen. Empty
    177 by default, in which case nothing is sent.
    178 
    179 *What is sent:* the visitor's browser sends a POST (`navigator.sendBeacon`)
    180 to `https://<the host you entered>/p` carrying the advertising click ID and
    181 its platform, the five UTM parameters, the referring URL, the landing page
    182 URL, and the GA client ID from the `_ga` cookie.
    183 
    184 *When:* on page load, only when a click ID is present in the URL or in the
    185 plugin's own click cookie.
    186 
    187 You choose the host. Point it at your own collector, or at the one Choice OMG
    188 operates for its clients, in which case Choice OMG receives the data above
    189 under the terms and privacy policy linked in the next section.
     109Used only when you enter a Click Collector Host on the settings screen. Empty by default, in which case nothing is sent.
     110
     111*What is sent:* the visitor's browser sends a POST (`navigator.sendBeacon`) to `https://<the host you entered>/p` carrying the advertising click ID and its platform, the five UTM parameters, the referring URL, the landing page URL, and the GA client ID from the `_ga` cookie.
     112
     113*When:* on page load, only when a click ID is present in the URL or in the plugin's own click cookie.
     114
     115You choose the host. Point it at your own collector, or at the one Choice OMG operates for its clients, in which case Choice OMG receives the data above under the terms and privacy policy linked in the next section.
    190116
    191117= Choice OMG phone validation service =
    192118
    193 Used only when an administrator switches phone validation on and registers
    194 the site with a registration secret that Choice OMG issues to its clients.
    195 Both are off and empty by default, so this service is never contacted on a
    196 standard install.
    197 
    198 *What is sent:* registering the site (the Register Site button) sends the
    199 site's domain name and the registration secret to
    200 `https://phone-validator.choice.zone`. After that, each phone number to be
    201 checked is sent from your server to
    202 `https://phone-validator.choice.zone/validate-phone` together with the
    203 site's registration token. Choice OMG's service looks the number up with its
    204 own providers and returns whether the number is valid, its line type, and a
    205 quality score. The plugin itself never contacts those providers.
    206 
    207 *When:* when a submitted phone number is validated. Results are cached on your
    208 site for 24 hours per number, so a repeated number is not sent again within
    209 that window.
     119Used only when an administrator switches phone validation on and registers the site with a registration secret that Choice OMG issues to its clients. Both are off and empty by default, so this service is never contacted on a standard install.
     120
     121*What is sent:* registering the site (the Register Site button) sends the site's domain name and the registration secret to `https://phone-validator.choice.zone`. After that, each phone number to be checked is sent from your server to `https://phone-validator.choice.zone/validate-phone` together with the site's registration token. Choice OMG's service looks the number up with its own providers and returns whether the number is valid, its line type, and a quality score. The plugin itself never contacts those providers.
     122
     123*When:* when a submitted phone number is validated. Results are cached on your site for 24 hours per number, so a repeated number is not sent again within that window.
    210124
    211125* Service: [Choice OMG](https://choice.marketing/tools/choice-uft/)
    … …  
    213127* Privacy policy: [https://choice.marketing/privacy-policy/](https://choice.marketing/privacy-policy/)
    214128
    215 Because form submissions can carry personal data, review your own privacy
    216 policy and any applicable data-protection obligations (GDPR, CCPA, PIPEDA and
    217 similar) before enabling any of these services.
     129Because form submissions can carry personal data, review your own privacy policy and any applicable data-protection obligations (GDPR, CCPA, PIPEDA and similar) before enabling any of these services.
    218130
    219131== Installation ==
    … …  
    231143= Do I need Google Tag Manager? =
    232144
    233 No. The plugin pushes its events to `window.dataLayer` whether or not it
    234 injects a container for you. If you already load GTM through a theme or
    235 another plugin, leave the container ID empty and the events will reach your
    236 existing container.
     145No. The plugin pushes its events to `window.dataLayer` whether or not it injects a container for you. If you already load GTM through a theme or another plugin, leave the container ID empty and the events will reach your existing container.
    237146
    238147= Can I use it alongside GTM4WP, GTM Kit or Site Kit? =
    239148
    240 Yes. When another plugin already loads your container, leave this plugin's
    241 container ID empty. Its events go into the same `window.dataLayer`, so your
    242 existing container receives them, provided that plugin keeps the default
    243 `dataLayer` name. Those plugins are separate products, and this one is not
    244 affiliated with their makers.
     149Yes. When another plugin already loads your container, leave this plugin's container ID empty. Its events go into the same `window.dataLayer`, so your existing container receives them, provided that plugin keeps the default `dataLayer` name. Those plugins are separate products, and this one is not affiliated with their makers.
    245150
    246151= Is there a paid version? =
    247152
    248 No. There is no paid tier and no upgrade prompt. Two optional features can
    249 use services Choice OMG runs for its agency clients: phone validation, which
    250 needs a registration secret Choice OMG issues to those clients, and the click
    251 collector, which can point at a collector of your own instead. Both are off by
    252 default and are described under External services.
     153No. There is no paid tier and no upgrade prompt. Two optional features can use services Choice OMG runs for its agency clients: phone validation, which needs a registration secret Choice OMG issues to those clients, and the click collector, which can point at a collector of your own instead. Both are off by default and are described under External services.
    253154
    254155= Which form plugins does it support? =
    255156
    256 Contact Form 7, Gravity Forms, Ninja Forms, Elementor Pro forms, and Avada /
    257 Fusion Builder forms. Detection is automatic; scripts for a form plugin that
    258 is not installed are never loaded.
     157Contact Form 7, Gravity Forms, Ninja Forms, Elementor Pro forms, and Avada / Fusion Builder forms. Detection is automatic; scripts for a form plugin that is not installed are never loaded.
    259158
    260159= What data does it collect, and where does it go? =
    261160
    262 Form submissions produce dataLayer events containing the email address and
    263 phone number entered, the form's identity, and the campaign parameters
    264 captured when the visitor arrived. Those events go to your own analytics
    265 setup. Nothing leaves your site unless you configure one of the services
    266 listed under External services.
     161Form submissions produce dataLayer events containing the email address and phone number entered, the form's identity, and the campaign parameters captured when the visitor arrived. Those events go to your own analytics setup. Nothing leaves your site unless you configure one of the services listed under External services.
    267162
    268163= How long is campaign attribution kept? =
    269164
    270 UTM parameters and click IDs are stored in the visitor's browser for 30 days
    271 from their first visit carrying them, so attribution survives a gap between
    272 the ad click and the form submission. First-touch attribution is kept for a
    273 year in a separate cookie.
    274 
    275 On the server, each tracked click is a row in the plugin's click tracking
    276 table: the click ID and its ad platform, the campaign parameters, a keyed
    277 hash of the visitor's IP address, the browser's user agent, the referring
    278 page, the landing page address, the Google Analytics client ID when the
    279 visitor has one, and the tracked events with their times. A click on a phone
    280 or email link by a visitor who did not arrive from an ad also creates a row,
    281 under a temporary ID. Rows older than the Click Data Retention setting (365
    282 days by default) are deleted once a day. The IP address itself is never
    283 stored, and the hash is keyed with the authentication keys in wp-config.php,
    284 so a copy of the database alone cannot reverse it.
     165UTM parameters and click IDs are stored in the visitor's browser for 30 days from their first visit carrying them, so attribution survives a gap between the ad click and the form submission. First-touch attribution is kept for a year in a separate cookie.
     166
     167On the server, each tracked click is a row in the plugin's click tracking table: the click ID and its ad platform, the campaign parameters, a keyed hash of the visitor's IP address, the browser's user agent, the referring page, the landing page address, the Google Analytics client ID when the visitor has one, and the tracked events with their times. A click on a phone or email link by a visitor who did not arrive from an ad also creates a row, under a temporary ID. Rows older than the Click Data Retention setting (365 days by default) are deleted once a day. The IP address itself is never stored, and the hash is keyed with the authentication keys in wp-config.php, so a copy of the database alone cannot reverse it.
    285168
    286169= Is it GDPR compliant? =
    287170
    288 That depends on how you configure it. The plugin writes campaign parameters to
    289 the visitor's browser and puts submitted contact details into your dataLayer,
    290 so it processes personal data and belongs in your privacy policy and your
    291 consent flow. Review the External services section for anything that leaves
    292 your site.
     171That depends on how you configure it. The plugin writes campaign parameters to the visitor's browser and puts submitted contact details into your dataLayer, so it processes personal data and belongs in your privacy policy and your consent flow. Review the External services section for anything that leaves your site.
    293172
    294173= Does it slow the site down? =
    295174
    296 The tracking scripts are plain JavaScript with no jQuery dependency, and each
    297 form plugin's handler loads only when that form plugin is present. Tracking
    298 errors are caught so a failure cannot block a form submission.
     175The tracking scripts are plain JavaScript with no jQuery dependency, and each form plugin's handler loads only when that form plugin is present. Tracking errors are caught so a failure cannot block a form submission.
    299176
    300177= Can my CRM or email platform update a lead's status? =
    301178
    302 Yes. The Click Tracking screen shows a webhook URL that marks a tracked click
    303 as qualified, scores it, or records a lifecycle status such as `qualify_lead`.
    304 Every request must carry the site's secret webhook key, which the same screen
    305 displays and can regenerate. Requests without the right key are rejected with
    306 HTTP 403 and change nothing.
     179Yes. The Click Tracking screen shows a webhook URL that marks a tracked click as qualified, scores it, or records a lifecycle status such as `qualify_lead`. Every request must carry the site's secret webhook key, which the same screen displays and can regenerate. Requests without the right key are rejected with HTTP 403 and change nothing.
    307180
    308181= Can I change what is tracked? =
    309182
    310 Yes. Filters cover the computed `lead_id` (`cuft_lead_id`), the phone country
    311 code used to normalise it (`cuft_lead_id_phone_country`), the attribution
    312 payload (`cuft_form_attribution_payload`), and whether the plugin runs at all
    313 (`cuft_enabled`). Since 3.30.0, `cuft_ignore_phone_numbers` adds numbers that
    314 are never tracked, `cuft_record_event` decides whether an event reported by
    315 the browser is recorded on its click, and `cuft_click_retention_days` sets how
    316 long click rows are kept.
     183Yes. Filters cover the computed `lead_id` (`cuft_lead_id`), the phone country code used to normalise it (`cuft_lead_id_phone_country`), the attribution payload (`cuft_form_attribution_payload`), and whether the plugin runs at all (`cuft_enabled`). Since 3.30.0, `cuft_ignore_phone_numbers` adds numbers that are never tracked, `cuft_record_event` decides whether an event reported by the browser is recorded on its click, and `cuft_click_retention_days` sets how long click rows are kept.
    317184
    318185= Can I keep one phone number or link out of tracking? =
    319186
    320 Yes. List the number under Settings > Ignored Phone Numbers, and clicks on any
    321 `tel:` link to it produce no `phone_click` in the dataLayer and nothing on the
    322 click record. Numbers match on their last 10 digits, so every formatting of the
    323 same number is covered. To exclude a single link of any kind, add the attribute
    324 `data-cuft-ignore` or the CSS class `cuft-ignore` to the link or to an element
    325 around it; in Elementor the class goes under Advanced > CSS Classes.
     187Yes. List the number under Settings > Ignored Phone Numbers, and clicks on any `tel:` link to it produce no `phone_click` in the dataLayer and nothing on the click record. Numbers match on their last 10 digits, so every formatting of the same number is covered. To exclude a single link of any kind, add the attribute `data-cuft-ignore` or the CSS class `cuft-ignore` to the link or to an element around it; in Elementor the class goes under Advanced > CSS Classes.
    326188
    327189== Screenshots ==
Note: See TracChangeset for help on using the changeset viewer.