1 · Install
Cutforge is built on React internally, so react and
react-dom (v18.3+) are peer dependencies — install them alongside the
package even if your host app isn't a React app.
npm install @cutforge/editor react react-dom
# or: pnpm add @cutforge/editor react react-dom
# or: bun add @cutforge/editor react react-dom The package is public on npm; the license key — not the install — is what unlocks production use.
2 · Mount the editor
Two ways in, depending on your stack. Always import the stylesheet once.
Vanilla / any framework
import { createEditor } from '@cutforge/editor';
import '@cutforge/editor/style.css';
const editor = createEditor(document.getElementById('app'), {
license: 'YOUR-LICENSE-KEY', // omit to run the watermarked demo
});
// when you're done (route change, unmount):
editor.destroy(); React
import { Editor } from '@cutforge/editor';
import '@cutforge/editor/style.css';
export default function App() {
return <Editor license="YOUR-LICENSE-KEY" />;
}
The container should have a real size — give it a height (e.g. height: 100vh);
the editor fills its parent.
3 · Add your license key
After purchase, your key arrives by email from the Cutforge checkout (and is shown on
the confirmation screen). Pass it as license. That's the only required
step to leave the demo:
- No key → watermarked demo: exports capped at 60s / 720p, saving disabled.
- Valid key → full editor: no watermark, uncapped exports, saving enabled.
You can verify any key from a terminal — handy when wiring up your build pipeline:
curl -X POST https://license.cutforge.dev/v1/validate -H 'Content-Type: application/json' -d '{"key":"YOUR-LICENSE-KEY"}'
# → {"valid":true,"tier":"indie", ...} 4 · Options reference
Both createEditor(el, options) and <Editor {...options} /> take the same object:
| Option | Type | Default | Description |
|---|---|---|---|
license | string | — | Your key from checkout. Omit to run the demo (eval) build. |
demo | boolean | false | Force the demo build regardless of any key — useful for a public "try it" page. |
theme | CutforgeTheme | dark | Light/dark base, accent + per-slot colours, font — or a named preset (themePresets.midnight / paper / contrast / sunset). Scoped to the container. |
customCss | string | — | Raw CSS escape hatch beyond theme tokens. Scope selectors under .cutforge-root. |
branding | CutforgeBranding | — | White-label: replace the topbar product name + logo, or hide the brand entirely. |
chrome | CutforgeChrome | — | Show/hide & relabel toolbar buttons; inject your own toolbar buttons (actions) and clip right-click items (clipActions). |
i18n | CutforgeI18n | English | Localize the whole UI (string catalog, English fallback) and set text direction — RTL is derived from the locale. |
preset | CutforgePreset | — | Preload media (fetched on boot, optionally laid on the timeline) and set the starting project name / dimensions. |
hooks | CutforgeHooks | — | Policy gates: canImport, onBeforeImport (sanitize/replace a file), onBeforeExport (veto). Throw or return false to block. |
assetProvider | CutforgeAssetProvider | — | Feed the media-library Browse panel from your own backend (DAM, stock, S3, CMS): an async (query) => AssetPage. Clicking a tile imports it through the normal pipeline. |
Full TypeScript definitions for every option ship with the package.
5 · Personalization & integration
Cutforge is built to feel native inside your product. Every option below is independent —
use only what you need, on both createEditor and <Editor>.
Theme, brand & localize
import { createEditor, themePresets } from '@cutforge/editor';
const editor = createEditor(el, {
theme: { ...themePresets.midnight, accent: '#ff0066' },
branding: { productName: 'Acme Studio', logo: '/logo.svg' },
chrome: {
hide: ['settings'],
labels: { export: 'Render' },
actions: [{ id: 'review', label: 'Send to review', onClick: () => post(editor.getProject()) }],
},
i18n: { locale: 'fr', messages: { 'topbar.export': 'Exporter' } }, // RTL auto for ar/he/…
}); Drop users into a ready-to-edit session
createEditor(el, {
preset: {
media: [{ url: '/intro.mp4' }, { url: '/outro.mp4' }],
arrange: 'sequence', // lay them on the timeline
project: { aspectRatio: '16:9', name: 'Promo' },
},
}); React to the editor, or drive it
// React: subscribe to events
editor.on('save', ({ project }) => fetch('/api/projects', { method: 'POST', body: project }));
editor.on('export-complete', ({ blob, filename }) => upload(blob, filename));
editor.on('timeupdate', ({ time }) => /* ... */);
// also available as <Editor onSave onExportComplete onTimeUpdate … /> props
// Drive: imperative commands
await editor.load(file);
editor.play(); editor.seek(2); editor.pause();
const json = editor.getProject(); // persist host-side
editor.loadProject(json);
const { blob } = await editor.export({ format: 'mp4' }); Bring your own media library
createEditor(el, {
// The Browse panel pulls from your backend; clicking a tile imports it.
assetProvider: async ({ search, cursor }) => {
const res = await fetch(`/api/media?q=${search ?? ''}&cursor=${cursor ?? ''}`);
const { assets, next } = await res.json();
return {
items: assets.map((a) => ({
id: a.id, name: a.title, kind: 'video', // 'video' | 'audio' | 'image'
url: a.src, thumbnailUrl: a.poster, duration: a.seconds,
})),
nextCursor: next, // omit when the source is exhausted
};
},
}); Gate imports & exports (policy hooks)
createEditor(el, {
hooks: {
canImport: (file) => file.size < 500_000_000, // reject huge files
onBeforeImport: (file) => stripExif(file), // return a File to swap it in
onBeforeExport: ({ project }) => project.duration < 600,
},
});
Events: ready · timeupdate · play · pause · change · save · export-progress ·
export-complete · error. Commands: load · play · pause · seek · getTime ·
getState · getProject · loadProject · export. Everything you set at init can also
be changed at runtime on the handle (setTheme, setBranding,
setI18n, setAssetProvider, …).
6 · Demo vs licensed
| Demo (no / invalid key) | Licensed (any tier) | |
|---|---|---|
| Watermark | Burned into every frame | None |
| Max export duration | 60 seconds | Unlimited |
| Max export resolution | 720p | Source resolution |
| Project save | Disabled | Enabled |
All paid tiers (Indie / Startup / Enterprise) lift every limit equally — tiers differ on seats, support, and source access, not editor features.
7 · Browser support & headers
Cutforge needs the modern web-media stack: WebGPU / WebGL2 and WebCodecs. That means Chromium 105+ (Chrome, Edge, Arc, Brave), recent Firefox, and recent Safari. Decode is whatever the browser supports natively (H.264, VP8/VP9, AV1, common audio); export is mp4 or webm.
Cross-origin isolation (recommended)
The full audio engine uses an AudioWorklet over SharedArrayBuffer, which
requires a cross-origin-isolated page. Serve your host page with:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp Without these headers the editor still works — it falls back to a simpler audio path. Two gotchas:
- Don't set them twice. If a parent/proxy already sends COOP/COEP, adding them again produces duplicate (invalid) headers.
- COEP blocks un-attributed cross-origin resources. With
require-corp, cross-origin images/fonts/media must sendCross-Origin-Resource-Policyor CORS headers, or they won't load.
8 · How licensing works
Keys are issued by Lemon Squeezy and validated through the Cutforge license service
(https://license.cutforge.dev/v1/validate), which checks the key server-side and maps your purchased
variant to a tier. The flow is built to be invisible at runtime:
- Instant start. A fresh cached validation starts the editor licensed with no demo flicker; otherwise it boots in demo and upgrades in place once validation returns.
- Cached 7 days. Valid results are cached in
localStorage(keys prefixedcutforge.license.), so reloads are instant and you stay well under rate limits. - Offline-safe. A network failure never downgrades a cached-valid session — only a definitive "invalid" response does.
- No footage leaves the browser. Only the key string is sent for validation; media never touches a server.
9 · Troubleshooting
| Symptom | Fix |
|---|---|
| Editor stays watermarked / capped with a valid key | Confirm the key validates (curl above), check the license endpoint is reachable, then clear any stale cutforge.license.* entries in localStorage and reload. |
| Audio glitches, or "SharedArrayBuffer required" | Add the COOP/COEP headers in §6 to enable cross-origin isolation. |
| Blank canvas / "WebGPU not available" | The browser lacks WebGPU/WebGL2. Update it, or enable the WebGPU flag on older builds. |
| A cross-origin image/font won't load after enabling COEP | Serve that asset with Cross-Origin-Resource-Policy: cross-origin (or proper CORS). |
10 · Enterprise
Enterprise includes private repository access plus full source. If you need a custom license term, extended seats, or an SLA, get in touch.