Setup guide and documentation
How to set up the GTM template for B2B websites
Import, configure and test the MILOWSKI.EU B2B GTM TEMPLATE v1.0 container step by step. Below you will also find the full documentation: events, GA4 parameters, Google Ads conversions, tags, triggers and variables.
What the template measures
The template adds contact tracking to Google Tag Manager for B2B websites without a shop: email and phone clicks, copied email addresses or phone numbers, and engaged time. Once you import it and enter your GA4 ID, more than 5 GA4 events work straight away. The remaining elements are paused, because they need your decision or an adjustment to your website.
| Event | When it fires | Parameters | Status after import |
|---|---|---|---|
| CLICK_EMAIL | click on a mailto: link | link_text | active |
| CLICK_TEL | click on a tel: or callto: link | link_text | active |
| COPY_TEL_OR_MAIL | an email address or phone number copied from the page | link_text, CONTACT_TYPE | active |
| MORE_THAN_1_MINUTE | 60 s of active time in a session (summed across pages, visible tab only), once per session | none | active |
| generate_lead | successful form submission | form_id, form_name, lead_source | paused, needs adjustment |
| Google Ads conversions: CLICK_EMAIL, CLICK_TEL, COPY_TEL_OR_MAIL | same as the GA4 events, at most once per page view | none | paused, need conversion labels |
| GO_TO_MAP | click on a Google Maps or OpenStreetMap link | link_url, link_text | active |
The full email address or phone number never reaches GA4 or Google Ads. Details are in the “Privacy and consent” section.
Requirements before import
For basic tracking you only need a GTM container, a GA4 property and a consent banner with Consent Mode v2. The other requirements apply only to the elements you want to switch on.
| Requirement | What it is for | Required |
|---|---|---|
| A correctly installed Web GTM container and permission to publish | importing and publishing the template | yes |
| A GA4 property and its measurement ID (G-…) | receiving the events | yes |
| A consent banner (CMP) with Consent Mode v2 | GDPR-compliant tag firing | yes, for EU traffic |
| A Google Ads account with 3 Website conversion actions | CLICK_EMAIL, CLICK_TEL and COPY_TEL_OR_MAIL conversions | if you run Google Ads campaigns |
| Forms built with Contact Form 7, or a developer | the generate_lead event | if you want to track forms |
| A CRM that receives hidden form fields | first and last visit source on each lead | optional |
The template is free.
Import into GTM step by step
Always import with the “Merge” option into a new workspace. The “Overwrite” option deletes everything in the workspace that is not in the file, which means your website’s existing tags.
- In GTM, open Admin → Import Container.
- Choose the file MILOWSKI.EU_B2B_GTM_TEMPLATE_v1.0_EN.json.
- Choose a New workspace, for example “Import B2B template”. You will see the changes before publishing and can discard them in one click if needed.
- Choose the Merge import option, then Rename conflicting tags, triggers and variables.
- Confirm the import. All elements land in the MILOWSKI.EU B2B GTM TEMPLATE v1.0 folder.
- Go to “Configuration after import”. Do not publish the container before running the tests from “Testing before you publish”.
Configuration after import
Two steps are enough for GA4 tracking alone: enter your GA4 ID and decide what to do with the main tag. You only set up the Google Ads elements and contact labels if you need them.
| Element in GTM | What to do | When |
|---|---|---|
| Variable “ENTER YOUR GA4 MEASUREMENT ID HERE” | enter the measurement ID that starts with G- | always |
| Tag “GA4 – MAIN TAG – …” | you already have a Google tag for GA4 → delete this tag; you don’t → enable it | always |
| Variable “ENTER YOUR GOOGLE ADS CONVERSION ID HERE” | enter only the digits from the AW-123456789 ID | if you use Google Ads |
| Tag “Google Ads – MAIN TAG – …” | you already have a Google tag with this AW- ID → delete this tag; you don’t → enable it | if you use Google Ads |
| Tags “Google Ads – CLICK_EMAIL”, “- CLICK_TEL”, “- COPY_TEL_OR_MAIL” | in the Conversion Label field replace ENTER_YOUR_CONVERSION_LABEL_FROM_GOOGLE_ADS with the label from your account, then enable | if you use Google Ads |
| Tag “Google Ads – Conversion Linker” | enable it if the container has no other Conversion Linker | if you use Google Ads |
| Variable “LT – CONTACT_LABEL” | give names to your contacts and delete the 2 example rows | optional |
You will find the measurement ID in GA4 under Admin → Data streams → your stream. Do not enter the GA4 account ID, which consists of digits only, because the events will not reach anywhere.
Google Ads shows the conversion ID and label when you set up the conversion tag and choose Google Tag Manager. In AW-123456789/AbC-D_efG the ID is 123456789 and the label is AbC-D_efG.
In the contact label table, enter email addresses in lowercase ([email protected]) and phone numbers as digits only, including the country code and without + or spaces (442071234567). Polish numbers are the exception: enter them as 9 digits without 48 (600100200). Contacts that are not in the table are sent to GA4 masked.
GA4 events and parameters
Wherever possible, the template uses parameters that are built into GA4, so it does not use up your custom dimension quota. Only 2 parameters need registering: CONTACT_TYPE and lead_source. Do it in GA4 under Admin → Custom definitions → Create custom dimension, scope “Event”.
| Event parameter | Dimension in GA4 | Example values | Events |
|---|---|---|---|
| link_text | built-in “Link text”, no registration | Office, in***(at)company.com, ***789, Directions | CLICK_EMAIL, CLICK_TEL, COPY_TEL_OR_MAIL, GO_TO_MAP |
| link_url | built-in “Link URL”, no registration; GA4 also derives “Link domain” from it | https://maps.app.goo.gl/AbC123 | GO_TO_MAP |
| form_id | built-in “Form ID”, no registration | 123 | generate_lead |
| form_name | built-in “Form name”, no registration | Contact form | generate_lead |
| CONTACT_TYPE | needs registering | email, phone | COPY_TEL_OR_MAIL |
| lead_source | needs registering | website_form (default), quote, demo | generate_lead |
For email and phone clicks, link_text holds the label from the “LT – CONTACT_LABEL” table or a mask, not the link text from the page, because that text is often the address itself. For the same reason, the contact tags do not send link_url. CONTACT_TYPE is only sent with COPY_TEL_OR_MAIL, because for clicks the type is already in the event name. You can read the map provider for GO_TO_MAP from “Link domain”. Data appears in reports with a delay of up to 48 hours, and in DebugView immediately.
Mark CLICK_EMAIL, CLICK_TEL and COPY_TEL_OR_MAIL as key events, and generate_lead too once you switch forms on. MORE_THAN_1_MINUTE is meant for segments and audiences, so do not import it into Google Ads as a primary conversion.
Uppercase marks the events and parameters of this template, so you can tell them apart from the names in Google’s documentation. Google’s names, meaning generate_lead, link_url, link_text, form_id, form_name and lead_source, are lowercase. Register CONTACT_TYPE in uppercase, because GA4 is case-sensitive.
Google Ads conversions
Each conversion tag fires at most once per page view. Two clicks on the same email address therefore do not create two conversions, even if Google Ads is set to count “Every”.
- In Google Ads, create 3 Website conversion actions, one each for CLICK_EMAIL, CLICK_TEL and COPY_TEL_OR_MAIL.
- Set counting to “One” for each, meaning one conversion per ad click. That is the standard for leads.
- In B2B, an email or phone contact is a weaker signal than a submitted form or a sale from your CRM. Set these conversions as secondary if you have a better primary conversion. Make them primary only when they are your only signal.
- Do not count the same action from two sources. If you import GA4 key events with the same names into Google Ads and also enable the template’s tags, only one source can be the primary conversion. Otherwise every conversion is counted twice.
- Google Ads tags check the ad_storage consent from Consent Mode v2 on their own, so they need no extra consent settings in GTM.
Paused elements: forms and visit source for your CRM
Form tracking and passing the source to your CRM depend on the CMS and CRM of each website. That is why these elements are paused and will not work just by enabling them.
Forms: generate_lead
The “GA4 – generate_lead” tag fires on the formSubmitSuccess event in the dataLayer. The form has to push it after a successful submission, not when someone clicks “Send”, because then submissions with errors would count too.
- Contact Form 7 (WordPress): enable the “cHTML – listener Contact Form 7” tag together with the generate_lead tag. form_id is the CF7 form ID, form_name is its title.
- Other forms (Elementor, Gravity Forms, HubSpot, custom): a developer adds this code after a successful submission:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ event: 'formSubmitSuccess', formId: 'contact', formName: 'Contact form', leadSource: 'website_form' });
- Exclude your recruitment form in the “formSubmitSuccess” trigger with a condition on the “DLV – formName” variable, for example does not contain “CV”, “Careers” or “Jobs”. Check your form names in GTM Preview. Without this, job applications inflate your lead count.
First and last visit source for your CRM
The “cHTML – first and last visit source to CRM” tag stores in the visitor’s browser where they came from and writes that data into hidden form fields. You then see the source of every lead in your CRM, and the gclid lets you import offline conversions into Google Ads, such as a signed contract.
| Hidden field | Content | Example |
|---|---|---|
| FIRST_SOURCE_MEDIUM | source / medium of the first visit within 90 days | linkedin / paid_social |
| LAST_SOURCE_MEDIUM | the last source other than direct | google / organic |
| LAST_CAMPAIGN | utm_campaign of the last source | autumn_2026 |
| GCLID | the last Google Ads click ID | Cj0KCQjw… |
The source is determined in this order: UTM parameters, gclid (google / cpc), msclkid (bing / cpc), fbclid (facebook / social), the referring site (search engines as organic, others as referral) and finally (direct) / (none). Navigation within your domain and its subdomains does not change the source. The data expires after 90 days, and you can change that period in the script (VALID_DAYS).
Before you enable it:
- Add hidden fields to the form in your CMS, with the names or ids from the table. Names such as form_fields[GCLID] from Elementor work too.
- Map those fields in your CRM.
- The tag requires analytics_storage and ad_storage consent. Add a second trigger on the consent update event from your CMP. Without it, the source from the first page view is not saved for a visitor who only gives consent on that page.
- Add to your privacy policy that the visit source is sent to your CRM together with the form.
I implement the template from €125 net. On request I add Consent Mode v2, Microsoft Clarity, LinkedIn Insight Tag and Meta Pixel.
Privacy and consent
GA4 and Google Ads only receive the contact label or its mask in the link_text parameter, plus the contact type for copies, never the full email address or phone number. Google Analytics policies forbid sending personal data to GA4, and named sales reps’ addresses are personal data.
- Emails are masked to the first 2 characters and the domain, for example in***(at)company.com. The value sent contains no @ sign.
- Phone numbers are masked to the last 3 digits, for example ***789.
- The full value exists only in the browser, in the “DLV – copiedText” and “JS – CONTACT_NORMALIZED” variables. Do not use these variables in any tag that sends data.
| Tag | Consent setting in GTM | Why |
|---|---|---|
| GA4 and Google Ads tags, including the main tags and the Conversion Linker | No additional consent required | Google tags check analytics_storage and ad_storage from Consent Mode v2 on their own |
| cHTML – contact copy listener, cHTML – listener Contact Form 7 | No additional consent required | they only pass an event to the dataLayer and store or send nothing |
| cHTML – active time counter | analytics_storage | stores the session counter in localStorage |
| cHTML – first and last visit source to CRM | analytics_storage and ad_storage | stores visit sources and gclid in localStorage |
Without a consent banner with Consent Mode v2, Google tags send data without the user’s consent. For EU traffic that breaks the GDPR and the ePrivacy rules.
Settings outside GTM (in GA4)
Three things that are useful on B2B websites are set up in GA4, not in the container, so the template does not include them.
| What | Where in GA4 | Notes |
|---|---|---|
| File downloads (PDF, PPTX, DOCX, XLSX, ZIP and others) | Admin → Data streams → your stream → Enhanced measurement | only works for links that end with a file extension; event file_download |
| Site search | Enhanced measurement → Site search | GA4 recognises the q, s, search, query and keyword parameters; WordPress (s) works straight away, in other CMSs check the parameter in the results URL and add it if needed |
| Excluding your company’s own traffic | Your stream → Configure tag settings → Define internal traffic, then Admin → Data filters | only with a fixed company IP address; test the “Internal traffic” filter in testing mode first, then activate it |
Testing before you publish
Before publishing, start GTM Preview, accept consent in the banner and tick off the scenarios below. Check the GA4 events in DebugView at the same time (GA4: Admin → DebugView).
Ticked 0 of 10
When everything checks out, click Submit and publish a version, for example “MILOWSKI.EU B2B GTM TEMPLATE v1.0”.
Common problems
| Symptom | Most common cause | Fix |
|---|---|---|
| No events reach DebugView | the GA4 ID variable holds the account ID (digits only) instead of G-…, or no Google tag loads GA4 | correct the ID; enable the GA4 main tag or check your existing one |
| Two page_views on every page view | the template’s GA4 main tag runs alongside another Google tag or plugin | keep a single GA4 implementation |
| CLICK_EMAIL or CLICK_TEL does not fire | the button is not a mailto: or tel: link but a script | check the Click URL variable in Preview; such a button needs its own trigger |
| COPY_TEL_OR_MAIL does not fire | more than 120 characters were selected, the number has neither 9 digits nor a country code with + or 00, or the page blocks copying | copy just the address or number; check whether the copyContact event appears in Preview |
| MORE_THAN_1_MINUTE does not fire | no analytics_storage consent, or consent was given on the current page | the counter starts from the next page view after consent |
| CONTACT_TYPE does not show in reports | the parameter is not registered as a custom dimension, or less than 48 hours have passed | register the dimension and wait |
| Google Ads conversions counted twice | the template’s tags and key events imported from GA4 are both primary conversions | keep one source as primary |
| Duplicate events after updating the template | elements from an older version remain in the container | delete the old elements before publishing |
Container contents
The container holds 16 tags, 6 triggers and 10 variables, all in the MILOWSKI.EU B2B GTM TEMPLATE v1.0 folder. After import, 7 tags are active and 9 are paused.
Tags
| Tag | Type | Status after import | Trigger |
|---|---|---|---|
| GA4 – MAIN TAG – IF YOU ALREADY HAVE A GOOGLE TAG FOR GA4 IN GTM, DELETE THIS ONE. IF NOT, SET YOUR GA4 ID IN THE VARIABLES AND ENABLE | Google tag | paused | All Pages |
| Google Ads – MAIN TAG – IF YOU ALREADY HAVE A GOOGLE ADS TAG IN GTM, DELETE THIS ONE. IF NOT, SET YOUR CONVERSION ID IN THE VARIABLES AND ENABLE | Google tag | paused | All Pages |
| GA4 – CLICK_EMAIL | GA4 event | active | mailto click |
| GA4 – CLICK_TEL | GA4 event | active | tel click |
| GA4 – COPY_TEL_OR_MAIL | GA4 event | active | copyContact |
| GA4 – MORE_THAN_1_MINUTE | GA4 event | active | activeTime60s |
| GA4 – generate_lead | GA4 event | paused | formSubmitSuccess |
| Google Ads – CLICK_EMAIL | Google Ads conversion, once per page view | paused | mailto click |
| Google Ads – CLICK_TEL | Google Ads conversion, once per page view | paused | tel click |
| Google Ads – COPY_TEL_OR_MAIL | Google Ads conversion, once per page view | paused | copyContact |
| Google Ads – Conversion Linker | Conversion Linker | paused | All Pages |
| cHTML – contact copy listener | Custom HTML | active | All Pages |
| cHTML – active time counter | Custom HTML | active, requires analytics_storage consent | All Pages |
| cHTML – listener Contact Form 7 | Custom HTML | paused | All Pages |
| cHTML – first and last visit source to CRM | Custom HTML | paused, requires analytics_storage and ad_storage consent | All Pages |
| GA4 – GO_TO_MAP | GA4 event | active | map click |
Triggers
| Trigger | Type | Condition |
|---|---|---|
| mailto click | Click – Just Links | Click URL starts with mailto:, case-insensitive |
| tel click | Click – Just Links | Click URL starts with tel: or callto: |
| copyContact | Custom Event | pushed by the copy listener tag |
| formSubmitSuccess | Custom Event | pushed by the Contact Form 7 listener or the developer’s code |
| activeTime60s | Custom Event | pushed by the active time counter |
| map click | Click – Just Links | Click URL contains a Google Maps address (google.*/maps, maps.google., maps.app.goo.gl, goo.gl/maps) or OpenStreetMap (openstreetmap.org, osm.org) |
Variables
| Variable | Type | What it is for |
|---|---|---|
| ENTER YOUR GA4 MEASUREMENT ID HERE | Constant | GA4 measurement ID for the main tag and all GA4 events |
| ENTER YOUR GOOGLE ADS CONVERSION ID HERE | Constant | digits from AW-… for the main tag and Google Ads conversions |
| DLV – copiedText | Data Layer Variable | the copied contact, browser only |
| JS – CONTACT_NORMALIZED | Custom JavaScript | the contact in a uniform format for the label table; do not send |
| JS – CONTACT_TYPE | Custom JavaScript | email or phone for CONTACT_TYPE |
| JS – CONTACT_MASKED | Custom JavaScript | mask such as in***(at)company.com or ***789 |
| LT – CONTACT_LABEL | Lookup Table | contact label or mask, sent as link_text |
| DLV – formId | Data Layer Variable | form ID for form_id |
| DLV – formName | Data Layer Variable | form name for form_name |
| DLV – leadSource | Data Layer Variable | lead source for lead_source, website_form by default |
The template also enables these built-in variables: Click URL, Click Text, Event, Page URL, Page Hostname, Page Path and Referrer.
Limitations, version history and help
The template covers tracking that works the same way on every website. Anything that depends on your CMS, CRM or third-party tools needs adjusting.
- Copied phone numbers are recognised as Polish numbers (9 digits) or international numbers written with + or 00. A local number copied without a country code, such as 020 7123 4567, is not recognised.
- Forms other than Contact Form 7, chats and booking calendars are not tracked without adjustment.
- The time counter treats a visible tab as activity, even when the user does not move the mouse.
- Contact buttons that work through JavaScript rather than a mailto: or tel: link need their own trigger.
- GO_TO_MAP only tracks clicks on a link to a map. GTM cannot see clicks inside a map embedded on the page (iframe).
| Version | Date | Changes |
|---|---|---|
| v1.0 | 2 October 2026 (English edition 13 October 2026) | first release |
MILOWSKI.EU implements the template with form tracking adjusted to your site, visit source passed to your CRM and full testing, from €125 net. See the implementation scope.
Template implementation from €125 net. The complete package also covers Consent Mode v2, Microsoft Clarity, LinkedIn Insight Tag and Meta Pixel.