Learn how to integrate IMG.LY’s video editor for React into your web app and make the most of all its features.

Short-form video changed what users expect from software. People who edit clips daily in TikTok or CapCut arrive at your product assuming they can trim, caption, and rearrange without leaving it. Sending them to a desktop tool and asking them to come back with an MP4 is where the workflow breaks.

This guide covers integrating a video editor into a React app with CreativeEditor SDK (CE.SDK): the component setup, the timeline and export APIs underneath it, and the customization points you will reach for once the default editor is running.

For a working reference, see the React video editor starter kit or try the interactive demo.

Check browser support before you scope the work

CE.SDK’s video mode encodes and decodes in the browser using the WebCodecs API. That is what lets you export an MP4 with no render farm behind it, and it is also the catch. Video mode needs a recent Chromium browser: Chrome, Edge, Opera, Arc, and Brave all work. Safari and Firefox do not currently support video editing.

Design and photo editing have no such restriction, so this only limits the video path. If your traffic skews heavily to Safari, plan for a fallback message rather than discovering it in QA. The supported browsers list has the current detail.

Why a video editor belongs in the app rather than beside it

TikTok reports over a billion monthly active users, and Google’s earnings calls have put YouTube Shorts around 70 billion daily views. Those products taught a very large number of people that trimming a clip and adding a caption takes seconds.

That expectation is why the round trip hurts. Every export-and-reupload cycle gives the user a chance to abandon the task, and every asset that leaves your system is one you can no longer template, version, or keep on brand. Editing in place keeps both the user and the asset.

React’s component model fits this well, because the editor is one mounted component with a configuration object and an initialization hook, not a page you navigate away to.

Getting started: adding a video editor in React

Requirements

  • Node.js 20+ and npm 10+. Download Node.js if needed.
  • A React 18+ project on a build tool such as Vite, Parcel, or RSBuild.
  • A CE.SDK license key. Start a free trial to get one.

If you do not have a project yet, create one with Vite:

npm create vite@latest my-react-video-editor -- --template react
cd my-react-video-editor

Step 1: Install CE.SDK

npm install @cesdk/cesdk-js

Step 2: Create the editor component

CE.SDK ships a React component, so you do not need to manage the instance lifecycle by hand. Import it from the /react entry point and pass it a config object and an init function.

Create src/VideoEditor.jsx:

// src/VideoEditor.jsx
import CreativeEditor from '@cesdk/cesdk-js/react';

const config = {
  license: 'YOUR_LICENSE_KEY', // replace with your key
  userId: 'YOUR_USER_ID', // optional, for MAU tracking across devices
};

// Runs once, after the SDK instance is created
const init = async (cesdk) => {
  await Promise.all([
    cesdk.addDefaultAssetSources(),
    cesdk.addDemoAssetSources({
      sceneMode: 'Video',
      withUploadAssetSources: true,
    }),
  ]);

  // Open the editor in video mode with an empty composition
  await cesdk.createVideoScene();
};

export default function VideoEditor() {
  return (
    <CreativeEditor config={config} init={init} width="100vw" height="100vh" />
  );
}

sceneMode: 'Video' on addDemoAssetSources loads the video and audio demo libraries rather than the design ones, so the asset panel contains clips and music instead of shapes. In production you will swap these for your own asset sources, which can be your backend or a third-party library such as Unsplash.

createVideoScene() is what puts the editor into video mode and gives you the timeline. Calling createDesignScene() instead gives you the design editor with no time dimension, which is the most common reason a first integration comes up without a timeline.

Step 3: Mount it

// src/App.jsx
import VideoEditor from './VideoEditor';

export default function App() {
  return <VideoEditor />;
}

Run npm run dev and open http://localhost:5173. You should get a full editor with a timeline: trimming, splitting, text overlays, audio tracks, filters, and transitions.

Name your own component something other than CreativeEditor or CreativeEditorSDK. Reusing either name collides with the import and produces an Identifier has already been declared error.

Four things that will cost you an afternoon

These are the failure modes that come up most often in React integrations. None of them produce a useful error message, which is what makes them expensive.

init swallows its own errors

If any line inside the init callback throws, the remaining lines are skipped and nothing appears in the console. You get an editor that loads with a half-built scene and no explanation.

Wrap the body while you are developing:

const init = async (cesdk) => {
  try {
    await cesdk.addDefaultAssetSources();
    await cesdk.createVideoScene();
    // scene setup...
  } catch (error) {
    console.error('[CE.SDK init] failed:', error);
  }
};

Symptoms to recognize: the editor renders, some blocks are missing, the console is clean, and the behavior changes depending on network timing.

Strict Mode runs init twice

In development, React 18+ Strict Mode mounts, unmounts, and remounts the component. init runs twice, and the first engine is disposed partway through. The editor you end up looking at is the second one.

This confuses debugging because logs from both runs interleave, and errors from the first run’s async continuation are noise. If you are chasing an initialization bug, tag your logs:

let runCount = 0;

const init = async (cesdk) => {
  const run = ++runCount;
  console.log(`[init ${run}] start`);
  await cesdk.createVideoScene();
  console.log(`[init ${run}] scene ready`);
};

Production builds run init once. There is nothing to fix here, only something to know.

Relative asset URLs resolve against the CDN

CE.SDK resolves relative URIs against config.baseURL, which points at the IMG.LY CDN. A path like /videos/intro.mp4 will not hit your own origin. Make local asset paths absolute before handing them to the engine:

const resolveUri = (uri) =>
  uri.startsWith('/') ? `${window.location.origin}${uri}` : uri;

Trim calls do nothing on unloaded media

setTrimOffset and setTrimLength need the clip’s duration metadata. Call them before the resource has loaded and they fail quietly. Always await engine.block.forceLoadAVResource(fill) first.

One more, if you are on Next.js: the editor is browser-only and will break server-side rendering. Load it with next/dynamic and ssr: false, or keep it behind a client component boundary.

Let an agent do the integration

If you work in an AI-assisted editor, you can skip most of the above. IMG.LY publishes Agent Skills for CE.SDK, which load CE.SDK’s documentation and conventions directly into Claude Code, Cursor, and other agent tools. The agent then writes the integration against the current API instead of whatever it remembers from training.

There is also an MCP server that exposes the docs for live lookup during a session.

In practice this matters most for the parts that change between versions, such as component names and configuration shapes. We wrote about the reasoning behind it in Introducing IMG.LY Agent Skills.

Working with the timeline in code

The default UI covers most user-facing editing. When you need to build compositions programmatically, for templates, automation, or a custom UI, you work with the engine directly through cesdk.engine.

Create a video scene and a page

A page in video mode owns a timeline. Its duration is the duration of the exported video.

const engine = cesdk.engine;

const scene = engine.scene.createVideo();

const page = engine.block.create('page');
engine.block.appendChild(scene, page);
engine.block.setWidth(page, 1280);
engine.block.setHeight(page, 720);
engine.block.setDuration(page, 20); // seconds

Add video clips

Video is not its own block type. You create a graphic block and give it a video fill, which means the same positioning, cropping, and animation APIs apply to video as to any other element.

const video1 = engine.block.create('graphic');
engine.block.setShape(video1, engine.block.createShape('rect'));

const fill1 = engine.block.createFill('video');
engine.block.setString(
  fill1,
  'fill/video/fileURI',
  'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4'
);
engine.block.setFill(video1, fill1);

Sequence clips with a track

You could position each clip by hand with time offsets, but a track does it for you. A track keeps its children playing back to back with no gaps, and recalculates the offsets whenever a duration changes.

const track = engine.block.create('track');
engine.block.appendChild(page, track);
engine.block.appendChild(track, video1);
engine.block.appendChild(track, video2);
engine.block.fillParent(track); // size children to the page

// The track shifts video2 automatically
engine.block.setDuration(video1, 15);

Do not call setWidth or setHeight on a track. A track derives its dimensions from its children, so size the children or use fillParent.

Trim clips

Blocks default to 5 seconds. If a clip is longer than its block it gets cut off; if shorter, it loops. To control which portion plays, set a trim offset and length on the fill, after the resource has loaded.

await engine.block.forceLoadAVResource(fill1);
engine.block.setTrimOffset(fill1, 1); // skip the first second
engine.block.setTrimLength(fill1, 10); // play 10 seconds
engine.block.setLooping(fill1, true);

The forceLoadAVResource call is required. Trim APIs need the duration metadata, and calling them on an unloaded resource is a common source of silent no-ops.

Add audio

A video fill plays its own audio track by default, which you can mute. Separate audio blocks handle music and voiceover.

engine.block.setMuted(fill1, true);

const audio = engine.block.create('audio');
engine.block.appendChild(page, audio);
engine.block.setString(
  audio,
  'audio/fileURI',
  'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a'
);
engine.block.setVolume(audio, 0.7);
engine.block.setTimeOffset(audio, 2); // start 2s in
engine.block.setDuration(audio, 7);

Export to MP4

Export runs in a background worker, so the editor stays interactive while it encodes. The scene state is frozen when the export starts, so later edits will not appear in the file.

const page = engine.scene.getCurrentPage();

const blob = await engine.block.exportVideo(page, {
  mimeType: 'video/mp4',
  onProgress: (rendered, encoded, total) => {
    setProgress(Math.round((encoded / total) * 100));
  },
});

const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = 'export.mp4';
anchor.click();
URL.revokeObjectURL(url);

Wire onProgress to React state and you get an export progress bar for free. Encoding a long composition takes time, and a silent wait is the fastest way to make users think the app has hung.

You can also pull just the audio with engine.block.exportAudio(page, { mimeType: 'audio/wav' }), which is useful for transcription and caption pipelines.

Two features worth knowing about once the basics work: transitions between clips, and caption presets for styling subtitles consistently. Captions in particular tend to arrive as a request about a week after launch, because most social video is watched muted.

For the full API surface, see the create and edit videos guide and the timeline editor reference.

Customizing the editor

CE.SDK is not a fixed editor with a theme switch. The main customization points:

  • Component order. Add, remove, and reorder UI elements with setComponentOrder({ in: location }, order), where locations include 'ly.img.dock', 'ly.img.canvas.menu', 'ly.img.inspector.bar', 'ly.img.navigation.bar', and 'ly.img.canvas.bar'.
  • Theming. Use cesdk.ui.setTheme() for light, dark, or system, or build a custom theme with the theme generator. See the theming demo.
  • Custom components. Register your own buttons, dropdowns, and inputs and place them alongside the built-ins, or replace built-ins entirely.
  • Feature API. Show and hide functionality by context, for example hiding controls for a particular block type.
  • Asset sources. Serve video, audio, and image libraries from your own backend, or connect a third-party source such as Unsplash.
  • Localization. Override and extend every string in any language with full i18n support.

When a customization is worth reusing across projects, package it as a plugin. Background removal and vectorization ship as plugins built on the same API you would use.

Where a React video editor pays off

Social publishing. Templates, an audio library, and configurable animations let users produce platform-ready clips, then export at the right aspect ratio per destination.

Digital asset management. Teams adapt and repurpose approved footage inside a central system. Templates with placeholders and locked layers keep output on brand. CE.SDK has no built-in role model, so you gate access in your own backend and use the Feature API to reflect it in the UI.

Marketing automation. Generate campaign variants from one template using text variables, and push template changes across every asset that uses it. This is where the headless engine matters more than the UI, and it is worth reading about automated video generation.

Sales outreach. Personalized video at volume, with localization for multi-market teams.

E-learning. Instructors record, trim, and caption in the browser, building a reusable template library rather than one-off files.

Next steps

The fastest path from here is the video editor starter kit, which is a working project rather than a snippet. If you would rather see it running first, the demo is the same editor this guide produces.

Building the same thing on another framework? We have equivalent guides for Vue.js and Angular, and a broader JavaScript video editing guide covering the underlying browser technologies. If you are still comparing options rather than writing code, we put together a breakdown of the leading video editing SDKs.

Questions about a specific use case? Talk to us.