Foldkit docs

The SDK

Zero dependencies, about 14 KB minified. ESM, CJS and UMD.

install
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:

OptionNotes
publishableKeyThe no-backend tier. Accepted only from an origin you verified.
tokenAn es_… session token, or — better — a function returning one. See server-to-server.
designIdOpen 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 on message — we reword messages.
  • Attach an error listener 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.