Web: JavaScript Preview
@nitrakit/web runs the editor in the browser. This page uses it directly; with React, Vue,
Svelte or Angular, read this page first, then your framework's page for the components.
By the end of this page your site shows the editor, lets users add their own files, and exports an MP4 they can download.
The web SDK is in preview: it works, and its API may still change.
Requirements
- A browser with WebGPU and WebCodecs: recent Chrome or Edge on desktop, or Safari 26.
Check with
isSupported()and show a message otherwise. - Videos in H.264 MP4 to export them. Other formats play in the preview only.
- Media from another origin needs CORS.
Install
The package includes a WebAssembly file (about 10 MB, 3 MB compressed) and the Inter font, which bundlers copy as assets. Serve them compressed. With Vite, exclude the package from dependency pre-bundling so those files resolve in development:
export default defineConfig({
optimizeDeps: { exclude: ['@nitrakit/web'] },
})
1. Load the SDK and install your license key
load() fetches and starts the WebAssembly module. Call it once, before anything else.
The license key unlocks full-length, watermark-free exports for your site's origins. Without a
key everything still works, and localhost always counts as development.
import { isSupported, load, setLicense } from '@nitrakit/web'
if (!isSupported()) showUnsupportedMessage()
await load()
setLicense('nk1.…')
See License key.
2. Show the editor
The editor has two parts: the preview, which plays the project and lets the user move and resize layers, and the editor controls: timeline, tools and panels. Give each an element with a size; they fill it and resize with it.
<div id="preview" style="height: 60vh"></div>
<div id="editor" style="height: 320px"></div>
import { createEditor } from '@nitrakit/web'
const editor = await createEditor({
preview: document.getElementById('preview')!,
editor: document.getElementById('editor')!,
width: 1080,
height: 1920, // the project's size: 9:16 vertical video
})
The preview keeps the project's aspect ratio inside its element. The controls switch to a
desktop layout (free playhead, drag to move clips) when their element is at least 900 px
wide; set layout to choose. Keyboard shortcuts (Space, J/K/L, arrows, S, Backspace,
⌘Z) are on unless you set keyboard: false.
3. Add media
Adding a file makes it a source; placing it on a track makes it a clip the user sees in the timeline.
const media = await editor.addMedia(assetId, file) // a File the user picked, a Blob or a URL
const track = editor.addTrack('visual', true)
editor.insertMedia(track, media.source, 0, 0, media.durationUs)
assetId is your app's id for the file, for example its key in your storage: the project
refers to media only by these ids. Editing starts right away; it doesn't wait for your
upload. Times are microseconds.
4. Tell the SDK where media lives
When the editor needs a file it doesn't have, for example after opening a saved project, it asks your app where that media id lives. Return a URL (it can be signed) or the file itself; the SDK downloads it and shows the progress on the clips:
const editor = await createEditor({
// …
resolveMedia: async (id) => (await api.signedUrl(id)).url,
})
See Projects and media.
5. Connect your pickers
When the user taps Add media, picks a font or a colour, the editor asks your app through
onRequest, so these screens can match your site and media can come from your own library.
Return the answer, or a promise of it. Return null if the user backed out, or undefined
(nothing) to use the SDK's built-in picker:
const editor = await createEditor({
// …
async onRequest(request) {
if (request.kind === 'addMedia') {
const file = await openMyMediaLibrary() // your picker
if (!file) return null
const id = startUpload(file) // your storage's id for it
return { source: (await editor.addMedia(id, file)).source }
}
// anything else: the built-in picker
},
})
The built-in pickers are simple: a file chooser for media, the browser's colour input, and lists for fonts, filters and text styles. Files from the built-in media picker get random ids and aren't uploaded anywhere, so use your own picker once you store media. See Your pickers for every request and its answer.
6. Save the project
Projects save as JSON, with media referred to by your ids. Store and sync them however you like, and open them anywhere:
const json = editor.toJson()
editor.openProject(json) // media arrives through resolveMedia
onChange in the options tells you after every change, to save as the user works. While your
app uploads a file, show the progress on its clips with editor.setMediaProgress(id, fraction).
7. Export
Export renders the project to an MP4 in the browser and returns it as a Blob. It runs in a
worker, so the editor stays smooth and the user can keep editing while it encodes:
const mp4 = await editor.export({
shortSide: 1080, // or omit for the project's size
onProgress: (f) => setProgress(f),
signal: abortController.signal, // to cancel
})
const link = Object.assign(document.createElement('a'), {
href: URL.createObjectURL(mp4),
download: 'video.mp4',
})
link.click()
See Export.
Fonts
The web has no system fonts, so the editor brings Inter for its labels and for text. Pass your
own in fonts (TTF or OTF URLs or bytes) to use your brand fonts; they appear in the font
picker. For languages in other scripts, add a font that covers them, for example Noto Sans
Arabic for Arabic.
Playback
editor.play()
editor.pause()
editor.seek(2_000_000)
editor.positionUs
Cleaning up
When the editor leaves the page (a single-page app changing route), call editor.destroy() to
stop it and free its memory.