Tag-loader (drop-in script)
Run Fabric Experiments on a marketing site or via Google Tag Manager without touching application code.
The tag-loader is a single <script> build of the web SDK. It auto-creates an
anonymous subject id, fetches the signed manifest, and applies declarative DOM
ops from each running experiment — Mojito-style, but governed.
Install — Fabric-hosted (recommended for marketing sites)
The fastest install is to load the bundle directly from the Fabric-operated CDN — no build pipeline, no upload step. This is the same model Google Analytics uses: paste the snippet, ship.
<script>
window.__FX__ = {
manifestUrl: 'https://manifest.example.com/<orgId>/manifest',
};
</script>
<script
src="https://cdn.fabric.pro/v/0/experiments.tag.global.js"
async
></script>URL scheme:
| Path | Behavior |
|---|---|
/v/0.5.3/experiments.tag.global.js | Pinned exact version. Immutable, 1-year browser cache. |
/v/0/experiments.tag.global.js | Major-pinned. Auto-updates within 0.x releases. |
/v/latest/experiments.tag.global.js | Always newest. Use for prototyping; pin a major in production. |
Subresource Integrity (SRI) hashes for every published version are
exposed at https://cdn.fabric.pro/index.json:
<script
src="https://cdn.fabric.pro/v/0.5.3/experiments.tag.global.js"
integrity="sha384-…" {/* paste from index.json */}
crossorigin="anonymous"
async
></script>Install — self-hosted (full control)
If you'd rather not load JavaScript from a third-party origin (CSP-strict shops, regulated industries, sites with strict supply-chain reviews), host the bundle yourself. Two ways:
Via the CLI — fx publish --static writes the signed manifest and
the tag-loader bundle into a directory you upload to your CDN. Walks
through this in the self-host page.
Via npm — install @fabricorg/experiments-web and copy the file from
node_modules/@fabricorg/experiments-web/dist/experiments.tag.global.js
into your asset pipeline.
Either way the snippet becomes:
<script src="https://cdn.example.com/experiments.tag.global.js" async></script>How it works
window.__FX__ is the only global the loader expects. Set it before the
loader script tag so the bootstrap reads it on first execution.
The loader exposes window.fx — a Promise<ClientApi> you can await from
later scripts:
<script>
window.fx?.then(client => {
if (client.treatment('homepage-cta') === 'treatment') {
console.log('user is in treatment');
}
});
</script>What's auto-wired
- Anonymous subject id — cookie
_fx_sid(365d, samesite=lax). Override with__FX__.cookieNameor pass__FX__.subjectIddirectly for logged-in users. - DOM ops — variants with
domOps(replaceText, setAttr, addClass, injectCSS, etc.) apply on assignment. - Triggers — when a manifest experiment declares a
trigger(urlMatch, waitForSelector, event), activation defers until the trigger fires. - Exposures — pushed to
window.dataLayerso GTM tags can listen for thefx_exposureevent. Set__FX__.noDefaultAdapter = trueto suppress. - Preview — when
?fxpreview=<expId>:<variant>&fxtoken=<jwt>is in the URL, the SDK verifies the short-lived token with public JWKS from/.well-known/jwks/<orgId>and forces the variant in this browser only. Preview assignments are deliberately not exposure-tracked.
Configuration reference
| Field | Type | Default | Notes |
|---|---|---|---|
manifestUrl | string | required | Origin must be CORS-friendly |
subjectId | string | auto | Override the anonymous id |
cookieName | string | _fx_sid | Anonymous-id cookie name |
cookiePrefix | string | 'fx.' | Sticky-bucket key prefix |
previewJwksUrl | string | derived from manifestUrl | Optional public JWKS URL for preview token verification |
previewSecret | string | — | Deprecated legacy HS256 verifier; do not embed in production HTML |
beaconEndpoint | string | — | URL for batched exposure POSTs via sendBeacon |
noDefaultAdapter | boolean | false | Suppress the default dataLayer.push |
onExposure | function | — | Custom exposure handler |
onRecipeFailure | function | — | Fires only when variant code throws — additive to onExposure |
excluded | bool | function | — | Truthy → SDK is no-op (bots, opt-outs, consent) |
maxErrorStackLength | number | 1000 | Cap on failure.stack chars sent to handlers |
Realistic install (with adapters)
<script>
window.__FX__ = {
manifestUrl: 'https://manifest.example.com/acme/manifest',
cookiePrefix: 'acme.fx.',
excluded: () => /bot|crawler/i.test(navigator.userAgent),
onRecipeFailure: (rec) => {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'fx_recipe_failure',
fx_experiment_id: rec.experimentId,
fx_variant_key: rec.variantKey,
fx_failure_message: rec.failure?.message,
});
},
};
</script>
<script src="https://cdn.example.com/experiments.tag.global.js" async></script>Recommended HTTP headers
Preview tokens are bearer credentials in URLs. They are signed by Studio/API and verified with public keys, so no preview secret belongs in customer HTML. Still set:
Referrer-Policy: no-referrer…on pages that may receive preview links so the token isn't leaked via the
Referer header to third-party origins.
See also
- Migrating from Mojito — same install pattern, no app code.
- Web adapters — Snowplow, GA, GTM.
- Web SDK — the underlying programmatic client.