Tracking and consent
Evolve renders one tag manager snippet, a Google Consent Mode default, an optional cookie banner, and sends two conversions from the server. Everything else about analytics and ads lives in the GTM container. This page is the webmaster's view: what fires where, which keys control it, and how to check. The container itself is on Google Tag Manager.
What renders on a page
In the theme's head partial, in this order:
- Site verification meta tags for Google, Bing and Pinterest, on every environment.
- Consent Mode default: a small inline script that defines
dataLayer, reads theevolve_consentcookie and callsgtag('consent', 'default', ...)with the visitor's state or the policy default. It renders on every environment, so the consent state is always defined before any tag. - The GTM snippet, only when the site emits tracking and
GTM_IDis set. - Hotjar, only when the site emits and
HOTJAR_SITE_IDis set. - Legacy globals (
window.GOOGLE_ANALYTICS_TRACKINGand friends), only when emitting andTRACKING_LEGACY_VARIABLES=true. Deprecated, for projects still on the pre-GTM adapter bundle. - The consent platform script (Cookiebot), last in the head partial, only when a consent platform driver and its ID are set. It renders on every environment, like the Evolve banner.
No GA4, Meta, LinkedIn or Pinterest script is loaded by Evolve. Those tags live in the GTM container and fire on the events the theme pushes to the dataLayer.
When a site emits
Tracking follows the environment type since 2.11: live emits, staging and development stay silent. A silent site still renders the consent script and the verification tags, but no GTM snippet, no Hotjar, no legacy globals, and the server sends no conversions. The same IDs can therefore sit in every .env without test traffic reaching the customer's properties.
| Variable | Default | Effect |
|---|---|---|
TRACKING_EMIT | unset | true makes a staging site fire for a test run, false silences a live site. Unset lets the type decide. A developer sets it in the Developers tab. |
The tracking health check fails on live when GTM or GA4 is missing or malformed or TRACKING_EMIT=false silences the site, and warns when TRACKING_EMIT=true is left on staging. HEALTH_CHECK_TRACKING=false turns the check off for a site without tracking on purpose.
Platform IDs
Public IDs are edited in the panel under Settings > Tracking. Since 2.18 the panel saves them to the settings store; the .env variable of the same setting is the fallback for an empty field, and the field's placeholder shows it. The GTM container must look like GTM-… and the GA4 ID like G-…, otherwise nothing is saved.
| Setting | .env fallback | Feeds |
|---|---|---|
| GTM container | GTM_ID | the snippet |
| GA4 measurement ID | ANALYTICS_MEASUREMENT_ID | server-side GA4 events, legacy globals, health check |
| Meta pixel | FACEBOOK_PIXEL_ID | server-side Meta events, legacy globals |
| Pinterest tag | PINTEREST_TAG_ID | legacy globals |
| LinkedIn partner ID, lead conversion ID | LINKEDIN_PARTNER_ID, LINKEDIN_LEAD_CONVERSION_ID | legacy globals |
| Hotjar site ID | HOTJAR_SITE_ID | the Hotjar snippet |
| Conversion data | ANALYTICS_CONVERSION_DATA (goals) | which analytics figures the panel dashboard shows |
Secrets never enter the store: ANALYTICS_API_SECRET and FACEBOOK_CONVERSIONS_API_KEY stay in the .env.
Consent
Evolve implements Google Consent Mode v2 with two categories, analytics and marketing, mapped to analytics_storage and ad_storage, ad_user_data, ad_personalization.
| Variable | Default | Effect |
|---|---|---|
TRACKING_CONSENT_POLICY | opt-out | opt-out: both categories granted until the visitor declines, the Swiss default under the DSG. opt-in: both denied until the visitor accepts, as the GDPR requires; tags wait up to 500 ms for the update. |
APP_COOKIE_CONSENT | false | Show the Evolve cookie banner. |
TRACKING_CONSENT_VIA_CMP | false | A consent management platform (Cookiebot, Usercentrics and the like) runs in the GTM container and sends the consent update (2.18). |
TRACKING_CMP | unset | A consent platform Evolve loads itself and bridges into its own consent cookie. cookiebot is the only driver (2.19). Counts as a consent platform, like the option above. |
COOKIEBOT_ID | unset | The domain group ID from the Cookiebot manager, used with TRACKING_CMP=cookiebot (2.19). |
All of them sit on the Consent card of Settings > Tracking (since 2.18; the banner switch was on the General tab before) and are saved to the settings store as consent_policy, cookie_banner, consent_via_cmp, consent_cmp and cookiebot_id. The variables are the fallback.
Opt-in needs somebody who grants it 2.18
With opt-in, Consent Mode starts denied. If neither the banner nor a consent platform can send the update, GA4 and Meta silently never measure. The panel therefore refuses to save opt-in with the banner off and the CMP option off, shows the error at the top of the form and writes nothing. An inherited value counts the same as a posted one.
What the consent script emits per combination:
| Policy | Banner | CMP option | Default | wait_for_update | EVOLVE_CONSENT.managedBy |
|---|---|---|---|---|---|
| opt-out | any | any | granted | no | evolve, or cmp with the option on |
| opt-in | on | off | denied | 500 ms | evolve (the banner grants) |
| opt-in | off | on | denied | 500 ms | cmp (the platform in GTM grants) |
| opt-in | on | on | denied | 500 ms | cmp (allowed, both ask) |
| opt-in | off | off | denied | 500 ms | evolve; only reachable through the .env |
The CMP mode keeps Evolve's denied defaults: if the platform is missing from the container, tags stay gated instead of firing ungated. The last row stays denied as well (no silent fallback to granted); the tracking health check warns about it, and with APP_DEBUG=true the browser console does too. window.EVOLVE_CONSENT also carries banner: true|false.
The banner is the theme component x-common.cookie-consent, included by the default layout when the banner is on. It offers accept all and decline, links to the privacy policy from the evolve::cookieconsent language strings, and writes the choice to the evolve_consent cookie for 365 days. On a change the theme calls gtag('consent', 'update', ...) and pushes consent_analytics and consent_marketing to the dataLayer, so GTM triggers can react. The policy, cookie name and lifetime sit in config/evolve.php under tracking.consent; only the policy has a variable.
With the banner off and opt-out, everything is granted and nobody can decline. That is a legal decision for the site owner, not a default to leave unexamined. The going-live checklist counts the cookie notice as done when the banner, the CMP option or a consent platform driver is on.
External consent platforms 2.19
Google Consent Mode is only half of the consent state. The other half is the evolve_consent cookie, which the server and a few scripts read: server-side conversions, Hotjar, the client tracking config and, since 2.19, the footprints cookie. The Evolve banner writes that cookie. A consent platform in the GTM container (TRACKING_CONSENT_VIA_CMP alone) does not, so under opt-in everything that reads the cookie stays denied for the whole visit, even after the visitor accepted.
Cookiebot. Choose Cookiebot as the consent platform on the Consent card of Settings > Tracking (or set TRACKING_CMP=cookiebot) and enter the Cookiebot ID (COOKIEBOT_ID). Evolve then:
- renders Cookiebot's
uc.jswithdata-blockingmode="auto", synchronous (noasync, nodefer), as the last element of the head partial. That is after the Consent Mode default, the GTM snippet and the module globals, the order Cookiebot documents for Google Consent Mode: the Google tags follow Cookiebot's consent signals, everything rendered later is blocked until the visitor consents. Evolve's own scripts and styles carrydata-cookieconsent="ignore". - listens to
CookiebotOnConsentReady,CookiebotOnAcceptandCookiebotOnDeclineand copies Cookiebot's statistics choice to analytics and its marketing choice to marketing in theevolve_consentcookie. A choice equal to the stored cookie is not written again, so the cookie does not change on every page load. - counts as a consent platform: opt-in without the Evolve banner can be saved, and
EVOLVE_CONSENT.managedByiscmp. Cookiebot sends the Consent Mode update itself, Evolve does not send a second one.
Do not load Cookiebot a second time, neither in the GTM container nor in a project partial. Leave the Evolve banner off; both would ask the visitor.
Other platforms. Any platform can feed the Evolve cookie by calling window.evolveConsentUpdate({ analytics, marketing }) from its consent callback. The theme developer wires this up, see Consent in the theme. Without that call (or the Cookiebot driver) the tracking health check warns on an opt-in site whose only consent source is TRACKING_CONSENT_VIA_CMP, and with TRACKING_CMP=cookiebot but no ID.
Cookies Evolve sets 2.19
Copy these into the cookie declaration of the consent platform. Cookies of the GTM tags (_ga, _fbp and the like) come from the container and are declared there.
| Cookie | Category | Purpose | Lifetime | Set |
|---|---|---|---|---|
evolve_consent | necessary | Stores the visitor's consent choice (analytics, marketing) | 365 days | by the browser, when the visitor decides in the banner or the consent platform |
footprints | marketing | Attribution: ties landing page, referrer, UTM parameters and gclid of each visit to a later lead | up to 400 days (the configured 5 years are capped by the browser) | by the server, only after marketing consent |
Laravel session (<app name>_session, config/session.php) | necessary | Session, form protection, attribution data of the current visit before consent | SESSION_LIFETIME (120 minutes by default), ends with the browser session when SESSION_EXPIRE_ON_CLOSE=true | by the server, on every visit |
XSRF-TOKEN | necessary | Protects forms and Livewire requests against cross-site request forgery | same as the session | by the server, on every visit |
Server-side conversions
Two events leave the server, independent of the browser:
| Event | Fired when | Platforms |
|---|---|---|
generate_lead | a form is submitted successfully | GA4 Measurement Protocol, Meta Conversions API |
purchase | a Shopify order is created or updated | GA4 Measurement Protocol, Meta Conversions API |
| Variable | Default | Effect |
|---|---|---|
TRACKING_SERVER_SIDE | true | Master switch. Drivers without credentials disable themselves, so leaving it on is safe. Settings > Tracking (settings store server_side, the variable is the fallback). |
ANALYTICS_API_SECRET | unset | GA4 Measurement Protocol secret for the measurement ID. Without it the GA4 driver is off. |
FACEBOOK_CONVERSIONS_API_KEY | unset | Meta CAPI access token for the pixel. Without it the Meta driver is off. |
FACEBOOK_TEST_EVENT_CODE | unset | Routes Meta events to the test events view in Events Manager. |
TRACKING_QUEUE | unset | Queue name for the delivery jobs. Unset uses the default queue. |
TRACKING_DEBUG | false | Sends GA4 events to the validation endpoint instead of the live one and logs every delivery. |
Each event is recorded in the request with the consent state at that moment, then delivered through queued jobs. A visitor who declined marketing produces no Meta event. Personal data (email, phone) is normalised and SHA-256 hashed before it leaves. The browser receives the same event ID for its own dataLayer push, so GA4 and Meta can deduplicate the client and server copy of one conversion. With QUEUE_CONNECTION=sync the delivery runs inline; with a worker the queue health check reports a stalled worker.
Events from the browser
The theme pushes canonical GA4-style events to the dataLayer: generate_lead and form_submit from the form component, purchase and the e-commerce funnel from the shop, and anything an element declares with an x-track attribute, for example the social icons' outbound clicks. The GTM container maps those events to the platform tags. The event catalogue and payloads are on the Google Tag Manager page.
Attribution
Independent of the tags above, every visit records its landing page, referrer, UTM parameters and gclid, so a lead can be attributed to its source in the panel. The visits of the current browser session sit in the session and are attached to a lead created in it. The footprints cookie carries a random token that links visits across sessions; browsers keep it for at most 400 days.
Since 2.19 the footprints cookie is only set once the visitor granted marketing consent. Until then the token stays in the session, so a lead submitted in the same visit keeps its attribution, and the first page after the consent click writes the cookie with that same token. A visitor who withdraws marketing consent has the cookie removed on the next page. Under opt-out consent is granted by default, so the cookie is set on the first page as before. A site with an external consent platform needs the bridge, otherwise marketing consent is never seen and the cookie is never set.
Configuration is config/tracking.php (cookie name, duration, excluded paths such as img, api, evolve); it has no .env keys.
Verifying
- On live,
curl -s https://<domain>/ | grep -c gtm.jsis1; on staging it is0unlessTRACKING_EMIT=true. - GTM Preview mode shows the container, the consent default and the
dataLayerevents as you click through. /evolve/healthruns the tracking check.GET /api/health-checkcarries atrackingblock with every ID, the emit decision, the consent policy,cookie_banner,consent_via_cmpandconsent_cmp, whether the secrets are set, andsourcessaying whether a value came from the store or the config. Fleet reads that block for every site.- With
TRACKING_DEBUG=true, submit a form on staging withTRACKING_EMIT=trueand read the GA4 validation response in the log.
Step by step
The recipe Set up tracking and consent walks through policy, container, IDs, a test run on staging and the live checks in order.