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.)
Google (gtag / GTM)
Section titled “Google (gtag / GTM)”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.jsor the GTM snippet, don’t settagId; Consent Mode signals still flow viagoogleConsentMode. - The loader is injected off the critical path (idle-time,
async), so it never blocks first paint.
HubSpot
Section titled “HubSpot”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".
Adobe (Experience Platform Web SDK)
Section titled “Adobe (Experience Platform Web SDK)”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
collectpurpose 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 →
collectis set to yes and events flow. - The
setConsentcommand (Adobe consent standard2.0) is queued before the loader runs, so nothing is sent under an unknown state. - Set
instanceNameif your Web SDK uses a command name other thanalloy.
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=falsebeforepd.jsloads, 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=trueand prospect tracking runs. Because the tag is loaded (not blocked), a later grant upgrades tracking live, without a page reload. - Set
trackerDomainif you servepd.jsfrom your own first-party tracking domain (defaultpi.pardot.com).
At a glance
Section titled “At a glance”| Bridge | Config key(s) | Gated on | Under denial |
|---|---|---|---|
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) |
Need a vendor that isn’t here?
Section titled “Need a vendor that isn’t here?”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.