Skip to main content
Concrete CMS → GA4Integration guide

Connecting Concrete CMS to GA4

Concrete CMS is a self-hosted PHP CMS used by small organisations and agencies, typically with commerce added through an add-on rather than built in. GA4 is where the business goes to understand what happened, which makes an unattributed conversion there expensive in decisions rather than in ad spend. The join between them succeeds or fails on one thing: whether the GA4 client_id reaches the Concrete CMS order before the conversion happens, because GA4 can only match a conversion it can tie back to a click it recognizes.

Free30 minutesNo obligation

Specific to this pair

Concrete CMS to GA4: purchase events that survive full page cache

Concrete CMS full page caching breaks template-based GA4 purchase tags. Store the _ga client_id and session_id as order attributes when the commerce add-on creates the order, then send purchase through Measurement Protocol from the order-completed event, with the order number as transaction_id, and remove the template call. Brochure sites send generate_lead from Express entries.

On a Concrete CMS store, GA4 purchase tracking usually lives in the order confirmation template, and Concrete's full page cache is what breaks it. If that page is served from cache, the template never runs and the gtag purchase call never fires, or a cached copy fires with a previous order's values. Exclude checkout and confirmation pages from full page caching first, then decide which side owns purchase.

The better owner is the server. Read the client_id from the _ga cookie, and the session_id from the _ga_<ID> cookie, in PHP at the moment the commerce add-on creates the order, and store both as order attributes. When the add-on's order-completed event fires, send purchase through Measurement Protocol with transaction_id set to the order number, value and currency from the add-on's totals, and items built from the order lines. Then remove the template's purchase call, or GA4 counts every order twice and does nothing to stop it.

Which add-on you run decides the hook. Community Store is the common one and dispatches its own order events, but confirm the event names in your installed version, since nothing transfers between two Concrete sites unchecked. On brochure-only Concrete sites the GA4 conversion is generate_lead sent when an Express form entry is saved, not purchase. Refunds or cancellations the add-on records can go to GA4 as a refund event with the same transaction_id.

Concrete CMS → GA4 · what breaks

Why this join usually fails — Concrete CMS → GA4

  1. 01

    The GA4 client_id never reaches Concrete CMS

    Full page caching and the add-on ecosystem both interfere. Because commerce is an add-on rather than core, where the order is created differs by which add-on is installed.

  2. 02

    Commerce as an add-on, not a core feature

    Which add-on is installed determines the entire integration surface, so nothing transfers between two Concrete sites without checking first.

  3. 03

    Everything lands under (direct)/(none)

    The classic Measurement Protocol symptom, and it always means the same thing: the client_id was missing or wrong, so GA4 had no session to attach the event to and invented a new user.

  4. 04

    Nothing reconciles afterwards

    GA4 DebugView shows events arriving with their parameters, and Realtime confirms the user and session they attached to. Neither tells you whether you are double-counting — for that, compare GA4's conversion count against the source system's record for the same window.

Concrete CMS → GA4 · the fix

What working looks like — Concrete CMS → GA4

  • The GA4 client_id on every order

  • Conversions arriving in GA4

    Conversions appear in GA4 attributed to the original session's source and medium, so acquisition reporting reflects what actually drove revenue.

  • Reconciled against the source

Concrete CMS → GA4 · architecture

How Concrete CMS and GA4 actually connect

  1. 1

    Concrete CMS holds the truth

    Self-hosted means you own and can query the data directly, which makes reconciliation straightforward once the identifier is present.

  2. 2

    GA4 needs a click to match

    GA4 needs the client_id to attach a server event to the user and session that produced the click. Without it there is no attribution to inherit and the event is orphaned.

  3. 3

    The identifier is the bridge

    The GA4 client_id, captured at the visit and carried into the Concrete CMS order.

  4. 4

    The event source decides reliability

    Fire server-side. Page caching means the confirmation template may not execute.

  5. 5

    Deduplication at the destination

    GA4 has no built-in deduplication for Measurement Protocol. If the browser also sends the event, it counts twice. The only reliable approach is to decide per event which side sends it, and enforce that decision — GA4 will not protect you from your own duplicates.

Concrete CMS → GA4 · normal or broken

Working vs. broken, side by side — Concrete CMS → GA4

Normal, not a fault

  • The GA4 client_id present on the order

  • Sent from the order-completed event from your commerce add-on

  • Deduplicated correctly

    GA4 has no built-in deduplication for Measurement Protocol. If the browser also sends the event, it counts twice. The only reliable approach is to decide per event which side sends it, and enforce that decision — GA4 will not protect you from your own duplicates.

  • Reconciles with the source

Actually broken

  • The GA4 client_id missing on most records

    Full page caching and the add-on ecosystem both interfere. Because commerce is an add-on rather than core, where the order is created differs by which add-on is installed.

  • Sent from the browser

    Which add-on is installed determines the entire integration surface, so nothing transfers between two Concrete sites without checking first.

  • Counted twice, or not at all

    The classic Measurement Protocol symptom, and it always means the same thing: the client_id was missing or wrong, so GA4 had no session to attach the event to and invented a new user.

  • Nobody has reconciled it

Concrete CMS → GA4 · diagnose

Diagnostic order — Concrete CMS → GA4

  1. 01

    Count orders carrying the GA4 client_id

  2. 02

    Follow one real conversion end to end

  3. 03

    Identify which commerce add-on is in use

    That decides where the order is created and what events exist to hook.

  4. 04

    Check whether server events carry a session and a source

    In GA4 Realtime or DebugView, find the event and look at its session and traffic source. A brand-new user with no source on every server event is the missing-client_id signature.

  5. 05

    Read the match rate, not the success message

    GA4 DebugView shows events arriving with their parameters, and Realtime confirms the user and session they attached to. Neither tells you whether you are double-counting — for that, compare GA4's conversion count against the source system's record for the same window.

Concrete CMS → GA4 · prove

How it gets proven — Concrete CMS → GA4

Source of truth

Your own database's order records, at agreed states.

  • The GA4 client_id confirmed present on a live order.
  • Server events confirmed in DebugView attaching to an existing session with the original traffic source intact.
  • Order attribute confirmed populated on a purchase through a cached page.

Concrete CMS → GA4 · free audit

What we check in the free audit

  • Count orders carrying the GA4 client_id
  • Follow one real conversion end to end
  • Identify which commerce add-on is in use
  • Check whether server events carry a session and a source
  • Read the match rate, not the success message

Checked against

Your own database's order records, at agreed states.

Get my free conversion tracking audit

Free30 minutesNo obligation

Concrete CMS → GA4 · when not to buy

When you don't need this — Concrete CMS → GA4

  • Measure identifier coverage yourself first — pull your last hundred orders and count how many carry the GA4 client_id.
  • If your Concrete site is brochure-only with enquiries by form, treat it as a lead-gen site rather than a commerce one.
  • GA4 is reporting, not bidding. If your problem is ad platform optimization, fix the ad platform destination first — GA4 will not change what any campaign does.

Concrete CMS → GA4 · what we deliver

What gets built — Concrete CMS → GA4

  • Identifier capture into Concrete CMS

    Capture the identifier and persist it as order attribute data through an event listener, server-side.

  • Event source: the order-completed event from your commerce add-on

    Fire server-side. Page caching means the confirmation template may not execute.

  • Dispatch to GA4

    Measurement Protocol events with the client_id, session_id and the correct measurement ID and API secret, sent server-side.

  • Reversals where they exist

    Order state changes recorded by the add-on.

Written guarantee

95%+ tracking accuracy

Measured against Your own database's order records, at agreed states.. Miss it and we keep working at no cost.

Get my free conversion tracking audit

From $400 · See packages

FAQ

Concrete CMS → GA4 questions, answered

Usually the order confirmation page is served from Concrete's full page cache, so the purchase tag never runs. Exclude checkout and confirmation pages from caching, or send purchase server-side from the commerce add-on's order event.

Free · 30 min

Get Concrete CMS and GA4 joined properly

Free30 minutesNo obligation