| 开发者 | jaffray |
|---|---|
| 更新时间 | 2026年10月19日 11:06 |
| PHP版本: | 7.4 及以上 |
| WordPress版本: | 7.1 |
| 版权: | GPLv2 or later |
| 版权网址: | 版权信息 |
form_submit event for all of them, so one tag in Google Tag Manager covers
the whole site.
Campaign 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.
Supported form plugins
Detection is automatic. No configuration is needed beyond activating the
plugin, and nothing is loaded for a form plugin that is not installed.
form_submit on every detected submission, carrying the form type, form ID,
and the email address and phone number the visitor enteredgenerate_lead when a submission includes a valid email address and the
Generate Lead Events setting is on (off by default on new installs)qualify_lead when a submission carries an email address, a phone number,
and an advertising click IDphone_click when a visitor clicks a tel: link, and email_click for a
mailto: link, except links and numbers you exclude (see the FAQ)gclid, gbraid, wbraid, fbclid, msclkid,
ttclid, twclid, rdt_cid, li_fat_id, snap_click_id, and pclidcuft_tracked: true, which gives you one reliable
condition to build triggers on.
Send it to GA4, Google Ads and Meta
One trigger in Google Tag Manager covers every form on the site, and a form
plugin added later needs no new tag:
cuft_tracked and for the fields you need,
such as form_type, form_id, utm_campaign, gclid and lead_id.form_submit with the condition
cuft_tracked equals true.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.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.
The 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.
The normalisation matches what Google Enhanced Conversions and Meta CAPI
expect, so the value doubles as a match key for offline conversion imports.
Built to stay out of the way
choice-universal-form-tracker folder to /wp-content/plugins/.No. 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.
Yes. 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.
No. 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.
Contact 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.
Form 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.
UTM 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. On 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.
That 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.
The 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.
Yes. 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.
Yes. 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.
Yes. 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.
phone_click and email_click events invalid, because it looked for field names the plugin does not send.tel: links to a listed number are not tracked: no phone_click in the dataLayer and no request to the server. Numbers match on their last 10 digits, ignoring formatting and extensions. The cuft_ignore_phone_numbers filter adds numbers from code.data-cuft-ignore or the class cuft-ignore is not tracked.cuft_record_event filter, which can stop an event reported by the browser from being recorded on its click.AUTH_KEY and AUTH_SALT in wp-config.php) instead of a plain SHA-256 of the address, which could be reversed by hashing every IPv4 address. Hashes stored before the upgrade are cleared by the daily job. Define CUFT_IP_HASH_KEY to use your own key.ip_hash or click_platform.cuft_enabled filter switched tracking off.google_tag_manager, and the probe follows no redirects. Two passes in a row select the gateway; two failures in a row return to the loader the site used before. The loader also falls back to Google in the browser if the gateway script errors or does not define the container./wp-admin/admin-ajax.php. The AJAX address comes only from admin_url( 'admin-ajax.php' ), passed in by PHP.gtm.example.com) instead of a URL. The plugin builds https://<host>/gtm.js?id=<container ID> itself; hostnames with a scheme other than https, a path, a query, a port, credentials, an IP address, localhost or an internal suffix are rejected. A URL saved by an earlier version is converted to its host automatically. A URL that carried a path keeps its host but falls back to Google's endpoint until you test the connection again..localnet exception is removed. A new or changed host must pass its own health checks before pages load from it.phone_click, email_click, form_submit, generate_lead, qualify_lead). CRM statuses reach the click table only through the webhook. Click IDs and UTM values longer than 255 characters are ignored or cut.events or ga_client_id columns added by earlier versions, is repaired automatically.generate_lead dual-fire that 3.28.0 kept for one version. A submission with an email, a phone number and a click ID was pushing generate_lead twice (once broad, once with the strict payload and cuft_deprecated: true) and recording it once, so a GTM trigger on generate_lead counted that lead twice. generate_lead now fires at most once per submission; qualify_lead is unchanged. Update any GTM trigger that matched on cuft_deprecated equals true, since that copy no longer fires; a trigger on the event name generate_lead alone needs no change beyond seeing one event per lead instead of two.generate_lead from the server (gform_after_submission) in addition to the client-side push every form framework already fires. Only Gravity Forms submissions that complete as a full page render (not an AJAX/iframe confirmation) were affected.generate_lead now fires only when the Generate Lead Events setting is on, covering the dataLayer push, the deprecated strict dual-fire, and the event recorded on the click. Earlier versions pushed it whatever the setting said. Sites upgrading from an earlier version have the setting switched on automatically so nothing changes for them; new installs start with it off.key are rejected with HTTP 403. On by default for new installs; sites upgrading keep it off until an administrator turns it on, so existing links keep working.test=1, cuft_test=1 or -test-form.$wpdb->prepare(), with table and column names bound through the %i placeholder that 6.2 introduced.manage_options capability.=, +, - or @, so a spreadsheet cannot run them as formulas.submitted_at reports the submit time rather than the time a later action fired.form_submit events on Avada/Fusion forms. The success check read the inline style attribute, so a success message that Avada ships in the page markup and hides with a CSS class counted as a submission on any render, including ad-crawler and lock-screen renders that never touched the form. Detection now uses computed style plus layout and requires the success state to appear after the submission.form_submit. Other form frameworks are unchanged.lead_id off an event, and makes the hash available synchronously.Plugin URI, Requires at least, Requires PHP, and License URI to the plugin header.lead_id on every lead: the SHA-256 of the normalised email address, or of the phone number in E.164 form when no email is present, so one identifier matches a lead across analytics, the stored entry, the notification email, and downstream systems.lead_id_source, recording whether the hash came from the email address or the phone number.cuft_lead_id and cuft_lead_id_phone_country filters.cuft_service_interest_map option and the cuft_service_interest and cuft_form_attribution_payload filters.rdt_cid to the tracked click IDs.qualify_lead for a submission with email, phone, and click ID; generate_lead for any submission with a valid email address.ga_client_id capture from the _ga cookie at submission time.HTTP_CF_CONNECTING_IP.