Skip to content

The CDN integration adds Frak’s rewarded referral program to any website: customers get share links and a share UI, you track purchases, and both the sharer and the friend who bought get rewarded when a sale confirms. No bundler, no build step: one script tag gives you the Frak web components and the full SDK on window.

  1. A merchant account on the Frak business dashboard with your domain registered. The main domain is registered at sign-up; add subdomains under Allowed Domains.
  2. Any HTML page you can add a script tag to.

If you have not done that yet, start with the Get started guide.

BundleURLWhat it is
Componentshttps://sdk.frak.id/components.jsThe first-party CDN pointer. Registers the <frak-*> elements, boots the SDK, and exposes the SDK on window.FrakSetup.core. This is the one you want.
Core onlyhttps://cdn.jsdelivr.net/npm/@frak-labs/core-sdk@1/cdn/bundle.jsIIFE exposing window.FrakSDK. No UI, no auto-boot. Still served directly from jsDelivr, not on the pointer. Only for a page that drives the SDK entirely by hand.

The rest of this page uses the components bundle. Swap the host for sdk-dev.frak.id to load pre-release (beta) builds instead: its onerror fallback below follows to jsDelivr’s @beta tag. This is the CDN host the bundle is fetched from; do not confuse it with the env config option further down, which targets the wallet/backend the SDK talks to once it is running.

The loader reads window.FrakSetup.config when it boots, so the config object must exist before the script tag runs.

index.html
<head>
<!-- 1. Configure Frak -->
<script>
window.FrakSetup = {
config: {
metadata: {
name: "Your Store",
currency: "eur",
},
},
};
</script>
<!-- 2. Load the components (registers the elements and boots the SDK) -->
<link rel="dns-prefetch" href="https://sdk.frak.id">
<link rel="preconnect" href="https://sdk.frak.id">
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<script
src="https://sdk.frak.id/components.js"
defer="defer"
onerror="var s=document.createElement('script');s.src='https://cdn.jsdelivr.net/npm/@frak-labs/components@latest/cdn/components.js';s.defer=true;document.head.appendChild(s)"
></script>
</head>

This is a classic deferred script, not type="module". sdk.frak.id is a single-line import() shim behind a CDN pointer with a 5-minute cache, so a release reaches you in minutes instead of jsDelivr’s 7-day floating-tag TTL. If it fails to load at all (network error, DNS, outage), onerror swaps in jsDelivr’s floating tag instead: the pointer file is a single import(), so a failed load ran nothing and nothing double-runs. The preconnects are asymmetric on purpose: the pointer’s own fetch is no-cors, so its <link> must not carry crossorigin, while the jsDelivr fallback’s import() is CORS-mode, so its <link> does: a preconnected socket is only reused when the credentials mode matches.

FieldDefaultMeaning
env"prod"The environment to run against: "prod", "dev", or { wallet, backend }.
metadata.namenoneYour application name, shown in modals and SSO.
metadata.merchantIdresolved from your domainYour merchant ID (UUID) from the dashboard.
metadata.currency"eur""eur", "usd", or "gbp".
metadata.langbrowser language"en" or "fr".
metadata.logoUrlnoneLogo used by some components.
metadata.homepageLinknoneFallback link used by some components.
domainwindow.location.hostOverride only if the page host differs from your registered domain.
customizations.cssnoneURL of a stylesheet applied to the modals and components.
customizations.i18nnoneInline translation overrides, per locale or flat.
waitForBackendConfigtrueWait for the backend configuration before rendering components.
attributionnoneDefault UTM, via, and ref values appended to sharing URLs.
preload["sharing"]Views preloaded inside the listener iframe. Pass [] to disable.

See Configuration and FrakSetup for the full reference.

The loader watches the DOM, so an element added at any time registers itself:

<body>
<frak-banner></frak-banner>
<frak-button-share classname="button"></frak-button-share>
</body>

Every element and attribute is documented in the components reference.

Once booted, the SDK publishes two things:

  • window.FrakSetup.client, the client instance
  • window.FrakSetup.core, every SDK function and action

The client is created asynchronously, so wait for the frak:client event before using it:

<script>
function waitForClient() {
if (window.FrakSetup?.client) return Promise.resolve(window.FrakSetup.client);
return new Promise((resolve) => {
window.addEventListener(
"frak:client",
() => resolve(window.FrakSetup.client),
{ once: true }
);
});
}
(async () => {
const client = await waitForClient();
// Watch the wallet status
await window.FrakSetup.core.watchWalletStatus(client, (status) => {
console.log(status.key === "connected" ? status.wallet : "not connected");
});
// Open a modal
await window.FrakSetup.core.displayModal(client, {
steps: {
login: {},
final: { action: { key: "reward" } },
},
});
})();
</script>

trackPurchaseStatus is the exception: it takes no client, so you can call it as soon as the SDK is loaded.

<script>
window.addEventListener("frak:client", () => {
window.FrakSetup.core.trackPurchaseStatus({
customerId: "cust_123",
orderId: "order_456",
token: "a-unique-order-token",
});
});
</script>

Rewards are only released once your backend confirms the order with a signed webhook. See Validate purchases from your backend.

EventTargetDetailFired when
frak:clientwindownoneThe client is ready, window.FrakSetup.client is set
frak:configwindowthe resolved configThe backend configuration is resolved or refreshed
frak:referral-successwindownoneAn inbound referral was processed successfully

The loader also handles ?frakAction=share on page load, with optional link, products, and placement parameters. It opens the sharing flow and then strips those parameters from the URL. That is how a link out of an email or a QR code can open the share sheet directly.

If you do not want the components at all, load the core bundle and create the client yourself. This bundle is not on the sdk.frak.id pointer: it is still loaded straight from jsDelivr, so pin the major the same way you would have pinned the components bundle before:

<script src="https://cdn.jsdelivr.net/npm/@frak-labs/core-sdk@1/cdn/bundle.js"></script>
<script>
let clientPromise;
function getClient() {
// Call setupClient once. Each call recreates the listener iframe.
clientPromise ??= FrakSDK.setupClient({
config: { metadata: { name: "Your Store" } },
});
return clientPromise;
}
(async () => {
const client = await getClient();
if (!client) return;
await FrakSDK.watchWalletStatus(client, console.log);
})();
</script>

window.FrakSDK carries the same functions and actions as window.FrakSetup.core. It exists only for this bundle: the components bundle is ESM and exposes no global.