Custom bridges
Need a vendor the CMP doesn’t ship an adapter for? Host a small bridge file
yourself and declare it under bridges.custom. The CMP loads it and drives it
exactly like a built-in bridge — including pushing the deny default before
your vendor loader runs, and forwarding every later change. No forking, no
onConsentChange glue.
The contract
Section titled “The contract”Your file registers a factory with LightningCMP.defineBridge(id, factory). The
factory returns a bridge object and is handed a small sdk so you reuse the
CMP’s off-critical-path loader:
type ConsentBridge = { id: string; init(choices: ConsentChoices): void; // once, with the pre-load state update(state: ConsentState): void; // on every later change};
type BridgeFactory = (options: unknown, sdk: BridgeSdk) => ConsentBridge;
type BridgeSdk = { // Injects a <script src> off the critical path (idle-time, async, deduped). injectScriptDeferred(src: string, attrs?: Record<string, string>): void;};initruns once with the pre-load choices;updateruns on every change.- Both are called fail-soft — a throwing bridge never breaks the banner or the page.
- Only category booleans cross the boundary (
choices.necessary/.preferences/.statistics/.marketing) — never visitor PII.
Write one
Section titled “Write one”-
Host a bridge file on your own origin. It registers a factory by
id:// https://cdn.acme.io/lightning-bridge.js (a plain script you host)LightningCMP.defineBridge("acme", (options, sdk) => ({id: "acme",init(choices) {// 1. Push your vendor's deny default FIRST.window.acmeq = window.acmeq || [];if (!choices.statistics) window.acmeq.push(["anonymize"]);// 2. Then load it off the critical path.sdk.injectScriptDeferred("https://cdn.acme.io/acme.js", {"data-lightning-bridge": "acme",});},update(state) {window.acmeq.push(["anonymize", !state.choices.statistics]);},})); -
Declare it in
init()—idmust match the id you passed todefineBridge:lightning("init", {bridges: {custom: [{id: "acme",src: "https://cdn.acme.io/lightning-bridge.js",options: { accountId: "A1" },integrity: "sha384-…", // recommended (SRI)},],},});Registration and request rendezvous either way — it works whether your file loads before or after the CMP asks for the bridge.
What you get, and what you own
Section titled “What you get, and what you own”- Same guarantees as native bridges —
initonce with the pre-load choices,updateon every change, both fail-soft. - Off-critical-path loading —
sdk.injectScriptDeferredinjects at idle time withasync, deduped persrc, so your vendor never blocks first paint. - Your code, your responsibility. A custom bridge is loaded from your origin
and runs on your site; you own its behaviour and its compliance. Pin it with
integrity(SRI); the CMP addscrossorigin="anonymous"when you do. - Forward-only. Custom bridges attach just after first paint (they own their own vendor loader), so ordering-critical tags like Google should stay native, not custom.
Guarding against CMP changes
Section titled “Guarding against CMP changes”The bridge contract is versioned. Check LightningCMP.BRIDGE_API_VERSION if you
need to guard against a CMP build your bridge wasn’t written for:
if (LightningCMP.BRIDGE_API_VERSION < 1) { // Bail out or degrade — this CMP predates the API your bridge needs.}See also
Section titled “See also”- Built-in bridges — the adapters the CMP ships.
- Script blocker — for vendors with no consent-aware mode at all.
- The full authoring contract lives in
docs/BRIDGE_SPEC.mdin the repo (category mapping, testing requirements, security & privacy checklist).