Installation
Frame.js ships from Frame's CDN as a single script — the supported install path is the script tag, which exposes the global Frame object on the page. There's no published npm package or bundler integration; load it from the CDN rather than self-hosting or bundling.
Add the script tag
Place the script in either the <head> section or just before the closing </body> tag.
<script src="https://js.framepayments.com/v1/index.js"></script>
Always load Frame.js directly from https://js.framepayments.com. Don't bundle it into your application or self-host it — you'll miss security patches and feature updates, and PCI compliance assumes the live CDN copy.
Asynchronous loading with async or defer avoids blocking DOM rendering. If you do, gate any Frame.* calls behind the script's load event — the global isn't available until the script finishes executing.
Initialize the SDK
Call Frame.init() with your publishable key. Grab the key from the Frame Developer section of the dashboard.
const frame = await Frame.init("<PUBLISHABLE_KEY>");
The publishable key identifies your application to Frame. It's safe to embed in client-side code — see Architecture for what each credential is allowed to do.
Create and reuse a single Frame instance for the lifetime of the page. Calling Frame.init() multiple times with the same key is wasteful and can produce unexpected behavior in component event ordering.
Link the session to an account
Frame.init() takes an optional second argument. Pass accountId whenever you know at page load which account the payment is for:
const frame = await Frame.init("<PUBLISHABLE_KEY>", {
accountId: "<ACCOUNT_ID>",
});
This links the Sonar session created at init to that account, so Charge Intents and Transfers you later create for it automatically pick up the session's device and IP signal — the inputs Frame's velocity, geo, and VPN checks run on.
Omit it and that attribution doesn't happen: risk scoring falls back to issuer and network signals alone, and legitimate payments are likelier to be flagged. This isn't a platform-only feature — it's the standard way to attribute risk signal to charges.
accountId set | accountId omitted | |
|---|---|---|
| Session created | Yes | Yes |
| Linked to the account | Yes | No |
| Device/IP signal on the account's charges | Automatic | Only if you forward sonar_session_id yourself |
If the account doesn't exist yet at init time — you create it from the checkout form — initialize without it and forward sonar_session_id on the charge instead. See Set up Sonar fraud protection.
For flows that only need one element type, lightweight factories like Frame.createTermsOfService() and Frame.createOnboarding() skip loading the full SDK dependency graph (Evervault, FingerprintJS, Sift) and ship faster. Use them when you don't need card tokenization on the same page.
Sandbox and production environments
Frame.js operates in two environments: sandbox and production. Use sandbox during development and switch to production once you're confident in the integration.
The environment is determined by the publishable key prefix you pass to Frame.init() — there is no separate environment switch.
| Key prefix | Environment | Notes |
|---|---|---|
pk_sandbox_* | Sandbox | Test cards only. See Testing. |
pk_production_* | Production | Live card processing. |
In sandbox you must use Frame's test card numbers — real cards are rejected with a require_test_cards error on the number field. In production the inverse holds: test card numbers are rejected.
Swap the key when you're ready to accept live payments. Nothing else in your integration needs to change.