---
title: "A Modern React Video Editor: Integration Guide"
description: "Learn how to integrate IMG.LY's video editor for React into your web app, from the first component to timeline edits and MP4 export."
url: "https://img.ly/blog/a-modern-react-video-editor/"
type: "blog"
date: "2025-01-08"
author: "Antonello"
tags: ["React","How-To","Video Editor","Video Editing"]
---

> This is the markdown version of [A Modern React Video Editor: Integration Guide](https://img.ly/blog/a-modern-react-video-editor/). For all pages in one file, see [llms-full.txt](https://img.ly/llms-full.txt). For an index of all available pages, see [llms.txt](https://img.ly/llms.txt).

---

_Learn how to integrate_ [_IMG.LY's video editor for React_](https://img.ly/docs/cesdk/react/starterkits/video-editor-e1nlor.md) _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](https://img.ly/docs/cesdk/react/starterkits/video-editor-e1nlor.md) or try the [interactive demo](https://img.ly/demos/video-ui.md).

## Check browser support before you scope the work

CE.SDK's video mode encodes and decodes in the browser using the [WebCodecs API](https://developer.mozilla.org/en-US/docs/Web/API/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](https://img.ly/docs/cesdk/react/create-video/overview-b06512.md) has the current detail.

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

[TikTok reports over a billion monthly active users](https://newsroom.tiktok.com/en-us/1-billion-people-on-tiktok), and [Google's earnings calls](https://abc.xyz/2024-q1-earnings-call/) 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](https://nodejs.org/download) 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](https://img.ly/docs/cesdk/) to get one.

If you do not have a project yet, create one with [Vite](https://vite.dev/guide/):

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

### Step 1: Install CE.SDK

```bash
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`:

```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](https://img.ly/docs/cesdk/react/import-media/from-remote-source/unsplash-8f31f0.md).

`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

```jsx
// 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:

```jsx
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:

```jsx
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:

```javascript
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](https://img.ly/docs/cesdk/react/get-started/agent-skills-f7g8h9.md), 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](https://img.ly/docs/cesdk/react/get-started/mcp-server-fde71c.md) 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](https://img.ly/blog/img-ly-agent-skills-web.md).

## 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.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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](https://img.ly/docs/cesdk/react/create-video/apply-transitions-146026.md), and [caption presets](https://img.ly/docs/cesdk/react/create-video/update-caption-presets-e9c385.md) 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](https://img.ly/docs/cesdk/react/create-video/overview-b06512.md) and the [timeline editor reference](https://img.ly/docs/cesdk/react/create-video/timeline-editor-912252.md).

## 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](https://img.ly/docs/cesdk/react/user-interface/appearance/theming-4b0938.md). See the [theming demo](https://img.ly/demos/theming.md).
- **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](https://img.ly/demos/unsplash-image-assets.md).
- **Localization.** Override and extend every string in any language with [full i18n support](https://img.ly/docs/cesdk/react/user-interface/localization-508e20.md).

When a customization is worth reusing across projects, package it as a [plugin](https://img.ly/docs/cesdk/react/user-interface/ui-extensions-d194d1.md). 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](https://img.ly/docs/cesdk/react/animation/overview-6a2ef2.md) 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](https://img.ly/docs/cesdk/react/create-templates/add-dynamic-content/text-variables-7ecb50.md), 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](https://img.ly/docs/cesdk/js/prebuilt-solutions/automated-video-generation-31187c/).

**Sales outreach.** Personalized video at volume, with [localization](https://img.ly/demos/language.md) 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](https://img.ly/docs/cesdk/react/starterkits/video-editor-e1nlor.md), which is a working project rather than a snippet. If you would rather see it running first, the [demo](https://img.ly/demos/video-ui.md) is the same editor this guide produces.

Building the same thing on another framework? We have equivalent guides for [Vue.js](https://img.ly/blog/a-modern-vue-js-video-editor.md) and [Angular](https://img.ly/blog/a-modern-angular-video-editor.md), and a broader [JavaScript video editing guide](https://img.ly/blog/javascript-video-editing-guide.md) 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](https://img.ly/blog/top-7-video-editing-sdks-in-2025.md).

Questions about a specific use case? [Talk to us](https://img.ly/forms/contact-sales.md).

---

## More Resources

- **[IMG.LY Website](https://img.ly/index.md)** - Creative editing SDKs for photo, video, and design
- **[Documentation](https://img.ly/docs/cesdk/)** - CE.SDK developer documentation
- **[Contact Sales](https://img.ly/forms/contact-sales.md)** - Get a custom quote. A public JSON API accepts the request directly, no account or key needed. Ask your user for consent and their details first.
