# CDN / Browser Integration

# CDN integration

No bundler, no build step: one script tag gives you the Frak web components and the full SDK on `window`.

## Prerequisites

1. A merchant account on the [Frak business dashboard](https://business.frak.id/) 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](/guides/).

## Which bundle

| Bundle | URL | What it is |
| --- | --- | --- |
| Components | `https://cdn.jsdelivr.net/npm/@frak-labs/components@latest` | ESM. Registers the `<frak-*>` elements, boots the SDK, and exposes the SDK on `window.FrakSetup.core`. This is the one you want. |
| Core only | `https://cdn.jsdelivr.net/npm/@frak-labs/core-sdk@latest/cdn/bundle.js` | IIFE exposing `window.FrakSDK`. No UI, no auto-boot. Only for a page that drives the SDK entirely by hand. |

The rest of this page uses the components bundle.

## 1. Set the config, then load the script

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

```html title="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) -->
  <script
    type="module"
    src="https://cdn.jsdelivr.net/npm/@frak-labs/components@latest"
    defer="defer"
  ></script>
</head>
```

**window.FrakSetup must exist first:** The loader writes `window.FrakSetup.core` as soon as it runs. If `window.FrakSetup` is undefined at that moment you get a `TypeError`, and if `window.FrakSetup.config` is missing the SDK logs `Configuration not found`. Keep the inline config script above the module script.

`type="module"` is required: the components bundle is ESM.

### Config options

| Field | Default | Meaning |
| --- | --- | --- |
| `env` | `"prod"` | The environment to run against: `"prod"`, `"dev"`, or `{ wallet, backend }`. |
| `metadata.name` | none | Your application name, shown in modals and SSO. |
| `metadata.merchantId` | resolved from your domain | Your merchant ID (UUID) from the dashboard. |
| `metadata.currency` | `"eur"` | `"eur"`, `"usd"`, or `"gbp"`. |
| `metadata.lang` | browser language | `"en"` or `"fr"`. |
| `metadata.logoUrl` | none | Logo used by some components. |
| `metadata.homepageLink` | none | Fallback link used by some components. |
| `domain` | `window.location.host` | Override only if the page host differs from your registered domain. |
| `customizations.css` | none | URL of a stylesheet applied to the modals and components. |
| `customizations.i18n` | none | Inline translation overrides, per locale or flat. |
| `waitForBackendConfig` | `true` | Wait for the backend configuration before rendering components. |
| `attribution` | none | Default UTM, `via`, and `ref` values appended to sharing URLs. |
| `preload` | `["sharing"]` | Views preloaded inside the listener iframe. Pass `[]` to disable. |

**Targeting the dev stage:** `env` states both origins the SDK talks to: `"prod"` (the default), `"dev"`, or an explicit `{ wallet, backend }` pair for local development. It replaces `walletUrl` and lands in the next major of the SDK, so on the version published today use `walletUrl` instead. See [Configuration](/developers/concepts/configuration/).

See [Configuration](/developers/concepts/configuration/) and [FrakSetup](/developers/components/frak-setup/) for the full reference.

## 2. Drop in the components

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

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

Every element and attribute is documented in the [components reference](/developers/components/).

## 3. Call the SDK from your own code

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:

```html
<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: "sharing" } },
      },
    });
  })();
</script>
```

### Track a purchase

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

```html
<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](/guides/platforms/custom/backend/).

### Events

| Event | Target | Detail | Fired when |
| --- | --- | --- | --- |
| `frak:client` | `window` | none | The client is ready, `window.FrakSetup.client` is set |
| `frak:config` | `window` | the resolved config | The backend configuration is resolved or refreshed |
| `frak:referral-success` | `window` | none | An inbound referral was processed successfully |

### The share query parameter

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.

## Advanced: the core-only bundle

If you do not want the components at all, load the core bundle and create the client yourself:

```html
<script src="https://cdn.jsdelivr.net/npm/@frak-labs/core-sdk@latest/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.

## Next steps

[Components reference](/developers/components/)
  [Package manager setup](/developers/integration/javascript/)
  [Interactions](/developers/concepts/interactions/)
  [Backend validation](/guides/platforms/custom/backend/)