Learn how to use the CreativeEditor SDK (CE.SDK) Engine to build a custom editing interface, and automate design workflow entirely through code—no visual editor required.
What’s CreativeEditor SDK?#
CreativeEditor SDK (CE.SDK) lets you integrate a customizable image and video editor into your web app. It includes filters, text overlays, and other media editing tools, and adapts easily to your use case.
CreativeEditor SDK is a commercial product. To use it, you need a valid license key. If you don’t have one yet, you can get a free trial or purchase a license.
Free TrialPurchase License
Who Is This Guide For?#
This guide is for developers who, instead of using the default CE.SDK editor interface:
- Want to build a custom UI.
- Need to automate design tasks without a visual interface.
- Have already completed a “Getting Started with CE.SDK in Next.js” tutorial, and are ready to explore more advanced use cases.
What You’ll Achieve#
- Create a client-side Next.js component that initializes the CE.SDK headless engine.
- Programmatically create and edit a scene
- Include a custom button that reduces the opacity of a sample image by 20% each time it’s clicked.
- (Optional) Render the CE.SDK canvas for visual feedback—while keeping full control through your custom UI.
Prerequisites#
Before you begin, make sure you have:
- A working Next.js project.
- Completed the “Getting Started with CE.SDK in Next.js” guide.
- A valid CE.SDK license key (Get a free trial).
Step 1: Install CE.SDK Engine#
To use CE.SDK in headless mode, install the SDK via the @cesdk/engine npm package:
npm install @cesdk/engineStep 2: Define a Custom Component for Your Headless CE.SDK Integration#
Inside your Next.js /components folder, create a new file according to the project’s language:
Create a file named CustomEditor.js, and add the following code to it:
'use client';
import CreativeEngine from '@cesdk/engine';import { useEffect, useRef, useCallback } from 'react';
export default function CustomEditor() { // Reference to store the DOM container where the CreativeEngine canvas will be attached const canvasRef = useRef(null); // Reference to store the CreativeEngine instance const engineRef = useRef(null); // Reference to store the the ID of the image block added to the scene const imageBlockIdRef = useRef(null);
useEffect(() => { // Your CE.SDK configurations const config = { license: '<YOUR_CESDK_LICENSE>', // Replace with your license key };
// Initialize CreativeEngine in headless mode CreativeEngine.init(config).then(engine => { // To avoid initializing CreativeEngine twice in strict mode if (!engineRef.current) { engineRef.current = engine;
// Append CE.SDK canvas to the DOM if (canvasRef.current) { canvasRef.current.appendChild(engine.element); }
// Get the current scene or create a new one let scene = engine.scene.get(); if (!scene) { scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.appendChild(scene, page); }
// Get the first page block const [page] = engine.block.findByType('page');
// Append a block to show an image on the page const imageBlockId = engine.block.create('graphic'); imageBlockIdRef.current = imageBlockId; engine.block.setShape(imageBlockId, engine.block.createShape('rect'));
// Fill the block with an image from a public source const imageFill = engine.block.createFill('image'); engine.block.setSourceSet(imageFill, 'fill/image/sourceSet', [ { uri: 'https://img.ly/static/ubq_samples/sample_1_1024x683.jpg', width: 1024, height: 683, }, ]); engine.block.setFill(imageBlockId, imageFill); engine.block.appendChild(page, imageBlockId);
// Zoom to fit the page in the editor view engine.scene.zoomToBlock(page); } }); }, []);
const changeOpacity = useCallback(() => { const engine = engineRef.current; const imageBlockId = imageBlockIdRef.current;
if (engine && imageBlockId != null) { // Get the current opacity value of the image const currentOpacity = engine.block.getOpacity(imageBlockId); // Reduce the opacity image by 20% at each click engine.block.setOpacity(imageBlockId, currentOpacity * 0.8); } }, [engineRef, imageBlockIdRef]);
return ( <div style={{ width: '100vw', height: '100vh', position: 'relative' }}> <div ref={canvasRef} style={{ width: '100%', height: '100%' }} /> <div style={{ position: 'fixed', top: 20, left: 20 }}> <button onClick={changeOpacity}>Reduce Opacity</button> </div> </div> );}Create a file named CustomEditor.tsx, and add the following code to it:
'use client';
import CreativeEngine from '@cesdk/engine';import { useEffect, useRef, useCallback } from 'react';
export default function CustomEditor() { // Reference to store the DOM container where the CreativeEngine canvas will be attached const canvasRef = useRef<HTMLDivElement | null>(null); // Reference to store the CreativeEngine instance const engineRef = useRef<CreativeEngine | null>(null); // Reference to store the the ID of the image block added to the scene const imageBlockIdRef = useRef<number | null>(null);
useEffect(() => { // Your CE.SDK configurations const config = { license: 'YOUR_LICENSE_KEY', // Replace with your license key };
// Initialize CreativeEngine in headless mode CreativeEngine.init(config).then(engine => { // To avoid initializing CreativeEngine twice in strict mode if (!engineRef.current) { engineRef.current = engine;
// Append CE.SDK canvas to the DOM if (canvasRef.current) { canvasRef.current.appendChild(engine.element); }
// Get the current scene or create a new one let scene = engine.scene.get(); if (!scene) { scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.appendChild(scene, page); }
// Get the first page block const [page] = engine.block.findByType('page');
// Append a block to show an image on the page const imageBlockId = engine.block.create('graphic'); imageBlockIdRef.current = imageBlockId; engine.block.setShape(imageBlockId, engine.block.createShape('rect'));
// Fill the block with an image from a public source const imageFill = engine.block.createFill('image'); engine.block.setSourceSet(imageFill, 'fill/image/sourceSet', [ { uri: 'https://img.ly/static/ubq_samples/sample_1_1024x683.jpg', width: 1024, height: 683, }, ]); engine.block.setFill(imageBlockId, imageFill); engine.block.appendChild(page, imageBlockId);
// Zoom to fit the page in the editor view engine.scene.zoomToBlock(page); } }); }, []);
const changeOpacity = useCallback(() => { const engine = engineRef.current; const imageBlockId = imageBlockIdRef.current;
if (engine && imageBlockId != null) { // Get the current opacity value of the image const currentOpacity = engine.block.getOpacity(imageBlockId); // Reduce the opacity image by 20% at each click engine.block.setOpacity(imageBlockId, currentOpacity * 0.8); } }, [engineRef, imageBlockIdRef]);
return ( <div style={{ width: '100vw', height: '100vh', position: 'relative' }}> <div ref={canvasRef} style={{ width: '100%', height: '100%' }} /> <div style={{ position: 'absolute', top: 20, left: 20 }}> <button type="button" onClick={changeOpacity}> Reduce Opacity </button> </div> </div> );}⚠️ Always include "use client" with your component, which uses React hooks and browser APIs.
Once your app initializes CreativeEngine, it allows you to programmatically create and manipulate the scene. In this example, the component includes:
- a sample image
- a ”Reduce Opacity” button
- every click of the button reduces the image’s opacity by 20%
”Reduce Opacity” uses:
- a
changeOpacity()callback on every click - CE.SDK
blockAPI to dynamically reduce the image’s opacity
Note: Visual rendering is completely optional. You can also run the engine purely in headless mode—for automation tasks like:
- Modifying graphics
- Generating exports
Step 3: Create a Client-Side Editor Component#
If you attach it to the canvas, CreativeEngine requires:
- Browser-specific features like
document - Dynamic DOM manipulation
Because of that, the CustomEditor:
- must only run on the client.
- should never be server-side rendered (SSR).
To ensure that, load it dynamically using Next.js’s dynamic import with ssr: false.
To avoid repeating this dynamic import in every file where you use the component, create a wrapper component named CustomEditorNoSSR inside your components folder.
Create a file named CustomEditorNoSSR.js, and add the following code in it:
'use client';
import dynamic from 'next/dynamic';
const CustomEditorNoSSR = dynamic(() => import('./CustomEditor'), { ssr: false,});
export default CustomEditorNoSSR;Create a file named CustomEditorNoSSR.tsx, and add the following code to it:
'use client';
import dynamic from 'next/dynamic';
const CustomEditorNoSSR = dynamic(() => import('./CustomEditor'), { ssr: false,});
export default CustomEditorNoSSR;This wrapper exports the CustomEditor component with SSR deactivated.
Step 4: Use the Creative Editor Component#
You can import the non-SSR version of your editor like this:
import { default as CustomEditor } from './components/CustomEditorNoSSR';Then, render it on the client by adding it to your JSX:
<CustomEditor />Next:
- Start your Next.js app locally.
- Navigate to the page where you’ve included
<CustomEditor>. - See the a sample image in the canvas.
- Use the “Reduce Opacity” button to adjust the image’s opacity by 20% with each click.
Use Cases#
Congratulations! You’ve just unlocked the foundation for more advanced use cases, such as:
- Building fully custom user interfaces using Next.js components alongside the CE.SDK canvas.
- Automating the generation of graphics or marketing assets directly in the browser.
- Integrating CE.SDK into complex, multi-step creative workflows using programmatic logic.
- Creating server-side image or video processing features with
@cesdk/node, as covered in “Getting Started with CE.SDK in Node.js” guide.
Troubleshooting & Common Errors#
❌ Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client.
- This warning appears only during development and doesn’t affect production. It’s linked to how Turbopack serves your app locally, and how CE.SDK dynamically mounts DOM elements. You can safely ignore this or suppress it by adding
suppressHydrationWarningto the affected component.
❌ Error: document is not defined
- This means CE.SDK tried to access the browser
documentobject on the server. To avoid that, ensure your app importsCustomEditordynamically withnext/dynamicandssr: false, using theCustomEditorNoSSRwrapper.
❌ Error: The CE.SDK canvas is rendered twice
- Although
useEffect(..., [])is designed to execute only once, React might trigger a re-render in strict mode, causing the engine to load twice. Add an additional check (if (!engineRef.current) { ... }) to initialize the engine only if it hasn’t been loaded already.
❌ Error: CE.SDK canvas doesn’t render
- Make sure that you’re appending
engine.elementto a valid DOM reference. Double-check that the reference is valid and is properly attached to the DOM element.
❌ Error: Module not found: Can't resolve '@cesdk/engine``'
- Verify that you’ve correctly installed the CE.SDK Engine using the command
npm install @cesdk/engine.
❌ Error: Editor engine could not be loaded: The License Key (API Key) you are using to access CE.SDK is invalid
- Double-check that your license key is valid, hasn’t expired, and is correctly set.
❌ The component doesn’t behave as expected
- Verify that your component paths and imports are correct.
- See if
use clientis declared at the top of client components. - Check the browser console for specific errors.
Next Steps#
This guide lays the foundation for: