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

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:

```ts
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;
};
```

- `init` runs **once** with the pre-load choices; `update` runs 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.

> **Warning — Push your vendor's deny default first**
> The golden rule of any bridge: in `init`, push your vendor's **deny/anonymised
> default synchronously — before** you inject its loader. Otherwise the vendor
> can fire un-gated during the async load. The CMP guarantees this ordering for
> native bridges; in a custom bridge it's your responsibility.

## Write one

1. **Host a bridge file** on your own origin. It registers a factory by `id`:

   ```js
   // 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]);
     },
   }));
   ```

2. **Declare it** in `init()` — `id` must match the id you passed to
   `defineBridge`:

   ```js
   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

- **Same guarantees as native bridges** — `init` once with the pre-load choices,
  `update` on every change, both fail-soft.
- **Off-critical-path loading** — `sdk.injectScriptDeferred` injects at idle time
  with `async`, deduped per `src`, 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 adds `crossorigin="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](/integrations/bridges/#google-gtag--gtm), not custom.

## 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:

```js
if (LightningCMP.BRIDGE_API_VERSION < 1) {
  // Bail out or degrade — this CMP predates the API your bridge needs.
}
```

## See also

- [Built-in bridges](/integrations/bridges/) — the adapters the CMP ships.
- [Script blocker](/integrations/script-blocker/) — for vendors with no
  consent-aware mode at all.
- The full authoring contract lives in `docs/BRIDGE_SPEC.md` in the repo
  (category mapping, testing requirements, security & privacy checklist).
