The SDK
Zero dependencies, about 14 KB minified. ESM, CJS and UMD.
npm install @foldkit/embed
Or from a CDN, pinned:
<script src="https://cdn.jsdelivr.net/npm/@foldkit/embed@0.1.0/dist/foldkit-embed.umd.js"></script>
Mounting
Foldkit.mount() creates the iframe, waits for the editor to boot, and resolves with a handle. It takes a CSS selector or an element.
import { Foldkit } from '@foldkit/embed'
const editor = await Foldkit.mount('#designer', {
publishableKey: 'pk_live_…',
baseUrl: 'https://foldkit.theboxes.asia',
})Every option is on the SDK reference. The three that matter on the first day:
| Option | Notes |
|---|---|
publishableKey | The no-backend tier. Accepted only from an origin you verified. |
token | An es_… session token, or — better — a function returning one. See server-to-server. |
designId | Open an existing design instead of starting a new one. |
Commands
Every command returns a promise. It rejects with a FoldkitError carrying a code and a requestId.
const { designId, savedAt } = await editor.save()
const { doc } = await editor.getDesign()
const { dataUrl } = await editor.getSnapshot({ width: 800 })
await editor.loadDesign(designId)
await editor.setBoxParams({
styleId: 'fefco-0201',
dimensions: { L: 120, W: 80, D: 40 },
})
await editor.setTheme({ primary: '#c81e2d' })
await editor.setLocale('fr')
// Resolves when the job is *accepted*. The file arrives as an event.
const { exportId } = await editor.export({
kind: 'print-pdf',
options: { bleedMm: 3 },
})
await editor.destroy()Events
editor.on('loaded', ({ designId, tenant, permissions, features }) => {})
editor.on('dirty', ({ dirty }) => setSaveEnabled(dirty))
editor.on('saved', ({ designId, savedAt }) => cart.attach(designId))
editor.on('export.started', ({ exportId, kind }) => {})
editor.on('export.progress', ({ percent, stage }) => bar.set(percent))
editor.on('export.complete', ({ url, kind }) => window.open(url))
editor.on('export.failed', ({ message }) => {})
editor.on('resize', ({ width, height }) => {})
editor.on('token.expiring', ({ expiresAt }) => {})
editor.on('error', (error) => report(error))on, off and once take the same arguments. An unknown event name is ignored rather than throwing — your page pins an SDK version and the iframe is whatever we deployed this morning, so both ends have to tolerate the other knowing about something they do not.
Which save signal to use
Use saved to update your page and the design.saved webhook to update your database. The event is immediate and can be lost if the tab closes at the wrong moment; the webhook is retried and cannot be. Neither substitutes for the other.
Token renewal
Access tokens last 15 minutes and there is no refresh token — renewal goes back to the party that was allowed to mint in the first place, which is what bounds a stolen token to its own lifetime.
const editor = await Foldkit.mount('#designer', {
token: () =>
fetch('/api/foldkit-session', { method: 'POST' })
.then((r) => r.json())
.then((s) => s.token),
})Pass a function and renewal happens by itself. Pass a plain string and you get one error with code: 'token_expired' shortly before the session ends, and the editor stops working — which is a support ticket rather than a bug, and entirely avoidable.
Cleanup
The React and Vue snippets on the quickstart both handle this, including React’s double-invoked development effect.
Errors
try {
await editor.export({ kind: 'print-pdf' })
} catch (error) {
error.code // switch on this
error.requestId // quote this to support
}- Switch on
code, never onmessage— we reword messages. - Attach an
errorlistener even if you catch every command: some failures happen without a command being in flight, and a page with no listener discards them. - Every code is on the errors page, keyed by exactly the string you get.
If you run a Content-Security-Policy
Your page needs to allow our frame and, if you load the SDK from a CDN, that script.
Content-Security-Policy: frame-src https://foldkit.theboxes.asia; script-src 'self' https://cdn.jsdelivr.net;
We send our own policy on the iframe document, built from your verified origins — so the framing restriction works in both directions and neither side has to trust the other.