# Advanced embedding

Topics for developers integrating Clarifier into a more demanding stack — Content-Security-Policy, single-page apps, the programmatic API. See the [installation guide](/en/docs/installation) for the basics.

## Content-Security-Policy

If your site sets a Content-Security-Policy header, the widget needs three directives to load and connect:

- script-src must allow our CDN, https://cdn.clarifier.dk
- connect-src must allow https://api.clarifier.dk for the config fetch and wss://api.clarifier.dk for the chat WebSocket
- style-src needs 'unsafe-inline' or a nonce for the styles the widget injects

```
Content-Security-Policy:
  script-src 'self' https://cdn.clarifier.dk;
  connect-src 'self' https://api.clarifier.dk wss://api.clarifier.dk;
  style-src 'self' 'unsafe-inline';
```

The widget renders inside a Shadow DOM, but the host page's CSP still applies to the style elements it inserts — adding 'unsafe-inline' to style-src is the simplest fix. If you can't allow inline styles, contact us and we'll work out a nonce-based setup.

## Shadow DOM and styling

The widget mounts inside a closed Shadow DOM root. That gives you a guarantee: nothing on your page — global CSS, framework styles, theme overrides — can leak in and break the widget's layout. The flip side is that you can't restyle the widget from the outside either; styling is configured through the dashboard (primary color, position, header icon).

If you need a visual customization the dashboard doesn't offer, get in touch — most requests can be handled with a new dashboard option rather than a custom build.

## Single-page apps and route changes

The widget mounts itself directly to document.body, not into your framework's root component. Client-side route changes don't unmount it — it stays put across navigations, which is what you want.

If your app does something unusual (rewriting document.body, full re-renders that detach foreign elements), the widget can disappear after a route change. Workaround: call window.Clarifier.destroy(), then re-execute the embed script. We're working on a cleaner re-init API; let us know if you hit this.

## Programmatic API

Once the widget has loaded, it exposes three methods on window.Clarifier:

```js
// Open the chat programmatically (e.g., from a "Help" button)
document.querySelector('#help-button')
  .addEventListener('click', () => window.Clarifier.open())

// Close it
window.Clarifier.close()

// Remove the widget entirely (e.g., before re-initializing in an SPA)
window.Clarifier.destroy()
```

These methods are only available after the widget has booted (after DOMContentLoaded). If you bind a click handler that calls window.Clarifier.open() during page load, guard against the global being undefined or wait for window.Clarifier to appear.

## Forcing a specific language

By default the widget reads the page's html lang attribute, then the visitor's browser language, then falls back to English. To override: set window.__CLARIFIER_LANG to a 2-letter code before the embed script runs. This is mainly useful for previews and testing — production sites should set html lang correctly instead.

## async vs defer vs lazyOnload

The default snippet uses async, which lets the browser fetch the script in parallel with HTML parsing and run it as soon as it arrives. That's the right choice for the vast majority of sites — fast, doesn't block rendering, widget appears as soon as possible.

If you're optimizing for absolute fastest first paint and don't mind the widget appearing a beat later, use defer or Next.js's strategy="lazyOnload". The widget initializes itself once the DOM is ready, regardless of which loading strategy you pick.
