Skip to content

Built-in bridges

A consent bridge loads a supported vendor and forwards the visitor’s decision to it, live — so the tag keeps sending privacy-safe signals under denied consent instead of being hard-blocked into silence. It’s the “bridge” half of bridge, then block, and it’s the setup most sites get wrong when they wire each vendor by hand.

Every bridge is one config flag. The CMP ships and manages the vendor-specific adapter; you don’t write onConsentChange glue or custom tag templates:

lightning("init", {
bridges: {
google: { tagId: "G-XXXXXXX" }, // CMP loads gtag.js for you
hubspot: { portalId: "1234567" }, // CMP loads HubSpot + forwards consent
},
});

Bridges are opt-in — omit a key and that vendor is never loaded, and the page pays no runtime cost for it. (Google Consent Mode signals are on by default via googleConsentMode; the google bridge below adds CMP-managed tag loading on top.)

Set bridges.google.tagId to a GA4 measurement ID (G-…), a Google Ads conversion ID (AW-…), or a GTM container (GTM-…) and the CMP loads it itself:

lightning("init", { bridges: { google: { tagId: "G-XXXXXXX" } } });

The win is ordering. The CMP pushes consent default (denied) before the tag executes — the single most common Consent Mode mistake when hand-rolling the head snippet is getting that order wrong and leaking before the default lands. Letting the CMP own the tag makes the ordering impossible to get wrong.

  • GTM containers work too. A GTM-… id loads your container the canonical way (gtm.start / gtm.js) with the same guaranteed consent-default-before-boot ordering. You still gate the container’s individual tags inside GTM via each tag’s Consent Settings — the bridge only handles loading plus Consent Mode signals — so confirm they fire in GTM Preview after wiring it up.
  • If you already hand-place gtag.js or the GTM snippet, don’t set tagId; Consent Mode signals still flow via googleConsentMode.
  • The loader is injected off the critical path (idle-time, async), so it never blocks first paint.

Set bridges.hubspot.portalId (the number in js.hs-scripts.com/<id>.js):

lightning("init", { bridges: { hubspot: { portalId: "1234567" } } });

HubSpot is a gated loader, because its tracker fans out to whatever the portal has connected — including HubSpot Ads pixels (which chain to LinkedIn Insight and similar), and those have no cookieless mode.

  • Before consent → the tracker does not load. Nothing HubSpot-related touches the page — no cookies, no requests.
  • On grant → the tracker loads and consent is forwarded: with statistics granted it does full, cookie-based tracking; with statistics denied (but the gate open) it records anonymous, cookieless page views.

By default the gate is the marketing category — the one that governs the ad fan-out. Override it with loadOn only for a portal with no ad pixels connected:

// Analytics-only portal: load as soon as statistics is granted.
lightning("init", {
bridges: { hubspot: { portalId: "1234567", loadOn: "statistics" } },
});

loadOn accepts "marketing" (default), "statistics", or "either".

Set bridges.adobe.scriptUrl to your Web SDK loader — the Adobe Tags (Launch) embed (assets.adobedtm.com/…) or a self-hosted alloy.js:

lightning("init", {
bridges: {
adobe: { scriptUrl: "https://assets.adobedtm.com/…/launch-….min.js" },
},
});
  • Statistics denied → the Web SDK’s collect purpose is set to no, so queued events are discarded rather than sent. Adobe collection has no cookieless middle tier, so denial is a clean opt-out.
  • Statistics granted → collect is set to yes and events flow.
  • The setConsent command (Adobe consent standard 2.0) is queued before the loader runs, so nothing is sent under an unknown state.
  • Set instanceName if your Web SDK uses a command name other than alloy.

Pardot / Marketing Cloud Account Engagement

Section titled “Pardot / Marketing Cloud Account Engagement”

Set bridges.pardot.accountId (piAId) and bridges.pardot.campaignId (piCId):

lightning("init", {
bridges: { pardot: { accountId: "1234", campaignId: "5678" } },
});
  • Marketing denied → the CMP sets pi_opt_in=false before pd.js loads, so Account Engagement records nothing. It’s a two-state tracker (no cookieless mode), so denial is a clean opt-out.
  • Marketing granted → pi_opt_in=true and prospect tracking runs. Because the tag is loaded (not blocked), a later grant upgrades tracking live, without a page reload.
  • Set trackerDomain if you serve pd.js from your own first-party tracking domain (default pi.pardot.com).
Bridge Config key(s) Gated on Under denial
Google google.tagId per-signal (Consent Mode) cookieless pings, modelling recovers data
HubSpot hubspot.portalId marketing (default) not loaded until gate opens; then cookieless views
Adobe adobe.scriptUrl statistics events discarded (clean opt-out)
Pardot pardot.accountId + campaignId marketing pi_opt_in=false (clean opt-out)

More bridges are added over time. For anything the CMP doesn’t ship an adapter for, host a small bridge file yourself — the CMP drives it exactly like a built-in one. See Custom bridges.