# Script blocking

The consent banner blocks scripts and iframes from the provider hosts you declare until the visitor allows their category. Blocking runs in the browser as part of `banner.js` and only covers resources it can intercept after installation.

Place the banner first in `<head>` without `async` or `defer`. Undeclared hosts, server-side requests and analytics bundled into your own app need separate handling.

## What is blocked

A resource is blocked when all three conditions hold:

- It is a `<script src>` or `<iframe src>`.
- Its hostname matches a host declared on a provider.
- The visitor has not allowed that provider's category.

Inline scripts, resources from your own origin, undeclared hosts and the `necessary` category pass through.

Host matching includes subdomains at a label boundary. For example, `google-analytics.com` matches `www.google-analytics.com`, but not `notgoogle-analytics.com`. Do not add wildcards; suffix matching already covers subdomains.

To allow additional hosts, set `data-lepsto-allow-hosts` on the banner snippet to a comma-separated list. This takes priority even when a host is also declared on a provider. To exempt one element, give it `data-lepsto-category="necessary"`.

## How a resource is made inert

The blocker intercepts resources through four browser mechanisms:

- The `HTMLScriptElement` `src` setter.
- `document.createElement`.
- A document-wide `MutationObserver`.
- `document.write` and `document.writeln`.

It uses no polling or timer. For a blocked script, it saves the original `src` and `type`, changes the type to an inert MIME type, and removes `src`. For an iframe, it saves the source and substitutes `about:blank`. The element remains in the document for later restoration.

Newly created scripts are marked inert before their source is assigned. If several interceptors encounter the same element, only the first capture saves its original attributes.

## How a resource comes back

When the visitor allows a category, its captured resources are restored in capture order. Non-`async` scripts load one at a time, with each awaited before the next starts. This preserves dependencies such as a tag manager loading before its tags. `async` scripts are replaced without waiting.

Restoration removes the blocking markers and restores the original `type` and `src`. Under a nonce-based Content Security Policy, the script keeps its own nonce if present; otherwise it receives the nonce used by `banner.js`.

A script captured before insertion is restored in place, preserving its identity and attached listeners. Once no categories remain blocked, the mutation observer disconnects. The patched setters remain installed and pass resources through.

## Tightening a choice

Withdrawing a category blocks it again, reinstalls the observer and attempts to clear its storage. Storage cleanup is best effort.

Blocking cannot undo JavaScript that has already run. The runtime therefore reloads the page when a category becomes newly denied, unless you pass `reload: false` and handle the change yourself. See [The banner runtime](/privacy/banner-runtime).

The `necessary` category always remains allowed.

## It fails open

A blocker installation error does not prevent the banner from running. A banner failure likewise does not stop an installed blocker. Check both the visible banner and network activity when verifying installation.

An unresolved product id is handled separately: the blocker starts with all consentable categories blocked and keeps them blocked until a decision is submitted.

## What this does not cover

**Trackers that run before the banner.** The blocker cannot intercept a tracker that ran before `banner.js`. Keep the banner first in `<head>` and synchronous.

**Server-side requests.** Calls from your server to a third party happen outside the browser.

**Analytics bundled into your app.** Code started through your own `import()` and `fetch` is not a third-party script element. Start and stop it according to Google Consent Mode or `window.LepstoConsent.getDecision()`, and subscribe to later changes. The [runtime example](/privacy/banner-runtime#windowlepstoconsent) handles both stored choices and new decisions.

## Next steps

- [Declare your providers and cookies](/privacy/providers-and-cookies): set the hosts and categories used by the blocker.
- [The banner runtime](/privacy/banner-runtime): installation order and the browser API.
