---
title: "Loading Indicator"
description: "Customize the loading screen that appears while the CE.SDK editor initializes by adding headings, body text, or removing the spinner."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/-575e91/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [User Interface](https://img.ly/docs/cesdk/sveltekit/user-interface-5a089a/) > [Customization](https://img.ly/docs/cesdk/sveltekit/user-interface/customization-72b2f8/) > [Loading Indicator](https://img.ly/docs/cesdk/sveltekit/-575e91/)
---
Customize the loading screen that appears while the CE.SDK editor initializes by configuring a spinner, heading, and body text.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-loading-indicator-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-loading-indicator-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-user-interface-customization-loading-indicator-browser/)
The loading indicator fills the editor canvas while the engine initializes and disappears once ready. By default only a spinner is shown, but you can add a heading and body text or remove the spinner entirely.
All configuration uses static pre-init APIs that must be called **before** `CreativeEditorSDK.create()`, because the loading screen renders before the editor instance exists.
```typescript file=@cesdk_web_examples/guides-user-interface-customization-loading-indicator-browser/index.ts reference-only
import CreativeEditorSDK from '@cesdk/cesdk-js';
import Example from './browser';
const customSpinner = `
`;
CreativeEditorSDK.ui.addIconSet('custom-loading-spinner', customSpinner);
CreativeEditorSDK.i18n.setTranslations({
en: {
'loading.heading': 'Welcome',
'loading.text': 'Preparing your editor...'
},
de: {
'loading.heading': 'Willkommen',
'loading.text': 'Editor wird vorbereitet...'
}
});
CreativeEditorSDK.ui.setComponentOrder({ in: 'ly.img.loading' }, [
'ly.img.loading.spinner',
{ id: 'ly.img.loading.heading', content: 'loading.heading' },
{ id: 'ly.img.loading.text', content: 'loading.text' }
]);
// To show only text without a spinner, omit the spinner:
// CreativeEditorSDK.ui.setComponentOrder({ in: 'ly.img.loading' }, [
// { id: 'ly.img.loading.heading', content: 'Loading' },
// { id: 'ly.img.loading.text', content: 'Please wait...' }
// ]);
// Apply theme from URL query param (used by hero image capture script)
const theme = new URLSearchParams(window.location.search).get('theme');
if (theme === 'dark' || theme === 'light') {
CreativeEditorSDK.ui.setTheme(theme);
}
const config = {
// license: import.meta.env.VITE_CESDK_LICENSE,
userId: 'guides-user',
// Use local assets when developing with local packages
...(import.meta.env.CESDK_USE_LOCAL && {
baseURL: import.meta.env.VITE_CESDK_ASSETS_BASE_URL
})
};
CreativeEditorSDK.create('#cesdk_container', config)
.then(async (cesdk) => {
// Expose cesdk for debugging and hero screenshot generation
(window as any).cesdk = cesdk;
// Load the example plugin
await cesdk.addPlugin(new Example());
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error('Failed to initialize CE.SDK:', error);
});
```
## Loading Components
Three component types are available:
- **`ly.img.loading.spinner`**: An animated spinner icon. Pass as a plain string ID.
- **`ly.img.loading.heading`**: Large heading text. Pass as `{ id: 'ly.img.loading.heading', content: 'text or translation key' }`.
- **`ly.img.loading.text`**: Body text below the heading. Pass as `{ id: 'ly.img.loading.text', content: 'text or translation key' }`.
We configure them by calling `CreativeEditorSDK.ui.setComponentOrder()` with the area `{ in: 'ly.img.loading' }` and an array of components. They render vertically in the order specified.
```typescript highlight=highlight-configure-loading
CreativeEditorSDK.ui.setComponentOrder({ in: 'ly.img.loading' }, [
'ly.img.loading.spinner',
{ id: 'ly.img.loading.heading', content: 'loading.heading' },
{ id: 'ly.img.loading.text', content: 'loading.text' }
]);
```
We then create the editor as usual. The loading screen displays our custom content while the engine initializes:
```typescript highlight=highlight-create-editor
CreativeEditorSDK.create('#cesdk_container', config)
.then(async (cesdk) => {
// Expose cesdk for debugging and hero screenshot generation
(window as any).cesdk = cesdk;
// Load the example plugin
await cesdk.addPlugin(new Example());
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error('Failed to initialize CE.SDK:', error);
});
```
To show only text without a spinner, omit the spinner from the array:
```typescript highlight=highlight-text-only
// To show only text without a spinner, omit the spinner:
// CreativeEditorSDK.ui.setComponentOrder({ in: 'ly.img.loading' }, [
// { id: 'ly.img.loading.heading', content: 'Loading' },
// { id: 'ly.img.loading.text', content: 'Please wait...' }
// ]);
```
## Custom Spinner Animation
The spinner renders an SVG icon with symbol ID `@imgly/EditorProgress`. To replace it, register a custom icon set via `CreativeEditorSDK.ui.addIconSet()` before creating the editor. The SVG must contain a `` element with `id="@imgly/EditorProgress"`. Use `currentColor` for stroke and fill values so the spinner adapts to the active theme.
```typescript highlight=highlight-custom-spinner
const customSpinner = `
`;
CreativeEditorSDK.ui.addIconSet('custom-loading-spinner', customSpinner);
```
## Translating Loading Text
The `content` values support i18n. If a `content` string matches a translation key, the translated value is displayed. Otherwise the literal string is used as a fallback.
Register translations via `CreativeEditorSDK.i18n.setTranslations()` before creating the editor, then pass the translation key as the `content` value:
```typescript highlight=highlight-translate-loading
CreativeEditorSDK.i18n.setTranslations({
en: {
'loading.heading': 'Welcome',
'loading.text': 'Preparing your editor...'
},
de: {
'loading.heading': 'Willkommen',
'loading.text': 'Editor wird vorbereitet...'
}
});
```
## Troubleshooting
**Loading indicator not changing.** Ensure `setComponentOrder` is called *before* `CreativeEditorSDK.create()`. Calls made after initialization have no effect on the loading screen.
**Text not appearing.** Verify that component objects include the `content` property. A string ID like `'ly.img.loading.heading'` without a content object will not render text.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Auto Captions"
description: "Integrate automatic caption generation into your CE.SDK application using the Auto Caption plugin with pluggable speech-to-text providers."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/-73368c/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [AI Integration](https://img.ly/docs/cesdk/sveltekit/user-interface/ai-integration-5aa356/) > [Auto Captions](https://img.ly/docs/cesdk/sveltekit/-73368c/)
---
Generate captions automatically from spoken audio in video and audio blocks using CE.SDK's Auto Caption plugin.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ai-integration-auto-captions-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ai-integration-auto-captions-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-user-interface-ai-integration-auto-captions-browser/)
The Auto Caption plugin extracts audio from media blocks, sends it to a speech-to-text provider, and creates timed caption blocks from the transcription. It supports both a built-in UI workflow where users select which blocks to transcribe and a programmatic API for automation. The plugin ships with an ElevenLabs Scribe V2 provider via fal.ai, and you can implement your own provider using the `TranscriptionProvider` interface. For manually creating and editing captions, see [Add Captions](https://img.ly/docs/cesdk/sveltekit/edit-video/add-captions-f67565/).
```typescript file=@cesdk_web_examples/guides-user-interface-ai-integration-auto-captions-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import type { TranscriptionProvider } from '@imgly/plugin-autocaption-web';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import AutocaptionPlugin from '@imgly/plugin-autocaption-web';
import { ElevenLabsScribeV2 } from '@imgly/plugin-autocaption-web/fal-ai';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins for the video editor
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: [
'ly.img.image.upload',
'ly.img.video.upload',
'ly.img.audio.upload'
]
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
// Register the Auto Caption plugin with the built-in ElevenLabs provider
await cesdk.addPlugin(
AutocaptionPlugin({
provider: ElevenLabsScribeV2({
// The proxy URL forwards requests to fal.ai with the API key added
// server-side, keeping the key out of the browser
proxyUrl: 'https://your-server.com/api/fal-proxy'
})
})
);
// Create a video scene and add a video clip with spoken audio
await cesdk.actions.run('scene.create', {
page: { width: 1920, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
engine.block.setDuration(page, 30);
const videoUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4';
const track = engine.block.create('track');
engine.block.appendChild(page, track);
const videoClip = await engine.block.addVideo(videoUrl, 1920, 1080, {
timeline: { duration: 30, timeOffset: 0 }
});
engine.block.appendChild(track, videoClip);
engine.block.fillParent(track);
// Example: Implementing a custom transcription provider
// Use this pattern to connect any speech-to-text service
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _customProvider: TranscriptionProvider = {
name: 'My Custom STT',
async transcribe(audio: Blob, options?) {
// Send the audio blob to your speech-to-text service
const formData = new FormData();
formData.append('audio', audio, 'audio.mp4');
if (options?.language) {
formData.append('language', options.language);
}
const response = await fetch('https://your-stt-api.com/transcribe', {
method: 'POST',
body: formData,
signal: options?.abortSignal
});
const result = await response.json();
// Return the transcription as a valid SRT string
return { srt: result.srt };
}
};
// To use the custom provider instead of ElevenLabs:
// AutocaptionPlugin({ provider: customProvider })
// Open the caption panel so the Auto Caption button is visible
cesdk.ui.openPanel('//ly.img.panel/inspector/caption');
}
}
export default Example;
```
This guide covers how to use the built-in auto caption UI, install and configure the plugin with the ElevenLabs provider, set up a proxy server for secure API communication, implement a custom transcription provider, and troubleshoot common issues.
## Using the Built-in Auto Caption UI
The Auto Caption plugin adds an "Auto Caption" button to the caption panel. Clicking it opens an auto-generate view where you select which media blocks to transcribe.
### Accessing the Auto-Generate View
Open the caption panel from the inspector and click the "Auto Caption" button. The auto-generate view lists all audio and video blocks in the scene with checkboxes. Muted blocks and video blocks without audio tracks appear disabled and cannot be selected.
### Selecting Blocks
Use the checkboxes to choose which blocks to include in caption generation. The "Select All" button selects every eligible block, and "Deselect All" clears the selection. Only blocks with audible audio content can be selected.
### Generating and Cancelling Captions
Click "Generate Captions" to start transcription. The plugin extracts audio from each selected block, sends it to the configured provider, and creates caption blocks from the returned transcription. You can cancel an in-progress generation at any time — the plugin cleans up any partially created caption blocks.
After generation completes, the view switches to the caption edit view where you can review, restyle, and adjust the generated captions.
## Installing the Plugin
Install `@imgly/plugin-autocaption-web` alongside `@cesdk/cesdk-js`. The plugin version must match the CE.SDK version (unified versioning).
```typescript highlight-install-plugin
import AutocaptionPlugin from '@imgly/plugin-autocaption-web';
import { ElevenLabsScribeV2 } from '@imgly/plugin-autocaption-web/fal-ai';
```
The plugin is imported as a default export, and the fal.ai provider is imported from the `/fal-ai` subpath. The `@fal-ai/client` dependency is bundled into the provider, so you don't need to install it separately.
## Configuring the Built-in Provider
We register the Auto Caption plugin with `cesdk.addPlugin()`, passing the ElevenLabs Scribe V2 provider configured with a `proxyUrl`. The proxy URL points to your server endpoint that forwards requests to fal.ai with the API key added server-side.
```typescript highlight-configure-provider
// Register the Auto Caption plugin with the built-in ElevenLabs provider
await cesdk.addPlugin(
AutocaptionPlugin({
provider: ElevenLabsScribeV2({
// The proxy URL forwards requests to fal.ai with the API key added
// server-side, keeping the key out of the browser
proxyUrl: 'https://your-server.com/api/fal-proxy'
})
})
);
```
The `ElevenLabsScribeV2()` factory accepts a configuration object with `proxyUrl` (required) and optional `headers` for custom authentication headers. You can also pass `debug: true` to the `AutocaptionPlugin()` call to enable console logging of each pipeline step, including audio sizes and provider timings.
## Setting Up a Proxy Server
The built-in provider communicates with fal.ai to run the ElevenLabs Scribe V2 model. Calling fal.ai directly from the browser would expose your API key in client-side code, which is insecure.
A proxy server sits between the browser and fal.ai. Your browser sends requests to your own server endpoint, which adds the fal.ai API key and forwards the request. The API key never leaves the server.
Your proxy endpoint needs to:
- Accept POST requests from the browser
- Add the `Authorization` header with your fal.ai API key
- Forward the request body to fal.ai
- Handle CORS so the browser can reach the endpoint
For detailed proxy implementation instructions, see [Proxy Server](https://img.ly/docs/cesdk/sveltekit/user-interface/ai-integration/proxy-server-61f901/).
## Implementing a Custom Transcription Provider
You can use any speech-to-text service by implementing the `TranscriptionProvider` interface. The interface requires a `name` string and a `transcribe()` method that receives an audio blob and returns SRT text.
```typescript highlight-custom-provider
// Example: Implementing a custom transcription provider
// Use this pattern to connect any speech-to-text service
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _customProvider: TranscriptionProvider = {
name: 'My Custom STT',
async transcribe(audio: Blob, options?) {
// Send the audio blob to your speech-to-text service
const formData = new FormData();
formData.append('audio', audio, 'audio.mp4');
if (options?.language) {
formData.append('language', options.language);
}
const response = await fetch('https://your-stt-api.com/transcribe', {
method: 'POST',
body: formData,
signal: options?.abortSignal
});
const result = await response.json();
// Return the transcription as a valid SRT string
return { srt: result.srt };
}
};
// To use the custom provider instead of ElevenLabs:
// AutocaptionPlugin({ provider: customProvider })
```
### The TranscriptionProvider Interface
The `transcribe()` method receives two arguments:
- **`audio`**: A `Blob` in audio/mp4 format, produced by `engine.block.exportAudio()`. Send this to your speech-to-text service.
- **`options`**: An optional object with:
- `language`: A BCP-47 language code (e.g., `"en"`, `"de"`, `"pt"`) for the expected spoken language.
- `abortSignal`: An `AbortSignal` to cancel the transcription request. Pass this to `fetch()` or your HTTP client to abort the request.
- `maxLineLength`: Maximum characters per SRT line (default 37).
- `maxLines`: Maximum lines per SRT cue (default 1).
- `debug`: Enable verbose logging to the console (default `false`).
The method must return `{ srt: string }` containing a valid SRT-formatted subtitle string. The plugin then passes this SRT to `engine.block.createCaptionsFromURI()` to create the caption blocks.
## Caption Generation Workflow
When the user clicks "Generate Captions", the plugin runs the following pipeline for each selected block:
1. **Load media** — Calls `engine.block.forceLoadAVResource()` to ensure the media is ready.
2. **Export audio** — Extracts audio via `engine.block.exportAudio()`, producing an MP4 blob.
3. **Transcribe** — Sends the blob to the `TranscriptionProvider.transcribe()` method.
4. **Create captions** — Converts the SRT string to caption blocks with `engine.block.createCaptionsFromURI()`.
5. **Add to track** — Appends caption blocks to a caption track on the page using `engine.block.appendChild()`.
6. **Style** — Applies the `ly.img.caption.presets.outline` preset via `engine.asset.fetchAsset()` and `engine.asset.applyToBlock()`.
7. **Position** — Centers the first caption with `engine.block.alignHorizontally()` and `engine.block.alignVertically()`.
8. **Undo step** — Wraps the entire operation in a single undo step with `engine.editor.addUndoStep()`.
Multiple blocks are processed in parallel using `Promise.all`. Existing caption tracks are removed before new ones are created.
## Handling Errors and Notifications
The plugin handles errors per-block, so if one block fails, others continue processing. When transcription fails or no speech is detected, the plugin shows a notification via `cesdk.ui.showNotification()`.
- **Total failure**: All blocks failed — shows an error notification.
- **Partial failure**: Some blocks succeeded, others failed — shows a warning listing which blocks had issues.
- **Cancellation**: When the user cancels, the plugin throws an `AbortError` and cleans up any caption blocks already created during the current generation.
## Troubleshooting
- **Plugin not appearing in caption panel**: Verify `cesdk.addPlugin()` is called with the `AutocaptionPlugin()` result and the caption panel is enabled in the editor.
- **Proxy errors**: Check that `proxyUrl` is accessible from the browser, handles CORS preflight requests, and correctly forwards the fal.ai API key.
- **"No speech detected" for all blocks**: Verify audio blocks are not muted and contain audible speech. Video blocks without audio tracks are intentionally disabled.
- **Generation hangs or times out**: Check network connectivity to fal.ai through your proxy. The ElevenLabs model can take 10-30 seconds for longer audio.
- **Captions not styled correctly**: Verify the `CaptionPresetsAssetSource` plugin is registered, which provides the `ly.img.caption.presets` asset source.
- **Muted blocks cannot be selected**: This is intentional. Muted audio blocks and video blocks without audio tracks produce no audio output for transcription.
## Next Steps
- [Add Captions](https://img.ly/docs/cesdk/sveltekit/edit-video/add-captions-f67565/) — Manually create and edit caption blocks
- [Update Caption Presets](https://img.ly/docs/cesdk/sveltekit/create-video/update-caption-presets-e9c385/) — Customize caption styling presets
- [Proxy Server](https://img.ly/docs/cesdk/sveltekit/user-interface/ai-integration/proxy-server-61f901/) — Set up secure API communication
- [Custom Provider](https://img.ly/docs/cesdk/sveltekit/user-interface/ai-integration/custom-provider-16e851/) — Create custom AI providers
- [Integrate AI Features](https://img.ly/docs/cesdk/sveltekit/user-interface/ai-integration/integrate-8e906c/) — Overview of AI integration
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Customize the Clip Context Menu"
description: "Control which actions appear in the video timeline clip context menu, reorder built-in actions, and add custom entries per clip type."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/-f8e7d6/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [User Interface](https://img.ly/docs/cesdk/sveltekit/user-interface-5a089a/) > [Customization](https://img.ly/docs/cesdk/sveltekit/user-interface/customization-72b2f8/) > [Clip Context Menu](https://img.ly/docs/cesdk/sveltekit/-f8e7d6/)
---
Customize the dropdown menu on video timeline clips — control which actions appear, their order, and add custom entries per clip type.

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-clip-context-menu-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-clip-context-menu-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-user-interface-customization-clip-context-menu-browser/)
The clip context menu is the dropdown that opens via a dedicated button on each clip in the video timeline. CE.SDK provides a different default menu per clip type: background clips show move, overlay, mute, and trim actions; overlay clips show layering and clip-conversion actions; caption clips show merge, add, and delete actions. All menus are customizable through the `ly.img.video.clip.menu` UI area using the Component Order API.
```typescript file=@cesdk_web_examples/guides-user-interface-customization-clip-context-menu-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
// Enable video editing features
cesdk.feature.enable('ly.img.video.*');
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: [
'ly.img.image.upload',
'ly.img.video.upload',
'ly.img.audio.upload'
]
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create');
// Retrieve the default clip menu order for background clips
const clipOrder = cesdk.ui.getComponentOrder({
in: 'ly.img.video.clip.menu',
when: { clipType: 'clip' }
});
// eslint-disable-next-line no-console
console.log('Default clip menu order:', clipOrder);
// Retrieve the default overlay menu order
const overlayOrder = cesdk.ui.getComponentOrder({
in: 'ly.img.video.clip.menu',
when: { clipType: 'overlay' }
});
// eslint-disable-next-line no-console
console.log('Default overlay menu order:', overlayOrder);
// Retrieve the default caption menu order
const captionOrder = cesdk.ui.getComponentOrder({
in: 'ly.img.video.clip.menu',
when: { clipType: 'caption' }
});
// eslint-disable-next-line no-console
console.log('Default caption menu order:', captionOrder);
// Replace the overlay menu with a simplified layout
cesdk.ui.setComponentOrder(
{ in: 'ly.img.video.clip.menu', when: { clipType: 'overlay' } },
[
'ly.img.video.clip.menu.setAsClip',
'ly.img.separator',
'ly.img.video.clip.menu.duplicate',
'ly.img.video.clip.menu.delete'
]
);
// Add a custom "Export Clip" action to the background clip menu
cesdk.ui.insertOrderComponent(
{
in: 'ly.img.video.clip.menu',
before: 'ly.img.video.clip.menu.delete',
when: { clipType: 'clip' }
},
{
id: 'ly.img.video.clip.menu.action',
key: 'export-clip',
label: 'Export Clip',
icon: '@imgly/Download',
onClick: () => {
// eslint-disable-next-line no-console
console.log('Export Clip action triggered');
}
}
);
// Remove the placeholder action from the background clip menu
cesdk.ui.removeOrderComponent({
in: 'ly.img.video.clip.menu',
match: 'ly.img.video.clip.menu.placeholder',
when: { clipType: 'clip' }
});
// Insert a separator after the trim action in the clip menu
cesdk.ui.insertOrderComponent(
{
in: 'ly.img.video.clip.menu',
after: 'ly.img.video.clip.menu.trim',
when: { clipType: 'clip' }
},
'ly.img.separator'
);
// Disable the custom export action conditionally
cesdk.ui.updateOrderComponent(
{
in: 'ly.img.video.clip.menu',
match: { id: 'ly.img.video.clip.menu.action', key: 'export-clip' },
when: { clipType: 'clip' }
},
{ isDisabled: false }
);
}
}
export default Example;
```
This guide covers reading the default menu order, replacing or modifying the menu per clip type, removing and inserting built-in actions, and adding custom actions with callbacks.
## Default Menu Actions
Each clip type has its own default order. The menu renders components in the order defined by `setComponentOrder` or the built-in defaults.
**Clip (Background Track):** Move Left, Move Right, Set as Overlay, Mute, Trim, Replace, Placeholder, Duplicate, Delete — with separators grouping related actions.
**Overlay (Foreground Track):** Bring Forward, Send Backward, Set as Clip, Mute, Trim, Replace, Placeholder, Duplicate, Delete.
**Caption:** Merge Captions, Add Caption, Delete.
### Available Component IDs
All built-in component IDs that you can use when configuring menu orders:
| Component ID | Purpose |
|---|---|
| `ly.img.video.clip.menu.moveLeft` | Move clip earlier in the track |
| `ly.img.video.clip.menu.moveRight` | Move clip later in the track |
| `ly.img.video.clip.menu.bringForward` | Move overlay one layer up |
| `ly.img.video.clip.menu.sendBackward` | Move overlay one layer down |
| `ly.img.video.clip.menu.setAsOverlay` | Convert clip to overlay |
| `ly.img.video.clip.menu.setAsClip` | Convert overlay to clip |
| `ly.img.video.clip.menu.mute` | Toggle audio mute |
| `ly.img.video.clip.menu.trim` | Open trim controls |
| `ly.img.video.clip.menu.caption.merge` | Merge adjacent captions |
| `ly.img.video.clip.menu.caption.add` | Add a new caption |
| `ly.img.video.clip.menu.replace` | Replace clip media |
| `ly.img.video.clip.menu.placeholder` | Toggle placeholder mode |
| `ly.img.video.clip.menu.duplicate` | Duplicate the clip |
| `ly.img.video.clip.menu.delete` | Delete the clip |
| `ly.img.video.clip.menu.action` | Generic custom action slot |
| `ly.img.separator` | Visual separator between groups |
## Reading the Current Order
We retrieve the current menu order for any clip type using `cesdk.ui.getComponentOrder()`. Pass the `when` option with `clipType` to target a specific clip type. Without a `when` clause, the default `'clip'` type order is returned.
```typescript highlight=highlight-read-order
// Retrieve the default clip menu order for background clips
const clipOrder = cesdk.ui.getComponentOrder({
in: 'ly.img.video.clip.menu',
when: { clipType: 'clip' }
});
// eslint-disable-next-line no-console
console.log('Default clip menu order:', clipOrder);
// Retrieve the default overlay menu order
const overlayOrder = cesdk.ui.getComponentOrder({
in: 'ly.img.video.clip.menu',
when: { clipType: 'overlay' }
});
// eslint-disable-next-line no-console
console.log('Default overlay menu order:', overlayOrder);
// Retrieve the default caption menu order
const captionOrder = cesdk.ui.getComponentOrder({
in: 'ly.img.video.clip.menu',
when: { clipType: 'caption' }
});
// eslint-disable-next-line no-console
console.log('Default caption menu order:', captionOrder);
```
This is useful for inspecting the current state before making modifications, or for building a UI that displays the current configuration.
## Replacing the Entire Order
We can override the full menu for a clip type using `cesdk.ui.setComponentOrder()`. Pass an array of component IDs to define the complete new order. Each clip type is configured independently — changing the overlay menu does not affect clip or caption menus.
```typescript highlight=highlight-replace-order
// Replace the overlay menu with a simplified layout
cesdk.ui.setComponentOrder(
{ in: 'ly.img.video.clip.menu', when: { clipType: 'overlay' } },
[
'ly.img.video.clip.menu.setAsClip',
'ly.img.separator',
'ly.img.video.clip.menu.duplicate',
'ly.img.video.clip.menu.delete'
]
);
```
Here we replace the overlay menu with only four items: a "Set as Clip" action, a separator, then duplicate and delete. All other default overlay actions are removed.
## Removing Actions
We remove a specific action from a clip type's menu using `cesdk.ui.removeOrderComponent()`. The `match` option accepts a component ID string, an index, or a predicate function.
```typescript highlight=highlight-remove-action
// Remove the placeholder action from the background clip menu
cesdk.ui.removeOrderComponent({
in: 'ly.img.video.clip.menu',
match: 'ly.img.video.clip.menu.placeholder',
when: { clipType: 'clip' }
});
```
This removes only the placeholder action from the background clip menu. The overlay and caption menus remain unchanged.
## Inserting Actions
We insert a component before or after an existing one using `cesdk.ui.insertOrderComponent()`. The options support `before`, `after`, and `position` (`'start'`, `'end'`, or a numeric index) placement.
```typescript highlight=highlight-insert-action
// Insert a separator after the trim action in the clip menu
cesdk.ui.insertOrderComponent(
{
in: 'ly.img.video.clip.menu',
after: 'ly.img.video.clip.menu.trim',
when: { clipType: 'clip' }
},
'ly.img.separator'
);
```
Here we add a separator after the trim action, visually grouping trim with the actions above it and separating it from the actions below.
## Adding Custom Actions
We register a custom action using the `ly.img.video.clip.menu.action` component ID. Pass a `ClipContextMenuCustomAction` object with `key`, `label`, `onClick`, and optionally `icon` and `isDisabled`.
```typescript highlight=highlight-custom-action
// Add a custom "Export Clip" action to the background clip menu
cesdk.ui.insertOrderComponent(
{
in: 'ly.img.video.clip.menu',
before: 'ly.img.video.clip.menu.delete',
when: { clipType: 'clip' }
},
{
id: 'ly.img.video.clip.menu.action',
key: 'export-clip',
label: 'Export Clip',
icon: '@imgly/Download',
onClick: () => {
// eslint-disable-next-line no-console
console.log('Export Clip action triggered');
}
}
);
```
The `key` must be unique across all custom actions in the same menu. The `onClick` handler runs when the user selects the action. You can use any icon from the `@imgly/icons` package.
The `ClipContextMenuCustomAction` interface:
```typescript
interface ClipContextMenuCustomAction {
id: 'ly.img.video.clip.menu.action';
key: string;
onClick: () => void | Promise;
label: string;
icon?: string;
isDisabled?: boolean;
}
```
## Updating Existing Actions
We modify properties of an existing component using `cesdk.ui.updateOrderComponent()`. The update can be a partial object or an updater function.
```typescript highlight=highlight-update-action
// Disable the custom export action conditionally
cesdk.ui.updateOrderComponent(
{
in: 'ly.img.video.clip.menu',
match: { id: 'ly.img.video.clip.menu.action', key: 'export-clip' },
when: { clipType: 'clip' }
},
{ isDisabled: false }
);
```
Use `match` with both `id` and `key` to target a specific custom action. You can update any property including `label`, `icon`, `isDisabled`, and `onClick`.
## Clip-Type-Specific Customization
Each of the three clip types (`'clip'`, `'overlay'`, `'caption'`) has its own independent order. Changes to one do not affect the others. Use the `when: { clipType }` option on every operation to target a specific clip type. Omitting it defaults to `'clip'`.
This independence lets you tailor each menu to its context. For example, overlays need layering controls while captions need merge actions — and you can customize each without worrying about side effects.
## Troubleshooting
- **Action not appearing**: Verify the component ID is included in the order for the correct clip type. Use `getComponentOrder` to inspect the current order.
- **Custom action not clickable**: Ensure `isDisabled` is not set to `true` and `onClick` is a valid function.
- **Changes affecting wrong clip type**: Always pass `when: { clipType }` to target the intended clip type. Without it, operations default to `'clip'`.
- **Separator not showing**: Separators are only rendered between visible actions. If adjacent actions are hidden, the separator may not appear.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Actions API"
description: "Learn how to use the Actions API to register and customize action handlers in CE.SDK"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/actions-6ch24x/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Actions](https://img.ly/docs/cesdk/sveltekit/actions-6ch24x/)
---
The Actions API provides a centralized way to manage and customize actions for various user interactions in CE.SDK.
> **Note:** The Actions API is available after CE.SDK initialization through
> `cesdk.actions`.
> **Tip:** CE.SDK also provides a Utils API (`cesdk.utils`) with utility functions for
> common operations like exporting, file handling, and UI dialogs. These
> utilities can be used directly or within your custom actions.
## API Methods
The Actions API provides four methods:
- `register(actionId, handler)` - Register an action function for a specific event
- `get(actionId)` - Retrieve a registered action function
- `run(actionId, ...args)` - Execute a registered action with the provided arguments (throws if not registered)
- `list(matcher)` - Lists registered action IDs, optionally filtered by wildcard pattern
## Getting Started
Register actions after initializing CE.SDK:
```javascript
import CreativeEditorSDK from '@cesdk/cesdk-js';
const cesdk = await CreativeEditorSDK.create(container, {
// license: 'YOUR_CESDK_LICENSE_KEY',
});
// Register an action
cesdk.actions.register('actionType', async (...args) => {
// Your custom implementation
return result;
});
// Execute a registered action
await cesdk.actions.run('actionType', arg1, arg2);
// Or retrieve an action to call it later
const action = cesdk.actions.get('actionType');
// List all registered actions
const allActions = cesdk.actions.list();
// List actions matching a pattern
const exportActions = cesdk.actions.list({ matcher: 'export*' });
```
## Default Actions
CE.SDK automatically registers the following default actions:
### Scene Creation
- `scene.create` - Creates a new scene with configurable layout and page sizes
### Action Handlers
- `saveScene` - Saves the current scene (default: downloads scene file)
- `shareScene` - Shares the current scene (no default implementation)
- `exportDesign` - Exports design in various formats (default: downloads the exported file)
- `importScene` - Imports scene or archive files (default: opens file picker)
- `exportScene` - Exports scene or archive (default: downloads the file)
- `uploadFile` - Uploads files to asset sources (default: local upload for development)
- `onUnsupportedBrowser` - Handles unsupported browsers (no default implementation)
- `video.decode.checkSupport` - Checks video decoding/playback support (shows blocking dialog if unsupported)
- `video.encode.checkSupport` - Checks video encoding/export support (shows warning dialog if unsupported)
### Zoom Actions
- `zoom.toBlock` - Zoom to a specific block with configurable padding
- `zoom.toPage` - Zoom to the current page with auto-fit support
- `zoom.toSelection` - Zoom to currently selected blocks
- `zoom.in` - Zoom in by one step with configurable maximum
- `zoom.out` - Zoom out by one step with configurable minimum
- `zoom.toLevel` - Set zoom to a specific level
### Scroll Actions
- `scroll.toPage` - Scroll the viewport to center on a specific page with optional animation
### Video Timeline Zoom Actions
- `timeline.zoom.in` - Zoom in the video timeline by one step
- `timeline.zoom.out` - Zoom out the video timeline by one step
- `timeline.zoom.fit` - Fit the video timeline to show all content
- `timeline.zoom.toLevel` - Set the video timeline zoom to a specific level
- `timeline.zoom.reset` - Reset the video timeline zoom to default (1.0)
> **Note:** The `shareScene` and `onUnsupportedBrowser` actions do not have default
> implementations and must be registered manually.
> **Tip:** CE.SDK provides both an Actions API for handling user actions and a Utils API
> for utility functions. See the Utils API section below for details on
> available utilities.
### Scene Creation
#### `scene.create`
Creates a new scene with configurable mode, layout and page sizes. Returns the scene block ID.
```javascript
// Create a scene
await cesdk.actions.run('scene.create');
```
**Options:**
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `layout` | `SceneLayout` | `'VerticalStack'` | The scene layout. |
| `page` | `PageSpec` | — | A single page specification. Cannot be used together with `pages`. |
| `pageCount` | `number` | `1` | Number of pages to create from the single `page` spec. Ignored when `pages` is used. |
| `pages` | `PageSpec[]` | — | An array of page specifications, one page per entry. Cannot be used together with `page`. |
#### Page Specification
Pages can be specified in three ways:
**1. Direct dimensions**
Specify width, height, and unit directly:
```javascript
await cesdk.actions.run('scene.create', {
page: { width: 1080, height: 1920, unit: 'Pixel' }
});
// With fixed orientation (prevents rotation from swapping dimensions)
await cesdk.actions.run('scene.create', {
page: { width: 1080, height: 1920, unit: 'Pixel', fixedOrientation: true }
});
```
**2. Asset source reference**
Reference a page preset from an asset source by its source and asset IDs:
```javascript
// Use an Instagram Story preset
await cesdk.actions.run('scene.create', {
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.instagram.story'
}
});
```
**3. Asset object**
Pass an asset object directly, for example one returned by `engine.asset.findAssets()`:
```javascript
const result = await cesdk.engine.asset.findAssets('ly.img.page.presets', {
query: ''
});
await cesdk.actions.run('scene.create', {
page: result.assets[0]
});
```
#### Multiple Pages
Create scenes with multiple pages:
```javascript
// Multiple pages with different sizes
await cesdk.actions.run('scene.create', {
pages: [
{ width: 1080, height: 1920, unit: 'Pixel' },
{ width: 1920, height: 1080, unit: 'Pixel' }
]
});
// Multiple identical pages using pageCount
await cesdk.actions.run('scene.create', {
page: { width: 1080, height: 1080, unit: 'Pixel' },
pageCount: 3
});
```
> **Tip:** When no `page` or `pages` option is provided, `scene.create` creates a single
> page with the default format from the configured page preset asset sources.
### Scene Management Actions
#### `saveScene`
Handles saving the current scene. Default implementation downloads the scene file.
```javascript
// Basic implementation
cesdk.actions.register('saveScene', async () => {
const scene = await cesdk.engine.scene.saveToString();
console.log('Scene saved:', scene.length, 'characters');
// Production:
// await yourAPI.saveScene(scene);
cesdk.ui.showNotification('Scene saved successfully');
});
// With loading dialog
cesdk.actions.register('saveScene', async () => {
const dialogController = cesdk.utils.showLoadingDialog({
title: 'Saving Scene',
message: 'Please wait...',
progress: 'indeterminate',
});
try {
const scene = await cesdk.engine.scene.saveToString();
console.log('Scene saved:', scene.length, 'characters');
// Production:
// await yourAPI.saveScene(scene);
dialogController.showSuccess({
title: 'Saved',
message: 'Scene saved successfully',
});
} catch (error) {
dialogController.showError({
title: 'Save Failed',
message: 'Could not save the scene',
});
throw error;
}
});
```
#### `shareScene`
Handles scene sharing. No default implementation.
```javascript
// Register share functionality
cesdk.actions.register('shareScene', async () => {
const scene = await cesdk.engine.scene.saveToString();
const shareUrl = 'https://example.com/shared-scene-placeholder';
console.log('Scene ready to share:', scene.length, 'characters');
// Production:
// const shareUrl = await yourAPI.createShareableLink(scene);
await navigator.share({ url: shareUrl });
});
```
#### `importScene` and `exportScene`
Handle scene import/export operations with support for both scene files and archives.
```javascript
// Import scene or archive
cesdk.actions.register('importScene', async ({ format }) => {
if (format === 'archive') {
console.log('Archive import requested');
// Production:
// const archive = await yourAPI.loadArchive();
// await cesdk.engine.scene.loadFromArchiveURL(archive);
} else {
console.log('Scene import requested');
// Production:
// const scene = await yourAPI.loadScene();
// await cesdk.engine.scene.loadFromString(scene);
}
});
// Export scene or archive
cesdk.actions.register('exportScene', async ({ format }) => {
if (format === 'archive') {
const archive = await cesdk.engine.scene.saveToArchive();
console.log('Archive ready for export:', archive.length, 'bytes');
// Production:
// await yourAPI.uploadArchive(archive);
} else {
const scene = await cesdk.engine.scene.saveToString();
console.log('Scene ready for export:', scene.length, 'characters');
// Production:
// await yourAPI.uploadScene(scene);
}
});
```
### Export Operations
#### `exportDesign`
Handles all export operations (images, PDFs, videos). Default implementation downloads the exported file.
```javascript
// Basic implementation
cesdk.actions.register('exportDesign', async options => {
// Use the utils API to perform the export with loading dialog
const { blobs, options: exportOptions } = await cesdk.utils.export(options);
console.log('Exported', blobs.length, 'files');
blobs.forEach((blob, i) => console.log(`File ${i + 1}:`, blob.size, 'bytes'));
// Production:
// await Promise.all(blobs.map(blob => yourCDN.upload(blob)));
cesdk.ui.showNotification('Export completed successfully');
});
// Direct engine export with custom loading dialog (bypassing utils)
cesdk.actions.register('exportDesign', async options => {
const dialogController = cesdk.utils.showLoadingDialog({
title: 'Exporting',
message: 'Processing your export...',
});
try {
const page = cesdk.engine.scene.getCurrentPage();
if (page === null) {
throw new Error('No page selected for export');
}
let result;
if (options?.mimeType?.startsWith('video/')) {
// Video export with progress
result = await cesdk.engine.block.exportVideo(page, {
...options,
onProgress: (rendered, encoded, total) => {
dialogController.updateProgress({
value: rendered,
max: total,
});
},
});
} else {
// Static export (image/PDF)
result = await cesdk.engine.block.export(page, options);
}
console.log('File ready for export:', result.size, 'bytes');
// Production:
// await yourCDN.upload(result);
dialogController.showSuccess({
title: 'Export Complete',
message: 'Files uploaded successfully',
});
} catch (error) {
dialogController.showError({
title: 'Export Failed',
message: 'Could not complete the export',
});
throw error;
}
});
```
### File Upload Action
#### `uploadFile`
Handles file uploads to asset sources. Default implementation uses local upload for development.
```javascript
// Register production upload handler
cesdk.actions.register('uploadFile', async (file, onProgress, context) => {
console.log('Uploading file:', file.name, file.size, 'bytes');
onProgress(50); // Simulate progress
await new Promise(resolve => setTimeout(resolve, 500));
onProgress(100);
// Production:
// const asset = await yourStorageService.upload(file, {
// onProgress: (percent) => onProgress(percent),
// context
// });
// Return AssetDefinition
return {
id: 'local-' + Date.now(),
label: { en: file.name },
meta: {
uri: URL.createObjectURL(file),
thumbUri: URL.createObjectURL(file),
kind: 'image',
width: 1920,
height: 1080,
// Production:
// uri: asset.url,
// thumbUri: asset.thumbnailUrl,
// width: asset.width,
// height: asset.height
},
};
});
```
You can control which file types users can upload by setting the `upload/supportedMimeTypes` setting:
```javascript
// Example 1: Only allow images
cesdk.engine.editor.setSettingString(
'upload/supportedMimeTypes',
'image/png,image/jpeg,image/gif,image/svg+xml',
);
// Example 2: Allow images and videos
cesdk.engine.editor.setSettingString(
'upload/supportedMimeTypes',
'image/png,image/jpeg,image/gif,video/mp4,video/quicktime',
);
// Example 3: Allow specific document types
cesdk.engine.editor.setSettingString(
'upload/supportedMimeTypes',
'application/pdf,image/png,image/jpeg',
);
```
> **Caution:** The default `uploadFile` implementation uses local upload for development
> only. Always register a proper upload handler for production.
### Unsupported Browser Action
#### `onUnsupportedBrowser`
Handles unsupported browser detection. No default implementation is provided.
```javascript
// Register handler for unsupported browsers
cesdk.actions.register('onUnsupportedBrowser', () => {
// Redirect to a custom compatibility page
window.location.href = '/browser-not-supported';
});
```
### Video Support Actions
CE.SDK provides actions to detect video capabilities at runtime. These actions help you handle browsers and platforms that lack required video codecs, particularly Linux browsers and Firefox.
#### `video.decode.checkSupport`
Checks if the browser supports video decoding and playback via the WebCodecs API. If unsupported, displays a blocking error dialog that users cannot dismiss.
Returns `true` if video decoding is supported, `false` otherwise.
```javascript
// Check video decode support before loading video content
const isSupported = cesdk.actions.run('video.decode.checkSupport');
if (!isSupported) {
// A blocking error dialog is shown automatically
// The user cannot proceed with video editing
return;
}
// Safe to proceed with video content
await cesdk.engine.scene.loadFromURL(videoSceneUrl);
// You can also disable the dialog and handle feedback yourself:
const supportedSilently = cesdk.actions.run('video.decode.checkSupport', {
dialog: false
});
```
**Options:**
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `dialog` | `boolean \| { show: boolean; backdrop?: 'transparent' \| 'opaque' }` | `true` (backdrop: `'opaque'`) | Controls dialog display. Use `false` to disable, or an object for fine-grained control. |
> **Caution:** The `video.decode.checkSupport` action shows a blocking dialog with no dismiss option
> when video is not supported. Only call this action when you intend to work
> with video content.
#### `video.encode.checkSupport`
Checks if the browser supports video encoding/export (H.264 video and AAC audio encoding). If unsupported, displays a warning dialog that users can dismiss to continue editing.
Returns a `Promise` - `true` if video encoding is supported, `false` otherwise.
```javascript
// Check video encode support before attempting export
const canExport = await cesdk.actions.run('video.encode.checkSupport');
if (!canExport) {
// A warning dialog is shown automatically
// User can dismiss and continue editing
// Consider offering server-side export as alternative
console.log('Video export unavailable - consider server-side rendering');
}
// You can also disable the dialog and handle feedback yourself:
const canExportSilently = await cesdk.actions.run('video.encode.checkSupport', {
dialog: false
});
```
**Options:**
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `dialog` | `boolean \| { show: boolean; backdrop?: 'transparent' \| 'opaque' }` | `true` (backdrop: `'transparent'`) | Controls dialog display. Use `false` to disable, or an object for fine-grained control. |
> **Tip:** For platforms that don't support client-side video export (Linux, Firefox),
> consider using [CE.SDK Renderer](#broken-link-7f3e9a)
> for server-side video rendering.
#### Platform Support
| Platform | Video Import | Video Export |
| -------- | ------------ | ------------ |
| Chrome/Edge (Windows, macOS) | ✅ | ✅ |
| Safari (macOS) | ✅ | ✅ |
| Chrome (Linux) | ❌ | ❌ |
| Firefox (all platforms) | ❌ | ❌ |
### Zoom Actions
CE.SDK provides built-in zoom actions for controlling the viewport zoom level and focus. These actions are automatically registered and can be customized or called programmatically.
#### Available Zoom Actions
- `zoom.toBlock` - Zoom to a specific block with configurable padding
- `zoom.toPage` - Zoom to the current page (or a specified page)
- `zoom.toSelection` - Zoom to the currently selected blocks
- `zoom.in` - Zoom in by one step
- `zoom.out` - Zoom out by one step
- `zoom.toLevel` - Set zoom to a specific level
#### `zoom.toBlock`
Zooms the viewport to fit a specific block.
```javascript
// Zoom to a block with default settings
await cesdk.actions.run('zoom.toBlock', blockId);
// Zoom with custom padding and animation
await cesdk.actions.run('zoom.toBlock', blockId, {
padding: 50, // Uniform padding on all sides
animate: true,
autoFit: false
});
// Different padding for each side
await cesdk.actions.run('zoom.toBlock', blockId, {
padding: { top: 20, bottom: 20, left: 40, right: 40 },
animate: {
duration: 0.3,
easing: 'EaseInOut'
}
});
```
#### `zoom.toPage`
Zooms to the current page or a specified page. If no options are provided, defaults to the current page.
```javascript
// Zoom to current page with auto-fit
await cesdk.actions.run('zoom.toPage', {
autoFit: true,
animate: false
});
// Zoom with custom padding
await cesdk.actions.run('zoom.toPage', {
padding: { x: 40, y: 80 },
animate: true
});
```
#### `zoom.toSelection`
Zooms to fit all currently selected blocks in the viewport.
```javascript
// Zoom to selection with animation
await cesdk.actions.run('zoom.toSelection', {
padding: 40,
animate: true
});
// Auto-fit to selection
await cesdk.actions.run('zoom.toSelection', {
autoFit: true,
padding: { x: 20, y: 20 }
});
```
#### `zoom.in` and `zoom.out`
Step-based zoom controls with configurable limits.
```javascript
// Zoom in with default settings
await cesdk.actions.run('zoom.in');
// Zoom in with custom maximum
await cesdk.actions.run('zoom.in', {
maxZoom: 4, // Maximum zoom level
animate: true
});
// Zoom out with custom minimum
await cesdk.actions.run('zoom.out', {
minZoom: 0.25, // Minimum zoom level
animate: {
duration: 0.2,
easing: 'EaseOut'
}
});
```
#### `zoom.toLevel`
Sets the zoom to a specific level.
```javascript
// Set zoom to 100%
await cesdk.actions.run('zoom.toLevel', 1.0);
// Set zoom to 200% with animation
await cesdk.actions.run('zoom.toLevel', 2.0, {
animate: true,
minZoom: 0.125,
maxZoom: 32
});
// Fit to width (50% zoom)
await cesdk.actions.run('zoom.toLevel', 0.5, {
animate: {
duration: 0.3,
easing: 'EaseInOut'
}
});
```
#### Padding Options
Padding can be specified in multiple ways:
```javascript
// Uniform padding on all sides
{ padding: 20 }
// Different horizontal and vertical padding
{ padding: { x: 40, y: 20 } }
// Individual padding for each side
{ padding: { top: 10, bottom: 20, left: 30, right: 40 } }
```
#### Animation Options
Animation can be a boolean or an object with detailed settings:
```javascript
// Simple animation toggle
{ animate: true } // Uses default duration and easing
{ animate: false } // No animation
// Detailed animation configuration
{
animate: {
duration: 0.3, // Duration in seconds
easing: 'EaseInOut', // 'Linear', 'EaseIn', 'EaseOut', or 'EaseInOut'
interruptible: true // Whether the animation can be interrupted
}
}
```
#### Auto-Fit Mode
The `autoFit` option enables automatic zoom adjustment when the viewport resizes:
```javascript
// Enable auto-fit to maintain proper framing
await cesdk.actions.run('zoom.toPage', {
autoFit: true,
padding: { x: 40, y: 80 }
});
```
When auto-fit is enabled, the zoom level will automatically adjust to keep the target properly framed when the viewport size changes.
#### Custom Zoom Action Example
You can override the default zoom actions with custom implementations:
```javascript
// Custom zoom to page with analytics
cesdk.actions.register('zoom.toPage', async (options) => {
// Track zoom event
console.log('User zoomed to page');
// Get current page
const currentPage = cesdk.engine.scene.getCurrentPage();
if (!currentPage) return;
// Apply custom zoom logic
await cesdk.engine.scene.zoomToBlock(currentPage, {
padding: options?.padding ?? { x: 50, y: 100 },
animate: options?.animate ?? true
});
// Custom post-zoom behavior
cesdk.ui.showNotification('Zoomed to page');
});
```
### Video Timeline Zoom Actions
The video timeline has its own set of zoom controls for managing the timeline view. These actions are registered when the video timeline component is active and provide instant zoom without animation.
#### `timeline.zoom.in`
Zooms in the video timeline by one step (multiplies current zoom level by 1.25).
```javascript
// Zoom in the timeline
await cesdk.actions.run('timeline.zoom.in');
```
#### `timeline.zoom.out`
Zooms out the video timeline by one step (divides current zoom level by 1.25).
```javascript
// Zoom out the timeline
await cesdk.actions.run('timeline.zoom.out');
```
#### `timeline.zoom.fit`
Automatically adjusts the timeline zoom to fit all content in the visible area.
```javascript
// Fit timeline to show all content
await cesdk.actions.run('timeline.zoom.fit');
```
#### `timeline.zoom.toLevel`
Sets the timeline zoom to a specific level.
```javascript
// Set timeline zoom to 100%
await cesdk.actions.run('timeline.zoom.toLevel', 1.0);
// Set timeline zoom to 150%
await cesdk.actions.run('timeline.zoom.toLevel', 1.5);
// Set timeline zoom to 50%
await cesdk.actions.run('timeline.zoom.toLevel', 0.5);
```
#### `timeline.zoom.reset`
Resets the timeline zoom to the default level (1.0 or 100%).
```javascript
// Reset timeline zoom to default
await cesdk.actions.run('timeline.zoom.reset');
```
### Scroll Actions
CE.SDK provides a scroll action for panning the viewport to different pages without changing the zoom level. This is useful for multi-page navigation where you want to maintain the current zoom.
#### `scroll.toPage`
Scrolls the viewport to center on a specific page without changing the zoom level.
```javascript
// Scroll to current page without animation
await cesdk.actions.run('scroll.toPage');
// Scroll to current page with smooth animation
await cesdk.actions.run('scroll.toPage', {
animate: true
});
// Scroll to a specific page
await cesdk.actions.run('scroll.toPage', {
pageId: myPageId,
animate: true
});
```
#### Parameters
The `scroll.toPage` action accepts an optional options object:
- `pageId` (optional): The ID of the page to scroll to. If not provided, scrolls to the current page.
- `animate` (optional): Whether to animate the scroll transition. Default is `false`.
#### Scroll vs Zoom
The key difference between `scroll.toPage` and `zoom.toPage`:
- **`scroll.toPage`**: Pans the viewport to center on the page while maintaining the current zoom level
- **`zoom.toPage`**: Adjusts the zoom level to fit the page within the viewport with padding
Use `scroll.toPage` when you want to navigate between pages in a multi-page document while keeping the same zoom level. Use `zoom.toPage` when you want to frame a page properly within the viewport.
## Utils API
CE.SDK provides a Utils API with utility functions for common operations. These utilities are available through `cesdk.utils`:
### Loading Dialogs
```javascript
// Create and manage loading dialogs
const dialogController = cesdk.utils.showLoadingDialog({
title: 'Processing...',
message: 'Please wait', // Can also be an array of strings
progress: 0, // Initial progress value or 'indeterminate'
cancelLabel: 'Cancel',
abortTitle: 'Abort Operation?',
abortMessage: 'Are you sure you want to abort?',
abortLabel: 'Abort',
size: 'large', // 'regular' or 'large'
clickOutsideToClose: false,
onAbort: () => console.log('User cancelled'),
onDone: () => console.log('Dialog closed'),
});
// Update progress
dialogController.updateProgress({ value: 50, max: 100 });
// Show success or error
dialogController.showSuccess({
title: 'Done!',
message: 'Operation completed',
});
dialogController.showError({ title: 'Error', message: 'Something went wrong' });
// Close dialog
dialogController.close();
```
### Export Utility
The export utility automatically handles both static (images, PDFs) and video exports:
```javascript
// Export image or PDF
const { blobs, options } = await cesdk.utils.export({
mimeType: 'image/png',
pngCompressionLevel: 7,
});
// Export video (automatically detected by MIME type)
const { blobs, options } = await cesdk.utils.export({
mimeType: 'video/mp4',
onProgress: (rendered, encoded, total) => {
console.log(`Progress: ${rendered}/${total} frames`);
},
});
```
### File Operations
```javascript
// Load file from user
const file = await cesdk.utils.loadFile({
accept: 'image/*',
returnType: 'File', // 'dataURL', 'text', 'blob', 'arrayBuffer', or 'File'
});
// Download file to user's device
await cesdk.utils.downloadFile(blob, 'image/png');
// Local upload (development only)
const asset = await cesdk.utils.localUpload(file, context);
```
### Video Support Detection
Check browser video capabilities before working with video content:
```javascript
// Check if video decoding/playback is supported
if (cesdk.utils.supportsVideoDecode()) {
// Safe to load and play video content
await cesdk.engine.scene.loadFromURL(videoSceneUrl);
} else {
// Show fallback UI or message
console.log('Video playback not available in this browser');
}
// Check if video encoding/export is supported (async)
if (await cesdk.utils.supportsVideoEncode()) {
// Video export is available
const blob = await cesdk.engine.block.exportVideo(page);
} else {
// Suggest server-side rendering alternative
console.log('Video export not available - consider using CE.SDK Renderer');
}
```
These utilities provide the same checks as the `video.decode.checkSupport` and `video.encode.checkSupport` actions, but without showing dialogs. Use them when you want to check support silently and handle the UI yourself.
## Implementation Examples
### Environment-Based Upload Strategy
```javascript
// Use local upload in development, CDN in production
cesdk.actions.register('uploadFile', async (file, onProgress, context) => {
if (process.env.NODE_ENV === 'development') {
// Use utils for local upload
return await cesdk.utils.localUpload(file, context);
} else {
console.log('Production upload for:', file.name);
onProgress(100);
// Production:
// const asset = await yourCDNService.upload(file, {
// onProgress: onProgress
// });
return {
id: 'prod-' + Date.now(),
label: { en: file.name },
meta: {
uri: URL.createObjectURL(file),
thumbUri: URL.createObjectURL(file),
// Production:
// uri: asset.url,
// thumbUri: asset.thumbnailUrl
},
};
}
});
```
### Combining Utils with Custom Logic
```javascript
// Use utils for heavy lifting, add custom business logic
cesdk.actions.register('exportDesign', async options => {
console.log('Export started:', { format: options?.mimeType });
// Production:
// analytics.track('export_started', { format: options?.mimeType });
// Use utils to handle the export with loading dialog
const { blobs, options: exportOptions } = await cesdk.utils.export(options);
// Custom post-processing
if (exportOptions.mimeType === 'application/pdf') {
console.log('PDF ready for watermarking:', blobs[0].size, 'bytes');
// Production:
// const watermarkedBlob = await addWatermark(blobs[0]);
// await cesdk.utils.downloadFile(watermarkedBlob, 'application/pdf');
await cesdk.utils.downloadFile(blobs[0], 'application/pdf');
} else {
// Direct download for other formats
await cesdk.utils.downloadFile(blobs[0], exportOptions.mimeType);
}
console.log('Export completed:', { format: exportOptions.mimeType });
// Production:
// analytics.track('export_completed', { format: exportOptions.mimeType });
});
```
## Registering Custom Actions with Custom IDs
Beyond the predefined action types, you can register actions with custom IDs for your own application-specific needs:
```javascript
// Register a custom action
cesdk.actions.register('myCustomAction', async data => {
console.log('Custom action triggered with:', data);
return { success: true, processedData: data };
});
// Execute the custom action using run
const result = await cesdk.actions.run('myCustomAction', { someData: 'value' });
// Or retrieve it for conditional execution
const customAction = cesdk.actions.get('myCustomAction');
if (customAction) {
const result = await customAction({ someData: 'value' });
}
```
## Discovering Registered Actions
Use `list()` to get all registered action IDs or find actions matching a pattern:
```javascript
// Get all registered action IDs
const registeredActions = cesdk.actions.list();
console.log('Available actions:', registeredActions);
// Find actions matching a pattern
const exportActions = cesdk.actions.list({ matcher: 'export*' });
console.log('Export actions:', exportActions);
```
## Using Actions with Navigation Actions
The navigation bar actions in CE.SDK automatically use the registered actions:
### Default Navigation Bar Actions
The default navigation bar actions map to actions:
- Save action → `saveScene` action
- Share action → `shareScene` action
- Export actions → `exportDesign` action
- Import scene/archive → `importScene` action
- Export scene/archive → `exportScene` action
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Animation"
description: "Add motion to designs with support for keyframes, timeline editing, and programmatic animation control."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/animation-ce900c/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/sveltekit/animation-ce900c/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/sveltekit/animation/overview-6a2ef2/) - Add motion to designs with support for keyframes, timeline editing, and programmatic animation control.
- [Supported Animation Types](https://img.ly/docs/cesdk/sveltekit/animation/types-4e5f41/) - Apply different animation types to design blocks in CE.SDK and configure their properties.
- [Create Animations](https://img.ly/docs/cesdk/sveltekit/animation/create-15cf50/) - Build animations manually or with presets to animate objects, text, and scenes within your design.
- [Edit Animations](https://img.ly/docs/cesdk/sveltekit/animation/edit-32c12a/) - Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Animations"
description: "Build animations manually or with presets to animate objects, text, and scenes within your design."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/animation/create-15cf50/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/sveltekit/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/sveltekit/animation/create-15cf50/)
---
Add motion to design elements by creating entrance, exit, and loop animations using CE.SDK's animation system.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-create-browser/)
CE.SDK provides a unified animation system for adding motion to design elements. Animations are created as separate block instances and attached to target blocks using type-specific methods. You can apply entrance animations (how blocks appear), exit animations (how blocks leave), and loop animations (continuous motion while visible). Text blocks support additional properties for word-by-word or character-by-character reveals.
```typescript file=@cesdk_web_examples/guides-animation-create-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Create Animations Guide
*
* Demonstrates animation features in CE.SDK:
* - Creating entrance (In) animations
* - Creating exit (Out) animations
* - Creating loop animations
* - Configuring duration and easing
* - Text animations with writing styles
* - Managing animation lifecycle
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: { width: 1920, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const pages = engine.block.findByType('page');
const page = pages[0];
// Set page dimensions and duration
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
engine.block.setDuration(page, 5.0);
// Create gradient background
if (engine.block.supportsFill(page)) {
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { r: 0.4, g: 0.2, b: 0.8, a: 1.0 }, stop: 0 },
{ color: { r: 0.1, g: 0.3, b: 0.6, a: 1.0 }, stop: 0.5 },
{ color: { r: 0.2, g: 0.1, b: 0.4, a: 1.0 }, stop: 1 }
]);
// Diagonal gradient from top-left to bottom-right
engine.block.setFloat(
gradientFill,
'fill/gradient/linear/startPointX',
0
);
engine.block.setFloat(
gradientFill,
'fill/gradient/linear/startPointY',
0
);
engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointX', 1);
engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointY', 1);
engine.block.setFill(page, gradientFill);
}
// ===== Title: "Create Animations" with entrance animation =====
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Create Animations');
engine.block.setTextFontSize(titleBlock, 120);
engine.block.setTextColor(titleBlock, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.appendChild(page, titleBlock);
engine.block.setDuration(titleBlock, 5.0);
// Check if block supports animations before applying
if (engine.block.supportsAnimation(titleBlock)) {
// Create an entrance animation
const slideIn = engine.block.createAnimation('slide');
engine.block.setInAnimation(titleBlock, slideIn);
engine.block.setDuration(slideIn, 1.2);
engine.block.setFloat(
slideIn,
'animation/slide/direction',
(3 * Math.PI) / 2
);
engine.block.setEnum(slideIn, 'animationEasing', 'EaseOut');
}
// Center title horizontally and position in upper third
const titleWidth = engine.block.getFrameWidth(titleBlock);
const titleHeight = engine.block.getFrameHeight(titleBlock);
engine.block.setPositionX(titleBlock, (pageWidth - titleWidth) / 2);
engine.block.setPositionY(titleBlock, pageHeight * 0.25);
// ===== IMG.LY Logo with pulsating loop animation =====
const logoBlock = await engine.block.addImage(
'https://img.ly/static/ubq_samples/imgly_logo.jpg',
{ size: { width: 200, height: 200 } }
);
engine.block.appendChild(page, logoBlock);
engine.block.setDuration(logoBlock, 5.0);
// Contain the image within the block frame
engine.block.setEnum(logoBlock, 'contentFill/mode', 'Contain');
// Create a pulsating loop animation
const pulsating = engine.block.createAnimation('pulsating_loop');
engine.block.setLoopAnimation(logoBlock, pulsating);
engine.block.setDuration(pulsating, 1.5);
// Add fade entrance for the logo
const logoFadeIn = engine.block.createAnimation('fade');
engine.block.setInAnimation(logoBlock, logoFadeIn);
engine.block.setDuration(logoFadeIn, 0.8);
engine.block.setEnum(logoFadeIn, 'animationEasing', 'EaseOut');
// Position logo below title, centered
const logoWidth = engine.block.getFrameWidth(logoBlock);
engine.block.setPositionX(logoBlock, (pageWidth - logoWidth) / 2);
engine.block.setPositionY(logoBlock, pageHeight * 0.25 + titleHeight + 40);
// ===== Subtitle with text animation =====
const subtitleBlock = engine.block.create('text');
engine.block.replaceText(subtitleBlock, 'Entrance • Exit • Loop');
engine.block.setTextFontSize(subtitleBlock, 48);
engine.block.setTextColor(subtitleBlock, {
r: 0.9,
g: 0.9,
b: 1.0,
a: 0.9
});
engine.block.setEnum(subtitleBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
engine.block.setDuration(subtitleBlock, 5.0);
// Create text animation with word-by-word reveal
const textAnim = engine.block.createAnimation('fade');
engine.block.setInAnimation(subtitleBlock, textAnim);
engine.block.setDuration(textAnim, 1.5);
// Configure text animation writing style (Line, Word, or Character)
engine.block.setEnum(textAnim, 'textAnimationWritingStyle', 'Word');
// Set overlap for cascading effect (0 = sequential, 0-1 = cascading)
engine.block.setFloat(textAnim, 'textAnimationOverlap', 0.3);
// Position subtitle below logo
const subtitleWidth = engine.block.getFrameWidth(subtitleBlock);
engine.block.setPositionX(subtitleBlock, (pageWidth - subtitleWidth) / 2);
engine.block.setPositionY(subtitleBlock, pageHeight * 0.65);
// ===== Bottom right text with exit animation =====
const footerBlock = engine.block.create('text');
engine.block.replaceText(footerBlock, 'Powered by CE.SDK');
engine.block.setTextFontSize(footerBlock, 32);
engine.block.setTextColor(footerBlock, { r: 1.0, g: 1.0, b: 1.0, a: 0.7 });
engine.block.setEnum(footerBlock, 'text/horizontalAlignment', 'Right');
engine.block.setWidthMode(footerBlock, 'Auto');
engine.block.setHeightMode(footerBlock, 'Auto');
engine.block.appendChild(page, footerBlock);
// Footer appears at start and fades out at the end
engine.block.setTimeOffset(footerBlock, 0);
engine.block.setDuration(footerBlock, 5.0);
// Create exit animation that plays at the end of the block's duration
const fadeOut = engine.block.createAnimation('fade');
engine.block.setOutAnimation(footerBlock, fadeOut);
engine.block.setDuration(fadeOut, 1.0);
engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn');
// Position footer at bottom right with padding
const footerWidth = engine.block.getFrameWidth(footerBlock);
const footerHeight = engine.block.getFrameHeight(footerBlock);
engine.block.setPositionX(footerBlock, pageWidth - footerWidth - 60);
engine.block.setPositionY(footerBlock, pageHeight - footerHeight - 40);
// ===== Animation Properties Demo =====
// Create slide animation and configure direction for title
const titleInAnim = engine.block.getInAnimation(titleBlock);
if (titleInAnim !== 0) {
// Discover all available properties for this animation
const properties = engine.block.findAllProperties(titleInAnim);
console.log('Slide animation properties:', properties);
}
// Example: Retrieve animations to verify they're attached
const currentTitleIn = engine.block.getInAnimation(titleBlock);
const currentLogoLoop = engine.block.getLoopAnimation(logoBlock);
const currentFooterOut = engine.block.getOutAnimation(footerBlock);
console.log(
'Animation IDs - Title In:',
currentTitleIn,
'Logo Loop:',
currentLogoLoop,
'Footer Out:',
currentFooterOut
);
// Get available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
// Set initial playback time to show the scene after animations start
engine.block.setPlaybackTime(page, 2.0);
}
}
export default Example;
```
This guide covers how to create and configure animations programmatically, including entrance, exit, loop, and text animations with customizable timing and easing.
## Animation Fundamentals
We first verify that a block supports animations before creating and attaching them. The basic pattern involves creating an animation instance with `createAnimation()`, then attaching it using the appropriate setter method.
```typescript highlight-check-support
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Create Animations');
engine.block.setTextFontSize(titleBlock, 120);
engine.block.setTextColor(titleBlock, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.appendChild(page, titleBlock);
engine.block.setDuration(titleBlock, 5.0);
// Check if block supports animations before applying
if (engine.block.supportsAnimation(titleBlock)) {
// Create an entrance animation
const slideIn = engine.block.createAnimation('slide');
engine.block.setInAnimation(titleBlock, slideIn);
engine.block.setDuration(slideIn, 1.2);
engine.block.setFloat(
slideIn,
'animation/slide/direction',
(3 * Math.PI) / 2
);
engine.block.setEnum(slideIn, 'animationEasing', 'EaseOut');
}
```
Animation support is available for:
- **Graphic blocks** with image or video fills
- **Text blocks** with additional writing style options
- **Shape blocks** with fills
CE.SDK provides several animation presets:
- **Entrance animations**: slide, fade, blur, zoom, pop, wipe, pan
- **Exit animations**: same types as entrance
- **Loop animations**: breathing\_loop, spin\_loop, fade\_loop, pulsating\_loop, jump\_loop, squeeze\_loop, sway\_loop
## Entrance Animations
Entrance animations define how blocks appear on screen. We create the animation with `createAnimation()`, attach it with `setInAnimation()`, and configure the duration with `setDuration()`.
```typescript highlight-entrance-animation
// Add fade entrance for the logo
const logoFadeIn = engine.block.createAnimation('fade');
engine.block.setInAnimation(logoBlock, logoFadeIn);
engine.block.setDuration(logoFadeIn, 0.8);
engine.block.setEnum(logoFadeIn, 'animationEasing', 'EaseOut');
```
The `animationEasing` property controls the animation curve. Available options include Linear, EaseIn, EaseOut, and EaseInOut.
## Exit Animations
Exit animations define how blocks leave the screen. We attach them with `setOutAnimation()`. CE.SDK manages timing automatically to prevent overlap between entrance and exit animations.
```typescript highlight-exit-animation
const footerBlock = engine.block.create('text');
engine.block.replaceText(footerBlock, 'Powered by CE.SDK');
engine.block.setTextFontSize(footerBlock, 32);
engine.block.setTextColor(footerBlock, { r: 1.0, g: 1.0, b: 1.0, a: 0.7 });
engine.block.setEnum(footerBlock, 'text/horizontalAlignment', 'Right');
engine.block.setWidthMode(footerBlock, 'Auto');
engine.block.setHeightMode(footerBlock, 'Auto');
engine.block.appendChild(page, footerBlock);
// Footer appears at start and fades out at the end
engine.block.setTimeOffset(footerBlock, 0);
engine.block.setDuration(footerBlock, 5.0);
// Create exit animation that plays at the end of the block's duration
const fadeOut = engine.block.createAnimation('fade');
engine.block.setOutAnimation(footerBlock, fadeOut);
engine.block.setDuration(fadeOut, 1.0);
engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn');
```
When a block has both entrance and exit animations, CE.SDK adjusts their timing based on the block's duration in the composition.
## Loop Animations
Loop animations run continuously while the block is visible. We use animation types ending in `_loop` and attach them with `setLoopAnimation()`.
```typescript highlight-loop-animation
const logoBlock = await engine.block.addImage(
'https://img.ly/static/ubq_samples/imgly_logo.jpg',
{ size: { width: 200, height: 200 } }
);
engine.block.appendChild(page, logoBlock);
engine.block.setDuration(logoBlock, 5.0);
// Contain the image within the block frame
engine.block.setEnum(logoBlock, 'contentFill/mode', 'Contain');
// Create a pulsating loop animation
const pulsating = engine.block.createAnimation('pulsating_loop');
engine.block.setLoopAnimation(logoBlock, pulsating);
engine.block.setDuration(pulsating, 1.5);
```
Loop animations continue throughout the block's visible duration, creating continuous motion effects like breathing, spinning, or pulsating.
## Animation Properties
Each animation type exposes configurable properties. We use `setFloat()` and `setEnum()` to adjust these properties, and `findAllProperties()` to discover available options.
```typescript highlight-animation-properties
// Create slide animation and configure direction for title
const titleInAnim = engine.block.getInAnimation(titleBlock);
if (titleInAnim !== 0) {
// Discover all available properties for this animation
const properties = engine.block.findAllProperties(titleInAnim);
console.log('Slide animation properties:', properties);
}
```
Common configurable properties include:
- **Direction**: Set in radians for slide animations (0=right, PI/2=bottom, PI=left, 3\*PI/2=top)
- **Easing**: Linear, EaseIn, EaseOut, EaseInOut
## Text Animations
Text blocks support additional animation properties for granular control over how text appears. The `textAnimationWritingStyle` property controls whether the animation applies to the entire text, line by line, word by word, or character by character.
```typescript highlight-text-animation
const subtitleBlock = engine.block.create('text');
engine.block.replaceText(subtitleBlock, 'Entrance • Exit • Loop');
engine.block.setTextFontSize(subtitleBlock, 48);
engine.block.setTextColor(subtitleBlock, {
r: 0.9,
g: 0.9,
b: 1.0,
a: 0.9
});
engine.block.setEnum(subtitleBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
engine.block.setDuration(subtitleBlock, 5.0);
// Create text animation with word-by-word reveal
const textAnim = engine.block.createAnimation('fade');
engine.block.setInAnimation(subtitleBlock, textAnim);
engine.block.setDuration(textAnim, 1.5);
// Configure text animation writing style (Line, Word, or Character)
engine.block.setEnum(textAnim, 'textAnimationWritingStyle', 'Word');
// Set overlap for cascading effect (0 = sequential, 0-1 = cascading)
engine.block.setFloat(textAnim, 'textAnimationOverlap', 0.3);
```
Writing style options:
- **Line**: Animate entire lines together
- **Word**: Animate word by word
- **Character**: Animate character by character
The `textAnimationOverlap` property (0 to 1) controls the cascading effect. A value of 0 means sequential animation, while values closer to 1 create more overlap between segments.
## Managing Animation Lifecycle
We can retrieve current animations using `getInAnimation()`, `getOutAnimation()`, and `getLoopAnimation()`. A return value of 0 indicates no animation is attached.
```typescript highlight-manage-lifecycle
// Example: Retrieve animations to verify they're attached
const currentTitleIn = engine.block.getInAnimation(titleBlock);
const currentLogoLoop = engine.block.getLoopAnimation(logoBlock);
const currentFooterOut = engine.block.getOutAnimation(footerBlock);
console.log(
'Animation IDs - Title In:',
currentTitleIn,
'Logo Loop:',
currentLogoLoop,
'Footer Out:',
currentFooterOut
);
// Get available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
```
When replacing animations, destroy the old instance with `destroy()` to prevent memory leaks.
## Troubleshooting
### Animation Not Playing
Verify the block supports animations with `supportsAnimation()`. Check that playback is active on the page.
### Duration Issues
Ensure the animation is attached to a block before setting its duration. Duration is set on the animation instance, not the block.
### Memory Leaks
When replacing an animation, destroy the old animation instance before creating a new one:
```typescript
const current = engine.block.getInAnimation(block);
if (current !== 0) {
engine.block.destroy(current);
}
const newAnim = engine.block.createAnimation('fade');
engine.block.setInAnimation(block, newAnim);
```
### Timing Conflicts
If entrance and exit animations seem to overlap incorrectly, CE.SDK automatically adjusts durations to prevent conflicts. Reduce individual animation durations if needed.
## API Reference
| Method | Description |
| --------------------------------------------- | ------------------------------------------ |
| `engine.block.createAnimation(type)` | Create animation instance |
| `engine.block.supportsAnimation(block)` | Check if block supports animations |
| `engine.block.setInAnimation(block, anim)` | Attach entrance animation |
| `engine.block.setOutAnimation(block, anim)` | Attach exit animation |
| `engine.block.setLoopAnimation(block, anim)` | Attach loop animation |
| `engine.block.getInAnimation(block)` | Get entrance animation (0 if none) |
| `engine.block.getOutAnimation(block)` | Get exit animation (0 if none) |
| `engine.block.getLoopAnimation(block)` | Get loop animation (0 if none) |
| `engine.block.setDuration(anim, seconds)` | Set animation duration |
| `engine.block.setEnum(anim, prop, value)` | Set enum property (easing, writing style) |
| `engine.block.setFloat(anim, prop, value)` | Set float property (direction, overlap) |
| `engine.block.findAllProperties(anim)` | List available properties |
| `engine.block.getEnumValues(prop)` | Get enum options |
| `engine.block.destroy(anim)` | Destroy animation instance |
## Next Steps
- [Base Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/base-0fc5c4/) — Detailed non-text block animations
- [Text Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/text-d6f4aa/) — Text-specific animation control
- [Edit Animations](https://img.ly/docs/cesdk/sveltekit/animation/edit-32c12a/) — Modify existing animations
- [Animation Overview](https://img.ly/docs/cesdk/sveltekit/animation/overview-6a2ef2/) — Animation concepts
---
## Related Pages
- [Base Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/base-0fc5c4/) - Apply movement, scaling, rotation, or opacity changes to elements using time-based keyframes.
- [Text Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/text-d6f4aa/) - Animate text elements with effects like fade, typewriter, and bounce for dynamic visual presentation.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Base Animations"
description: "Apply movement, scaling, rotation, or opacity changes to elements using time-based keyframes."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/animation/create/base-0fc5c4/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/sveltekit/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/sveltekit/animation/create-15cf50/) > [Base Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/base-0fc5c4/)
---
Add motion to design blocks with entrance, exit, and loop animations using
CE.SDK's animation system.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-base-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-base-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-create-base-browser/)
Base animations in CE.SDK add motion to design blocks through entrance (In), exit (Out), and loop animations. Animations are created as separate objects and attached to blocks, enabling reusable configurations across multiple elements.
```typescript file=@cesdk_web_examples/guides-animation-create-base-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
import { calculateGridLayout } from './utils';
/**
* CE.SDK Plugin: Base Animations Guide
*
* Demonstrates animation features for design blocks in CE.SDK:
* - Creating and applying entrance (In) animations
* - Creating and applying exit (Out) animations
* - Creating and applying loop animations
* - Configuring duration and easing
* - Managing animation lifecycle
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: { width: 1920, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const scene = engine.scene.get();
const pages = engine.block.findByType('page');
const page = pages.length > 0 ? pages[0] : scene;
// Set white background color
if (!engine.block.supportsFill(page) || !engine.block.getFill(page)) {
const fill = engine.block.createFill('color');
engine.block.setFill(page, fill);
}
engine.block.setColor(engine.block.getFill(page), 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Calculate grid layout for 6 demonstration blocks
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const layout = calculateGridLayout(pageWidth, pageHeight, 6);
const { blockWidth, blockHeight, getPosition } = layout;
// Helper to create an image block
const createImageBlock = async (index: number, imageUrl: string) => {
const graphic = engine.block.create('graphic');
const imageFill = engine.block.createFill('image');
engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUrl);
engine.block.setFill(graphic, imageFill);
engine.block.setShape(graphic, engine.block.createShape('rect'));
engine.block.setWidth(graphic, blockWidth);
engine.block.setHeight(graphic, blockHeight);
const pos = getPosition(index);
engine.block.setPositionX(graphic, pos.x);
engine.block.setPositionY(graphic, pos.y);
engine.block.appendChild(page, graphic);
return graphic;
};
// Sample images for demonstration
const imageUrls = [
'https://img.ly/static/ubq_samples/sample_1.jpg',
'https://img.ly/static/ubq_samples/sample_2.jpg',
'https://img.ly/static/ubq_samples/sample_3.jpg',
'https://img.ly/static/ubq_samples/sample_4.jpg',
'https://img.ly/static/ubq_samples/sample_5.jpg',
'https://img.ly/static/ubq_samples/sample_6.jpg'
];
// Block 1: Check animation support and create entrance animation
const block1 = await createImageBlock(0, imageUrls[0]);
// Check if block supports animations before applying
if (engine.block.supportsAnimation(block1)) {
// Create an entrance animation
const slideAnimation = engine.block.createAnimation('slide');
engine.block.setInAnimation(block1, slideAnimation);
engine.block.setDuration(slideAnimation, 1.0);
}
// Block 2: Entrance animation with easing configuration
const block2 = await createImageBlock(1, imageUrls[1]);
// Create a fade entrance animation with easing
const fadeInAnimation = engine.block.createAnimation('fade');
engine.block.setInAnimation(block2, fadeInAnimation);
engine.block.setDuration(fadeInAnimation, 1.0);
engine.block.setEnum(fadeInAnimation, 'animationEasing', 'EaseOut');
// Block 3: Exit animation
const block3 = await createImageBlock(2, imageUrls[2]);
// Create an exit animation
const zoomInAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block3, zoomInAnimation);
engine.block.setDuration(zoomInAnimation, 1.0);
const fadeOutAnimation = engine.block.createAnimation('fade');
engine.block.setOutAnimation(block3, fadeOutAnimation);
engine.block.setDuration(fadeOutAnimation, 1.0);
engine.block.setEnum(fadeOutAnimation, 'animationEasing', 'EaseIn');
// Block 4: Loop animation
const block4 = await createImageBlock(3, imageUrls[3]);
// Create a breathing loop animation
const breathingLoop = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block4, breathingLoop);
engine.block.setDuration(breathingLoop, 1.0);
// Block 5: Animation properties and slide direction
const block5 = await createImageBlock(4, imageUrls[4]);
// Create slide animation and configure direction
const slideFromTop = engine.block.createAnimation('slide');
engine.block.setInAnimation(block5, slideFromTop);
engine.block.setDuration(slideFromTop, 1.0);
// Set slide direction (in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top)
engine.block.setFloat(
slideFromTop,
'animation/slide/direction',
Math.PI / 2
);
engine.block.setEnum(slideFromTop, 'animationEasing', 'EaseInOut');
// Discover all available properties for this animation
const properties = engine.block.findAllProperties(slideFromTop);
// eslint-disable-next-line no-console
console.log('Slide animation properties:', properties);
// Block 6: Get animations and replace them
const block6 = await createImageBlock(5, imageUrls[5]);
// Set initial animations
const initialIn = engine.block.createAnimation('pan');
engine.block.setInAnimation(block6, initialIn);
engine.block.setDuration(initialIn, 1.0);
const spinLoop = engine.block.createAnimation('spin_loop');
engine.block.setLoopAnimation(block6, spinLoop);
engine.block.setDuration(spinLoop, 1.0);
// Get current animations
const currentIn = engine.block.getInAnimation(block6);
const currentLoop = engine.block.getLoopAnimation(block6);
const currentOut = engine.block.getOutAnimation(block6);
// eslint-disable-next-line no-console
console.log(
'Animation IDs - In:',
currentIn,
'Loop:',
currentLoop,
'Out:',
currentOut
);
// Replace in animation (destroy old one first to avoid memory leaks)
if (currentIn !== 0) {
engine.block.destroy(currentIn);
}
const newInAnimation = engine.block.createAnimation('wipe');
engine.block.setInAnimation(block6, newInAnimation);
engine.block.setDuration(newInAnimation, 1.0);
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
// eslint-disable-next-line no-console
console.log('Available easing options:', easingOptions);
// Set initial playback time to 1 second (after entrance animations)
engine.block.setPlaybackTime(page, 1.0);
}
}
export default Example;
```
This guide covers creating animations, attaching them to blocks, configuring properties like duration and easing, and managing animation lifecycle.
## Animation Fundamentals
Before applying animations to a block, we verify it supports them using `supportsAnimation()`. Once confirmed, we create an animation instance and attach it to the block.
```typescript highlight-supports-animation
// Check if block supports animations before applying
if (engine.block.supportsAnimation(block1)) {
// Create an entrance animation
const slideAnimation = engine.block.createAnimation('slide');
engine.block.setInAnimation(block1, slideAnimation);
engine.block.setDuration(slideAnimation, 1.0);
}
```
We use `createAnimation()` with an animation type like `'slide'`, `'fade'`, or `'zoom'`. The animation is then attached using `setInAnimation()` for entrance animations. Duration is set with `setDuration()` in seconds.
CE.SDK provides several animation types:
- **Entrance animations**: `slide`, `fade`, `blur`, `grow`, `zoom`, `pop`, `wipe`, `pan`, `baseline`, `spin`
- **Loop animations**: `spin_loop`, `fade_loop`, `blur_loop`, `pulsating_loop`, `breathing_loop`, `jump_loop`, `squeeze_loop`, `sway_loop`
## Entrance Animations
Entrance animations (In animations) define how a block appears on screen. We create the animation, attach it with `setInAnimation()`, and configure its properties.
```typescript highlight-entrance-animation
// Create a fade entrance animation with easing
const fadeInAnimation = engine.block.createAnimation('fade');
engine.block.setInAnimation(block2, fadeInAnimation);
engine.block.setDuration(fadeInAnimation, 1.0);
engine.block.setEnum(fadeInAnimation, 'animationEasing', 'EaseOut');
```
We use `setEnum()` to configure the easing function. Available easing options include `'Linear'`, `'EaseIn'`, `'EaseOut'`, and `'EaseInOut'`. The `'EaseOut'` easing starts fast and slows down toward the end, creating a natural deceleration effect.
## Exit Animations
Exit animations (Out animations) define how a block leaves the screen. We use `setOutAnimation()` to attach them.
```typescript highlight-exit-animation
// Create an exit animation
const zoomInAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block3, zoomInAnimation);
engine.block.setDuration(zoomInAnimation, 1.0);
const fadeOutAnimation = engine.block.createAnimation('fade');
engine.block.setOutAnimation(block3, fadeOutAnimation);
engine.block.setDuration(fadeOutAnimation, 1.0);
engine.block.setEnum(fadeOutAnimation, 'animationEasing', 'EaseIn');
```
When using both entrance and exit animations, CE.SDK automatically manages their timing to prevent overlap. Changing the duration of an In animation may adjust the Out animation's duration to maintain valid timing.
## Loop Animations
Loop animations run continuously while the block is visible. We use `setLoopAnimation()` to attach them.
```typescript highlight-loop-animation
// Create a breathing loop animation
const breathingLoop = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block4, breathingLoop);
engine.block.setDuration(breathingLoop, 1.0);
```
The duration for loop animations defines the length of each cycle. A 2-second breathing loop will complete one full pulse every 2 seconds.
## Animation Properties
Each animation type has specific configurable properties. We use `findAllProperties()` to discover available properties for an animation.
```typescript highlight-animation-properties
// Create slide animation and configure direction
const slideFromTop = engine.block.createAnimation('slide');
engine.block.setInAnimation(block5, slideFromTop);
engine.block.setDuration(slideFromTop, 1.0);
// Set slide direction (in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top)
engine.block.setFloat(
slideFromTop,
'animation/slide/direction',
Math.PI / 2
);
engine.block.setEnum(slideFromTop, 'animationEasing', 'EaseInOut');
// Discover all available properties for this animation
const properties = engine.block.findAllProperties(slideFromTop);
// eslint-disable-next-line no-console
console.log('Slide animation properties:', properties);
```
For slide animations, the `animation/slide/direction` property controls the entry direction in radians:
- `0` — From the right
- `Math.PI / 2` — From the bottom
- `Math.PI` — From the left
- `3 * Math.PI / 2` — From the top
## Managing Animation Lifecycle
Animation objects must be properly managed to avoid memory leaks. When replacing an animation, we destroy the old one before setting the new one. We can retrieve current animations using `getInAnimation()`, `getOutAnimation()`, and `getLoopAnimation()`.
```typescript highlight-manage-animations
// Set initial animations
const initialIn = engine.block.createAnimation('pan');
engine.block.setInAnimation(block6, initialIn);
engine.block.setDuration(initialIn, 1.0);
const spinLoop = engine.block.createAnimation('spin_loop');
engine.block.setLoopAnimation(block6, spinLoop);
engine.block.setDuration(spinLoop, 1.0);
// Get current animations
const currentIn = engine.block.getInAnimation(block6);
const currentLoop = engine.block.getLoopAnimation(block6);
const currentOut = engine.block.getOutAnimation(block6);
// eslint-disable-next-line no-console
console.log(
'Animation IDs - In:',
currentIn,
'Loop:',
currentLoop,
'Out:',
currentOut
);
// Replace in animation (destroy old one first to avoid memory leaks)
if (currentIn !== 0) {
engine.block.destroy(currentIn);
}
const newInAnimation = engine.block.createAnimation('wipe');
engine.block.setInAnimation(block6, newInAnimation);
engine.block.setDuration(newInAnimation, 1.0);
```
A return value of `0` indicates no animation is attached. Destroying a design block also destroys all its attached animations, but detached animations must be destroyed manually.
## Easing Functions
We can query available easing options using `getEnumValues()`.
```typescript highlight-easing-options
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
// eslint-disable-next-line no-console
console.log('Available easing options:', easingOptions);
```
Easing functions control animation acceleration:
| Easing | Description |
| ----------- | --------------------------------------------- |
| `Linear` | Constant speed throughout |
| `EaseIn` | Starts slow, accelerates toward the end |
| `EaseOut` | Starts fast, decelerates toward the end |
| `EaseInOut` | Starts slow, speeds up, then slows down again |
## API Reference
| Method | Description |
| ------------------------------- | ------------------------------------------ |
| `createAnimation(type)` | Create a new animation instance |
| `supportsAnimation(block)` | Check if block supports animations |
| `setInAnimation(block, anim)` | Apply entrance animation to block |
| `setOutAnimation(block, anim)` | Apply exit animation to block |
| `setLoopAnimation(block, anim)` | Apply loop animation to block |
| `getInAnimation(block)` | Get entrance animation (returns 0 if none) |
| `getOutAnimation(block)` | Get exit animation (returns 0 if none) |
| `getLoopAnimation(block)` | Get loop animation (returns 0 if none) |
| `setDuration(anim, seconds)` | Set animation duration |
| `getDuration(anim)` | Get animation duration |
| `setEnum(anim, prop, value)` | Set enum property (easing, etc.) |
| `setFloat(anim, prop, value)` | Set float property (direction, etc.) |
| `findAllProperties(anim)` | Get all available properties for animation |
| `getEnumValues(prop)` | Get available values for enum property |
| `destroy(anim)` | Destroy animation instance |
## Next Steps
- [Text Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/text-d6f4aa/) — Animate text with writing styles
and character control
- [Animation Overview](https://img.ly/docs/cesdk/sveltekit/animation/overview-6a2ef2/) — Understand animation concepts
and capabilities
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Text Animations"
description: "Animate text elements with effects like fade, typewriter, and bounce for dynamic visual presentation."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/animation/create/text-d6f4aa/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/sveltekit/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/sveltekit/animation/create-15cf50/) > [Text Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/text-d6f4aa/)
---
Create engaging text animations that reveal content line by line, word by word, or character by character with granular control over timing and overlap.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-text-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-text-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-create-text-browser/)
Text animations in CE.SDK allow you to animate text blocks with granular control over how the text appears. Unlike standard block animations, text animations support writing styles that determine whether animation applies to the entire text, line by line, word by word, or character by character.
```typescript file=@cesdk_web_examples/guides-animation-create-text-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
import { calculateGridLayout } from './utils';
/**
* CE.SDK Plugin: Text Animations Guide
*
* Demonstrates text-specific animation features in CE.SDK:
* - Creating and applying animations to text blocks
* - Text animation writing styles (line, word, character)
* - Segment overlap configuration
* - Combining with easing and duration properties
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: { width: 1920, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const scene = engine.scene.get();
const pages = engine.block.findByType('page');
const page = pages.length > 0 ? pages[0] : scene;
// Set white background color for the page
// First check if page supports fill, if not or doesn't have one, create one
if (!engine.block.supportsFill(page) || !engine.block.getFill(page)) {
const fill = engine.block.createFill('color');
engine.block.setFill(page, fill);
}
engine.block.setColor(engine.block.getFill(page), 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Calculate responsive grid layout for demonstrations
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const layout = calculateGridLayout(pageWidth, pageHeight, 6);
const { blockWidth, blockHeight, getPosition } = layout;
// Create a text block and animation
// Animations are created separately and then attached to blocks
const text1 = engine.block.create('text');
engine.block.setWidth(text1, blockWidth);
engine.block.setHeight(text1, blockHeight);
engine.block.appendChild(page, text1);
const pos1 = getPosition(0);
engine.block.setPositionX(text1, pos1.x);
engine.block.setPositionY(text1, pos1.y);
engine.block.replaceText(text1, 'Creating\nText\nAnimations');
engine.block.setFloat(text1, 'text/fontSize', 48);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text1, 'text/verticalAlignment', 'Center');
// Create an animation instance with the 'baseline' type
const animation1 = engine.block.createAnimation('baseline');
// Apply the animation to the text block's entrance
engine.block.setInAnimation(text1, animation1);
// Set basic animation properties
engine.block.setDuration(animation1, 2.0);
// Writing Style: Line-by-line animation
// Text animates one line at a time from top to bottom
const text2 = engine.block.create('text');
engine.block.setWidth(text2, blockWidth);
engine.block.setHeight(text2, blockHeight);
engine.block.appendChild(page, text2);
const pos2 = getPosition(1);
engine.block.setPositionX(text2, pos2.x);
engine.block.setPositionY(text2, pos2.y);
engine.block.replaceText(text2, 'Line by line\nanimation\nfor text');
engine.block.setFloat(text2, 'text/fontSize', 42);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text2, 'text/verticalAlignment', 'Center');
const animation2 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text2, animation2);
engine.block.setDuration(animation2, 2.0);
// Set writing style to 'Line' for line-by-line animation
engine.block.setEnum(animation2, 'textAnimationWritingStyle', 'Line');
engine.block.setEnum(animation2, 'animationEasing', 'EaseOut');
// Writing Style: Word-by-word animation
// Text animates one word at a time in reading order
const text3 = engine.block.create('text');
engine.block.setWidth(text3, blockWidth);
engine.block.setHeight(text3, blockHeight);
engine.block.appendChild(page, text3);
const pos3 = getPosition(2);
engine.block.setPositionX(text3, pos3.x);
engine.block.setPositionY(text3, pos3.y);
engine.block.replaceText(text3, 'Animate word by word for emphasis');
engine.block.setFloat(text3, 'text/fontSize', 42);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text3, 'text/verticalAlignment', 'Center');
const animation3 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text3, animation3);
engine.block.setDuration(animation3, 2.5);
// Set writing style to 'Word' for word-by-word animation
engine.block.setEnum(animation3, 'textAnimationWritingStyle', 'Word');
engine.block.setEnum(animation3, 'animationEasing', 'EaseOut');
// Writing Style: Character-by-character animation
// Text animates one character at a time (typewriter effect)
const text4 = engine.block.create('text');
engine.block.setWidth(text4, blockWidth);
engine.block.setHeight(text4, blockHeight);
engine.block.appendChild(page, text4);
const pos4 = getPosition(3);
engine.block.setPositionX(text4, pos4.x);
engine.block.setPositionY(text4, pos4.y);
engine.block.replaceText(
text4,
'Character by character for typewriter effect'
);
engine.block.setFloat(text4, 'text/fontSize', 38);
engine.block.setEnum(text4, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text4, 'text/verticalAlignment', 'Center');
const animation4 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text4, animation4);
engine.block.setDuration(animation4, 3.0);
// Set writing style to 'Character' for character-by-character animation
engine.block.setEnum(animation4, 'textAnimationWritingStyle', 'Character');
engine.block.setEnum(animation4, 'animationEasing', 'Linear');
// Segment Overlap: Sequential animation (overlap = 0)
// Each segment completes before the next begins
const text5 = engine.block.create('text');
engine.block.setWidth(text5, blockWidth);
engine.block.setHeight(text5, blockHeight);
engine.block.appendChild(page, text5);
const pos5 = getPosition(4);
engine.block.setPositionX(text5, pos5.x);
engine.block.setPositionY(text5, pos5.y);
engine.block.replaceText(text5, 'Sequential animation with zero overlap');
engine.block.setFloat(text5, 'text/fontSize', 40);
engine.block.setEnum(text5, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text5, 'text/verticalAlignment', 'Center');
const animation5 = engine.block.createAnimation('pan');
engine.block.setInAnimation(text5, animation5);
engine.block.setDuration(animation5, 2.0);
engine.block.setEnum(animation5, 'textAnimationWritingStyle', 'Word');
// Set overlap to 0 for sequential animation
engine.block.setFloat(animation5, 'textAnimationOverlap', 0.0);
engine.block.setEnum(animation5, 'animationEasing', 'EaseOut');
// Segment Overlap: Cascading animation (overlap = 0.4)
// Segments overlap partially for smooth flow
const text6 = engine.block.create('text');
engine.block.setWidth(text6, blockWidth);
engine.block.setHeight(text6, blockHeight);
engine.block.appendChild(page, text6);
const pos6 = getPosition(5);
engine.block.setPositionX(text6, pos6.x);
engine.block.setPositionY(text6, pos6.y);
engine.block.replaceText(text6, 'Cascading animation with partial overlap');
engine.block.setFloat(text6, 'text/fontSize', 40);
engine.block.setEnum(text6, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text6, 'text/verticalAlignment', 'Center');
const animation6 = engine.block.createAnimation('pan');
engine.block.setInAnimation(text6, animation6);
engine.block.setDuration(animation6, 1.5);
engine.block.setEnum(animation6, 'textAnimationWritingStyle', 'Word');
// Set overlap to 0.4 for cascading effect
engine.block.setFloat(animation6, 'textAnimationOverlap', 0.4);
engine.block.setEnum(animation6, 'animationEasing', 'EaseOut');
// Combining animation properties: Duration and Easing
// Duration controls overall timing, easing controls acceleration
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
// eslint-disable-next-line no-console
console.log('Available easing options:', easingOptions);
// Query available writing style options
const writingStyleOptions = engine.block.getEnumValues(
'textAnimationWritingStyle'
);
// eslint-disable-next-line no-console
console.log('Available writing style options:', writingStyleOptions);
// Start playback to see animations
engine.block.setPlaying(page, true);
engine.block.setLooping(page, true);
}
}
export default Example;
```
This guide covers text-specific animation properties like writing styles and segment overlap, enabling dynamic and engaging text presentations in your designs.
## Text Animation Fundamentals
We create animations by first creating an animation instance, then attaching it to a text block. The animation block defines how the text will animate, while the text block contains the content and styling.
```typescript highlight-create-animation
// Create an animation instance with the 'baseline' type
const animation1 = engine.block.createAnimation('baseline');
// Apply the animation to the text block's entrance
engine.block.setInAnimation(text1, animation1);
// Set basic animation properties
engine.block.setDuration(animation1, 2.0);
```
Animations are created separately using `engine.block.createAnimation()` with an animation type like 'baseline', 'fade', or 'pan'. We then attach the animation to the text block's entrance using `engine.block.setInAnimation()`. The animation duration is set with `engine.block.setDuration()`.
## Writing Style Control
Text animations support different granularity levels through the `textAnimationWritingStyle` property. This controls whether the animation applies to the entire text at once, or breaks it into segments (lines, words, or characters). We can query available options using `engine.block.getEnumValues('textAnimationWritingStyle')`.
### Line-by-Line Animation
The `Line` writing style animates text one line at a time from top to bottom. Each line appears sequentially, creating a structured reveal effect.
```typescript highlight-writing-style-line
const animation2 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text2, animation2);
engine.block.setDuration(animation2, 2.0);
// Set writing style to 'Line' for line-by-line animation
engine.block.setEnum(animation2, 'textAnimationWritingStyle', 'Line');
engine.block.setEnum(animation2, 'animationEasing', 'EaseOut');
```
We use `engine.block.setEnum()` to set the writing style to `'Line'`. This is ideal for revealing multi-line text in a clear, organized manner.
### Word-by-Word Animation
The `Word` writing style animates text one word at a time in reading order. This creates emphasis and draws attention to individual words.
```typescript highlight-writing-style-word
const animation3 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text3, animation3);
engine.block.setDuration(animation3, 2.5);
// Set writing style to 'Word' for word-by-word animation
engine.block.setEnum(animation3, 'textAnimationWritingStyle', 'Word');
engine.block.setEnum(animation3, 'animationEasing', 'EaseOut');
```
Setting the writing style to `'Word'` is perfect for creating dynamic, engaging text reveals that emphasize key phrases.
### Character-by-Character Animation
The `Character` writing style animates text one character at a time, creating a classic typewriter effect. This is the most granular animation option.
```typescript highlight-writing-style-character
const animation4 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text4, animation4);
engine.block.setDuration(animation4, 3.0);
// Set writing style to 'Character' for character-by-character animation
engine.block.setEnum(animation4, 'textAnimationWritingStyle', 'Character');
engine.block.setEnum(animation4, 'animationEasing', 'Linear');
```
The `'Character'` writing style is ideal for typewriter effects and when you want maximum control over the animation timing.
## Segment Overlap Configuration
The `textAnimationOverlap` property controls timing between animation segments. A value of 0 means segments animate sequentially, while values between 0 and 1 create cascading effects where segments overlap partially. We use `engine.block.setFloat()` to set the overlap value.
### Sequential Animation (Overlap = 0)
When overlap is set to 0, each segment completes before the next begins, creating a clear, structured reveal effect.
```typescript highlight-overlap-sequential
// Set overlap to 0 for sequential animation
engine.block.setFloat(animation5, 'textAnimationOverlap', 0.0);
engine.block.setEnum(animation5, 'animationEasing', 'EaseOut');
```
Sequential animation ensures each text segment fully appears before the next one starts, making it perfect for emphasis and readability.
### Cascading Animation (Overlap = 0.4)
When overlap is set to a value between 0 and 1, segments animate in a cascading pattern, creating a smooth, flowing effect as they blend together.
```typescript highlight-overlap-cascading
// Set overlap to 0.4 for cascading effect
engine.block.setFloat(animation6, 'textAnimationOverlap', 0.4);
engine.block.setEnum(animation6, 'animationEasing', 'EaseOut');
```
Cascading animation with partial overlap creates dynamic, fluid text reveals that feel natural and engaging.
## Combining with Animation Properties
Text animations can be enhanced with standard animation properties like duration and easing. Duration controls the overall timing of the animation, while easing controls the acceleration curve.
```typescript highlight-duration-easing
// Combining animation properties: Duration and Easing
// Duration controls overall timing, easing controls acceleration
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
// eslint-disable-next-line no-console
console.log('Available easing options:', easingOptions);
// Query available writing style options
const writingStyleOptions = engine.block.getEnumValues(
'textAnimationWritingStyle'
);
// eslint-disable-next-line no-console
console.log('Available writing style options:', writingStyleOptions);
```
We use `engine.block.setEnum()` to set the easing function ('EaseIn', 'EaseOut', 'EaseInOut', 'Linear'). We can query available easing options using `engine.block.getEnumValues('animationEasing')`. Combining writing style, overlap, duration, and easing gives us complete control over how text animates.
## API Reference
| Method | Description |
| ------------------------------------------------- | -------------------------------------------------- |
| `createAnimation(type)` | Create a new animation instance |
| `setInAnimation(block, animation)` | Apply animation to block entrance |
| `setLoopAnimation(block, animation)` | Apply looping animation to block |
| `setOutAnimation(block, animation)` | Apply animation to block exit |
| `getInAnimation(block)` | Get the entrance animation of a block |
| `getLoopAnimation(block)` | Get the looping animation of a block |
| `getOutAnimation(block)` | Get the exit animation of a block |
| `setDuration(animation, seconds)` | Set animation duration in seconds |
| `getDuration(animation)` | Get animation duration |
| `setEnum(animation, property, value)` | Set enum property (writing style, easing) |
| `getEnum(animation, property)` | Get enum property value |
| `setFloat(animation, property, value)` | Set float property (overlap value) |
| `getFloat(animation, property)` | Get float property value |
| `getEnumValues(property)` | Get available enum options for a property |
| `supportsAnimation(block)` | Check if block supports animations |
| `replaceText(block, text)` | Set text content of a text block |
| `setPlaying(block, enabled)` | Start or stop playback |
| `setLooping(block, enabled)` | Enable or disable looping playback |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Edit Animations"
description: "Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/animation/edit-32c12a/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/sveltekit/animation-ce900c/) > [Edit Animations](https://img.ly/docs/cesdk/sveltekit/animation/edit-32c12a/)
---
Modify existing animations by reading properties, changing duration and easing, and replacing or removing animations from blocks.

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-edit-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-edit-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-edit-browser/)
Editing animations in CE.SDK involves retrieving existing animations from blocks and modifying their properties. This guide assumes you've already created and attached animations to blocks as covered in the [Base Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/base-0fc5c4/) guide.
```typescript file=@cesdk_web_examples/guides-animation-edit-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
import { calculateGridLayout } from './utils';
/**
* CE.SDK Plugin: Edit Animations Guide
*
* Demonstrates how to edit existing animations in CE.SDK:
* - Retrieving animations from blocks
* - Reading animation properties (type, duration, easing)
* - Modifying animation duration and easing
* - Adjusting animation-specific properties
* - Replacing and removing animations
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: { width: 1920, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const pages = engine.block.findByType('page');
const page = pages[0]!;
// Set white background color
const pageFill = engine.block.getFill(page);
if (pageFill) {
engine.block.setColor(pageFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
}
// Calculate grid layout for 6 demonstration blocks
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const layout = calculateGridLayout(pageWidth, pageHeight, 6);
const { blockWidth, blockHeight, getPosition } = layout;
// Helper to create an image block with an initial animation
const createAnimatedBlock = async (index: number, imageUrl: string) => {
const graphic = engine.block.create('graphic');
const imageFill = engine.block.createFill('image');
engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUrl);
engine.block.setFill(graphic, imageFill);
engine.block.setShape(graphic, engine.block.createShape('rect'));
engine.block.setWidth(graphic, blockWidth);
engine.block.setHeight(graphic, blockHeight);
const pos = getPosition(index);
engine.block.setPositionX(graphic, pos.x);
engine.block.setPositionY(graphic, pos.y);
engine.block.appendChild(page, graphic);
// Add an initial slide animation
const slideAnim = engine.block.createAnimation('slide');
engine.block.setInAnimation(graphic, slideAnim);
engine.block.setDuration(slideAnim, 1.0);
return graphic;
};
// Sample images for demonstration
const imageUrls = [
'https://img.ly/static/ubq_samples/sample_1.jpg',
'https://img.ly/static/ubq_samples/sample_2.jpg',
'https://img.ly/static/ubq_samples/sample_3.jpg',
'https://img.ly/static/ubq_samples/sample_4.jpg',
'https://img.ly/static/ubq_samples/sample_5.jpg',
'https://img.ly/static/ubq_samples/sample_6.jpg'
];
// Block 1: Retrieve animations and check their existence
const block1 = await createAnimatedBlock(0, imageUrls[0]);
// Retrieve animations from a block
const inAnimation = engine.block.getInAnimation(block1);
const outAnimation = engine.block.getOutAnimation(block1);
const loopAnimation = engine.block.getLoopAnimation(block1);
// Check if animations exist (0 means no animation)
console.log('In animation:', inAnimation !== 0 ? 'exists' : 'none');
console.log('Out animation:', outAnimation !== 0 ? 'exists' : 'none');
console.log('Loop animation:', loopAnimation !== 0 ? 'exists' : 'none');
// Get animation type if it exists
if (inAnimation !== 0) {
const animationType = engine.block.getType(inAnimation);
console.log('Animation type:', animationType);
}
// Block 2: Read animation properties
const block2 = await createAnimatedBlock(1, imageUrls[1]);
// Read animation properties
const animation2 = engine.block.getInAnimation(block2);
if (animation2 !== 0) {
// Get current duration
const duration = engine.block.getDuration(animation2);
console.log('Duration:', duration, 'seconds');
// Get current easing
const easing = engine.block.getEnum(animation2, 'animationEasing');
console.log('Easing:', easing);
// Discover all available properties
const allProperties = engine.block.findAllProperties(animation2);
console.log('Available properties:', allProperties);
}
// Block 3: Modify animation duration
const block3 = await createAnimatedBlock(2, imageUrls[2]);
// Modify animation duration
const animation3 = engine.block.getInAnimation(block3);
if (animation3 !== 0) {
// Change duration to 1.5 seconds
engine.block.setDuration(animation3, 1.5);
// Verify the change
const newDuration = engine.block.getDuration(animation3);
console.log('Updated duration:', newDuration, 'seconds');
}
// Block 4: Change easing function
const block4 = await createAnimatedBlock(3, imageUrls[3]);
// Change animation easing
const animation4 = engine.block.getInAnimation(block4);
if (animation4 !== 0) {
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
// Set easing to EaseInOut for smooth acceleration and deceleration
engine.block.setEnum(animation4, 'animationEasing', 'EaseInOut');
}
// Block 5: Adjust animation-specific properties (slide direction)
const block5 = await createAnimatedBlock(4, imageUrls[4]);
// Adjust animation-specific properties
const animation5 = engine.block.getInAnimation(block5);
if (animation5 !== 0) {
// Get current direction (for slide animations)
const currentDirection = engine.block.getFloat(
animation5,
'animation/slide/direction'
);
console.log('Current direction (radians):', currentDirection);
// Change direction to slide from top (3*PI/2 radians)
engine.block.setFloat(
animation5,
'animation/slide/direction',
(3 * Math.PI) / 2
);
}
// Block 6: Replace and remove animations
const block6 = await createAnimatedBlock(5, imageUrls[5]);
// Replace an existing animation
const oldAnimation = engine.block.getInAnimation(block6);
if (oldAnimation !== 0) {
// Destroy the old animation to prevent memory leaks
engine.block.destroy(oldAnimation);
}
// Create and set a new animation
const newAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block6, newAnimation);
engine.block.setDuration(newAnimation, 1.2);
engine.block.setEnum(newAnimation, 'animationEasing', 'EaseOut');
// Add a loop animation to demonstrate removal
const loopAnim = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block6, loopAnim);
engine.block.setDuration(loopAnim, 1.0);
// Remove the loop animation by destroying it
const currentLoop = engine.block.getLoopAnimation(block6);
if (currentLoop !== 0) {
engine.block.destroy(currentLoop);
// Verify removal - should now return 0
const verifyLoop = engine.block.getLoopAnimation(block6);
console.log(
'Loop animation after removal:',
verifyLoop === 0 ? 'none' : 'exists'
);
}
}
}
export default Example;
```
This guide covers retrieving animations, reading and modifying properties, changing easing functions, adjusting animation-specific settings, and replacing or removing animations.
## Retrieving Animations
Before modifying an animation, we retrieve it from the block using `getInAnimation()`, `getOutAnimation()`, or `getLoopAnimation()`. A return value of `0` indicates no animation is attached.
```typescript highlight-retrieve-animations
// Retrieve animations from a block
const inAnimation = engine.block.getInAnimation(block1);
const outAnimation = engine.block.getOutAnimation(block1);
const loopAnimation = engine.block.getLoopAnimation(block1);
// Check if animations exist (0 means no animation)
console.log('In animation:', inAnimation !== 0 ? 'exists' : 'none');
console.log('Out animation:', outAnimation !== 0 ? 'exists' : 'none');
console.log('Loop animation:', loopAnimation !== 0 ? 'exists' : 'none');
// Get animation type if it exists
if (inAnimation !== 0) {
const animationType = engine.block.getType(inAnimation);
console.log('Animation type:', animationType);
}
```
We use `getType()` to identify the animation type (slide, fade, zoom, etc.). This is useful when you need to apply type-specific modifications.
## Reading Animation Properties
We can inspect current animation settings using property getters. `getDuration()` returns the animation length in seconds, while `getEnum()` retrieves values like easing functions.
```typescript highlight-read-properties
// Read animation properties
const animation2 = engine.block.getInAnimation(block2);
if (animation2 !== 0) {
// Get current duration
const duration = engine.block.getDuration(animation2);
console.log('Duration:', duration, 'seconds');
// Get current easing
const easing = engine.block.getEnum(animation2, 'animationEasing');
console.log('Easing:', easing);
// Discover all available properties
const allProperties = engine.block.findAllProperties(animation2);
console.log('Available properties:', allProperties);
}
```
Use `findAllProperties()` to discover all configurable properties for an animation. Different animation types expose different properties—slide animations have direction, while loop animations may have intensity or scale properties.
## Modifying Animation Duration
Change animation timing with `setDuration()`. The duration is specified in seconds.
```typescript highlight-modify-duration
// Modify animation duration
const animation3 = engine.block.getInAnimation(block3);
if (animation3 !== 0) {
// Change duration to 1.5 seconds
engine.block.setDuration(animation3, 1.5);
// Verify the change
const newDuration = engine.block.getDuration(animation3);
console.log('Updated duration:', newDuration, 'seconds');
}
```
When modifying In or Out animation durations, CE.SDK automatically adjusts the paired animation to prevent overlap. For loop animations, the duration defines the cycle length.
## Changing Easing Functions
Easing controls animation acceleration. We use `setEnum()` with the `'animationEasing'` property to change it.
```typescript highlight-change-easing
// Change animation easing
const animation4 = engine.block.getInAnimation(block4);
if (animation4 !== 0) {
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
// Set easing to EaseInOut for smooth acceleration and deceleration
engine.block.setEnum(animation4, 'animationEasing', 'EaseInOut');
}
```
Use `getEnumValues('animationEasing')` to discover available options:
| Easing | Description |
| ----------- | ----------------------------------------------- |
| `Linear` | Constant speed throughout |
| `EaseIn` | Starts slow, accelerates toward the end |
| `EaseOut` | Starts fast, decelerates toward the end |
| `EaseInOut` | Starts slow, speeds up, then slows down again |
## Adjusting Animation-Specific Properties
Each animation type has unique configurable properties. For slide animations, we can change the entry direction.
```typescript highlight-adjust-properties
// Adjust animation-specific properties
const animation5 = engine.block.getInAnimation(block5);
if (animation5 !== 0) {
// Get current direction (for slide animations)
const currentDirection = engine.block.getFloat(
animation5,
'animation/slide/direction'
);
console.log('Current direction (radians):', currentDirection);
// Change direction to slide from top (3*PI/2 radians)
engine.block.setFloat(
animation5,
'animation/slide/direction',
(3 * Math.PI) / 2
);
}
```
The `animation/slide/direction` property uses radians:
- `0` — From the right
- `Math.PI / 2` — From the bottom
- `Math.PI` — From the left
- `3 * Math.PI / 2` — From the top
For text animations, you can adjust `textAnimationWritingStyle` (Line, Word, Character) and `textAnimationOverlap` (0 for sequential, 1 for simultaneous).
## Replacing Animations
To swap an animation type, destroy the existing animation before setting a new one. This prevents memory leaks from orphaned animation objects.
```typescript highlight-replace-animation
// Replace an existing animation
const oldAnimation = engine.block.getInAnimation(block6);
if (oldAnimation !== 0) {
// Destroy the old animation to prevent memory leaks
engine.block.destroy(oldAnimation);
}
// Create and set a new animation
const newAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block6, newAnimation);
engine.block.setDuration(newAnimation, 1.2);
engine.block.setEnum(newAnimation, 'animationEasing', 'EaseOut');
```
We first retrieve and destroy the old animation, then create and attach a new one with the desired type and properties.
## Removing Animations
Remove an animation by destroying it. After destruction, the getter returns `0`.
```typescript highlight-remove-animation
// Add a loop animation to demonstrate removal
const loopAnim = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block6, loopAnim);
engine.block.setDuration(loopAnim, 1.0);
// Remove the loop animation by destroying it
const currentLoop = engine.block.getLoopAnimation(block6);
if (currentLoop !== 0) {
engine.block.destroy(currentLoop);
// Verify removal - should now return 0
const verifyLoop = engine.block.getLoopAnimation(block6);
console.log(
'Loop animation after removal:',
verifyLoop === 0 ? 'none' : 'exists'
);
}
```
Destroying a design block automatically destroys all its attached animations. However, detached animations must be destroyed manually to free memory.
## API Reference
| Method | Description |
| ------------------------------------- | -------------------------------------------------- |
| `block.getInAnimation(block)` | Get entrance animation (returns 0 if none) |
| `block.getOutAnimation(block)` | Get exit animation (returns 0 if none) |
| `block.getLoopAnimation(block)` | Get loop animation (returns 0 if none) |
| `block.getType(anim)` | Get animation type string |
| `block.getDuration(anim)` | Get animation duration in seconds |
| `block.setDuration(anim, seconds)` | Set animation duration |
| `block.getEnum(anim, prop)` | Get enum property value |
| `block.setEnum(anim, prop, value)` | Set enum property value |
| `block.getFloat(anim, prop)` | Get float property value |
| `block.setFloat(anim, prop, value)` | Set float property value |
| `block.findAllProperties(anim)` | Get all available properties |
| `block.getEnumValues(prop)` | Get available values for enum property |
| `block.destroy(anim)` | Destroy animation and free memory |
## Next Steps
- [Base Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/base-0fc5c4/) — Create entrance, exit, and loop animations
- [Text Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/text-d6f4aa/) — Animate text with writing styles and character control
- [Animation Overview](https://img.ly/docs/cesdk/sveltekit/animation/overview-6a2ef2/) — Understand animation concepts and capabilities
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Add motion to designs with support for keyframes, timeline editing, and programmatic animation control."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/animation/overview-6a2ef2/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/sveltekit/animation-ce900c/) > [Overview](https://img.ly/docs/cesdk/sveltekit/animation/overview-6a2ef2/)
---
Animations in CreativeEditor SDK (CE.SDK) bring your designs to life by adding motion to images, text, and design elements. Whether you're creating a dynamic social media post, a video ad, or an engaging product demo, animations help capture attention and communicate ideas more effectively.
With CE.SDK, you can create and edit animations either through the built-in UI timeline or programmatically using the CreativeEngine API. Animated designs can be exported as MP4 videos, allowing you to deliver polished, motion-rich content entirely client-side.
[Launch Web Demo](https://img.ly/showcases/cesdk)
[Get Started](https://img.ly/docs/cesdk/sveltekit/get-started/overview-e18f40/)
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Supported Animation Types"
description: "Apply different animation types to design blocks in CE.SDK and configure their properties."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/animation/types-4e5f41/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/sveltekit/animation-ce900c/) > [Supported Animation Types](https://img.ly/docs/cesdk/sveltekit/animation/types-4e5f41/)
---
Apply entrance, exit, and loop animations to design blocks using the available animation types in CE.SDK.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-types-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-types-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-types-browser/)
CE.SDK organizes animations into three categories: entrance (In), exit (Out), and loop. Each category determines when the animation plays during the block's lifecycle. This guide demonstrates different animation types and their configurable properties.
```typescript file=@cesdk_web_examples/guides-animation-types-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
import { calculateGridLayout } from './utils';
/**
* CE.SDK Plugin: Supported Animation Types Guide
*
* Demonstrates how to use different animation types in CE.SDK:
* - Entrance animations (slide, fade, zoom, spin)
* - Exit animations with timing and easing
* - Loop animations for continuous motion
* - Animation property configuration
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: { width: 1920, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const scene = engine.scene.get()!;
const pages = engine.block.findByType('page');
const page = pages.length > 0 ? pages[0] : scene;
// Set white background color
if (!engine.block.supportsFill(page) || !engine.block.getFill(page)) {
const fill = engine.block.createFill('color');
engine.block.setFill(page, fill);
}
const pageFill = engine.block.getFill(page)!;
engine.block.setColor(pageFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Calculate grid layout for 6 demonstration blocks
const pageWidth = engine.block.getWidth(page)!;
const pageHeight = engine.block.getHeight(page)!;
const layout = calculateGridLayout(pageWidth, pageHeight, 6);
const { blockWidth, blockHeight, getPosition } = layout;
// Helper to create an image block
const createImageBlock = async (index: number, imageUrl: string) => {
const graphic = engine.block.create('graphic');
const imageFill = engine.block.createFill('image');
engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUrl);
engine.block.setFill(graphic, imageFill);
engine.block.setShape(graphic, engine.block.createShape('rect'));
engine.block.setWidth(graphic, blockWidth);
engine.block.setHeight(graphic, blockHeight);
const pos = getPosition(index);
engine.block.setPositionX(graphic, pos.x);
engine.block.setPositionY(graphic, pos.y);
engine.block.appendChild(page, graphic);
return graphic;
};
// Sample images for demonstration
const imageUrls = [
'https://img.ly/static/ubq_samples/sample_1.jpg',
'https://img.ly/static/ubq_samples/sample_2.jpg',
'https://img.ly/static/ubq_samples/sample_3.jpg',
'https://img.ly/static/ubq_samples/sample_4.jpg',
'https://img.ly/static/ubq_samples/sample_5.jpg',
'https://img.ly/static/ubq_samples/sample_6.jpg'
];
// Block 1: Slide entrance animation with direction
const block1 = await createImageBlock(0, imageUrls[0]);
// Create a slide animation that enters from the left
const slideAnimation = engine.block.createAnimation('slide');
engine.block.setInAnimation(block1, slideAnimation);
engine.block.setDuration(slideAnimation, 1.0);
// Direction in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top
engine.block.setFloat(slideAnimation, 'animation/slide/direction', Math.PI);
engine.block.setEnum(slideAnimation, 'animationEasing', 'EaseOut');
// Block 2: Fade animation with easing
const block2 = await createImageBlock(1, imageUrls[1]);
// Create a fade entrance animation
const fadeAnimation = engine.block.createAnimation('fade');
engine.block.setInAnimation(block2, fadeAnimation);
engine.block.setDuration(fadeAnimation, 1.0);
engine.block.setEnum(fadeAnimation, 'animationEasing', 'EaseInOut');
// Block 3: Zoom animation
const block3 = await createImageBlock(2, imageUrls[2]);
// Create a zoom animation with fade effect
const zoomAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block3, zoomAnimation);
engine.block.setDuration(zoomAnimation, 1.0);
engine.block.setBool(zoomAnimation, 'animation/zoom/fade', true);
// Block 4: Exit animation
const block4 = await createImageBlock(3, imageUrls[3]);
// Create entrance and exit animations
const wipeIn = engine.block.createAnimation('wipe');
engine.block.setInAnimation(block4, wipeIn);
engine.block.setDuration(wipeIn, 1.0);
engine.block.setEnum(wipeIn, 'animation/wipe/direction', 'Right');
// Exit animation plays before block disappears
const fadeOut = engine.block.createAnimation('fade');
engine.block.setOutAnimation(block4, fadeOut);
engine.block.setDuration(fadeOut, 1.0);
engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn');
// Block 5: Loop animation
const block5 = await createImageBlock(4, imageUrls[4]);
// Create a breathing loop animation
const breathingLoop = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block5, breathingLoop);
engine.block.setDuration(breathingLoop, 2.0);
// Intensity: 0 = 1.25x max scale, 1 = 2.5x max scale
engine.block.setFloat(
breathingLoop,
'animation/breathing_loop/intensity',
0.3
);
// Block 6: Combined animations
const block6 = await createImageBlock(5, imageUrls[5]);
// Apply entrance, exit, and loop animations together
const spinIn = engine.block.createAnimation('spin');
engine.block.setInAnimation(block6, spinIn);
engine.block.setDuration(spinIn, 1.0);
engine.block.setEnum(spinIn, 'animation/spin/direction', 'Clockwise');
engine.block.setFloat(spinIn, 'animation/spin/intensity', 0.5);
const blurOut = engine.block.createAnimation('blur');
engine.block.setOutAnimation(block6, blurOut);
engine.block.setDuration(blurOut, 1.0);
const swayLoop = engine.block.createAnimation('sway_loop');
engine.block.setLoopAnimation(block6, swayLoop);
engine.block.setDuration(swayLoop, 1.5);
// Discover available properties for any animation
const properties = engine.block.findAllProperties(slideAnimation);
// eslint-disable-next-line no-console
console.log('Slide animation properties:', properties);
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
// eslint-disable-next-line no-console
console.log('Available easing options:', easingOptions);
// Set initial playback time to see animations
engine.block.setPlaybackTime(page, 1.9);
}
}
export default Example;
```
This guide covers applying entrance animations (slide, fade, zoom), exit animations, loop animations, and configuring animation properties like direction, easing, and intensity.
## Entrance Animations
Entrance animations define how a block appears. We use `createAnimation()` with the animation type and attach it using `setInAnimation()`.
### Slide Animation
The slide animation moves a block in from a specified direction. The `direction` property uses radians where 0 is right, π/2 is bottom, π is left, and 3π/2 is top.
```typescript highlight-entrance-slide
// Create a slide animation that enters from the left
const slideAnimation = engine.block.createAnimation('slide');
engine.block.setInAnimation(block1, slideAnimation);
engine.block.setDuration(slideAnimation, 1.0);
// Direction in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top
engine.block.setFloat(slideAnimation, 'animation/slide/direction', Math.PI);
engine.block.setEnum(slideAnimation, 'animationEasing', 'EaseOut');
```
### Fade Animation
The fade animation transitions opacity from invisible to fully visible. Easing controls the animation curve.
```typescript highlight-entrance-fade
// Create a fade entrance animation
const fadeAnimation = engine.block.createAnimation('fade');
engine.block.setInAnimation(block2, fadeAnimation);
engine.block.setDuration(fadeAnimation, 1.0);
engine.block.setEnum(fadeAnimation, 'animationEasing', 'EaseInOut');
```
### Zoom Animation
The zoom animation scales the block from a smaller size to its final dimensions. The `fade` property adds an opacity transition during scaling.
```typescript highlight-entrance-zoom
// Create a zoom animation with fade effect
const zoomAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block3, zoomAnimation);
engine.block.setDuration(zoomAnimation, 1.0);
engine.block.setBool(zoomAnimation, 'animation/zoom/fade', true);
```
Other entrance animation types include:
- `blur` — Transitions from blurred to clear
- `wipe` — Reveals with a directional wipe
- `pop` — Bouncy scale effect
- `spin` — Rotates the block into view
- `grow` — Scales up from a point
## Exit Animations
Exit animations define how a block leaves the screen. We use `setOutAnimation()` to attach them. CE.SDK prevents overlap between entrance and exit durations automatically.
```typescript highlight-exit-animation
// Create entrance and exit animations
const wipeIn = engine.block.createAnimation('wipe');
engine.block.setInAnimation(block4, wipeIn);
engine.block.setDuration(wipeIn, 1.0);
engine.block.setEnum(wipeIn, 'animation/wipe/direction', 'Right');
// Exit animation plays before block disappears
const fadeOut = engine.block.createAnimation('fade');
engine.block.setOutAnimation(block4, fadeOut);
engine.block.setDuration(fadeOut, 1.0);
engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn');
```
In this example, a wipe entrance transitions to a fade exit. Mirror entrance effects for visual consistency, or use contrasting effects for emphasis.
## Loop Animations
Loop animations run continuously while the block is visible. They can combine with entrance and exit animations. We use `setLoopAnimation()` to attach them.
```typescript highlight-loop-animation
// Create a breathing loop animation
const breathingLoop = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block5, breathingLoop);
engine.block.setDuration(breathingLoop, 2.0);
// Intensity: 0 = 1.25x max scale, 1 = 2.5x max scale
engine.block.setFloat(
breathingLoop,
'animation/breathing_loop/intensity',
0.3
);
```
The duration controls each cycle length. Loop animation types include:
- `breathing_loop` — Slow scale pulse
- `pulsating_loop` — Rhythmic scale
- `spin_loop` — Continuous rotation
- `fade_loop` — Opacity cycling
- `sway_loop` — Rotational oscillation
- `jump_loop` — Jumping motion
- `blur_loop` — Blur cycling
- `squeeze_loop` — Squeezing effect
## Combined Animations
A single block can have entrance, exit, and loop animations running together. The loop animation runs throughout the block's visibility while entrance and exit animations play at the appropriate times.
```typescript highlight-combined-animations
// Apply entrance, exit, and loop animations together
const spinIn = engine.block.createAnimation('spin');
engine.block.setInAnimation(block6, spinIn);
engine.block.setDuration(spinIn, 1.0);
engine.block.setEnum(spinIn, 'animation/spin/direction', 'Clockwise');
engine.block.setFloat(spinIn, 'animation/spin/intensity', 0.5);
const blurOut = engine.block.createAnimation('blur');
engine.block.setOutAnimation(block6, blurOut);
engine.block.setDuration(blurOut, 1.0);
const swayLoop = engine.block.createAnimation('sway_loop');
engine.block.setLoopAnimation(block6, swayLoop);
engine.block.setDuration(swayLoop, 1.5);
```
## Configuring Animation Properties
Each animation type has specific configurable properties. We use `findAllProperties()` to discover available properties and `getEnumValues()` to query options for enum properties.
```typescript highlight-discover-properties
// Discover available properties for any animation
const properties = engine.block.findAllProperties(slideAnimation);
// eslint-disable-next-line no-console
console.log('Slide animation properties:', properties);
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
// eslint-disable-next-line no-console
console.log('Available easing options:', easingOptions);
```
Common configurable properties include:
- **Direction**: Controls entry/exit direction in radians or enum values
- **Easing**: Animation curve (`Linear`, `EaseIn`, `EaseOut`, `EaseInOut`)
- **Intensity**: Strength of the effect (varies by animation type)
- **Fade**: Whether to include opacity transition
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.block.createAnimation(type)` | Create animation by type string |
| `engine.block.setInAnimation(block, anim)` | Attach entrance animation |
| `engine.block.setOutAnimation(block, anim)` | Attach exit animation |
| `engine.block.setLoopAnimation(block, anim)` | Attach loop animation |
| `engine.block.setDuration(anim, seconds)` | Set animation duration |
| `engine.block.setFloat(anim, property, value)` | Set numeric property |
| `engine.block.setEnum(anim, property, value)` | Set enum property |
| `engine.block.setBool(anim, property, value)` | Set boolean property |
| `engine.block.findAllProperties(anim)` | Discover configurable properties |
| `engine.block.getEnumValues(property)` | Get available enum values |
## Next Steps
- [Base Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/base-0fc5c4/) — Create and attach animations to blocks
- [Text Animations](https://img.ly/docs/cesdk/sveltekit/animation/create/text-d6f4aa/) — Animate text with writing styles
- [Animation Overview](https://img.ly/docs/cesdk/sveltekit/animation/overview-6a2ef2/) — Animation concepts and capabilities
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "API Reference"
description: "Find out how to use the API of the CESDK."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/api-reference/overview-8f24e1/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [API Reference](https://img.ly/docs/cesdk/sveltekit/api-reference/overview-8f24e1/)
---
For , the following packages are available:
- [@cesdk/cesdk-js](`$\{props.platform.slug}/api/cesdk-js/`)
- [@cesdk/engine](`$\{props.platform.slug}/api/engine/`)
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Automate Workflows"
description: "Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/automation-715209/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/sveltekit/automation-715209/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/sveltekit/automation/overview-34d971/) - Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale.
- [Batch Processing](https://img.ly/docs/cesdk/sveltekit/automation/batch-processing-ab2d18/) - Manage batch processing workflows in web apps with CE.SDK
- [Auto-Resize](https://img.ly/docs/cesdk/sveltekit/automation/auto-resize-4c2d58/) - Configure blocks to dynamically adjust dimensions using Absolute, Percent, and Auto sizing modes for responsive layouts and content-driven expansion.
- [Data Merge](https://img.ly/docs/cesdk/sveltekit/automation/data-merge-ae087c/) - Generate personalized designs from templates by merging external data using text variables and placeholder blocks
- [Automate Design Generation](https://img.ly/docs/cesdk/sveltekit/automation/design-generation-98a99e/) - Generate on-brand designs programmatically using templates, variables, and CE.SDK’s headless API.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Auto-Resize"
description: "Configure blocks to dynamically adjust dimensions using Absolute, Percent, and Auto sizing modes for responsive layouts and content-driven expansion."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/automation/auto-resize-4c2d58/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/sveltekit/automation-715209/) > [Auto-Resize](https://img.ly/docs/cesdk/sveltekit/automation/auto-resize-4c2d58/)
---
Configure blocks to dynamically adjust their dimensions using three sizing modes: Absolute for fixed values, Percent for parent-relative sizing, and Auto for content-driven expansion.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-automation-auto-resize-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-automation-auto-resize-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-automation-auto-resize-browser/)
CE.SDK provides three sizing modes for controlling block dimensions. Absolute mode uses fixed pixel values. Percent mode sizes blocks relative to their parent container. Auto mode automatically expands blocks to fit their content. You can set width and height modes independently, allowing flexible combinations like fixed width with auto height for text that wraps.
```typescript file=@cesdk_web_examples/guides-automation-auto-resize-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Auto-Resize Guide
*
* Demonstrates block sizing modes and responsive layout patterns:
* - Setting width and height modes (Absolute, Percent, Auto)
* - Reading computed frame dimensions after layout
* - Centering text blocks based on computed dimensions
* - Creating responsive layouts with percentage-based sizing
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
// Create a text block with Auto sizing mode
// Auto mode makes the block expand to fit its content
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Auto-Resize Demo');
engine.block.setFloat(titleBlock, 'text/fontSize', 64);
// Set width and height modes to Auto
// The block will automatically size to fit the text content
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.appendChild(page, titleBlock);
// Read computed frame dimensions after layout
// getFrameWidth/getFrameHeight return the actual rendered size
const titleWidth = engine.block.getFrameWidth(titleBlock);
const titleHeight = engine.block.getFrameHeight(titleBlock);
console.log(
`Title dimensions: ${titleWidth.toFixed(0)}x${titleHeight.toFixed(
0
)} pixels`
);
// Calculate centered position using frame dimensions
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const centerX = (pageWidth - titleWidth) / 2;
const centerY = (pageHeight - titleHeight) / 2 - 100; // Offset up for layout
// Position the title at center
engine.block.setPositionX(titleBlock, centerX);
engine.block.setPositionY(titleBlock, centerY);
// Create a block using Percent mode for responsive sizing
// Percent mode sizes the block relative to its parent
const backgroundBlock = engine.block.create('graphic');
engine.block.setShape(backgroundBlock, engine.block.createShape('rect'));
const fill = engine.block.createFill('color');
engine.block.setColor(fill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.8,
a: 0.3
});
engine.block.setFill(backgroundBlock, fill);
// Set to Percent mode - values are normalized (0-1)
engine.block.setWidthMode(backgroundBlock, 'Percent');
engine.block.setHeightMode(backgroundBlock, 'Percent');
engine.block.setWidth(backgroundBlock, 0.8); // 80% of parent width
engine.block.setHeight(backgroundBlock, 0.3); // 30% of parent height
// Center the background block
engine.block.setPositionX(backgroundBlock, pageWidth * 0.1); // 10% margin
engine.block.setPositionY(backgroundBlock, pageHeight * 0.6);
engine.block.appendChild(page, backgroundBlock);
// Create a subtitle with Auto mode
const subtitleBlock = engine.block.create('text');
engine.block.replaceText(
subtitleBlock,
'Text automatically sizes to fit content'
);
engine.block.setFloat(subtitleBlock, 'text/fontSize', 32);
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
// Read computed dimensions and center
const subtitleWidth = engine.block.getFrameWidth(subtitleBlock);
const subtitleCenterX = (pageWidth - subtitleWidth) / 2;
engine.block.setPositionX(subtitleBlock, subtitleCenterX);
engine.block.setPositionY(subtitleBlock, pageHeight * 0.7);
// Verify sizing modes
const titleWidthMode = engine.block.getWidthMode(titleBlock);
const titleHeightMode = engine.block.getHeightMode(titleBlock);
const bgWidthMode = engine.block.getWidthMode(backgroundBlock);
const bgHeightMode = engine.block.getHeightMode(backgroundBlock);
console.log(
`Title modes: width=${titleWidthMode}, height=${titleHeightMode}`
);
console.log(
`Background modes: width=${bgWidthMode}, height=${bgHeightMode}`
);
// Select the title block to show the auto-sized result
engine.block.select(titleBlock);
console.log(
'Auto-resize guide initialized. Try changing text content to see auto-sizing in action.'
);
}
}
export default Example;
```
This guide covers how to set and query sizing modes, read computed frame dimensions after layout, center blocks using frame dimensions, and create responsive layouts with percentage-based sizing.
## Initialize the Editor
We start by initializing CE.SDK with a Design scene and setting up the page dimensions for our layout.
```typescript highlight=highlight-setup
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
```
## Size Modes
CE.SDK supports three sizing modes for block dimensions:
- **Absolute**: Fixed dimensions in design units. The default mode where `setWidth()` and `setHeight()` set exact pixel values.
- **Percent**: Dimensions relative to parent container. A value of 80 makes the block 80% of its parent's size.
- **Auto**: Content-driven sizing. The block expands or contracts to fit its content, primarily useful for text blocks.
## Setting Size Modes
Use `setWidthMode()` and `setHeightMode()` to configure how a block calculates its dimensions. Width and height modes can be set independently.
### Auto Mode for Text
Auto mode makes text blocks expand to fit their content:
```typescript highlight=highlight-auto-mode
// Create a text block with Auto sizing mode
// Auto mode makes the block expand to fit its content
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Auto-Resize Demo');
engine.block.setFloat(titleBlock, 'text/fontSize', 64);
// Set width and height modes to Auto
// The block will automatically size to fit the text content
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.appendChild(page, titleBlock);
```
With Auto mode, the block's dimensions are calculated automatically based on the content. This is useful when the text content varies and you want the block to always fit exactly.
### Percent Mode for Responsive Layouts
Percent mode sizes blocks relative to their parent:
```typescript highlight=highlight-percent-mode
// Create a block using Percent mode for responsive sizing
// Percent mode sizes the block relative to its parent
const backgroundBlock = engine.block.create('graphic');
engine.block.setShape(backgroundBlock, engine.block.createShape('rect'));
const fill = engine.block.createFill('color');
engine.block.setColor(fill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.8,
a: 0.3
});
engine.block.setFill(backgroundBlock, fill);
// Set to Percent mode - values are normalized (0-1)
engine.block.setWidthMode(backgroundBlock, 'Percent');
engine.block.setHeightMode(backgroundBlock, 'Percent');
engine.block.setWidth(backgroundBlock, 0.8); // 80% of parent width
engine.block.setHeight(backgroundBlock, 0.3); // 30% of parent height
// Center the background block
engine.block.setPositionX(backgroundBlock, pageWidth * 0.1); // 10% margin
engine.block.setPositionY(backgroundBlock, pageHeight * 0.6);
engine.block.appendChild(page, backgroundBlock);
```
Percent values represent the percentage of the parent container. A width of 80 with Percent mode means 80% of the parent's width.
## Reading Frame Dimensions
After layout, use `getFrameWidth()` and `getFrameHeight()` to read the computed dimensions:
```typescript highlight=highlight-read-frame-dimensions
// Read computed frame dimensions after layout
// getFrameWidth/getFrameHeight return the actual rendered size
const titleWidth = engine.block.getFrameWidth(titleBlock);
const titleHeight = engine.block.getFrameHeight(titleBlock);
console.log(
`Title dimensions: ${titleWidth.toFixed(0)}x${titleHeight.toFixed(
0
)} pixels`
);
```
Frame dimensions return the actual rendered size regardless of the sizing mode. This is essential when using Auto mode since you need the computed size for positioning calculations.
## Centering Blocks
Combine Auto mode with frame dimensions to center blocks based on their actual size:
```typescript highlight=highlight-center-block
// Calculate centered position using frame dimensions
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const centerX = (pageWidth - titleWidth) / 2;
const centerY = (pageHeight - titleHeight) / 2 - 100; // Offset up for layout
// Position the title at center
engine.block.setPositionX(titleBlock, centerX);
engine.block.setPositionY(titleBlock, centerY);
```
This pattern reads the computed dimensions after Auto sizing and calculates the centered position.
## Additional Auto-Sized Content
You can create multiple auto-sized blocks and position them relative to each other:
```typescript highlight=highlight-subtitle-auto
// Create a subtitle with Auto mode
const subtitleBlock = engine.block.create('text');
engine.block.replaceText(
subtitleBlock,
'Text automatically sizes to fit content'
);
engine.block.setFloat(subtitleBlock, 'text/fontSize', 32);
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
// Read computed dimensions and center
const subtitleWidth = engine.block.getFrameWidth(subtitleBlock);
const subtitleCenterX = (pageWidth - subtitleWidth) / 2;
engine.block.setPositionX(subtitleBlock, subtitleCenterX);
engine.block.setPositionY(subtitleBlock, pageHeight * 0.7);
```
## Verifying Size Modes
Query the current size modes to verify your configuration:
```typescript highlight=highlight-check-modes
// Verify sizing modes
const titleWidthMode = engine.block.getWidthMode(titleBlock);
const titleHeightMode = engine.block.getHeightMode(titleBlock);
const bgWidthMode = engine.block.getWidthMode(backgroundBlock);
const bgHeightMode = engine.block.getHeightMode(backgroundBlock);
console.log(
`Title modes: width=${titleWidthMode}, height=${titleHeightMode}`
);
console.log(
`Background modes: width=${bgWidthMode}, height=${bgHeightMode}`
);
```
## Troubleshooting
**Frame dimensions return 0**: Layout may not have updated yet. Read frame dimensions after all content is set and the block is attached to the scene hierarchy.
**Percent mode not working**: The block must have a parent container. Percent mode calculates size relative to the parent's dimensions.
**Auto mode not resizing**: Auto mode works with content that has intrinsic size, primarily text blocks. Graphics require explicit dimensions.
**Unexpected dimensions**: Check which mode is active using `getWidthMode()` and `getHeightMode()`. The mode affects how width and height values are interpreted.
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.block.getWidth(block)` | Get block width in current mode |
| `engine.block.setWidth(block, value)` | Set block width in current mode |
| `engine.block.getWidthMode(block)` | Get current width mode: Absolute, Percent, or Auto |
| `engine.block.setWidthMode(block, mode)` | Set width mode: Absolute, Percent, or Auto |
| `engine.block.getHeight(block)` | Get block height in current mode |
| `engine.block.setHeight(block, value)` | Set block height in current mode |
| `engine.block.getHeightMode(block)` | Get current height mode: Absolute, Percent, or Auto |
| `engine.block.setHeightMode(block, mode)` | Set height mode: Absolute, Percent, or Auto |
| `engine.block.getFrameWidth(block)` | Get computed width after layout |
| `engine.block.getFrameHeight(block)` | Get computed height after layout |
| `engine.block.setPositionX(block, value)` | Set block X position |
| `engine.block.setPositionY(block, value)` | Set block Y position |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Batch Processing"
description: "Manage batch processing workflows in web apps with CE.SDK"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/automation/batch-processing-ab2d18/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/sveltekit/automation-715209/) > [Batch Processing](https://img.ly/docs/cesdk/sveltekit/automation/batch-processing-ab2d18/)
---
This guide shows you how to use CE.SDK to create and manage batch processing workflows in the browser. Batch processing automates creative operations at scale, from enabling template population and multi-format exports, to bulk transformations and production pipelines.
In the browser, batch processing means automating the same CreativeEngine workflow while the tab stays open. Instead of the user editing/exporting items one by one, your front-end:
1. Loops through a dataset.
2. Produces a series of outputs.
This guides helps you understand how the CE.SDK can work in a batch process workflow.
## What You’ll Learn
- Two different batch processing approaches:
- Sequential
- Parallel
- How to batch:
- Templates population with data.
- Exports to different formats (PNG, JPEG, PDF, MP4).
- Thumbnails generation.
- How to optimize memory usage.
## Batch Processing Strategies
You can run batch operations in two ways:
- **Sequential:** a single engine loop.
- **Parallel:** multiple workers spinning up.
The following examples show both approaches when running a batch export in the browser:
```ts
// ... downloadBlob logic
//Start the engine and download the scene
const engine = await CreativeEngine.init({ license: LICENSE_KEY });
for (const record of records) {
await engine.scene.loadFromString(record.scene);
const blob = await engine.block.export(engine.scene.getPages()[0], 'image/png');
await downloadBlob(blob, `${record.id}.png`);
}
engine.dispose();
```
1. `CreativeEngine.init` spins up a single engine instance for the tab.
2. The loop iterates over the `record` dataset.
3. The Engine loads the scene.
4. The `export` call renders the first page as a PNG blob.
5. The code disposes of the engine to free resources.
```ts
const workers = [new Worker('worker.js'), new Worker('worker.js')];
await Promise.all(
records.map((record, idx) =>
workers[idx % workers.length].postMessage({ type: 'PROCESS', record })
)
);
```
In this code:
1. 2 workers run in separate threads.
2. Each worker receives a different data set.
3. Each worker runs the heavier CreativeEngine work off the main thread.
4. `Promise.all` waits for every worker call to finish before moving on.
The following table summarizes the pros and cons of each approach:
| Approach | When to use | Pros | Cons |
| --- | --- | --- | --- |
| **Sequential** | - Default browser workload - Small batch sizes- Limited RAM on user devices | - Lower memory footprint - Simpler code path- Easy cleanup |- Slower total runtime - UI can feel locked if not chunked |
| **Parallel** | - Big datasets Enough resources in user devices| - Higher throughput - Can keep UI responsive | - More memory consumption per tab Coordination complexity- Throttling risk |
## How To Batch Template Population
For this operation, you generate personalized outputs at scale by combining:
- Templates
- Structured data
### Set the Data Sources
Batch workflows can use a variety of data sources to populate a template, such as:
- CSV files with parsing libraries
- JSON from REST APIs
- Databases (SQL, NoSQL)
- Stream data
The following examples show how to set three different data sources:
```ts
await fetch('path/to/dataset.json').then((r) => r.json());
```
```ts
await fetch('https://api.example.com/dataset').then((r) => r.json());
```
```ts
// Define key variables
let textVariables = {
first_name: '',
last_name: '',
address: '',
city: '',
};
```
### Update the Template
You can automate template population, update media, and show conditional content based on data. Find some examples in existing guides:
| Action | EngineAPI function | Related guide |
| --- | --- | --- |
| Set text variables | `engine.variable.setString(variableId, value)` | [Text Variables](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/) |
| Update image fills | `engine.block.setString(block, 'fill/image/imageFileURI', url)` | [Insert Images](https://img.ly/docs/cesdk/sveltekit/insert-media/images-63848a/) |
| Edit block properties | `engine.block.setFloat(block, key, value)` / `engine.block.setColor(block, key, color)` | [Apply Effects](https://img.ly/docs/cesdk/sveltekit/filters-and-effects/apply-2764e4/) |
### Batch Export the Design
The CE.SDK provides a set of format options when exporting the edited designs:
| Format | EngineAPI function | Related guide |
| --- | --- | --- |
| PNG | `engine.block.export(block, 'image/png')` | [PNG](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/to-png-f87eaf/) |
| JPEG | `engine.block.export(block, 'image/jpeg', 0.95)` | [JPEG](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/to-jpeg-6f88e9/) |
| PDF | `engine.block.export(block, 'application/pdf')` | [PDF](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/to-pdf-95e04b/) |
| MP4 | `engine.block.exportVideo(block, MimeType.Mp4)` | [MP4](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/to-mp4-c998a8/) |
Check all the export options in the [Export section](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/overview-9ed3a8/).
### Batch Thumbnail Generation from Static Scenes
The export feature allows you to automate thumbnails generation by tweaking the format and the size of the design, for example:
```ts
// Example: Real-time thumbnail generation
const thumbnailEngine = await CreativeEngine.init({ container: null });
async function generateThumbnail(sceneData) {
await thumbnailEngine.scene.loadFromString(sceneData);
const page = thumbnailEngine.scene.getPages()[0];
// Generate small preview
const thumbnail = await thumbnailEngine.block.export(page, 'image/jpeg', {
targetWidth: 200,
targetHeight: 200,
quality: 0.7,
});
return thumbnail;
}
```
Read more about thumbnails generation in [the Engine guide](https://img.ly/docs/cesdk/sveltekit/engine-interface-6fb7cf/).
The CE.SDK also provides over 20 pre-designed text layouts to apply on thumbnails. Check the [relevant guide](https://img.ly/docs/cesdk/sveltekit/text/text-designs-a1b2c3/) to use them.
### Batch Thumbnail Generation from Video Scenes
Extract representative frames from videos efficiently, and automate this action using the dedicated CE.SDK features:
| Action | EngineAPI function | Related guide |
| --- | --- | --- |
| Load video source | `engine.scene.createFromVideo()` | [Create from Video](https://img.ly/docs/cesdk/sveltekit/create-video/control-daba54/) |
| Seek to timestamp | `engine.block.setPlaybackTime()` | [Control Audio and Video](https://img.ly/docs/cesdk/sveltekit/create-video/control-daba54/) |
| Export single frame | `engine.block.export(block, options)` | [To PNG](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/to-png-f87eaf/) [Text Designs](https://img.ly/docs/cesdk/sveltekit/text/text-designs-a1b2c3/) |
| Generate sequence thumbnails | `engine.block.generateVideoThumbnailSequence()` | [Trim Video Clips](https://img.ly/docs/cesdk/sveltekit/edit-video/trim-4f688b/) |
| Size thumbnails consistently | `targetWidth / targetHeight` export options | [To PNG](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/to-png-f87eaf/) |
The following code shows how to **generate thumbnails from a video**:
```ts
import CreativeEngine from '@cesdk/engine';
const engine = await CreativeEngine.init({ license: LICENSE_KEY });
await engine.scene.loadFromURL('/assets/video-scene.scene');
const [page] = engine.scene.getPages();
const videoBlock = engine.block
.getChildren(page)
.find((child) => engine.block.getType(child) === 'video');
if (videoBlock) {
const videoFill = engine.block.getFill(videoBlock);
await engine.block.setPlaybackTime(videoFill, 4.2);
const thumbnail = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 640,
targetHeight: 360
});
await downloadBlob(thumbnail, 'scene-thumb.png');
}
engine.dispose();
```
The preceding code:
1. Loads a scene containing a video.
2. Seeks to 4.2 s.
3. Exports the page as a PNG.
4. Saves the thumbnail.
## Optimize Memory Usage
Every export produces and accumulates:
- Blobs
- URLs
- Engine state
Proper **cleanup** ensures batch processes complete without resource exhaustion. Without proper cleanup, the browser might:
- Hits memory ceiling.
- Crash.
- Slow down.
Consider the following actions to **avoid exhausting the client**:
| Strategy | Code |
| --- | --- |
| Revoke blob URLs immediately after use | `URL.revokeObjectURL()` |
| Dispose engine instances when finished | `engine.dispose()` |
| Chunk large datasets into smaller batches | |
| Consider garbage collection timing | |
Treat cleanup as part of **each loop** iteration, by either:
- Freeing resources **after each item**.
- Chunking resources, by loading smaller parts of your datasets at a time.
> **Note:** To **handle large batches**, consider the following workflows:- Split into smaller chunks.
> - Log progress.
> - Monitor status.
## Apply Error Handling
Batch runs often work with **large records of data**. Some factors can make the job crash, such as:
- A malformed asset
- Timeouts
When your job encounters one of these errors, you can proactively **avoid the job’s failure** using the following patterns:
- Catch errors inside each loop iteration.
- Log failing records so you can retry them later.
- Decide whether to keep going or stop when an error happens.
- Collect a summary of all failures for post-run review.
For example, the preceding code to generate thumbnails now handles errors gracefully to avoid crashes:
```ts
import CreativeEngine from '@cesdk/engine';
let engine;
try {
engine = await CreativeEngine.init({ license: LICENSE_KEY });
await engine.scene.loadFromURL('/assets/video-scene.scene');
const [page] = engine.scene.getPages();
if (!page) throw new Error('Scene has no pages.');
const videoBlock = engine.block
.getChildren(page)
.find((child) => engine.block.getType(child) === 'video');
if (!videoBlock) throw new Error('No video block found.');
const videoFill = engine.block.getFill(videoBlock);
if (!videoFill) throw new Error('Video block is missing its fill.');
await engine.block.setPlaybackTime(videoFill, 4.2);
const thumbnail = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 640,
targetHeight: 360
});
await downloadBlob(thumbnail, 'scene-thumb.png');
} catch (error) {
console.error('Failed to generate thumbnail', error);
} finally {
engine?.dispose();
}
```
### Use Retry Logic
Some errors are temporary due to factors such as:
- Network hiccup
- Rate limits
- Busy CDN
To avoid saturating the related service, you can use smart retries after a short delay. If the error persist:
1. Double the delay.
2. Retry
3. Double again the delay exponentially after each retry.
This strategy allows you to identify temporary failures that could be resolved later.
For **API failures**, consider using circuit breaking patterns that:
- Pause the calls on repeated errors.
- Test again after a delay.
### Check the Input Data Before Processing
Lightweight checks can help you with:
- Catching bad inputs early.
- Preventing waste of time and compute on batches that’ll fail.
Add checks **before**:
- Launching the CE.SDK.
- Loading scenes.
- Exporting large scenes.
The following table contains some checks **examples**:
| Check | Example |
| --- | --- |
| Check input data structure | `if (!isValidRecord(record)) throw new Error('Invalid payload');` |
| Check file existence and accessibility | `await fs.promises.access(filePath, fs.constants.R_OK);` |
| Verify templates load correctly | `await engine.scene.loadFromURL(templateUrl);` |
| Use dry-run mode for testing | `if (options.dryRun) return simulate(record);` |
For example, the following **data validation function** checks:
- The record type
- The `id`
- The HTTPS template URL
- The presence of variants
It throws descriptive errors if any of these elements are missing.
```ts
function validateRecord(record) {
if (typeof record !== 'object' || record === null) {
throw new Error('Record must be an object');
}
if (typeof record.id !== 'string') {
throw new Error('Missing record id');
}
if (!record.templateUrl?.startsWith('https://')) {
throw new Error('Invalid template URL');
}
if (!Array.isArray(record.variants) || record.variants.length === 0) {
throw new Error('Record requires at least one variant');
}
return true;
}
```
## Batch Process on Production
When running on production, enhance browser-based batch processes with architecture and UX decisions that help the user run the workflow, such as:
- **User-initiated batches**: keep work tied to explicit user actions; show confirmation dialogs for large jobs.
- **Chunked processing**: split datasets into small slices (for example, 20 records) to avoid blocking the main thread.
- **Resource caps**: document safe limits (for example, 50–100 exports per session) and enforce them in the UI.
- **Persistence**: use `localStorage` or IndexedDB to cache progress so reloads can resume work.
### Monitor the Process
Give users visibility inside the tab and send lightweight telemetry upstream.
- Render UI elements that show the state, such as:
- Progress bars
- Per-item status chips
- Send `fetch` calls to your backend for:
- Error logs
- Aggregated stats
- When a chunk fails:
1. Show in-app notifications/snackbars.
2. Offer retries.
For example, the following code:
- Structures logging.
- Renders it with timestamps.
```ts
function reportBatchMetrics(batchMetrics) {
const entry = {
timestamp: new Date().toISOString(),
...batchMetrics,
};
console.table([entry]);
return fetch('/api/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(entry),
});
}
```
## Troubleshooting
| Issue | Cause | Solution |
| -------------------------- | ------------------------------------------ | --------------------------------------------------- |
| Out of memory errors | Blob URLs not revoked, engine not disposed | Call `URL.revokeObjectURL()` and `engine.dispose()` |
| Slow processing speed | Template loaded each iteration | Load template once, modify variables only |
| Items fail silently | Missing error handling | Wrap processing in try-catch blocks |
| Inconsistent outputs | Shared state between iterations | Reset state or reload template each iteration |
| Process hangs indefinitely | Uncaught promise rejection | Use error handling and timeouts |
| Performance bottlenecks | Multiple | - Profile batch operations - Identify slow operations- Optimize export settings - Reduce template complexity |
### Debugging Strategies
Effective troubleshooting techniques for batch processing in web apps include:
- Retry with small batches.
- Console log detailed error information.
- Isolate problematic items.
## Next Steps
- [Headless Mode](https://img.ly/docs/cesdk/sveltekit/concepts/headless-mode/browser-24ab98/) - Learn headless engine operation basics
- [Design Generation](https://img.ly/docs/cesdk/sveltekit/automation/design-generation-98a99e/) - Automate single design generation workflows
- [Export Designs](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/overview-9ed3a8/) - Deep dive into export options and formats
- [Text Variables](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/) - Work with dynamic text content in templates
- [Source Sets](https://img.ly/docs/cesdk/sveltekit/import-media/source-sets-5679c8/) - Specify assets sources for each block.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Data Merge"
description: "Generate personalized designs from templates by merging external data using text variables and placeholder blocks"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/automation/data-merge-ae087c/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/sveltekit/automation-715209/) > [Data Merge](https://img.ly/docs/cesdk/sveltekit/automation/data-merge-ae087c/)
---
Generate personalized designs from a single template by merging external data into CE.SDK templates using text variables and placeholder blocks.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-automation-data-merge-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-automation-data-merge-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-automation-data-merge-browser/)
Data merge generates multiple personalized designs from a single template by replacing variable content with external data. Use it for certificates, badges, team cards, or any design requiring consistent layout with varying content.
```typescript file=@cesdk_web_examples/guides-automation-data-merge-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Data Merge Guide
*
* Demonstrates merging external data into templates:
* - Setting text variables with engine.variable.setString()
* - Finding variables with engine.variable.findAll()
* - Finding blocks by name with engine.block.findByName()
* - Updating image content in placeholder blocks
* - Exporting personalized designs
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 400, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
// Sample data to merge into the template
const sampleData = {
name: 'Alex Smith',
title: 'Creative Developer',
email: 'alex.smith@example.com',
photoUrl: 'https://img.ly/static/ubq_samples/sample_1.jpg'
};
// Create a profile photo block with a semantic name
const photoBlock = engine.block.create('graphic');
engine.block.setShape(photoBlock, engine.block.createShape('rect'));
const photoFill = engine.block.createFill('image');
engine.block.setString(
photoFill,
'fill/image/imageFileURI',
sampleData.photoUrl
);
engine.block.setFill(photoBlock, photoFill);
engine.block.setWidth(photoBlock, 150);
engine.block.setHeight(photoBlock, 150);
engine.block.setPositionX(photoBlock, 50);
engine.block.setPositionY(photoBlock, 125);
engine.block.setName(photoBlock, 'profile-photo');
engine.block.appendChild(page, photoBlock);
// Create a text block with variable placeholders
const textBlock = engine.block.create('text');
const textContent = `{{name}}
{{title}}
{{email}}`;
engine.block.replaceText(textBlock, textContent);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setFloat(textBlock, 'text/fontSize', 32);
engine.block.setPositionX(textBlock, 230);
engine.block.setPositionY(textBlock, 140);
engine.block.appendChild(page, textBlock);
// Set the variable values from data
engine.variable.setString('name', sampleData.name);
engine.variable.setString('title', sampleData.title);
engine.variable.setString('email', sampleData.email);
// Discover all variables in the scene
const variables = engine.variable.findAll();
console.log('Variables in scene:', variables);
// Check if the text block references any variables
const hasVariables = engine.block.referencesAnyVariables(textBlock);
console.log('Text block has variables:', hasVariables);
// Find blocks by their semantic name
const [foundPhotoBlock] = engine.block.findByName('profile-photo');
if (foundPhotoBlock) {
console.log('Found profile-photo block:', foundPhotoBlock);
// Update the image content
const fill = engine.block.getFill(foundPhotoBlock);
engine.block.setString(
fill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_2.jpg'
);
}
// Export the personalized design
const blob = await engine.block.export(page, { mimeType: 'image/png' });
console.log('Exported PNG blob:', blob.size, 'bytes');
// Create a download link for the exported image
const url = URL.createObjectURL(blob);
console.log('Download URL created:', url);
// Select the text block to show the variable values
engine.block.select(textBlock);
console.log(
'Data merge guide initialized. Try changing variable values in the console.'
);
}
}
export default Example;
```
This guide covers how to prepare templates with variables, set values from data, and export personalized designs.
## Initialize the Editor
We start by initializing CE.SDK with a Design scene and setting up the page dimensions for our template.
```typescript highlight=highlight-setup
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 400, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
```
## Prepare Sample Data
In a real application, data comes from a CSV file, database, or API. Here we define a sample record with the fields we want to merge into the template.
```typescript highlight=highlight-sample-data
// Sample data to merge into the template
const sampleData = {
name: 'Alex Smith',
title: 'Creative Developer',
email: 'alex.smith@example.com',
photoUrl: 'https://img.ly/static/ubq_samples/sample_1.jpg'
};
```
Each data record contains field names that map to template variables and placeholder blocks.
## Create Template Layout
We build the template by creating blocks and assigning semantic names. The profile photo block uses `setName()` so we can find and update it later.
```typescript highlight=highlight-create-template
// Create a profile photo block with a semantic name
const photoBlock = engine.block.create('graphic');
engine.block.setShape(photoBlock, engine.block.createShape('rect'));
const photoFill = engine.block.createFill('image');
engine.block.setString(
photoFill,
'fill/image/imageFileURI',
sampleData.photoUrl
);
engine.block.setFill(photoBlock, photoFill);
engine.block.setWidth(photoBlock, 150);
engine.block.setHeight(photoBlock, 150);
engine.block.setPositionX(photoBlock, 50);
engine.block.setPositionY(photoBlock, 125);
engine.block.setName(photoBlock, 'profile-photo');
engine.block.appendChild(page, photoBlock);
```
Using semantic names like `profile-photo` makes it easy to locate and modify blocks when processing different data records.
## Add Text with Variables
Text variables use double curly brace syntax: `{{variableName}}`. We create a text block with variable placeholders for name, title, and email.
```typescript highlight=highlight-create-text-with-variables
// Create a text block with variable placeholders
const textBlock = engine.block.create('text');
const textContent = `{{name}}
{{title}}
{{email}}`;
engine.block.replaceText(textBlock, textContent);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setFloat(textBlock, 'text/fontSize', 32);
engine.block.setPositionX(textBlock, 230);
engine.block.setPositionY(textBlock, 140);
engine.block.appendChild(page, textBlock);
```
Variables in text blocks automatically display their values when set through the Variable API.
## Set Variable Values
We use `engine.variable.setString()` to define the value for each variable. When a variable is set, all text blocks referencing that variable update automatically.
```typescript highlight=highlight-set-variables
// Set the variable values from data
engine.variable.setString('name', sampleData.name);
engine.variable.setString('title', sampleData.title);
engine.variable.setString('email', sampleData.email);
```
Variable values persist throughout the engine session. Setting a variable to a new value updates all references immediately.
## Discover Variables
Use `engine.variable.findAll()` to discover which variables exist in the scene. Use `engine.block.referencesAnyVariables()` to check if a specific block contains variable references.
```typescript highlight=highlight-discover-variables
// Discover all variables in the scene
const variables = engine.variable.findAll();
console.log('Variables in scene:', variables);
// Check if the text block references any variables
const hasVariables = engine.block.referencesAnyVariables(textBlock);
console.log('Text block has variables:', hasVariables);
```
This is useful when loading existing templates to determine which data fields are required.
## Find and Update Placeholder Blocks
Use `engine.block.findByName()` to locate blocks by their semantic name. Once found, you can update properties like image content by modifying the fill URI.
```typescript highlight=highlight-find-by-name
// Find blocks by their semantic name
const [foundPhotoBlock] = engine.block.findByName('profile-photo');
if (foundPhotoBlock) {
console.log('Found profile-photo block:', foundPhotoBlock);
// Update the image content
const fill = engine.block.getFill(foundPhotoBlock);
engine.block.setString(
fill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_2.jpg'
);
}
```
This pattern works well for updating profile photos, logos, or other image placeholders in templates.
## Export the Design
After merging data into the template, export the personalized design using `engine.block.export()`.
```typescript highlight=highlight-export
// Export the personalized design
const blob = await engine.block.export(page, { mimeType: 'image/png' });
console.log('Exported PNG blob:', blob.size, 'bytes');
// Create a download link for the exported image
const url = URL.createObjectURL(blob);
console.log('Download URL created:', url);
```
You can export to PNG, JPEG, WebP, or PDF formats. For batch processing, collect blobs in an array or write directly to a file system.
## Troubleshooting
### Variables Not Rendering
If variable placeholders show instead of values:
- Verify the variable name matches exactly (case-sensitive)
- Use `engine.variable.findAll()` to check which variables are defined
- Ensure `engine.variable.setString()` was called before rendering
### Block Not Found
If `findByName()` returns an empty array:
- Check the block name was set with `engine.block.setName()`
- Verify the name string matches exactly (case-sensitive)
- Ensure the block exists in the current scene
### Image Not Updating
If placeholder images don't update:
- Get the fill block first with `engine.block.getFill()`
- Use the correct property path: `fill/image/imageFileURI`
- Verify the image URL is accessible and valid
## API Reference
| Method | Description |
|--------|-------------|
| `engine.variable.setString(name, value)` | Set a text variable's value |
| `engine.variable.getString(name)` | Get a text variable's value |
| `engine.variable.findAll()` | List all variable names in the scene |
| `engine.variable.remove(name)` | Remove a variable |
| `engine.block.findByName(name)` | Find blocks by their semantic name |
| `engine.block.setName(block, name)` | Set a block's semantic name |
| `engine.block.replaceText(block, text)` | Replace text content in a text block |
| `engine.block.referencesAnyVariables(block)` | Check if block contains variable references |
| `engine.block.getFill(block)` | Get the fill block of a design block |
| `engine.block.setString(block, property, value)` | Set a string property value |
| `engine.block.export(block, options)` | Export a block to an image format |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Automate Design Generation"
description: "Generate on-brand designs programmatically using templates, variables, and CE.SDK’s headless API."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/automation/design-generation-98a99e/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/sveltekit/automation-715209/) > [Design Generation](https://img.ly/docs/cesdk/sveltekit/automation/design-generation-98a99e/)
---
Automating design generation simplifies workflows and allows you to create
dynamic, personalized designs at scale. By combining design templates with
external data or user-provided input, you can quickly generate professional
outputs for various use cases, from banner ads to direct mail.
With IMG.LY, you can use templates to define dynamic elements such as text, images, or other assets. These elements are populated with real-time data or user inputs during the generation process. This guide will walk you through the process of using the CE.SDK for programmatic design generation.
[Launch Web Demo](https://img.ly/showcases/cesdk/headless-design/web)
## Populating a Template
A design template is a pre-configured layout that includes placeholders for dynamic elements such as text, images, or other assets. These placeholders define where and how specific content will appear in the final design. During the generation process, the placeholders are replaced with actual data to create a completed output.
- **Creating or Editing Templates:** Design templates can be created or edited directly within the CE.SDK using our UI or programmatically. Learn more in the [Create Templates guide](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/).
- **Dynamic Content Sources:** Templates can be populated with data from various sources, such as:
- **JSON files:** Useful for batch operations where data is pre-prepared.
- **External APIs:** Ideal for real-time updates and dynamic integrations.
- **User Input:** Data provided directly by the user through a UI.
For detailed information on using and managing templates, see [Use Templates](https://img.ly/docs/cesdk/sveltekit/use-templates/overview-ae74e1/).
Below is a diagram illustrating how data is merged into a template to produce a final design:

## Example Workflow
### 1. Prepare the Template
Start by designing a template with text variables. Here's an example postcard template with placeholders for the recipient's details:

### 2. Load the Template into the Editor
Initialize the CE.SDK and load your prepared template:
```ts example=basic-scene marker=cesdk-init-after
// Load a template from your server or a CDN
const sceneUrl =
'https://cdn.img.ly/assets/demo/v4/ly.img.template/templates/cesdk_postcard_2.scene';
await engine.scene.loadFromURL(sceneUrl);
```
### 3. Provide Data to Populate the Template
Populate your template with data from your chosen source:
```ts example=basic-scene marker=cesdk-init-after
// Option 1: Prepare your data as a javascript object
const data = {
textVariables: {
first_name: 'John',
last_name: 'Doe',
address: '123 Main St.',
city: 'Anytown',
},
};
// Option 2: Fetch from an API
// const data = await fetch('https://api.example.com/design-data').then(res => res.json());
engine.variable.setString('first_name', data.textVariables.first_name);
engine.variable.setString('last_name', data.textVariables.last_name);
engine.variable.setString('address', data.textVariables.address);
engine.variable.setString('city', data.textVariables.city);
```
### 4. Export the Final Design
Once the template is populated, export the final design in your preferred format:
```ts example=basic-scene marker=cesdk-init-after
const output = await engine.block.export(engine.scene.get(), {
mimeType: 'application/pdf',
});
// Success: 'output' contains your generated design as a PDF Blob
// You can now save it or display it in your application
```
Here's what your final output should look like:

Need help with exports? Check out the [Export Guide](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export-82f968/) for detailed instructions and options.
## Troubleshooting
If you encounter issues during the generation process:
- Verify that all your variable names exactly match those in your template
- Ensure your template is accessible from the provided URL
- Check that your data values are in the correct format (strings for text variables)
- Monitor the console for detailed error messages from the CE.SDK
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/automation/overview-34d971/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/sveltekit/automation-715209/) > [Overview](https://img.ly/docs/cesdk/sveltekit/automation/overview-34d971/)
---
### Output Formats
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Browser Support"
description: "Find out which browsers and versions fully support CE.SDK features, including editing and video capabilities."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/browser-support-28c1b0/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/sveltekit/compatibility-fef719/) > [Browser Support](https://img.ly/docs/cesdk/sveltekit/browser-support-28c1b0/)
---
The CreativeEditor SDK requires specific APIs to fully function.
For video-related features, the required APIs are only supported in certain browsers.
As a result, the list of supported browsers is currently limited to the following:
| Supported Browser | Graphics Editing | Video Editing | Video Export |
| ----------------- | --------------------------------------------- | ----------------- | ----------------- |
| Chrome | **114** or newer | **114** or newer | **114** or newer |
| Chrome Android | **114** or newer | not supported | not supported |
| Chrome iOS | **114** or newer (on iOS/iPadOS 15 or newer) | not supported | not supported |
| Edge | **114** or newer | **114** or newer | **114** or newer |
| Firefox | **115** or newer | **130** or newer | not supported |
| Safari | **15.6** or newer | **26.0** or newer | **26.0** or newer |
| Safari iOS | **15.6** or newer (on iOS/iPadOS 15 or newer) | not supported | not supported |
**Note:** Firefox supports video editing (decoding) starting with version 130 via the WebCodecs API. However, video export (encoding) is not supported because Firefox does not include the patent-encumbered H.264 and AAC codecs required for video encoding.
For video features, CE.SDK automatically shows warning dialogs when unsupported browsers try to use video functionality. You can also detect video support programmatically using the `video.decode.checkSupport` and `video.encode.checkSupport` actions, or the silent `cesdk.utils.supportsVideoDecode()` and `cesdk.utils.supportsVideoEncode()` utilities. See the [Actions API](https://img.ly/docs/cesdk/sveltekit/actions-6ch24x/) for implementation details.
While other browsers based on the Chromium project might work fine (Arc, Brave, Opera, Vivaldi etc.) they are not officially supported.
## Host Platform Restrictions
All supported browsers rely on the host's platform APIs for different kind of functionality (e.g. video support). Check our [known editor limitations](https://img.ly/docs/cesdk/sveltekit/compatibility-139ef9/) for more details on these.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Colors"
description: "Manage color usage in your designs, from applying brand palettes to handling print and screen formats."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/sveltekit/colors/overview-16a177/) - Manage color usage in your designs, from applying brand palettes to handling print and screen formats.
- [Color Basics](https://img.ly/docs/cesdk/sveltekit/colors/basics-307115/) - Learn how color works in CE.SDK, including the three supported color spaces (sRGB, CMYK, and Spot) and when to use each for screen display or print workflows.
- [For Print](https://img.ly/docs/cesdk/sveltekit/colors/for-print-59bc05/) - Use print-ready color models and settings for professional-quality, production-ready exports.
- [For Screen](https://img.ly/docs/cesdk/sveltekit/colors/for-screen-1911f8/) - Documentation for For Screen
- [Apply Colors](https://img.ly/docs/cesdk/sveltekit/colors/apply-2211e3/) - Apply solid colors to shapes, backgrounds, and other design elements.
- [Create a Color Palette](https://img.ly/docs/cesdk/sveltekit/colors/create-color-palette-7012e0/) - Build reusable color palettes to maintain consistency and streamline user choices.
- [Replace Individual Colors](https://img.ly/docs/cesdk/sveltekit/colors/replace-48cd71/) - Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects to swap colors or remove backgrounds.
- [Adjust Colors](https://img.ly/docs/cesdk/sveltekit/colors/adjust-590d1e/) - Fine-tune images and design elements by adjusting brightness, contrast, saturation, exposure, and other color properties using CE.SDK's adjustments effect system.
- [Color Conversion](https://img.ly/docs/cesdk/sveltekit/colors/conversion-bcd82b/) - Learn how to convert colors between color spaces in CE.SDK. Convert sRGB, CMYK, and spot colors programmatically for screen display or print workflows.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Adjust Colors"
description: "Fine-tune images and design elements by adjusting brightness, contrast, saturation, exposure, and other color properties using CE.SDK's adjustments effect system."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/adjust-590d1e/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [Adjust Colors](https://img.ly/docs/cesdk/sveltekit/colors/adjust-590d1e/)
---
Fine-tune images and design elements using CE.SDK's color adjustments system to control brightness, contrast, saturation, and other visual properties.

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-adjust-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-adjust-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-adjust-browser/)
Color adjustments allow you to modify the visual appearance of images and graphics by changing properties like brightness, contrast, saturation, and color temperature. CE.SDK implements color adjustments as an "adjustments" effect type that you can apply to compatible blocks.
```typescript file=@cesdk_web_examples/guides-colors-adjust-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Adjust Colors Guide
*
* Demonstrates how to adjust color properties of images and design elements:
* - Creating adjustments effects
* - Setting brightness, contrast, saturation, and other properties
* - Enabling/disabling adjustments
* - Reading adjustment values
* - Applying different adjustment styles
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
// Enable adjustments in the inspector panel
cesdk.feature.enable('ly.img.adjustment');
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
// Create a sample image to demonstrate color adjustments
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Check if a block supports effects before applying adjustments
const imageBlock = await engine.block.addImage(imageUri, {
size: { width: 400, height: 300 }
});
engine.block.appendChild(page, imageBlock);
engine.block.setPositionX(imageBlock, 200);
engine.block.setPositionY(imageBlock, 150);
const supportsEffects = engine.block.supportsEffects(imageBlock);
console.log('Block supports effects:', supportsEffects);
// Create an adjustments effect
const adjustmentsEffect = engine.block.createEffect('adjustments');
// Attach the adjustments effect to the image block
engine.block.appendEffect(imageBlock, adjustmentsEffect);
// Set brightness - positive values lighten, negative values darken
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/brightness',
0.4
);
// Set contrast - increases or decreases tonal range
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/contrast',
0.35
);
// Set saturation - increases or decreases color intensity
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/saturation',
0.5
);
// Set temperature - positive for warmer, negative for cooler tones
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/temperature',
0.25
);
// Read current adjustment values
const brightness = engine.block.getFloat(
adjustmentsEffect,
'effect/adjustments/brightness'
);
console.log('Current brightness:', brightness);
// Discover all available adjustment properties
const allProperties = engine.block.findAllProperties(adjustmentsEffect);
console.log('Available adjustment properties:', allProperties);
// Disable adjustments temporarily (effect remains attached)
engine.block.setEffectEnabled(adjustmentsEffect, false);
console.log(
'Adjustments enabled:',
engine.block.isEffectEnabled(adjustmentsEffect)
);
// Re-enable adjustments
engine.block.setEffectEnabled(adjustmentsEffect, true);
// Create a second image to demonstrate a different adjustment style
const secondImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
engine.block.appendChild(page, secondImageBlock);
engine.block.setPositionX(secondImageBlock, 50);
engine.block.setPositionY(secondImageBlock, 50);
// Apply a contrasting style: darker, high contrast, desaturated (moody look)
const combinedAdjustments = engine.block.createEffect('adjustments');
engine.block.appendEffect(secondImageBlock, combinedAdjustments);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/brightness',
-0.15
);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/contrast',
0.4
);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/saturation',
-0.3
);
// List all effects on the block
const effects = engine.block.getEffects(secondImageBlock);
console.log('Effects on second image:', effects.length);
// Demonstrate removing an effect
const tempBlock = await engine.block.addImage(imageUri, {
size: { width: 150, height: 100 }
});
engine.block.appendChild(page, tempBlock);
engine.block.setPositionX(tempBlock, 550);
engine.block.setPositionY(tempBlock, 50);
const tempEffect = engine.block.createEffect('adjustments');
engine.block.appendEffect(tempBlock, tempEffect);
engine.block.setFloat(tempEffect, 'effect/adjustments/brightness', 0.5);
// Remove the effect by index
const tempEffects = engine.block.getEffects(tempBlock);
const effectIndex = tempEffects.indexOf(tempEffect);
if (effectIndex !== -1) {
engine.block.removeEffect(tempBlock, effectIndex);
}
// Destroy the removed effect to free memory
engine.block.destroy(tempEffect);
// Add refinement adjustments to demonstrate subtle enhancement properties
const refinementEffect = engine.block.createEffect('adjustments');
engine.block.appendEffect(tempBlock, refinementEffect);
// Sharpness - enhances edge definition
engine.block.setFloat(
refinementEffect,
'effect/adjustments/sharpness',
0.4
);
// Clarity - increases mid-tone contrast for more detail
engine.block.setFloat(refinementEffect, 'effect/adjustments/clarity', 0.35);
// Highlights - adjusts bright areas
engine.block.setFloat(
refinementEffect,
'effect/adjustments/highlights',
-0.2
);
// Shadows - adjusts dark areas
engine.block.setFloat(refinementEffect, 'effect/adjustments/shadows', 0.3);
// Select the main image block to show adjustments panel
engine.block.select(imageBlock);
console.log(
'Color adjustments guide initialized. Select an image to see the adjustments panel.'
);
}
}
export default Example;
```
This guide covers how to use the built-in adjustments UI panel and how to apply color adjustments programmatically using the block API.
## Using the Built-in Adjustments UI
CE.SDK provides a built-in adjustments panel that allows users to modify color properties interactively. Users can access this panel by selecting an image or graphic block in the editor.
### Enable Adjustments Features
To give users access to adjustments in the inspector panel, we enable the adjustments feature using CE.SDK's Feature API.
```typescript highlight=highlight-feature-enable
// Enable adjustments in the inspector panel
cesdk.feature.enable('ly.img.adjustment');
```
With adjustments enabled, users can:
- **Adjust sliders** for brightness, contrast, saturation, exposure, and more
- **See real-time preview** of changes as they adjust values
- **Reset adjustments** individually or all at once to restore defaults
## Programmatic Color Adjustments
For applications that need to apply adjustments programmatically—whether for automation, batch processing, or dynamic user experiences—we use the block API.
### Check Block Compatibility
Before applying adjustments, we verify the block supports effects. Not all block types support adjustments—for example, page blocks don't support effects directly, but image and graphic blocks do.
```typescript highlight=highlight-check-support
// Check if a block supports effects before applying adjustments
const imageBlock = await engine.block.addImage(imageUri, {
size: { width: 400, height: 300 }
});
engine.block.appendChild(page, imageBlock);
engine.block.setPositionX(imageBlock, 200);
engine.block.setPositionY(imageBlock, 150);
const supportsEffects = engine.block.supportsEffects(imageBlock);
console.log('Block supports effects:', supportsEffects);
```
### Create and Apply Adjustments Effect
Once we've confirmed a block supports effects, we create an adjustments effect and attach it to the block using `appendEffect()`.
```typescript highlight=highlight-create-adjustments
// Create an adjustments effect
const adjustmentsEffect = engine.block.createEffect('adjustments');
// Attach the adjustments effect to the image block
engine.block.appendEffect(imageBlock, adjustmentsEffect);
```
Each block can have one adjustments effect in its effect stack. The adjustments effect provides access to all color adjustment properties through a single effect instance.
### Modify Adjustment Properties
We set individual adjustment values using `setFloat()` with the effect block ID and property path. Each property uses the `effect/adjustments/` prefix followed by the property name.
```typescript highlight=highlight-set-properties
// Set brightness - positive values lighten, negative values darken
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/brightness',
0.4
);
// Set contrast - increases or decreases tonal range
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/contrast',
0.35
);
// Set saturation - increases or decreases color intensity
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/saturation',
0.5
);
// Set temperature - positive for warmer, negative for cooler tones
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/temperature',
0.25
);
```
CE.SDK provides the following adjustment properties:
| Property | Description |
|----------|-------------|
| `brightness` | Overall lightness—positive values lighten, negative values darken |
| `contrast` | Tonal range—increases or decreases the difference between light and dark |
| `saturation` | Color intensity—positive values increase vibrancy, negative values desaturate |
| `exposure` | Exposure compensation—simulates camera exposure adjustments |
| `gamma` | Gamma curve—adjusts midtone brightness |
| `highlights` | Bright area intensity—controls the lightest parts of the image |
| `shadows` | Dark area intensity—controls the darkest parts of the image |
| `whites` | White point—adjusts the brightest pixels |
| `blacks` | Black point—adjusts the darkest pixels |
| `temperature` | Warm/cool color cast—positive for warmer, negative for cooler tones |
| `sharpness` | Edge sharpness—enhances or softens edges |
| `clarity` | Midtone contrast—increases local contrast for more definition |
All properties accept float values. Experiment with different values to achieve the desired visual result.
### Read Adjustment Values
We can read current adjustment values using `getFloat()` with the same property paths. Use `findAllProperties()` to discover all available properties on an adjustments effect.
```typescript highlight=highlight-read-values
// Read current adjustment values
const brightness = engine.block.getFloat(
adjustmentsEffect,
'effect/adjustments/brightness'
);
console.log('Current brightness:', brightness);
// Discover all available adjustment properties
const allProperties = engine.block.findAllProperties(adjustmentsEffect);
console.log('Available adjustment properties:', allProperties);
```
This is useful for building custom UI controls or syncing adjustment values across your application.
### Enable and Disable Adjustments
CE.SDK allows you to temporarily toggle adjustments on and off without removing them from the block. This is useful for before/after comparisons.
```typescript highlight=highlight-enable-disable
// Disable adjustments temporarily (effect remains attached)
engine.block.setEffectEnabled(adjustmentsEffect, false);
console.log(
'Adjustments enabled:',
engine.block.isEffectEnabled(adjustmentsEffect)
);
// Re-enable adjustments
engine.block.setEffectEnabled(adjustmentsEffect, true);
```
When you disable an adjustments effect, it remains attached to the block but won't be rendered until you enable it again. This preserves all adjustment values while giving you control over when adjustments are applied.
## Applying Different Adjustment Styles
You can apply different adjustment combinations to create distinct visual styles. This example demonstrates a contrasting moody look using negative brightness, high contrast, and desaturation.
```typescript highlight=highlight-combine-effects
// Create a second image to demonstrate a different adjustment style
const secondImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
engine.block.appendChild(page, secondImageBlock);
engine.block.setPositionX(secondImageBlock, 50);
engine.block.setPositionY(secondImageBlock, 50);
// Apply a contrasting style: darker, high contrast, desaturated (moody look)
const combinedAdjustments = engine.block.createEffect('adjustments');
engine.block.appendEffect(secondImageBlock, combinedAdjustments);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/brightness',
-0.15
);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/contrast',
0.4
);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/saturation',
-0.3
);
// List all effects on the block
const effects = engine.block.getEffects(secondImageBlock);
console.log('Effects on second image:', effects.length);
```
By combining different adjustment properties, you can create warm and vibrant looks, cool and desaturated styles, or high-contrast dramatic effects.
## Refinement Adjustments
Beyond basic color corrections, CE.SDK provides refinement adjustments for fine-tuning image detail and tonal balance.
```typescript highlight=highlight-refinement-adjustments
// Add refinement adjustments to demonstrate subtle enhancement properties
const refinementEffect = engine.block.createEffect('adjustments');
engine.block.appendEffect(tempBlock, refinementEffect);
// Sharpness - enhances edge definition
engine.block.setFloat(
refinementEffect,
'effect/adjustments/sharpness',
0.4
);
// Clarity - increases mid-tone contrast for more detail
engine.block.setFloat(refinementEffect, 'effect/adjustments/clarity', 0.35);
// Highlights - adjusts bright areas
engine.block.setFloat(
refinementEffect,
'effect/adjustments/highlights',
-0.2
);
// Shadows - adjusts dark areas
engine.block.setFloat(refinementEffect, 'effect/adjustments/shadows', 0.3);
```
Refinement properties include:
- **Sharpness** - Enhances edge definition for crisper details
- **Clarity** - Increases mid-tone contrast for more depth and definition
- **Highlights** - Controls the intensity of bright areas
- **Shadows** - Controls the intensity of dark areas
These adjustments are particularly useful for enhancing photos or preparing images for print.
## Managing Adjustments
### Remove Adjustments
When you no longer need adjustments, you can remove them from the effect stack and free resources. Always destroy effects that are no longer in use to prevent memory leaks.
```typescript highlight=highlight-remove-adjustments
// Demonstrate removing an effect
const tempBlock = await engine.block.addImage(imageUri, {
size: { width: 150, height: 100 }
});
engine.block.appendChild(page, tempBlock);
engine.block.setPositionX(tempBlock, 550);
engine.block.setPositionY(tempBlock, 50);
const tempEffect = engine.block.createEffect('adjustments');
engine.block.appendEffect(tempBlock, tempEffect);
engine.block.setFloat(tempEffect, 'effect/adjustments/brightness', 0.5);
// Remove the effect by index
const tempEffects = engine.block.getEffects(tempBlock);
const effectIndex = tempEffects.indexOf(tempEffect);
if (effectIndex !== -1) {
engine.block.removeEffect(tempBlock, effectIndex);
}
// Destroy the removed effect to free memory
engine.block.destroy(tempEffect);
```
The `removeEffect()` method takes an index position. After removal, destroy the effect instance to ensure proper cleanup.
### Reset Adjustments
To reset all adjustments to their default values, you can either:
- Set each property to `0.0` individually using `setFloat()`
- Remove the adjustments effect and create a new one
For most cases, setting properties to `0.0` is more efficient than recreating the effect.
## Troubleshooting
### Adjustments Not Visible
If adjustments don't appear after applying them:
- Verify the block supports effects using `supportsEffects()`
- Check that the effect is enabled with `isEffectEnabled()`
- Ensure the adjustments effect was appended to the block, not just created
- Confirm adjustment values are non-zero
### Unexpected Results
If adjustments produce unexpected visual results:
- Check the effect stack order—adjustments applied before or after other effects may produce different results
- Verify property paths include the `effect/adjustments/` prefix
- Use `findAllProperties()` to verify correct property names
### Property Not Found
If you encounter property not found errors:
- Use `findAllProperties()` to list all available properties
- Ensure property paths use the correct `effect/adjustments/` prefix format
## API Reference
| Method | Description |
|--------|-------------|
| `block.supportsEffects(block)` | Check if a block supports effects |
| `block.createEffect('adjustments')` | Create an adjustments effect |
| `block.appendEffect(block, effect)` | Add effect to the end of the effect stack |
| `block.insertEffect(block, effect, index)` | Insert effect at a specific position |
| `block.getEffects(block)` | Get all effects applied to a block |
| `block.removeEffect(block, index)` | Remove effect at the specified index |
| `block.setEffectEnabled(effect, enabled)` | Enable or disable an effect |
| `block.isEffectEnabled(effect)` | Check if an effect is enabled |
| `block.setFloat(effect, property, value)` | Set a float property value |
| `block.getFloat(effect, property)` | Get a float property value |
| `block.findAllProperties(effect)` | List all properties of an effect |
| `block.destroy(effect)` | Destroy an effect and free resources |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Apply Colors"
description: "Apply solid colors to shapes, backgrounds, and other design elements."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/apply-2211e3/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [Apply Color](https://img.ly/docs/cesdk/sveltekit/colors/apply-2211e3/)
---
Apply solid colors to design elements like shapes, text, and backgrounds using CE.SDK's color system with support for RGB, CMYK, and spot colors.

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-apply-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-apply-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-apply-browser/)
Colors in CE.SDK are applied to block properties like fill, stroke, and shadow using `engine.block.setColor()`. The engine supports three color spaces: sRGB for screen display, CMYK for print production, and spot colors for specialized printing requirements.
```typescript file=@cesdk_web_examples/guides-colors-apply-browser/browser.ts reference-only
import type {
EditorPlugin,
EditorPluginContext,
RGBAColor
} from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Apply Colors Guide
*
* Demonstrates how to apply solid colors to design elements:
* - Creating color objects in RGB, CMYK, and spot color spaces
* - Applying colors to fill, stroke, and shadow properties
* - Defining and managing spot colors
* - Converting colors between color spaces
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
// Create a graphic block to apply colors to
const block = engine.block.create('graphic');
engine.block.setShape(block, engine.block.createShape('rect'));
engine.block.setFill(block, engine.block.createFill('color'));
engine.block.setWidth(block, 200);
engine.block.setHeight(block, 150);
engine.block.setPositionX(block, 100);
engine.block.setPositionY(block, 100);
engine.block.appendChild(page, block);
// Create RGB color (values 0.0-1.0)
const rgbaBlue: RGBAColor = { r: 0.0, g: 0.0, b: 1.0, a: 1.0 };
// Create CMYK color (cyan, magenta, yellow, black, tint)
const cmykRed = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 };
// Create spot color reference
const spotPink = {
name: 'Pink-Flamingo',
tint: 1.0,
externalReference: 'Pantone'
};
// Define spot colors with screen preview approximations
engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.41, 0.71);
engine.editor.setSpotColorCMYK('Corporate-Blue', 1.0, 0.5, 0.0, 0.2);
// Apply RGB color to fill
const fill = engine.block.getFill(block);
engine.block.setColor(fill, 'fill/color/value', rgbaBlue);
// Read the current fill color
const currentFillColor = engine.block.getColor(fill, 'fill/color/value');
console.log('Current fill color:', currentFillColor);
// Enable and apply stroke color
engine.block.setStrokeEnabled(block, true);
engine.block.setStrokeWidth(block, 4);
engine.block.setColor(block, 'stroke/color', cmykRed);
// Enable and apply drop shadow color
engine.block.setDropShadowEnabled(block, true);
engine.block.setDropShadowOffsetX(block, 5);
engine.block.setDropShadowOffsetY(block, 5);
engine.block.setColor(block, 'dropShadow/color', spotPink);
// Convert colors between color spaces
const cmykFromRgb = engine.editor.convertColorToColorSpace(
rgbaBlue,
'CMYK'
);
console.log('CMYK from RGB:', cmykFromRgb);
const rgbFromCmyk = engine.editor.convertColorToColorSpace(cmykRed, 'sRGB');
console.log('RGB from CMYK:', rgbFromCmyk);
// List all defined spot colors
const allSpotColors = engine.editor.findAllSpotColors();
console.log('Defined spot colors:', allSpotColors);
// Update a spot color definition
engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.6, 0.8);
console.log('Updated Pink-Flamingo spot color');
// Remove a spot color definition (falls back to magenta)
engine.editor.removeSpotColor('Corporate-Blue');
console.log('Removed Corporate-Blue spot color');
// Select the block to show in the editor
engine.block.select(block);
console.log('Apply colors guide initialized.');
}
}
export default Example;
```
This guide covers how to create color objects in different color spaces, apply colors to fill, stroke, and shadow properties, work with spot colors including defining and managing them, and convert colors between color spaces.
## Create Color Objects
CE.SDK represents colors as JavaScript objects with properties specific to each color space. We create color objects that match our target output—RGB for screens, CMYK for print, or spot colors for precise color matching.
```typescript highlight=highlight-create-colors
// Create RGB color (values 0.0-1.0)
const rgbaBlue: RGBAColor = { r: 0.0, g: 0.0, b: 1.0, a: 1.0 };
// Create CMYK color (cyan, magenta, yellow, black, tint)
const cmykRed = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 };
// Create spot color reference
const spotPink = {
name: 'Pink-Flamingo',
tint: 1.0,
externalReference: 'Pantone'
};
```
RGB colors use `{ r, g, b, a }` with values from 0.0 to 1.0 for each channel, where `a` is alpha (opacity). CMYK colors use `{ c, m, y, k, tint }` where tint controls the overall intensity. Spot colors use `{ name, tint, externalReference }` to reference a defined spot color by name.
## Define Spot Colors
Before applying a spot color, we must define its screen preview approximation. The engine needs to know how to display the color since spot colors represent inks that can't be directly rendered on screens.
```typescript highlight=highlight-define-spot
// Define spot colors with screen preview approximations
engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.41, 0.71);
engine.editor.setSpotColorCMYK('Corporate-Blue', 1.0, 0.5, 0.0, 0.2);
```
Use `engine.editor.setSpotColorRGB()` to define the RGB approximation with red, green, and blue values from 0.0 to 1.0. Use `engine.editor.setSpotColorCMYK()` for the CMYK approximation with cyan, magenta, yellow, black, and tint values. A spot color can have both RGB and CMYK approximations defined.
## Apply Fill Colors
To set a block's fill color, we first get the fill block using `engine.block.getFill()`, then apply the color using `engine.block.setColor()` with the `'fill/color/value'` property.
```typescript highlight=highlight-apply-fill
// Apply RGB color to fill
const fill = engine.block.getFill(block);
engine.block.setColor(fill, 'fill/color/value', rgbaBlue);
// Read the current fill color
const currentFillColor = engine.block.getColor(fill, 'fill/color/value');
console.log('Current fill color:', currentFillColor);
```
The fill block is a separate entity from the design block. We can read the current color using `engine.block.getColor()` with the same property path.
## Apply Stroke Colors
Stroke colors are applied directly to the design block using the `'stroke/color'` property. We enable the stroke first using `engine.block.setStrokeEnabled()`.
```typescript highlight=highlight-apply-stroke
// Enable and apply stroke color
engine.block.setStrokeEnabled(block, true);
engine.block.setStrokeWidth(block, 4);
engine.block.setColor(block, 'stroke/color', cmykRed);
```
The stroke renders around the edges of the block with the specified color. Set the stroke width using `engine.block.setStrokeWidth()` to control the line thickness.
## Apply Shadow Colors
Drop shadow colors use the `'dropShadow/color'` property on the design block. Enable shadows first using `engine.block.setDropShadowEnabled()`.
```typescript highlight=highlight-apply-shadow
// Enable and apply drop shadow color
engine.block.setDropShadowEnabled(block, true);
engine.block.setDropShadowOffsetX(block, 5);
engine.block.setDropShadowOffsetY(block, 5);
engine.block.setColor(block, 'dropShadow/color', spotPink);
```
Control the shadow position using `setDropShadowOffsetX()` and `setDropShadowOffsetY()`. Spot colors work with shadows just like RGB or CMYK colors.
## Convert Between Color Spaces
Use `engine.editor.convertColorToColorSpace()` to convert any color to a different color space. This is useful when you need to output designs in a specific color format.
```typescript highlight=highlight-convert-color
// Convert colors between color spaces
const cmykFromRgb = engine.editor.convertColorToColorSpace(
rgbaBlue,
'CMYK'
);
console.log('CMYK from RGB:', cmykFromRgb);
const rgbFromCmyk = engine.editor.convertColorToColorSpace(cmykRed, 'sRGB');
console.log('RGB from CMYK:', rgbFromCmyk);
```
Pass the source color object and target color space (`'sRGB'` or `'CMYK'`). Spot colors convert to their defined approximation in the target space. Note that color conversions are approximations—CMYK has a smaller color gamut than sRGB.
## List Defined Spot Colors
Query all spot colors currently defined in the editor using `engine.editor.findAllSpotColors()`. This returns an array of spot color names.
```typescript highlight=highlight-list-spot
// List all defined spot colors
const allSpotColors = engine.editor.findAllSpotColors();
console.log('Defined spot colors:', allSpotColors);
```
This is useful for building color pickers or validating that required spot colors are defined before export.
## Update Spot Color Definitions
Redefine a spot color's approximation by calling `setSpotColorRGB()` or `setSpotColorCMYK()` with the same name. All blocks using that spot color automatically update their rendered appearance.
```typescript highlight=highlight-update-spot
// Update a spot color definition
engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.6, 0.8);
console.log('Updated Pink-Flamingo spot color');
```
This allows you to adjust how spot colors appear on screen without modifying every block that uses them.
## Remove Spot Color Definitions
Remove a spot color definition using `engine.editor.removeSpotColor()`. Blocks still referencing that color fall back to the default magenta approximation.
```typescript highlight=highlight-remove-spot
// Remove a spot color definition (falls back to magenta)
engine.editor.removeSpotColor('Corporate-Blue');
console.log('Removed Corporate-Blue spot color');
```
This is useful when cleaning up unused spot colors or when you need to signal that a spot color is no longer valid.
## Troubleshooting
### Spot Color Appears Magenta
The spot color wasn't defined before use. Call `setSpotColorRGB()` or `setSpotColorCMYK()` with the exact spot color name before applying it to blocks.
### Stroke or Shadow Color Not Visible
The effect isn't enabled. Call `setStrokeEnabled(block, true)` or `setDropShadowEnabled(block, true)` before setting the color.
### Color Looks Different After Conversion
Color space conversions are approximations. CMYK has a smaller gamut than sRGB, so vibrant colors may appear muted after conversion.
### Can't Apply Color to Fill
Apply colors to the fill block obtained from `getFill()`, not the parent design block. The fill is a separate entity with its own color property.
## API Reference
| Method | Description |
|--------|-------------|
| `block.setColor(block, property, color)` | Set a color property on a block |
| `block.getColor(block, property)` | Get a color property from a block |
| `block.getFill(block)` | Get the fill block of a design block |
| `block.setStrokeEnabled(block, enabled)` | Enable or disable stroke on a block |
| `block.setDropShadowEnabled(block, enabled)` | Enable or disable drop shadow on a block |
| `editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with RGB approximation |
| `editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with CMYK approximation |
| `editor.findAllSpotColors()` | List all defined spot colors |
| `editor.removeSpotColor(name)` | Remove a spot color definition |
| `editor.convertColorToColorSpace(color, colorSpace)` | Convert a color to a different color space |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Color Basics"
description: "Learn how color works in CE.SDK, including the three supported color spaces (sRGB, CMYK, and Spot) and when to use each for screen display or print workflows."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/basics-307115/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [Basics](https://img.ly/docs/cesdk/sveltekit/colors/basics-307115/)
---
Understand the three color spaces in CE.SDK and when to use each for screen or print workflows.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-basics-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-basics-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-basics-browser/)
CE.SDK supports three color spaces: **sRGB** for screen display, **CMYK** for print workflows, and **Spot Color** for specialized printing. Each color space serves different output types and has its own object format for the `setColor()` API.
```typescript file=@cesdk_web_examples/guides-colors-basics-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
// Enable spot color feature for the UI
cesdk.feature.enable('ly.img.spotColor');
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Calculate block sizes for three columns
const margin = 40;
const spacing = 30;
const availableWidth = pageWidth - 2 * margin - 2 * spacing;
const blockWidth = availableWidth / 3;
const blockHeight = pageHeight - 2 * margin - 80; // Leave space for labels
// Define a spot color with RGB approximation for screen preview
engine.editor.setSpotColorRGB('MyBrand Red', 0.95, 0.25, 0.21);
// Create three blocks to demonstrate each color space
// Block 1: sRGB color (for screen display)
const srgbBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
srgbBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using RGBAColor object (values 0.0-1.0)
engine.block.setColor(srgbFill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1.0
});
engine.block.setFill(srgbBlock, srgbFill);
engine.block.setWidth(srgbBlock, blockWidth);
engine.block.setHeight(srgbBlock, blockHeight);
engine.block.appendChild(page, srgbBlock);
// Block 2: CMYK color (for print workflows)
const cmykBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
cmykBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using CMYKColor object (values 0.0-1.0, tint controls opacity)
engine.block.setColor(cmykFill, 'fill/color/value', {
c: 0.0,
m: 0.8,
y: 0.95,
k: 0.0,
tint: 1.0
});
engine.block.setFill(cmykBlock, cmykFill);
engine.block.setWidth(cmykBlock, blockWidth);
engine.block.setHeight(cmykBlock, blockHeight);
engine.block.appendChild(page, cmykBlock);
// Block 3: Spot color (for specialized printing)
const spotBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
spotBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const spotFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using SpotColor object (references the defined spot color)
engine.block.setColor(spotFill, 'fill/color/value', {
name: 'MyBrand Red',
tint: 1.0,
externalReference: ''
});
engine.block.setFill(spotBlock, spotFill);
engine.block.setWidth(spotBlock, blockWidth);
engine.block.setHeight(spotBlock, blockHeight);
engine.block.appendChild(page, spotBlock);
// Add strokes to demonstrate stroke color property
engine.block.setStrokeEnabled(srgbBlock, true);
engine.block.setStrokeWidth(srgbBlock, 4);
engine.block.setColor(srgbBlock, 'stroke/color', {
r: 0.1,
g: 0.2,
b: 0.5,
a: 1.0
});
engine.block.setStrokeEnabled(cmykBlock, true);
engine.block.setStrokeWidth(cmykBlock, 4);
engine.block.setColor(cmykBlock, 'stroke/color', {
c: 0.0,
m: 0.5,
y: 0.6,
k: 0.2,
tint: 1.0
});
engine.block.setStrokeEnabled(spotBlock, true);
engine.block.setStrokeWidth(spotBlock, 4);
engine.block.setColor(spotBlock, 'stroke/color', {
name: 'MyBrand Red',
tint: 0.7,
externalReference: ''
});
// Create labels for each color space
const labelY = margin + blockHeight + 20;
const fontSize = 24;
const labels = [
{ text: 'sRGB', x: margin + blockWidth / 2 },
{ text: 'CMYK', x: margin + blockWidth + spacing + blockWidth / 2 },
{
text: 'Spot Color',
x: margin + 2 * (blockWidth + spacing) + blockWidth / 2
}
];
for (const label of labels) {
const textBlock = engine.block.create('//ly.img.ubq/text');
engine.block.replaceText(textBlock, label.text);
engine.block.setTextFontSize(textBlock, fontSize);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.appendChild(page, textBlock);
// Center the label below each block
const textWidth = engine.block.getWidth(textBlock);
engine.block.setPositionX(textBlock, label.x - textWidth / 2);
engine.block.setPositionY(textBlock, labelY);
}
// Position all color blocks
engine.block.setPositionX(srgbBlock, margin);
engine.block.setPositionY(srgbBlock, margin);
engine.block.setPositionX(cmykBlock, margin + blockWidth + spacing);
engine.block.setPositionY(cmykBlock, margin);
engine.block.setPositionX(spotBlock, margin + 2 * (blockWidth + spacing));
engine.block.setPositionY(spotBlock, margin);
// Retrieve and log color values to demonstrate getColor()
const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value');
const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value');
const spotColor = engine.block.getColor(spotFill, 'fill/color/value');
console.log('sRGB Color:', srgbColor);
console.log('CMYK Color:', cmykColor);
console.log('Spot Color:', spotColor);
console.log('Color Basics example loaded successfully');
}
}
export default Example;
```
This guide covers how to choose the correct color space, define and apply colors using the unified `setColor()` API, and configure spot colors with screen preview approximations.
## Color Spaces Overview
CE.SDK represents colors as objects with different properties depending on the color space. Use `engine.block.setColor()` to apply any color type to supported properties.
**Supported color properties:**
- `'fill/color/value'` - Fill color of a block
- `'stroke/color'` - Stroke/outline color
- `'dropShadow/color'` - Drop shadow color
- `'backgroundColor/color'` - Background color
- `'camera/clearColor'` - Canvas clear color
## sRGB Colors
sRGB is the default color space for screen display. Pass an `RGBAColor` object with `r`, `g`, `b`, `a` components, each in the range 0.0 to 1.0. The `a` (alpha) component controls transparency.
```typescript highlight=highlight-srgb-color
// Block 1: sRGB color (for screen display)
const srgbBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
srgbBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using RGBAColor object (values 0.0-1.0)
engine.block.setColor(srgbFill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1.0
});
engine.block.setFill(srgbBlock, srgbFill);
engine.block.setWidth(srgbBlock, blockWidth);
engine.block.setHeight(srgbBlock, blockHeight);
engine.block.appendChild(page, srgbBlock);
```
sRGB colors are ideal for web and digital content where the output is displayed on screens.
## CMYK Colors
CMYK is the color space for print workflows. Pass a `CMYKColor` object with `c`, `m`, `y`, `k` components (0.0 to 1.0) plus a `tint` value that controls opacity.
```typescript highlight=highlight-cmyk-color
// Block 2: CMYK color (for print workflows)
const cmykBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
cmykBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using CMYKColor object (values 0.0-1.0, tint controls opacity)
engine.block.setColor(cmykFill, 'fill/color/value', {
c: 0.0,
m: 0.8,
y: 0.95,
k: 0.0,
tint: 1.0
});
engine.block.setFill(cmykBlock, cmykFill);
engine.block.setWidth(cmykBlock, blockWidth);
engine.block.setHeight(cmykBlock, blockHeight);
engine.block.appendChild(page, cmykBlock);
```
When rendered on screen, CMYK colors are converted to RGB using standard conversion formulas. The `tint` value (0.0 to 1.0) is rendered as transparency.
> **Note:** During PDF export, CMYK colors are currently converted to RGB using the standard conversion. Tint values are retained in the alpha channel.
## Spot Colors
Spot colors are named colors used for specialized printing. Before using a spot color, you must define it with an RGB or CMYK approximation for screen preview.
### Defining Spot Colors
Use `engine.editor.setSpotColorRGB()` or `engine.editor.setSpotColorCMYK()` to register a spot color with its screen preview approximation.
```typescript highlight=highlight-define-spot-color
// Define a spot color with RGB approximation for screen preview
engine.editor.setSpotColorRGB('MyBrand Red', 0.95, 0.25, 0.21);
```
### Applying Spot Colors
Reference a defined spot color using a `SpotColor` object with the `name`, `tint`, and `externalReference` properties.
```typescript highlight=highlight-spot-color
// Block 3: Spot color (for specialized printing)
const spotBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
spotBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const spotFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using SpotColor object (references the defined spot color)
engine.block.setColor(spotFill, 'fill/color/value', {
name: 'MyBrand Red',
tint: 1.0,
externalReference: ''
});
engine.block.setFill(spotBlock, spotFill);
engine.block.setWidth(spotBlock, blockWidth);
engine.block.setHeight(spotBlock, blockHeight);
engine.block.appendChild(page, spotBlock);
```
When rendered on screen, the spot color uses its RGB or CMYK approximation. During PDF export, spot colors are saved as a [Separation Color Space](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.6.pdf#G9.1850648) that preserves print information.
> **Note:** If a block references an undefined spot color, CE.SDK displays magenta (RGB: 1, 0, 1) as a fallback.
## Applying Stroke Colors
Strokes support all three color spaces. Enable the stroke, set its width, then apply a color using the `'stroke/color'` property.
```typescript highlight=highlight-stroke-color
// Add strokes to demonstrate stroke color property
engine.block.setStrokeEnabled(srgbBlock, true);
engine.block.setStrokeWidth(srgbBlock, 4);
engine.block.setColor(srgbBlock, 'stroke/color', {
r: 0.1,
g: 0.2,
b: 0.5,
a: 1.0
});
engine.block.setStrokeEnabled(cmykBlock, true);
engine.block.setStrokeWidth(cmykBlock, 4);
engine.block.setColor(cmykBlock, 'stroke/color', {
c: 0.0,
m: 0.5,
y: 0.6,
k: 0.2,
tint: 1.0
});
engine.block.setStrokeEnabled(spotBlock, true);
engine.block.setStrokeWidth(spotBlock, 4);
engine.block.setColor(spotBlock, 'stroke/color', {
name: 'MyBrand Red',
tint: 0.7,
externalReference: ''
});
```
## Reading Color Values
Use `engine.block.getColor()` to retrieve the current color value from a property. The returned object's shape indicates the color space (RGBAColor, CMYKColor, or SpotColor).
```typescript highlight=highlight-get-color
// Retrieve and log color values to demonstrate getColor()
const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value');
const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value');
const spotColor = engine.block.getColor(spotFill, 'fill/color/value');
console.log('sRGB Color:', srgbColor);
console.log('CMYK Color:', cmykColor);
console.log('Spot Color:', spotColor);
```
## Choosing the Right Color Space
| Color Space | Use Case | Output |
|-------------|----------|--------|
| **sRGB** | Web, digital, screen display | PNG, JPEG, WebP |
| **CMYK** | Print workflows (converts to RGB) | PDF (converted) |
| **Spot Color** | Specialized printing, brand colors | PDF (Separation Color Space) |
## API Reference
| Method | Description |
|--------|-------------|
| `engine.block.setColor(id, property, value)` | Set a color property on a block. Pass an `RGBAColor`, `CMYKColor`, or `SpotColor` object. |
| `engine.block.getColor(id, property)` | Get the current color value from a property. Returns an `RGBAColor`, `CMYKColor`, or `SpotColor` object. |
| `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with an RGB approximation for screen preview. Components range from 0.0 to 1.0. |
| `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with a CMYK approximation for screen preview. Components range from 0.0 to 1.0. |
| Type | Properties | Description |
|------|------------|-------------|
| `RGBAColor` | `r`, `g`, `b`, `a` (0.0-1.0) | sRGB color for screen display. Alpha controls transparency. |
| `CMYKColor` | `c`, `m`, `y`, `k`, `tint` (0.0-1.0) | CMYK color for print. Tint controls opacity. |
| `SpotColor` | `name`, `tint`, `externalReference` | Named color for specialized printing. |
## Next Steps
- [Apply Colors](https://img.ly/docs/cesdk/sveltekit/colors/apply-2211e3/) - Apply colors to design elements programmatically
- [CMYK Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-print/cmyk-8a1334/) - Work with CMYK for print workflows
- [Spot Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-print/spot-c3a150/) - Define and manage spot colors for specialized printing
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Color Conversion"
description: "Learn how to convert colors between color spaces in CE.SDK. Convert sRGB, CMYK, and spot colors programmatically for screen display or print workflows."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/conversion-bcd82b/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [Color Conversion](https://img.ly/docs/cesdk/sveltekit/colors/conversion-bcd82b/)
---
Convert colors between sRGB, CMYK, and spot color spaces programmatically in CE.SDK.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-conversion-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-conversion-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-conversion-browser/)
CE.SDK supports three color spaces: sRGB, CMYK, and SpotColor. When building color interfaces or preparing designs for export, you may need to convert colors between these spaces. The engine handles the mathematical conversion automatically through the `convertColorToColorSpace()` API.
```typescript file=@cesdk_web_examples/guides-colors-conversion-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
// Type guard helpers for identifying color types
function isRGBAColor(
color: any
): color is { r: number; g: number; b: number; a: number } {
return 'r' in color && 'g' in color && 'b' in color && 'a' in color;
}
function isCMYKColor(
color: any
): color is { c: number; m: number; y: number; k: number; tint: number } {
return 'c' in color && 'm' in color && 'y' in color && 'k' in color;
}
function isSpotColor(
color: any
): color is { name: string; tint: number; externalReference: string } {
return 'name' in color && 'tint' in color && 'externalReference' in color;
}
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
// Enable spot color feature for the UI
cesdk.feature.enable('ly.img.spotColor');
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Calculate block sizes for three columns
const margin = 40;
const spacing = 30;
const availableWidth = pageWidth - 2 * margin - 2 * spacing;
const blockWidth = availableWidth / 3;
const blockHeight = pageHeight - 2 * margin - 80; // Leave space for labels
// Define a spot color with RGB approximation for screen preview
engine.editor.setSpotColorRGB('Brand Red', 0.95, 0.25, 0.21);
// Create three blocks with different color spaces
// Block 1: sRGB color (for screen display)
const srgbBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
srgbBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(srgbFill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1.0
});
engine.block.setFill(srgbBlock, srgbFill);
engine.block.setWidth(srgbBlock, blockWidth);
engine.block.setHeight(srgbBlock, blockHeight);
engine.block.appendChild(page, srgbBlock);
// Block 2: CMYK color (for print workflows)
const cmykBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
cmykBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(cmykFill, 'fill/color/value', {
c: 0.0,
m: 0.8,
y: 0.95,
k: 0.0,
tint: 1.0
});
engine.block.setFill(cmykBlock, cmykFill);
engine.block.setWidth(cmykBlock, blockWidth);
engine.block.setHeight(cmykBlock, blockHeight);
engine.block.appendChild(page, cmykBlock);
// Block 3: Spot color (for specialized printing)
const spotBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
spotBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const spotFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(spotFill, 'fill/color/value', {
name: 'Brand Red',
tint: 1.0,
externalReference: ''
});
engine.block.setFill(spotBlock, spotFill);
engine.block.setWidth(spotBlock, blockWidth);
engine.block.setHeight(spotBlock, blockHeight);
engine.block.appendChild(page, spotBlock);
// Position all color blocks
engine.block.setPositionX(srgbBlock, margin);
engine.block.setPositionY(srgbBlock, margin);
engine.block.setPositionX(cmykBlock, margin + blockWidth + spacing);
engine.block.setPositionY(cmykBlock, margin);
engine.block.setPositionX(spotBlock, margin + 2 * (blockWidth + spacing));
engine.block.setPositionY(spotBlock, margin);
// Create labels for each color space
const labelY = margin + blockHeight + 20;
const fontSize = 24;
const labels = [
{ text: 'sRGB', x: margin + blockWidth / 2 },
{ text: 'CMYK', x: margin + blockWidth + spacing + blockWidth / 2 },
{
text: 'Spot Color',
x: margin + 2 * (blockWidth + spacing) + blockWidth / 2
}
];
for (const label of labels) {
const textBlock = engine.block.create('//ly.img.ubq/text');
engine.block.replaceText(textBlock, label.text);
engine.block.setTextFontSize(textBlock, fontSize);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.appendChild(page, textBlock);
// Center the label below each block
const textWidth = engine.block.getWidth(textBlock);
engine.block.setPositionX(textBlock, label.x - textWidth / 2);
engine.block.setPositionY(textBlock, labelY);
}
// Convert colors to sRGB for screen display
const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value');
const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value');
const spotColor = engine.block.getColor(spotFill, 'fill/color/value');
// Convert CMYK to sRGB
const cmykToRgba = engine.editor.convertColorToColorSpace(
cmykColor,
'sRGB'
);
console.log('CMYK converted to sRGB:', cmykToRgba);
// Convert Spot color to sRGB (uses defined RGB approximation)
const spotToRgba = engine.editor.convertColorToColorSpace(
spotColor,
'sRGB'
);
console.log('Spot color converted to sRGB:', spotToRgba);
// Convert colors to CMYK for print workflows
const srgbToCmyk = engine.editor.convertColorToColorSpace(
srgbColor,
'CMYK'
);
console.log('sRGB converted to CMYK:', srgbToCmyk);
// Convert Spot color to CMYK for print output
// First define CMYK approximation for the spot color
engine.editor.setSpotColorCMYK('Brand Red', 0.0, 0.85, 0.9, 0.05);
const spotToCmyk = engine.editor.convertColorToColorSpace(
spotColor,
'CMYK'
);
console.log('Spot color converted to CMYK:', spotToCmyk);
// Use type guards to identify color space before conversion
if (isRGBAColor(srgbColor)) {
console.log(
'sRGB color components:',
`R: ${srgbColor.r}, G: ${srgbColor.g}, B: ${srgbColor.b}, A: ${srgbColor.a}`
);
}
if (isCMYKColor(cmykColor)) {
console.log(
'CMYK color components:',
`C: ${cmykColor.c}, M: ${cmykColor.m}, Y: ${cmykColor.y}, K: ${cmykColor.k}, Tint: ${cmykColor.tint}`
);
}
if (isSpotColor(spotColor)) {
console.log('Spot color name:', spotColor.name, 'Tint:', spotColor.tint);
}
console.log('Color Conversion example loaded successfully');
}
}
export default Example;
```
This guide covers how to convert colors between sRGB and CMYK, handle spot color conversions, identify color types with type guards, and understand how tint and alpha values are preserved during conversion.
## Supported Color Spaces
CE.SDK supports conversion between three color spaces:
| Color Space | Format | Use Case |
|-------------|--------|----------|
| **sRGB** | `RGBAColor` with `r`, `g`, `b`, `a` (0.0-1.0) | Screen display, web output |
| **CMYK** | `CMYKColor` with `c`, `m`, `y`, `k`, `tint` (0.0-1.0) | Print workflows |
| **SpotColor** | `SpotColor` with `name`, `tint`, `externalReference` | Specialized printing |
## Setting Up Colors
Before converting colors, you need colors in different spaces. This example creates blocks with sRGB, CMYK, and spot colors.
First, define a spot color with its RGB approximation for screen preview:
```typescript highlight=highlight-define-spot-color
// Define a spot color with RGB approximation for screen preview
engine.editor.setSpotColorRGB('Brand Red', 0.95, 0.25, 0.21);
```
Create an sRGB color block for screen display:
```typescript highlight=highlight-srgb-color
// Block 1: sRGB color (for screen display)
const srgbBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
srgbBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(srgbFill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1.0
});
engine.block.setFill(srgbBlock, srgbFill);
engine.block.setWidth(srgbBlock, blockWidth);
engine.block.setHeight(srgbBlock, blockHeight);
engine.block.appendChild(page, srgbBlock);
```
Create a CMYK color block for print workflows:
```typescript highlight=highlight-cmyk-color
// Block 2: CMYK color (for print workflows)
const cmykBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
cmykBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(cmykFill, 'fill/color/value', {
c: 0.0,
m: 0.8,
y: 0.95,
k: 0.0,
tint: 1.0
});
engine.block.setFill(cmykBlock, cmykFill);
engine.block.setWidth(cmykBlock, blockWidth);
engine.block.setHeight(cmykBlock, blockHeight);
engine.block.appendChild(page, cmykBlock);
```
Create a spot color block for specialized printing:
```typescript highlight=highlight-spot-color
// Block 3: Spot color (for specialized printing)
const spotBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
spotBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const spotFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(spotFill, 'fill/color/value', {
name: 'Brand Red',
tint: 1.0,
externalReference: ''
});
engine.block.setFill(spotBlock, spotFill);
engine.block.setWidth(spotBlock, blockWidth);
engine.block.setHeight(spotBlock, blockHeight);
engine.block.appendChild(page, spotBlock);
```
## Converting to sRGB
Use `engine.editor.convertColorToColorSpace(color, 'sRGB')` to convert any color to sRGB format. This is useful for displaying color values on screen or when you need RGB components for CSS or other web-based color operations.
```typescript highlight=highlight-convert-to-srgb
// Convert colors to sRGB for screen display
const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value');
const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value');
const spotColor = engine.block.getColor(spotFill, 'fill/color/value');
// Convert CMYK to sRGB
const cmykToRgba = engine.editor.convertColorToColorSpace(
cmykColor,
'sRGB'
);
console.log('CMYK converted to sRGB:', cmykToRgba);
// Convert Spot color to sRGB (uses defined RGB approximation)
const spotToRgba = engine.editor.convertColorToColorSpace(
spotColor,
'sRGB'
);
console.log('Spot color converted to sRGB:', spotToRgba);
```
When converting CMYK or spot colors to sRGB, the engine returns an `RGBAColor` object with `r`, `g`, `b`, `a` properties. The tint value from CMYK or spot colors becomes the alpha value in the returned sRGB color.
## Converting to CMYK
Use `engine.editor.convertColorToColorSpace(color, 'CMYK')` to convert any color to CMYK format. This is essential for print workflows where you need to ensure colors are in the correct space before export.
```typescript highlight=highlight-convert-to-cmyk
// Convert colors to CMYK for print workflows
const srgbToCmyk = engine.editor.convertColorToColorSpace(
srgbColor,
'CMYK'
);
console.log('sRGB converted to CMYK:', srgbToCmyk);
// Convert Spot color to CMYK for print output
// First define CMYK approximation for the spot color
engine.editor.setSpotColorCMYK('Brand Red', 0.0, 0.85, 0.9, 0.05);
const spotToCmyk = engine.editor.convertColorToColorSpace(
spotColor,
'CMYK'
);
console.log('Spot color converted to CMYK:', spotToCmyk);
```
When converting sRGB colors to CMYK, the alpha value becomes the tint value in the returned CMYK color. For spot colors, define a CMYK approximation with `setSpotColorCMYK()` before converting.
> **Note:** Color space conversions may not be perfectly reversible. Some sRGB colors cannot be exactly represented in CMYK due to different color gamuts.
## Identifying Color Types
Before converting a color, you may need to identify its current color space. CE.SDK provides type guard functions to check the color type.
```typescript highlight=highlight-type-guards
// Use type guards to identify color space before conversion
if (isRGBAColor(srgbColor)) {
console.log(
'sRGB color components:',
`R: ${srgbColor.r}, G: ${srgbColor.g}, B: ${srgbColor.b}, A: ${srgbColor.a}`
);
}
if (isCMYKColor(cmykColor)) {
console.log(
'CMYK color components:',
`C: ${cmykColor.c}, M: ${cmykColor.m}, Y: ${cmykColor.y}, K: ${cmykColor.k}, Tint: ${cmykColor.tint}`
);
}
if (isSpotColor(spotColor)) {
console.log('Spot color name:', spotColor.name, 'Tint:', spotColor.tint);
}
```
Import the type guards from `@cesdk/cesdk-js`:
- `isRGBAColor()` - Returns true if the color is an sRGB color
- `isCMYKColor()` - Returns true if the color is a CMYK color
- `isSpotColor()` - Returns true if the color is a spot color
## Handling Tint and Alpha
The tint and alpha values represent transparency in different color spaces:
| Source | Target | Transformation |
|--------|--------|----------------|
| sRGB (alpha) | CMYK | Alpha becomes tint |
| CMYK (tint) | sRGB | Tint becomes alpha |
| SpotColor (tint) | sRGB | Tint becomes alpha |
| SpotColor (tint) | CMYK | Tint is preserved |
## Practical Use Cases
### Building a Color Picker
When displaying a color value from a block in a custom color picker, convert to sRGB to show RGB values:
```typescript
const fillColor = engine.block.getColor(fillId, 'fill/color/value');
const rgbaColor = engine.editor.convertColorToColorSpace(fillColor, 'sRGB');
// Display: R: ${rgbaColor.r * 255}, G: ${rgbaColor.g * 255}, B: ${rgbaColor.b * 255}
```
### Export Preparation
Before PDF export for print, verify colors are in CMYK format:
```typescript
const color = engine.block.getColor(blockId, 'fill/color/value');
if (!isCMYKColor(color)) {
const cmykColor = engine.editor.convertColorToColorSpace(color, 'CMYK');
// Log or display the CMYK values
console.log(`C: ${cmykColor.c}, M: ${cmykColor.m}, Y: ${cmykColor.y}, K: ${cmykColor.k}`);
}
```
## Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| Spot color converts to unexpected values | Spot color not defined | Call `setSpotColorRGB()` or `setSpotColorCMYK()` before conversion |
| Colors look different after conversion | Color gamut differences | Some sRGB colors cannot be exactly represented in CMYK |
| Type errors with converted colors | Wrong type assumption | Use type guards (`isRGBAColor`, `isCMYKColor`, `isSpotColor`) before accessing properties |
## API Reference
| Method | Description |
|--------|-------------|
| `engine.editor.convertColorToColorSpace(color, colorSpace)` | Convert a color to the target color space. Returns an `RGBAColor` for 'sRGB' or `CMYKColor` for 'CMYK'. |
| `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with an RGB approximation. Components range from 0.0 to 1.0. |
| `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with a CMYK approximation. Components range from 0.0 to 1.0. |
| Type Guard | Description |
|------------|-------------|
| `isRGBAColor(color)` | Returns true if the color is an `RGBAColor` object |
| `isCMYKColor(color)` | Returns true if the color is a `CMYKColor` object |
| `isSpotColor(color)` | Returns true if the color is a `SpotColor` object |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create a Color Palette"
description: "Build reusable color palettes to maintain consistency and streamline user choices."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/create-color-palette-7012e0/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [Create a Color Palette](https://img.ly/docs/cesdk/sveltekit/colors/create-color-palette-7012e0/)
---
Build custom color palettes that appear in the CE.SDK color picker using sRGB, CMYK, and Spot colors.

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-create-color-palette-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-create-color-palette-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-create-color-palette-browser/)
Color libraries in CE.SDK are implemented as asset sources containing individual colors as assets. Each library has a unique source ID and can include sRGB colors for screen display, CMYK colors for print workflows, and Spot colors for specialized printing applications. You configure which libraries appear in the color picker through the `'ly.img.colors'` asset library entry.
```typescript file=@cesdk_web_examples/guides-colors-create-color-palette-browser/browser.ts reference-only
import type {
AssetDefinition,
EditorPlugin,
EditorPluginContext
} from '@cesdk/cesdk-js';
import packageJson from './package.json';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
// Define color assets for each color space type
const colors: AssetDefinition[] = [
{
id: 'brand-blue',
label: { en: 'Brand Blue' },
tags: { en: ['brand', 'blue', 'primary'] },
payload: {
color: {
colorSpace: 'sRGB',
r: 0.2,
g: 0.4,
b: 0.8
}
}
},
{
id: 'brand-coral',
label: { en: 'Brand Coral' },
tags: { en: ['brand', 'coral', 'secondary'] },
payload: {
color: {
colorSpace: 'sRGB',
r: 0.95,
g: 0.45,
b: 0.4
}
}
},
{
id: 'print-magenta',
label: { en: 'Print Magenta' },
tags: { en: ['print', 'magenta', 'cmyk'] },
payload: {
color: {
colorSpace: 'CMYK',
c: 0,
m: 0.9,
y: 0.2,
k: 0
}
}
},
{
id: 'metallic-gold',
label: { en: 'Metallic Gold' },
tags: { en: ['spot', 'metallic', 'gold'] },
payload: {
color: {
colorSpace: 'SpotColor',
name: 'Metallic Gold Ink',
externalReference: 'Custom Inks',
representation: {
colorSpace: 'sRGB',
r: 0.85,
g: 0.65,
b: 0.13
}
}
}
}
];
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
// Create a local asset source and add each color
const sourceId = 'my-brand-colors';
engine.asset.addLocalSource(sourceId);
for (const color of colors) {
await engine.asset.addAssetToSource(sourceId, color);
}
// Set labels for the color library using i18n
cesdk.i18n.setTranslations({
en: {
'libraries.my-brand-colors.label': 'Brand Colors'
}
});
// Configure the color picker to show custom colors first, then defaults
cesdk.ui.updateAssetLibraryEntry('ly.img.color.palette', {
sourceIds: ['my-brand-colors', 'ly.img.color.palette']
});
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
// Configure the color picker to show custom colors alongside the defaults
cesdk.ui.updateAssetLibraryEntry('ly.img.colors', {
sourceIds: ['my-brand-colors', 'ly.img.color.palette']
});
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
// Set up the page with dimensions
const page = engine.block.findByType('page')[0];
// Apply a soft cream background to the page fill
// This complements the Brand Blue rectangle
const pageFill = engine.block.getFill(page);
engine.block.setColor(pageFill, 'fill/color/value', {
r: 0.98,
g: 0.96,
b: 0.92,
a: 1.0
});
// Create a graphic block with Brand Blue from the custom palette
const block = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
block,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const fill = engine.block.createFill('//ly.img.ubq/fill/color');
// Use Brand Blue from our custom palette
engine.block.setColor(fill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.8,
a: 1.0
});
engine.block.setFill(block, fill);
engine.block.setWidth(block, 200);
engine.block.setHeight(block, 200);
// Center the block on the page
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
engine.block.setPositionX(block, (pageWidth - 200) / 2);
engine.block.setPositionY(block, (pageHeight - 200) / 2);
engine.block.appendChild(page, block);
// Select the block and open the fill inspector to show the color picker
engine.block.select(block);
cesdk.ui.openPanel('//ly.img.panel/inspector/fill');
console.log('Create Color Palette example loaded successfully');
}
}
export default Example;
```
This guide covers how to define colors in different color spaces, create and configure color libraries, set custom labels, and control the display order in the color picker.
## Defining Color Assets
Colors are added to libraries as `AssetDefinition` objects. Each color asset has an `id`, optional `label` and `tags` for display and search, and a `payload.color` property containing the color data. The color type determines which color space is used.
### sRGB Colors
sRGB colors use the `AssetRGBColor` type with `colorSpace: 'sRGB'` and `r`, `g`, `b` components as floats from 0.0 to 1.0. Use sRGB colors for screen-based designs and web content.
```typescript highlight=highlight-definitions
// Define color assets for each color space type
const colors: AssetDefinition[] = [
{
id: 'brand-blue',
label: { en: 'Brand Blue' },
tags: { en: ['brand', 'blue', 'primary'] },
payload: {
color: {
colorSpace: 'sRGB',
r: 0.2,
g: 0.4,
b: 0.8
}
}
},
{
id: 'brand-coral',
label: { en: 'Brand Coral' },
tags: { en: ['brand', 'coral', 'secondary'] },
payload: {
color: {
colorSpace: 'sRGB',
r: 0.95,
g: 0.45,
b: 0.4
}
}
},
{
id: 'print-magenta',
label: { en: 'Print Magenta' },
tags: { en: ['print', 'magenta', 'cmyk'] },
payload: {
color: {
colorSpace: 'CMYK',
c: 0,
m: 0.9,
y: 0.2,
k: 0
}
}
},
{
id: 'metallic-gold',
label: { en: 'Metallic Gold' },
tags: { en: ['spot', 'metallic', 'gold'] },
payload: {
color: {
colorSpace: 'SpotColor',
name: 'Metallic Gold Ink',
externalReference: 'Custom Inks',
representation: {
colorSpace: 'sRGB',
r: 0.85,
g: 0.65,
b: 0.13
}
}
}
}
];
```
The example defines four colors demonstrating different color spaces. The first two colors—"Brand Blue" and "Brand Coral"—use sRGB for screen display.
### CMYK Colors
CMYK colors use the `AssetCMYKColor` type with `colorSpace: 'CMYK'` and `c`, `m`, `y`, `k` components as floats from 0.0 to 1.0. Use CMYK colors for print workflows where color accuracy in printing is critical.
The "Print Magenta" color in the example demonstrates the CMYK color space with cyan at 0, magenta at 0.9, yellow at 0.2, and black at 0.
### Spot Colors
Spot colors use the `AssetSpotColor` type with `colorSpace: 'SpotColor'`, a `name` that identifies the spot color, an `externalReference` indicating the color book or ink system, and a `representation` using sRGB or CMYK for screen preview.
The "Metallic Gold" color demonstrates the spot color format, using a custom ink reference with an sRGB representation for on-screen preview.
## Creating a Color Library
We create a local asset source using `engine.asset.addLocalSource()` with a unique source ID. Then we add each color asset using `engine.asset.addAssetToSource()`.
```typescript highlight=highlight-add-library
// Create a local asset source and add each color
const sourceId = 'my-brand-colors';
engine.asset.addLocalSource(sourceId);
for (const color of colors) {
await engine.asset.addAssetToSource(sourceId, color);
}
```
The source ID `'my-brand-colors'` identifies this library throughout the application. You can create multiple libraries with different source IDs to organize colors by purpose—for example, separate libraries for brand colors, print colors, and seasonal palettes.
## Configuring Library Labels
We set display labels for color libraries using `cesdk.i18n.setTranslations()`. Labels use the pattern `libraries..label` where `` matches the ID used when creating the source.
```typescript highlight=highlight-config-labels
// Set labels for the color library using i18n
cesdk.i18n.setTranslations({
en: {
'libraries.my-brand-colors.label': 'Brand Colors'
}
});
```
The label "Brand Colors" appears as the section header in the color picker. You can provide translations for multiple locales by adding additional language keys to the translations object.
## Configuring the Color Picker
We control which libraries appear in the color picker and their display order using `cesdk.ui.updateAssetLibraryEntry()`. The `sourceIds` array determines both visibility and order—libraries appear in the picker in the same order as the array.
```typescript highlight=highlight-config-order
// Configure the color picker to show custom colors first, then defaults
cesdk.ui.updateAssetLibraryEntry('ly.img.color.palette', {
sourceIds: ['my-brand-colors', 'ly.img.color.palette']
});
```
The special source ID `'ly.img.color.palette'` represents CE.SDK's built-in default color palette. Include it in the array to show the default colors alongside your custom library. Remove it from the array to hide the default palette entirely.
## Removing Colors
You can remove individual colors from a library using `engine.asset.removeAssetFromSource()` with the source ID and the color's asset ID.
```typescript
engine.asset.removeAssetFromSource('my-brand-colors', 'brand-blue');
```
This removes the color from the library immediately. The color picker updates to reflect the change the next time it renders.
## Troubleshooting
### Colors Not Appearing in Picker
If your colors don't appear in the color picker:
- Verify the source ID is included in the `sourceIds` array passed to `updateAssetLibraryEntry()`
- Check that colors were added using `addAssetToSource()` with the correct source ID
- Ensure the asset source was created with `addLocalSource()` before adding colors
### Label Not Showing
If the library label doesn't appear:
- Verify the translation key follows the `libraries..label` pattern exactly
- Check that the source ID in the translation key matches the source ID used in `addLocalSource()`
- Ensure `setTranslations()` was called before the color picker renders
### Spot Color Appears Incorrect
If a spot color displays incorrectly:
- Check that the `representation` property contains a valid sRGB or CMYK color for screen preview
- Verify the `name` property is defined and not empty
- Ensure the `colorSpace` is set to `'SpotColor'`
### Wrong Library Order
The order of libraries in the color picker matches the order in the `sourceIds` array. To change the order:
- Reorder the source IDs in the array passed to `updateAssetLibraryEntry()`
- The first source ID appears at the top of the color picker
## API Reference
| Method | Description |
|--------|-------------|
| `engine.asset.addLocalSource(sourceId)` | Create a local asset source for colors |
| `engine.asset.addAssetToSource(sourceId, asset)` | Add a color asset to a source |
| `engine.asset.removeAssetFromSource(sourceId, assetId)` | Remove a color asset from a source |
| `cesdk.ui.updateAssetLibraryEntry(entryId, config)` | Configure color library display order |
| `cesdk.i18n.setTranslations(translations)` | Set labels for color libraries |
| Type | Properties | Description |
|------|------------|-------------|
| `AssetRGBColor` | `colorSpace`, `r`, `g`, `b` | sRGB color for screen display |
| `AssetCMYKColor` | `colorSpace`, `c`, `m`, `y`, `k` | CMYK color for print workflows |
| `AssetSpotColor` | `colorSpace`, `name`, `externalReference`, `representation` | Named spot color for specialized printing |
| `AssetDefinition` | `id`, `label`, `tags`, `payload` | Color asset structure with metadata |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "For Print"
description: "Use print-ready color models and settings for professional-quality, production-ready exports."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/for-print-59bc05/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/sveltekit/colors/for-print-59bc05/)
---
---
## Related Pages
- [CMYK Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-print/cmyk-8a1334/) - Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control.
- [Spot Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-print/spot-c3a150/) - Define, apply, and manage spot colors for professional print workflows with premixed inks.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "CMYK Colors"
description: "Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/for-print/cmyk-8a1334/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/sveltekit/colors/for-print-59bc05/) > [CMYK Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-print/cmyk-8a1334/)
---
Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-print-cmyk-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-print-cmyk-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-for-print-cmyk-browser/)
CMYK (Cyan, Magenta, Yellow, Key/Black) is the standard color model for print production. Unlike RGB which is additive and designed for screens, CMYK uses subtractive color mixing to represent how inks combine on paper. CE.SDK supports CMYK colors natively, allowing you to prepare designs for professional print output while maintaining accurate color representation.
```typescript file=@cesdk_web_examples/guides-colors-for-print-cmyk-browser/browser.ts reference-only
import type {
CMYKColor,
EditorPlugin,
EditorPluginContext,
RGBAColor
} from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
// Type guard to check if a color is CMYK
// Color can be RGBAColor, CMYKColor, or SpotColor
const isCMYKColor = (color: unknown): color is CMYKColor => {
return (
typeof color === 'object' &&
color !== null &&
'c' in color &&
'm' in color &&
'y' in color &&
'k' in color
);
};
/**
* CE.SDK Plugin: CMYK Colors Guide
*
* This example demonstrates:
* - Creating CMYK color values for print workflows
* - Applying CMYK colors to fills, strokes, and shadows
* - Using the tint property for color intensity control
* - Converting between RGB and CMYK color spaces
* - Checking color types with type guards
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
// Create a design scene using CE.SDK convenience method
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the page
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Set page background to light gray for visibility (using CMYK)
const pageFill = engine.block.getFill(page);
engine.block.setColor(pageFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.04,
tint: 1.0
});
// Helper function to create a graphic block with a color fill
const createColorBlock = (
x: number,
y: number,
width: number,
height: number,
shape: 'rect' | 'ellipse' = 'rect'
): { block: number; fill: number } => {
const block = engine.block.create('graphic');
const blockShape = engine.block.createShape(shape);
engine.block.setShape(block, blockShape);
engine.block.setWidth(block, width);
engine.block.setHeight(block, height);
engine.block.setPositionX(block, x);
engine.block.setPositionY(block, y);
engine.block.appendChild(page, block);
const colorFill = engine.block.createFill('color');
engine.block.setFill(block, colorFill);
return { block, fill: colorFill };
};
// Create CMYK color objects for print production
// CMYK values range from 0.0 to 1.0
const cmykCyan: CMYKColor = { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 };
const cmykMagenta: CMYKColor = {
c: 0.0,
m: 1.0,
y: 0.0,
k: 0.0,
tint: 1.0
};
const cmykYellow: CMYKColor = { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 };
const cmykBlack: CMYKColor = { c: 0.0, m: 0.0, y: 0.0, k: 1.0, tint: 1.0 };
// Example 1: Apply CMYK Cyan to a fill
const { fill: cyanFill } = createColorBlock(50, 50, 150, 150);
engine.block.setColor(cyanFill, 'fill/color/value', cmykCyan);
// Example 2: Apply CMYK Magenta to a fill
const { fill: magentaFill } = createColorBlock(220, 50, 150, 150);
engine.block.setColor(magentaFill, 'fill/color/value', cmykMagenta);
// Example 3: Apply CMYK Yellow to a fill
const { fill: yellowFill } = createColorBlock(390, 50, 150, 150);
engine.block.setColor(yellowFill, 'fill/color/value', cmykYellow);
// Example 4: Apply CMYK Black to a fill
const { fill: blackFill } = createColorBlock(560, 50, 150, 150);
engine.block.setColor(blackFill, 'fill/color/value', cmykBlack);
// Example 5: Use tint for partial color intensity
// Tint of 0.5 gives 50% color intensity
const cmykHalfMagenta: CMYKColor = {
c: 0.0,
m: 1.0,
y: 0.0,
k: 0.0,
tint: 0.5
};
const { fill: tintedFill } = createColorBlock(50, 220, 150, 150, 'ellipse');
engine.block.setColor(tintedFill, 'fill/color/value', cmykHalfMagenta);
// Example 6: Apply CMYK color to stroke
const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock(
220,
220,
150,
150
);
// Set fill to white (using CMYK)
engine.block.setColor(strokeBlockFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.0,
tint: 1.0
});
// Enable stroke and set CMYK color
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 8);
const cmykStrokeColor: CMYKColor = {
c: 0.8,
m: 0.2,
y: 0.0,
k: 0.1,
tint: 1.0
};
engine.block.setColor(strokeBlock, 'stroke/color', cmykStrokeColor);
// Example 7: Apply CMYK color to drop shadow
const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock(
390,
220,
150,
150
);
// Set fill to light gray (using CMYK)
engine.block.setColor(shadowBlockFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.05,
tint: 1.0
});
// Enable drop shadow and set CMYK color
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowOffsetX(shadowBlock, 10);
engine.block.setDropShadowOffsetY(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 15);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 15);
const cmykShadowColor: CMYKColor = {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.6,
tint: 0.8
};
engine.block.setColor(shadowBlock, 'dropShadow/color', cmykShadowColor);
// Example 8: Read CMYK color from a block
const { fill: readFill } = createColorBlock(560, 220, 150, 150, 'ellipse');
const cmykOrange: CMYKColor = { c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0 };
engine.block.setColor(readFill, 'fill/color/value', cmykOrange);
// Retrieve and check the color
const retrievedColor = engine.block.getColor(readFill, 'fill/color/value');
if (isCMYKColor(retrievedColor)) {
// eslint-disable-next-line no-console
console.log(
`CMYK Color - C: ${retrievedColor.c}, M: ${retrievedColor.m}, Y: ${retrievedColor.y}, K: ${retrievedColor.k}, Tint: ${retrievedColor.tint}`
);
}
// Example 9: Convert RGB to CMYK
const rgbBlue: RGBAColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 };
const convertedCmyk = engine.editor.convertColorToColorSpace(
rgbBlue,
'CMYK'
);
const { fill: convertedFill } = createColorBlock(50, 390, 150, 150);
engine.block.setColor(convertedFill, 'fill/color/value', convertedCmyk);
// eslint-disable-next-line no-console
console.log('RGB to CMYK conversion:', convertedCmyk);
// Example 10: Convert CMYK to RGB (for demonstration)
const cmykGreen: CMYKColor = { c: 0.7, m: 0.0, y: 1.0, k: 0.2, tint: 1.0 };
const convertedRgb = engine.editor.convertColorToColorSpace(
cmykGreen,
'sRGB'
);
// eslint-disable-next-line no-console
console.log('CMYK to RGB conversion:', convertedRgb);
// Display using original CMYK color
const { fill: previewFill } = createColorBlock(220, 390, 150, 150);
engine.block.setColor(previewFill, 'fill/color/value', cmykGreen);
// Example 11: Use CMYK colors in gradients
const gradientBlock = engine.block.create('graphic');
const gradientShape = engine.block.createShape('rect');
engine.block.setShape(gradientBlock, gradientShape);
engine.block.setWidth(gradientBlock, 320);
engine.block.setHeight(gradientBlock, 150);
engine.block.setPositionX(gradientBlock, 390);
engine.block.setPositionY(gradientBlock, 390);
engine.block.appendChild(page, gradientBlock);
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setFill(gradientBlock, gradientFill);
// Set gradient stops with CMYK colors
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0 },
{ color: { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0.5 },
{ color: { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 }
]);
// Zoom to fit all content
await engine.scene.zoomToBlock(page, {
padding: {
left: 40,
top: 40,
right: 40,
bottom: 40
}
});
}
}
export default Example;
```
This guide covers how to create CMYK color values, apply them to fills, strokes, and shadows, use the tint property for color intensity control, and convert between color spaces.
## Understanding CMYK Colors
### When to Use CMYK
Use CMYK colors when preparing designs for commercial printing or when print service providers require CMYK values. Screen displays convert CMYK to RGB for preview, but exported PDFs retain the original CMYK values for accurate print reproduction.
CMYK colors in CE.SDK have five properties:
- `c` (Cyan): 0.0 to 1.0
- `m` (Magenta): 0.0 to 1.0
- `y` (Yellow): 0.0 to 1.0
- `k` (Key/Black): 0.0 to 1.0
- `tint`: 0.0 to 1.0 (controls overall color intensity)
## Creating CMYK Colors
Create CMYK color objects using the `CMYKColor` interface. All component values range from 0.0 to 1.0:
```typescript highlight-create-cmyk
// Create CMYK color objects for print production
// CMYK values range from 0.0 to 1.0
const cmykCyan: CMYKColor = { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 };
const cmykMagenta: CMYKColor = {
c: 0.0,
m: 1.0,
y: 0.0,
k: 0.0,
tint: 1.0
};
const cmykYellow: CMYKColor = { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 };
const cmykBlack: CMYKColor = { c: 0.0, m: 0.0, y: 0.0, k: 1.0, tint: 1.0 };
```
The tint property acts as a multiplier for the entire color—a tint of 1.0 applies the full color, while 0.5 applies 50% intensity.
## Applying CMYK Colors to Fills
Apply CMYK colors to color fills using `engine.block.setColor()`. First create a color fill with `engine.block.createFill('color')`, then set its color value:
```typescript highlight-apply-fill
// Example 1: Apply CMYK Cyan to a fill
const { fill: cyanFill } = createColorBlock(50, 50, 150, 150);
engine.block.setColor(cyanFill, 'fill/color/value', cmykCyan);
```
The same method works for any color type—RGBA, CMYK, or SpotColor. CE.SDK automatically handles the color representation internally.
## Using the Tint Property
The tint property provides fine-grained control over color intensity without modifying the base CMYK values. This is useful for creating lighter variations of a color:
```typescript highlight-tint
// Example 5: Use tint for partial color intensity
// Tint of 0.5 gives 50% color intensity
const cmykHalfMagenta: CMYKColor = {
c: 0.0,
m: 1.0,
y: 0.0,
k: 0.0,
tint: 0.5
};
const { fill: tintedFill } = createColorBlock(50, 220, 150, 150, 'ellipse');
engine.block.setColor(tintedFill, 'fill/color/value', cmykHalfMagenta);
```
A tint of 0.5 creates a 50% lighter version of the color, useful for secondary elements or subtle backgrounds.
## Applying CMYK to Strokes
Apply CMYK colors to strokes using the `stroke/color` property path:
```typescript highlight-stroke
// Example 6: Apply CMYK color to stroke
const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock(
220,
220,
150,
150
);
// Set fill to white (using CMYK)
engine.block.setColor(strokeBlockFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.0,
tint: 1.0
});
// Enable stroke and set CMYK color
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 8);
const cmykStrokeColor: CMYKColor = {
c: 0.8,
m: 0.2,
y: 0.0,
k: 0.1,
tint: 1.0
};
engine.block.setColor(strokeBlock, 'stroke/color', cmykStrokeColor);
```
Enable the stroke first with `setStrokeEnabled()`, set the stroke width, then apply the CMYK color.
## Applying CMYK to Drop Shadows
Apply CMYK colors to drop shadows using the `dropShadow/color` property path:
```typescript highlight-shadow
// Example 7: Apply CMYK color to drop shadow
const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock(
390,
220,
150,
150
);
// Set fill to light gray (using CMYK)
engine.block.setColor(shadowBlockFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.05,
tint: 1.0
});
// Enable drop shadow and set CMYK color
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowOffsetX(shadowBlock, 10);
engine.block.setDropShadowOffsetY(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 15);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 15);
const cmykShadowColor: CMYKColor = {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.6,
tint: 0.8
};
engine.block.setColor(shadowBlock, 'dropShadow/color', cmykShadowColor);
```
Enable the drop shadow first, configure its offset and blur radius, then apply the CMYK color.
## Reading CMYK Colors
Retrieve color values from blocks using `engine.block.getColor()`. The returned color could be RGBA, CMYK, or SpotColor, so use the `isCMYKColor()` type guard to check:
```typescript highlight-read-color
// Example 8: Read CMYK color from a block
const { fill: readFill } = createColorBlock(560, 220, 150, 150, 'ellipse');
const cmykOrange: CMYKColor = { c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0 };
engine.block.setColor(readFill, 'fill/color/value', cmykOrange);
// Retrieve and check the color
const retrievedColor = engine.block.getColor(readFill, 'fill/color/value');
if (isCMYKColor(retrievedColor)) {
// eslint-disable-next-line no-console
console.log(
`CMYK Color - C: ${retrievedColor.c}, M: ${retrievedColor.m}, Y: ${retrievedColor.y}, K: ${retrievedColor.k}, Tint: ${retrievedColor.tint}`
);
}
```
The `isCMYKColor()` type guard checks if a color has the CMYK properties (`c`, `m`, `y`, `k`).
## Converting Between Color Spaces
Use `engine.editor.convertColorToColorSpace()` to convert colors between 'sRGB' and 'CMYK':
```typescript highlight-convert
// Example 9: Convert RGB to CMYK
const rgbBlue: RGBAColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 };
const convertedCmyk = engine.editor.convertColorToColorSpace(
rgbBlue,
'CMYK'
);
const { fill: convertedFill } = createColorBlock(50, 390, 150, 150);
engine.block.setColor(convertedFill, 'fill/color/value', convertedCmyk);
// eslint-disable-next-line no-console
console.log('RGB to CMYK conversion:', convertedCmyk);
// Example 10: Convert CMYK to RGB (for demonstration)
const cmykGreen: CMYKColor = { c: 0.7, m: 0.0, y: 1.0, k: 0.2, tint: 1.0 };
const convertedRgb = engine.editor.convertColorToColorSpace(
cmykGreen,
'sRGB'
);
// eslint-disable-next-line no-console
console.log('CMYK to RGB conversion:', convertedRgb);
// Display using original CMYK color
const { fill: previewFill } = createColorBlock(220, 390, 150, 150);
engine.block.setColor(previewFill, 'fill/color/value', cmykGreen);
```
Note that color conversions may not be perfectly reversible due to differences in color gamuts between RGB and CMYK. Some RGB colors cannot be accurately represented in CMYK and vice versa.
## Using CMYK in Gradients
CMYK colors work in gradient color stops. Create a gradient fill and set stops using `engine.block.setGradientColorStops()`:
```typescript highlight-gradient
// Example 11: Use CMYK colors in gradients
const gradientBlock = engine.block.create('graphic');
const gradientShape = engine.block.createShape('rect');
engine.block.setShape(gradientBlock, gradientShape);
engine.block.setWidth(gradientBlock, 320);
engine.block.setHeight(gradientBlock, 150);
engine.block.setPositionX(gradientBlock, 390);
engine.block.setPositionY(gradientBlock, 390);
engine.block.appendChild(page, gradientBlock);
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setFill(gradientBlock, gradientFill);
// Set gradient stops with CMYK colors
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0 },
{ color: { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0.5 },
{ color: { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 }
]);
```
This creates a gradient transitioning through the primary CMYK colors—cyan, magenta, and yellow.
## Troubleshooting
### Colors Look Different on Screen vs Print
Screen displays convert CMYK to RGB for preview. The exported PDF retains original CMYK values. For accurate color preview, use calibrated monitors and proof prints.
### Tint Not Having Expected Effect
Ensure the tint value is between 0 and 1. A tint of 0 makes the color fully transparent, while 1 applies full intensity.
### Type Guard Returns False
Make sure you're checking a `Color` value returned from `engine.block.getColor()`. The `isCMYKColor()` function only works with color values, not arbitrary objects.
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.block.setColor()` | Set a color property value |
| `engine.block.getColor()` | Get a color property from a block |
| `engine.editor.convertColorToColorSpace()` | Convert color to a different color space |
| `engine.block.createFill()` | Create a color fill |
| `engine.block.setFill()` | Assign a fill to a block |
| `engine.block.getFill()` | Get the fill from a block |
| `engine.block.setGradientColorStops()` | Set gradient color stops |
| `isCMYKColor()` | Check if a color is CMYK |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Spot Colors"
description: "Define, apply, and manage spot colors for professional print workflows with premixed inks."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/for-print/spot-c3a150/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/sveltekit/colors/for-print-59bc05/) > [Spot Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-print/spot-c3a150/)
---
Define and manage spot colors programmatically for professional print workflows with exact color matching through premixed inks.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-print-spot-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-print-spot-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-for-print-spot-browser/)
Spot colors are named colors reproduced using premixed inks in print production, providing exact color matching that CMYK process colors cannot guarantee. CE.SDK maintains a registry of spot color definitions at the editor level, where each spot color has a name and screen approximations (RGB and/or CMYK) for display purposes. The actual premixed ink is used during printing based on the color name.
```typescript file=@cesdk_web_examples/guides-colors-for-print-spot-browser/browser.ts reference-only
import type {
EditorPlugin,
EditorPluginContext,
SpotColor
} from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
// Type guard to check if a color is a SpotColor
// Color can be RGBAColor, CMYKColor, or SpotColor
const isSpotColor = (color: unknown): color is SpotColor => {
return (
typeof color === 'object' &&
color !== null &&
'name' in color &&
'tint' in color &&
'externalReference' in color
);
};
/**
* CE.SDK Plugin: Spot Colors Guide
*
* This example demonstrates:
* - Defining spot colors with RGB and CMYK approximations
* - Applying spot colors to fills, strokes, and shadows
* - Using tints for lighter color variations
* - Querying and updating spot color definitions
* - Removing spot colors and handling the magenta fallback
* - Assigning spot colors to cutout types
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
// Create a design scene using CE.SDK convenience method
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the page
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Set page background to light gray for visibility
const pageFill = engine.block.getFill(page);
engine.block.setColor(pageFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
// Helper function to create a graphic block with a color fill
const createColorBlock = (
x: number,
y: number,
width: number,
height: number,
shape: 'rect' | 'ellipse' = 'rect'
): { block: number; fill: number } => {
const block = engine.block.create('graphic');
const blockShape = engine.block.createShape(shape);
engine.block.setShape(block, blockShape);
engine.block.setWidth(block, width);
engine.block.setHeight(block, height);
engine.block.setPositionX(block, x);
engine.block.setPositionY(block, y);
engine.block.appendChild(page, block);
const colorFill = engine.block.createFill('color');
engine.block.setFill(block, colorFill);
return { block, fill: colorFill };
};
// Define a spot color with RGB approximation
// RGB values range from 0.0 to 1.0
engine.editor.setSpotColorRGB('Brand-Primary', 0.8, 0.1, 0.2);
// Add CMYK approximation for the same spot color
// This provides print-accurate preview in addition to screen display
engine.editor.setSpotColorCMYK('Brand-Primary', 0.05, 0.95, 0.85, 0.0);
// Define another spot color with both approximations
engine.editor.setSpotColorRGB('Brand-Accent', 0.2, 0.4, 0.8);
engine.editor.setSpotColorCMYK('Brand-Accent', 0.75, 0.5, 0.0, 0.0);
// Apply spot colors to fills using SpotColor objects
// The tint property (0.0 to 1.0) controls color intensity
// The externalReference field stores metadata like color system origin
const brandPrimary: SpotColor = {
name: 'Brand-Primary',
tint: 1.0,
externalReference: ''
};
// Create a block and apply the Brand-Primary spot color
const { fill: primaryFill } = createColorBlock(50, 50, 150, 150);
engine.block.setColor(primaryFill, 'fill/color/value', brandPrimary);
// Apply Brand-Accent to another block
const brandAccent: SpotColor = {
name: 'Brand-Accent',
tint: 1.0,
externalReference: ''
};
const { fill: accentFill } = createColorBlock(220, 50, 150, 150);
engine.block.setColor(accentFill, 'fill/color/value', brandAccent);
// Use tints for lighter variations without defining new spot colors
// Tint of 0.5 gives 50% color intensity
const brandPrimaryHalfTint: SpotColor = {
name: 'Brand-Primary',
tint: 0.5,
externalReference: ''
};
const { fill: tintedFill } = createColorBlock(390, 50, 150, 150, 'ellipse');
engine.block.setColor(tintedFill, 'fill/color/value', brandPrimaryHalfTint);
// Create a gradient of tints
const brandAccentLightTint: SpotColor = {
name: 'Brand-Accent',
tint: 0.3,
externalReference: ''
};
const { fill: lightTintFill } = createColorBlock(560, 50, 150, 150);
engine.block.setColor(
lightTintFill,
'fill/color/value',
brandAccentLightTint
);
// Apply spot colors to strokes and shadows
const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock(
50,
220,
150,
150
);
// Set fill to white
engine.block.setColor(strokeBlockFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Enable stroke and apply spot color
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 8);
const strokeColor: SpotColor = {
name: 'Brand-Primary',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(strokeBlock, 'stroke/color', strokeColor);
// Apply spot color to drop shadow
const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock(
220,
220,
150,
150
);
engine.block.setColor(shadowBlockFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowOffsetX(shadowBlock, 10);
engine.block.setDropShadowOffsetY(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 15);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 15);
const shadowColor: SpotColor = {
name: 'Brand-Accent',
tint: 0.8,
externalReference: ''
};
engine.block.setColor(shadowBlock, 'dropShadow/color', shadowColor);
// Query all defined spot colors
const spotColors = engine.editor.findAllSpotColors();
// eslint-disable-next-line no-console
console.log('Defined spot colors:', spotColors);
// Query RGB approximation for a spot color
const rgbaApprox = engine.editor.getSpotColorRGBA('Brand-Primary');
// eslint-disable-next-line no-console
console.log('Brand-Primary RGB approximation:', rgbaApprox);
// Query CMYK approximation for a spot color
const cmykApprox = engine.editor.getSpotColorCMYK('Brand-Primary');
// eslint-disable-next-line no-console
console.log('Brand-Primary CMYK approximation:', cmykApprox);
// Read back the color from a block and check if it's a spot color
const retrievedColor = engine.block.getColor(
primaryFill,
'fill/color/value'
);
if (isSpotColor(retrievedColor)) {
// eslint-disable-next-line no-console
console.log(
`Retrieved SpotColor - Name: ${retrievedColor.name}, Tint: ${retrievedColor.tint}`
);
}
// Update an existing spot color's approximation
// This changes how the color appears on screen without affecting the color name
engine.editor.setSpotColorRGB('Brand-Accent', 0.3, 0.5, 0.9);
// eslint-disable-next-line no-console
console.log('Updated Brand-Accent RGB approximation');
// Show the updated color in a new block
const { fill: updatedFill } = createColorBlock(390, 220, 150, 150);
const updatedAccent: SpotColor = {
name: 'Brand-Accent',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(updatedFill, 'fill/color/value', updatedAccent);
// Define a temporary spot color
engine.editor.setSpotColorRGB('Temporary-Color', 0.5, 0.8, 0.3);
// Create a block using the temporary color
const { fill: tempFill } = createColorBlock(560, 220, 150, 150);
const tempColor: SpotColor = {
name: 'Temporary-Color',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(tempFill, 'fill/color/value', tempColor);
// Remove the spot color definition
// Blocks using this color will display magenta (default fallback)
engine.editor.removeSpotColor('Temporary-Color');
// eslint-disable-next-line no-console
console.log('Removed Temporary-Color - block now shows magenta fallback');
// Verify the color is no longer defined
const remainingSpotColors = engine.editor.findAllSpotColors();
// eslint-disable-next-line no-console
console.log('Remaining spot colors:', remainingSpotColors);
// Assign spot colors to cutout types for die-cutting operations
// First define a spot color for the die line
engine.editor.setSpotColorRGB('DieLine', 1.0, 0.0, 1.0);
engine.editor.setSpotColorCMYK('DieLine', 0.0, 1.0, 0.0, 0.0);
// Associate the spot color with a cutout type
// CutoutType can be 'Solid' or 'Dashed'
engine.editor.setSpotColorForCutoutType('Solid', 'DieLine');
// Query the assigned spot color
const cutoutSpotColor = engine.editor.getSpotColorForCutoutType('Solid');
// eslint-disable-next-line no-console
console.log('Cutout type Solid uses spot color:', cutoutSpotColor);
// Create a legend block with text
const textBlock = engine.block.create('text');
engine.block.replaceText(textBlock, 'Spot Colors Demo');
engine.block.setTextFontSize(textBlock, 36);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setPositionX(textBlock, 50);
engine.block.setPositionY(textBlock, 400);
engine.block.appendChild(page, textBlock);
// Create smaller label texts
const labels = [
{ text: 'Brand-Primary', x: 50, y: 205 },
{ text: 'Brand-Accent', x: 220, y: 205 },
{ text: 'Primary 50%', x: 390, y: 205 },
{ text: 'Accent 30%', x: 560, y: 205 },
{ text: 'Stroke', x: 50, y: 375 },
{ text: 'Shadow', x: 220, y: 375 },
{ text: 'Updated', x: 390, y: 375 },
{ text: 'Removed', x: 560, y: 375 }
];
for (const label of labels) {
const labelBlock = engine.block.create('text');
engine.block.replaceText(labelBlock, label.text);
engine.block.setTextFontSize(labelBlock, 14);
engine.block.setWidthMode(labelBlock, 'Auto');
engine.block.setHeightMode(labelBlock, 'Auto');
engine.block.setPositionX(labelBlock, label.x);
engine.block.setPositionY(labelBlock, label.y);
engine.block.appendChild(page, labelBlock);
}
// Zoom to fit all content
await engine.scene.zoomToBlock(page, {
padding: {
left: 40,
top: 40,
right: 40,
bottom: 40
}
});
}
}
export default Example;
```
This guide covers how to define spot colors with RGB and CMYK approximations, apply them to design elements with varying tints, query and update definitions, and assign spot colors to cutout types for die-cutting operations.
## Understanding Spot Colors
Spot colors differ from CMYK process colors in several important ways:
- **Exact color matching** - Premixed inks guarantee consistent color reproduction across print runs
- **Brand consistency** - Essential for logos and brand colors (e.g., Pantone colors)
- **Specialty effects** - Enable metallic, fluorescent, and other specialty inks
- **Color gamut** - Some colors are unreproducible with CMYK process inks
In CE.SDK, spot colors have three components:
- **Name** - The identifier used in print output (e.g., "Pantone-485-C")
- **Approximations** - RGB and/or CMYK values for screen display
- **Tint** - A value from 0.0 to 1.0 controlling color intensity
## Define Spot Colors
### RGB Approximation
We register spot colors using `engine.editor.setSpotColorRGB()`. This creates a new spot color if the name doesn't exist, or updates the approximation if it does.
```typescript highlight-define-spot-rgb
// Define a spot color with RGB approximation
// RGB values range from 0.0 to 1.0
engine.editor.setSpotColorRGB('Brand-Primary', 0.8, 0.1, 0.2);
```
RGB approximations display the spot color on screen during editing. The values range from 0.0 to 1.0 for each channel.
### CMYK Approximation
We can add a CMYK approximation using `engine.editor.setSpotColorCMYK()`. This provides print-accurate previews alongside the RGB screen display.
```typescript highlight-define-spot-cmyk
// Add CMYK approximation for the same spot color
// This provides print-accurate preview in addition to screen display
engine.editor.setSpotColorCMYK('Brand-Primary', 0.05, 0.95, 0.85, 0.0);
// Define another spot color with both approximations
engine.editor.setSpotColorRGB('Brand-Accent', 0.2, 0.4, 0.8);
engine.editor.setSpotColorCMYK('Brand-Accent', 0.75, 0.5, 0.0, 0.0);
```
For best results, provide both RGB and CMYK approximations for each spot color. RGB displays on screen while CMYK enables accurate print preview.
## Apply Spot Colors to Design Elements
We apply spot colors to blocks using `engine.block.setColor()` with a SpotColor object containing `name`, `tint`, and `externalReference`.
```typescript highlight-apply-spot-fill
// Apply spot colors to fills using SpotColor objects
// The tint property (0.0 to 1.0) controls color intensity
// The externalReference field stores metadata like color system origin
const brandPrimary: SpotColor = {
name: 'Brand-Primary',
tint: 1.0,
externalReference: ''
};
// Create a block and apply the Brand-Primary spot color
const { fill: primaryFill } = createColorBlock(50, 50, 150, 150);
engine.block.setColor(primaryFill, 'fill/color/value', brandPrimary);
```
The SpotColor object has these properties:
- **name** - Must match a defined spot color exactly (case-sensitive)
- **tint** - Controls intensity from 0.0 (transparent) to 1.0 (full strength)
- **externalReference** - Optional metadata like the color system origin (e.g., "Pantone")
> **Note:** The spot color must be defined before applying it to blocks. Undefined spot colors display as magenta—the default fallback color.
### Using Tints
Tints create lighter variations without defining separate spot colors for each shade. A tint of 0.5 gives 50% color intensity.
```typescript highlight-tint
// Use tints for lighter variations without defining new spot colors
// Tint of 0.5 gives 50% color intensity
const brandPrimaryHalfTint: SpotColor = {
name: 'Brand-Primary',
tint: 0.5,
externalReference: ''
};
const { fill: tintedFill } = createColorBlock(390, 50, 150, 150, 'ellipse');
engine.block.setColor(tintedFill, 'fill/color/value', brandPrimaryHalfTint);
// Create a gradient of tints
const brandAccentLightTint: SpotColor = {
name: 'Brand-Accent',
tint: 0.3,
externalReference: ''
};
const { fill: lightTintFill } = createColorBlock(560, 50, 150, 150);
engine.block.setColor(
lightTintFill,
'fill/color/value',
brandAccentLightTint
);
```
Use tints for:
- Color variations in design systems
- Lighter backgrounds using brand colors
- Gradient-like effects with consistent spot color names
### Strokes and Shadows
Spot colors work with any color property, including strokes and drop shadows.
```typescript highlight-stroke-shadow
// Apply spot colors to strokes and shadows
const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock(
50,
220,
150,
150
);
// Set fill to white
engine.block.setColor(strokeBlockFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Enable stroke and apply spot color
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 8);
const strokeColor: SpotColor = {
name: 'Brand-Primary',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(strokeBlock, 'stroke/color', strokeColor);
// Apply spot color to drop shadow
const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock(
220,
220,
150,
150
);
engine.block.setColor(shadowBlockFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowOffsetX(shadowBlock, 10);
engine.block.setDropShadowOffsetY(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 15);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 15);
const shadowColor: SpotColor = {
name: 'Brand-Accent',
tint: 0.8,
externalReference: ''
};
engine.block.setColor(shadowBlock, 'dropShadow/color', shadowColor);
```
The `stroke/color` and `dropShadow/color` properties accept the same SpotColor objects as fill colors.
## Query Spot Color Definitions
### List Defined Spot Colors
We retrieve all defined spot colors with `engine.editor.findAllSpotColors()`.
```typescript highlight-query-spot
// Query all defined spot colors
const spotColors = engine.editor.findAllSpotColors();
// eslint-disable-next-line no-console
console.log('Defined spot colors:', spotColors);
// Query RGB approximation for a spot color
const rgbaApprox = engine.editor.getSpotColorRGBA('Brand-Primary');
// eslint-disable-next-line no-console
console.log('Brand-Primary RGB approximation:', rgbaApprox);
// Query CMYK approximation for a spot color
const cmykApprox = engine.editor.getSpotColorCMYK('Brand-Primary');
// eslint-disable-next-line no-console
console.log('Brand-Primary CMYK approximation:', cmykApprox);
// Read back the color from a block and check if it's a spot color
const retrievedColor = engine.block.getColor(
primaryFill,
'fill/color/value'
);
if (isSpotColor(retrievedColor)) {
// eslint-disable-next-line no-console
console.log(
`Retrieved SpotColor - Name: ${retrievedColor.name}, Tint: ${retrievedColor.tint}`
);
}
```
This returns an array of spot color names currently registered in the editor.
### Get Color Approximations
Query individual color approximations with `engine.editor.getSpotColorRGBA()` or `engine.editor.getSpotColorCMYK()`.
Querying an undefined spot color returns magenta values—use this to detect missing definitions.
### Read Colors from Blocks
When reading a color back from a block, `engine.block.getColor()` can return an `RGBAColor`, `CMYKColor`, or `SpotColor`. Use a type guard to check if it's a SpotColor:
```typescript
const isSpotColor = (color: unknown): color is SpotColor => {
return (
typeof color === 'object' &&
color !== null &&
'name' in color &&
'tint' in color &&
'externalReference' in color
);
};
const retrievedColor = engine.block.getColor(fill, 'fill/color/value');
if (isSpotColor(retrievedColor)) {
console.log(`Name: ${retrievedColor.name}, Tint: ${retrievedColor.tint}`);
}
```
## Update and Remove Spot Colors
### Update Approximations
We update spot colors by calling the set methods again with the same name. This changes how the color appears on screen without affecting the color name in the print output.
```typescript highlight-update-spot
// Update an existing spot color's approximation
// This changes how the color appears on screen without affecting the color name
engine.editor.setSpotColorRGB('Brand-Accent', 0.3, 0.5, 0.9);
// eslint-disable-next-line no-console
console.log('Updated Brand-Accent RGB approximation');
// Show the updated color in a new block
const { fill: updatedFill } = createColorBlock(390, 220, 150, 150);
const updatedAccent: SpotColor = {
name: 'Brand-Accent',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(updatedFill, 'fill/color/value', updatedAccent);
```
Existing blocks using that spot color automatically reflect the updated approximation.
### Remove Spot Colors
We remove spot colors with `engine.editor.removeSpotColor()` when they're no longer needed.
```typescript highlight-remove-spot
// Define a temporary spot color
engine.editor.setSpotColorRGB('Temporary-Color', 0.5, 0.8, 0.3);
// Create a block using the temporary color
const { fill: tempFill } = createColorBlock(560, 220, 150, 150);
const tempColor: SpotColor = {
name: 'Temporary-Color',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(tempFill, 'fill/color/value', tempColor);
// Remove the spot color definition
// Blocks using this color will display magenta (default fallback)
engine.editor.removeSpotColor('Temporary-Color');
// eslint-disable-next-line no-console
console.log('Removed Temporary-Color - block now shows magenta fallback');
// Verify the color is no longer defined
const remainingSpotColors = engine.editor.findAllSpotColors();
// eslint-disable-next-line no-console
console.log('Remaining spot colors:', remainingSpotColors);
```
Removing a spot color doesn't affect blocks already using it—they display magenta until redefined or until you apply a different color.
## Spot Colors for Cutouts
CE.SDK supports assigning spot colors to cutout types for die-cutting, embossing, and other print finishing operations.
```typescript highlight-cutout
// Assign spot colors to cutout types for die-cutting operations
// First define a spot color for the die line
engine.editor.setSpotColorRGB('DieLine', 1.0, 0.0, 1.0);
engine.editor.setSpotColorCMYK('DieLine', 0.0, 1.0, 0.0, 0.0);
// Associate the spot color with a cutout type
// CutoutType can be 'Solid' or 'Dashed'
engine.editor.setSpotColorForCutoutType('Solid', 'DieLine');
// Query the assigned spot color
const cutoutSpotColor = engine.editor.getSpotColorForCutoutType('Solid');
// eslint-disable-next-line no-console
console.log('Cutout type Solid uses spot color:', cutoutSpotColor);
```
Use `engine.editor.setSpotColorForCutoutType()` to associate a spot color with a specific cutout type. Available cutout types are `'Solid'` and `'Dashed'`, representing different die-line styles used in print finishing. All cutout blocks of that type automatically use the assigned spot color in the output. Query the assignment with `engine.editor.getSpotColorForCutoutType()`.
## Best Practices
**Define early** - Register spot colors at initialization before applying them to blocks. Undefined colors display as magenta, which can confuse users.
**Use descriptive names** - Match your print vendor's reference (e.g., "Pantone-485-C") to ensure correct ink matching in production.
**Provide both approximations** - RGB for screen display, CMYK for print-accurate previews. This gives designers the best experience across different workflows.
**Use tints sparingly** - Prefer tints (0.0-1.0) for lighter variations rather than defining separate spot colors for each shade. This keeps your spot color list manageable.
**Validate before export** - Query `findAllSpotColors()` to verify all expected spot colors are defined before exporting for print.
## Troubleshooting
### Spot Color Displays as Magenta
The spot color hasn't been defined. Call `setSpotColorRGB()` or `setSpotColorCMYK()` with the color name before applying it to blocks.
### Color Approximation Looks Wrong
Update the approximation values using `setSpotColorRGB()` or `setSpotColorCMYK()`. Remember that RGB values are for screen display while CMYK values are for print preview.
### Spot Color Not in Output
Verify the spot color name matches exactly (names are case-sensitive). Check that the block is using a SpotColor object, not an RGB or CMYK color value.
### Can't Remove Spot Color
Ensure you're using the exact name string. Note that removing a spot color doesn't update existing blocks—they'll show magenta until redefined or replaced with a different color.
## API Reference
| Method | Description |
| ------------------------------------------------- | ------------------------------------------------ |
| `engine.editor.setSpotColorRGB(name, r, g, b)` | Define/update spot color with RGB approximation |
| `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define/update spot color with CMYK approximation |
| `engine.editor.findAllSpotColors()` | Get array of all defined spot color names |
| `engine.editor.getSpotColorRGBA(name)` | Query RGB approximation for a spot color |
| `engine.editor.getSpotColorCMYK(name)` | Query CMYK approximation for a spot color |
| `engine.editor.removeSpotColor(name)` | Remove a spot color from the registry |
| `engine.editor.setSpotColorForCutoutType(type, color)` | Assign spot color to a cutout type |
| `engine.editor.getSpotColorForCutoutType(type)` | Get spot color assigned to a cutout type |
| `engine.block.setColor(block, property, color)` | Apply color (including SpotColor) to a property |
| `engine.block.getColor(block, property)` | Read color from a block property |
## Next Steps
- [Export for Printing](https://img.ly/docs/cesdk/sveltekit/export-save-publish/for-printing-bca896/) - Export designs with spot colors for professional print production
- [Apply Colors](https://img.ly/docs/cesdk/sveltekit/colors/apply-2211e3/) - Apply colors to fills, strokes, and shadows
- [CMYK Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-print/cmyk-8a1334/) - Work with CMYK process colors
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "For Screen"
description: "Documentation for For Screen"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/for-screen-1911f8/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/sveltekit/colors/for-screen-1911f8/)
---
---
## Related Pages
- [sRGB Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-screen/srgb-e6f59b/) - Work with sRGB colors for screen-based designs including creating RGBA colors, applying them to design elements, and converting from other color spaces.
- [P3 Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-screen/p3-706127/) - Understand the P3 wide color gamut, its platform availability in CE.SDK, and alternatives for Web development.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "P3 Colors"
description: "Understand the P3 wide color gamut, its platform availability in CE.SDK, and alternatives for Web development."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/for-screen/p3-706127/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/sveltekit/colors/for-screen-1911f8/) > [P3 Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-screen/p3-706127/)
---
Understand the P3 wide color gamut and its availability across CE.SDK platforms.
> **Not Available on Web:** P3 colors are not currently supported on Web platforms. Use [sRGB colors](https://img.ly/docs/cesdk/sveltekit/colors/for-screen/srgb-e6f59b/) instead.
P3 enables more vibrant reds, greens, and other colors beyond the standard sRGB gamut. This is valuable for displays that support the DCI-P3 color space, including modern Apple devices and high-end monitors.
## What is P3?
The DCI-P3 color space was developed for digital cinema and has been widely adopted in consumer displays, particularly by Apple since 2016. P3 covers roughly 25% more visible colors than sRGB, especially in the red, orange, and green-cyan regions.
Key differences from sRGB:
- **Gamut size**: P3 encompasses a larger color range
- **Primary colors**: P3 red and green are more saturated
- **Backwards compatibility**: P3 content on sRGB displays is automatically converted
P3 colors only appear more vibrant on P3-capable displays. On sRGB displays, colors are converted and may appear less saturated.
## Platform Support
**Supported platforms:**
- **Android**: `supportsP3()` and `checkP3Support()` APIs
- **iOS/Swift**: `supportsP3()` and `checkP3Support()` APIs
**Not supported:**
- Browser
- Server (Node.js)
On Web platforms, CE.SDK uses sRGB as the working color space. The Web binding supports sRGB, CMYK, and Spot Colors.
## P3 vs sRGB: When to Use Each
| Use Case | Recommended |
| --- | --- |
| Native mobile apps (Apple devices) | P3 |
| Photo/video editing with color accuracy | P3 |
| Web applications | sRGB |
| Cross-platform consistency | sRGB |
## Next Steps
- [sRGB Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-screen/srgb-e6f59b/) — Apply sRGB colors for screen output
- [Color Conversion](https://img.ly/docs/cesdk/sveltekit/colors/conversion-bcd82b/) — Convert between color spaces
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "sRGB Colors"
description: "Work with sRGB colors for screen-based designs including creating RGBA colors, applying them to design elements, and converting from other color spaces."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/for-screen/srgb-e6f59b/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/sveltekit/colors/for-screen-1911f8/) > [sRGB Colors](https://img.ly/docs/cesdk/sveltekit/colors/for-screen/srgb-e6f59b/)
---
Apply sRGB colors to design elements for screen-based output using RGBA color values with red, green, blue, and alpha components.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-screen-srgb-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-screen-srgb-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-for-screen-srgb-browser/)
sRGB is the standard color space for screen displays. CE.SDK represents sRGB colors as RGBA objects where each component (red, green, blue, alpha) uses floating-point values between 0.0 and 1.0. This differs from the traditional 0-255 integer range used in many design tools.
```typescript file=@cesdk_web_examples/guides-colors-for-screen-srgb-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import { isRGBAColor } from '@cesdk/engine';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: sRGB Colors Guide
*
* This example demonstrates:
* - Creating sRGB/RGBA colors
* - Applying sRGB colors to fills, strokes, and shadows
* - Retrieving colors from design elements
* - Converting colors to sRGB
* - Working with alpha transparency
* - Using type guards to identify RGBA colors
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
// Create a design scene using CE.SDK
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the page
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Create RGBA color objects for sRGB color space
// Values are floating-point numbers between 0.0 and 1.0
const blueColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 };
const redColor = { r: 0.9, g: 0.2, b: 0.2, a: 1.0 };
const greenColor = { r: 0.2, g: 0.8, b: 0.3, a: 1.0 };
// Create semi-transparent colors using the alpha channel
// Alpha of 0.5 means 50% opacity
const semiTransparentPurple = { r: 0.6, g: 0.2, b: 0.8, a: 0.5 };
const semiTransparentOrange = { r: 1.0, g: 0.5, b: 0.0, a: 0.7 };
// Create blocks to demonstrate color application
const block1 = engine.block.create('graphic');
engine.block.setShape(block1, engine.block.createShape('rect'));
engine.block.setWidth(block1, 150);
engine.block.setHeight(block1, 150);
engine.block.setPositionX(block1, 50);
engine.block.setPositionY(block1, 50);
engine.block.appendChild(page, block1);
const block2 = engine.block.create('graphic');
engine.block.setShape(block2, engine.block.createShape('ellipse'));
engine.block.setWidth(block2, 150);
engine.block.setHeight(block2, 150);
engine.block.setPositionX(block2, 250);
engine.block.setPositionY(block2, 50);
engine.block.appendChild(page, block2);
const block3 = engine.block.create('graphic');
engine.block.setShape(block3, engine.block.createShape('rect'));
engine.block.setWidth(block3, 150);
engine.block.setHeight(block3, 150);
engine.block.setPositionX(block3, 450);
engine.block.setPositionY(block3, 50);
engine.block.appendChild(page, block3);
// Apply sRGB colors to block fills
// First create a color fill, then set its color value
const fill1 = engine.block.createFill('color');
engine.block.setFill(block1, fill1);
engine.block.setColor(fill1, 'fill/color/value', blueColor);
const fill2 = engine.block.createFill('color');
engine.block.setFill(block2, fill2);
engine.block.setColor(fill2, 'fill/color/value', redColor);
const fill3 = engine.block.createFill('color');
engine.block.setFill(block3, fill3);
engine.block.setColor(fill3, 'fill/color/value', greenColor);
// Create blocks for stroke demonstration
const strokeBlock = engine.block.create('graphic');
engine.block.setShape(strokeBlock, engine.block.createShape('rect'));
engine.block.setWidth(strokeBlock, 150);
engine.block.setHeight(strokeBlock, 150);
engine.block.setPositionX(strokeBlock, 50);
engine.block.setPositionY(strokeBlock, 250);
engine.block.appendChild(page, strokeBlock);
const strokeFill = engine.block.createFill('color');
engine.block.setFill(strokeBlock, strokeFill);
engine.block.setColor(strokeFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
// Apply sRGB color to stroke
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 5);
engine.block.setColor(strokeBlock, 'stroke/color', {
r: 0.1,
g: 0.1,
b: 0.5,
a: 1.0
});
// Create block for drop shadow demonstration
const shadowBlock = engine.block.create('graphic');
engine.block.setShape(shadowBlock, engine.block.createShape('rect'));
engine.block.setWidth(shadowBlock, 150);
engine.block.setHeight(shadowBlock, 150);
engine.block.setPositionX(shadowBlock, 250);
engine.block.setPositionY(shadowBlock, 250);
engine.block.appendChild(page, shadowBlock);
const shadowFill = engine.block.createFill('color');
engine.block.setFill(shadowBlock, shadowFill);
engine.block.setColor(shadowFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Apply sRGB color to drop shadow
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 10);
engine.block.setDropShadowOffsetX(shadowBlock, 5);
engine.block.setDropShadowOffsetY(shadowBlock, 5);
engine.block.setColor(shadowBlock, 'dropShadow/color', {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.4
});
// Create blocks for transparency demonstration
const transparentBlock1 = engine.block.create('graphic');
engine.block.setShape(transparentBlock1, engine.block.createShape('rect'));
engine.block.setWidth(transparentBlock1, 150);
engine.block.setHeight(transparentBlock1, 150);
engine.block.setPositionX(transparentBlock1, 450);
engine.block.setPositionY(transparentBlock1, 250);
engine.block.appendChild(page, transparentBlock1);
const transparentFill1 = engine.block.createFill('color');
engine.block.setFill(transparentBlock1, transparentFill1);
engine.block.setColor(
transparentFill1,
'fill/color/value',
semiTransparentPurple
);
// Overlapping block to show transparency
const transparentBlock2 = engine.block.create('graphic');
engine.block.setShape(
transparentBlock2,
engine.block.createShape('ellipse')
);
engine.block.setWidth(transparentBlock2, 150);
engine.block.setHeight(transparentBlock2, 150);
engine.block.setPositionX(transparentBlock2, 500);
engine.block.setPositionY(transparentBlock2, 300);
engine.block.appendChild(page, transparentBlock2);
const transparentFill2 = engine.block.createFill('color');
engine.block.setFill(transparentBlock2, transparentFill2);
engine.block.setColor(
transparentFill2,
'fill/color/value',
semiTransparentOrange
);
// Retrieve the current color from a design element
const currentColor = engine.block.getColor(fill1, 'fill/color/value');
console.log('Current color:', currentColor);
// Use type guard to check if color is RGBA (sRGB)
if (isRGBAColor(currentColor)) {
console.log('Color is sRGB/RGBA');
console.log('Red:', currentColor.r);
console.log('Green:', currentColor.g);
console.log('Blue:', currentColor.b);
console.log('Alpha:', currentColor.a);
}
// Convert a CMYK color to sRGB
const cmykColor = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 };
const convertedToSrgb = engine.editor.convertColorToColorSpace(
cmykColor,
'sRGB'
);
console.log('Converted to sRGB:', convertedToSrgb);
// Zoom to fit content
await engine.scene.zoomToBlock(page, {
padding: {
left: 40,
top: 40,
right: 40,
bottom: 40
}
});
}
}
export default Example;
```
This guide covers creating RGBA color objects, applying them to fills, strokes, and shadows, retrieving colors from elements, converting colors to sRGB, and working with transparency.
## Using the Built-in Color Picker UI
The built-in color picker allows users to select sRGB colors visually. Users can pick colors from a gradient, enter hex values, or adjust RGB sliders. The color picker automatically handles value conversion between hex and floating-point formats.
The UI includes:
- Color picker gradient and hue slider
- Hex value input field
- RGB component sliders
- Opacity/alpha slider for transparency control
## Creating sRGB Colors Programmatically
We first set up a design scene and get a reference to the page.
```typescript highlight=highlight-setup
// Create a design scene using CE.SDK
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the page
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
```
We create RGBA color objects by specifying r, g, b, and a properties. All four components are required and use values from 0.0 to 1.0.
```typescript highlight=highlight-create-rgba
// Create RGBA color objects for sRGB color space
// Values are floating-point numbers between 0.0 and 1.0
const blueColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 };
const redColor = { r: 0.9, g: 0.2, b: 0.2, a: 1.0 };
const greenColor = { r: 0.2, g: 0.8, b: 0.3, a: 1.0 };
```
The alpha channel controls transparency. A value of 1.0 is fully opaque, while 0.0 is fully transparent.
```typescript highlight=highlight-create-transparent
// Create semi-transparent colors using the alpha channel
// Alpha of 0.5 means 50% opacity
const semiTransparentPurple = { r: 0.6, g: 0.2, b: 0.8, a: 0.5 };
const semiTransparentOrange = { r: 1.0, g: 0.5, b: 0.0, a: 0.7 };
```
## Applying sRGB Colors to Fills
We use `engine.block.setColor()` to apply colors to block properties. For fills, we first create a color fill and then set its color value.
```typescript highlight=highlight-apply-fill
// Apply sRGB colors to block fills
// First create a color fill, then set its color value
const fill1 = engine.block.createFill('color');
engine.block.setFill(block1, fill1);
engine.block.setColor(fill1, 'fill/color/value', blueColor);
const fill2 = engine.block.createFill('color');
engine.block.setFill(block2, fill2);
engine.block.setColor(fill2, 'fill/color/value', redColor);
const fill3 = engine.block.createFill('color');
engine.block.setFill(block3, fill3);
engine.block.setColor(fill3, 'fill/color/value', greenColor);
```
## Applying sRGB Colors to Strokes
We can apply sRGB colors to strokes using the `'stroke/color'` property path.
```typescript highlight=highlight-apply-stroke
// Apply sRGB color to stroke
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 5);
engine.block.setColor(strokeBlock, 'stroke/color', {
r: 0.1,
g: 0.1,
b: 0.5,
a: 1.0
});
```
## Applying sRGB Colors to Shadows
Drop shadows also support sRGB colors. We use the `'dropShadow/color'` property path. A semi-transparent black creates a natural shadow effect.
```typescript highlight=highlight-apply-shadow
// Apply sRGB color to drop shadow
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 10);
engine.block.setDropShadowOffsetX(shadowBlock, 5);
engine.block.setDropShadowOffsetY(shadowBlock, 5);
engine.block.setColor(shadowBlock, 'dropShadow/color', {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.4
});
```
## Retrieving Colors from Elements
We use `engine.block.getColor()` to read the current color from a design element. The returned color could be RGBA, CMYK, or a spot color depending on what was set.
```typescript highlight=highlight-get-color
// Retrieve the current color from a design element
const currentColor = engine.block.getColor(fill1, 'fill/color/value');
console.log('Current color:', currentColor);
```
## Identifying sRGB Colors
We use the `isRGBAColor()` type guard to check if a color is sRGB. This is useful when working with colors that could be from any supported color space.
```typescript highlight=highlight-identify-rgba
// Use type guard to check if color is RGBA (sRGB)
if (isRGBAColor(currentColor)) {
console.log('Color is sRGB/RGBA');
console.log('Red:', currentColor.r);
console.log('Green:', currentColor.g);
console.log('Blue:', currentColor.b);
console.log('Alpha:', currentColor.a);
}
```
## Converting Colors to sRGB
We use `engine.editor.convertColorToColorSpace()` to convert CMYK or spot colors to sRGB for screen display.
```typescript highlight=highlight-convert-to-srgb
// Convert a CMYK color to sRGB
const cmykColor = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 };
const convertedToSrgb = engine.editor.convertColorToColorSpace(
cmykColor,
'sRGB'
);
console.log('Converted to sRGB:', convertedToSrgb);
```
## API Reference
| Method | Description |
| ---------------------------------------- | ---------------------------------------------- |
| `createFill('color')` | Create a new color fill object |
| `setFill(block, fill)` | Assign fill to a block |
| `setColor(block, property, color)` | Set color value (RGBA) |
| `getColor(block, property)` | Get current color value |
| `setStrokeEnabled(block, enabled)` | Enable or disable stroke rendering |
| `setStrokeWidth(block, width)` | Set stroke width |
| `setDropShadowEnabled(block, enabled)` | Enable or disable drop shadow |
| `convertColorToColorSpace(color, space)` | Convert color to specified color space |
| `isRGBAColor(color)` | Type guard to check if color is RGBA |
## Troubleshooting
**Colors appear incorrect:** Verify values are in the 0.0-1.0 range, not 0-255. A value of 255 would be clamped to 1.0.
**Color not visible:** Check that the alpha value is not 0.0 and that the fill or stroke is enabled on the block.
**Type errors:** Ensure all four components (r, g, b, a) are provided for RGBA colors. Omitting alpha will cause validation errors.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Manage color usage in your designs, from applying brand palettes to handling print and screen formats."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/overview-16a177/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [Overview](https://img.ly/docs/cesdk/sveltekit/colors/overview-16a177/)
---
Colors are a fundamental part of design in the CreativeEditor SDK (CE.SDK). Whether you're designing for digital screens or printed materials, consistent color management ensures your creations look the way you intend. CE.SDK offers flexible tools for working with colors through both the user interface and programmatically, making it easy to manage color workflows at any scale.
[Launch Web Demo](https://img.ly/showcases/cesdk)
[Get Started](https://img.ly/docs/cesdk/sveltekit/get-started/overview-e18f40/)
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Replace Individual Colors"
description: "Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects to swap colors or remove backgrounds."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/colors/replace-48cd71/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/sveltekit/colors-a9b79c/) > [Replace Individual Colors](https://img.ly/docs/cesdk/sveltekit/colors/replace-48cd71/)
---
Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-replace-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-replace-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-replace-browser/)
CE.SDK provides two specialized effects for color replacement: the **Recolor** effect swaps pixels matching a source color with a target color, while the **Green Screen** effect removes pixels matching a specified color to create transparency. Both effects use configurable tolerance parameters to control which pixels are affected, enabling use cases from product color variations to background removal.
```typescript file=@cesdk_web_examples/guides-colors-replace-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
import { calculateGridLayout } from './utils';
/**
* CE.SDK Plugin: Replace Colors Guide
*
* Demonstrates color replacement using Recolor and Green Screen effects:
* - Using the built-in effects UI
* - Creating and applying Recolor effects
* - Creating and applying Green Screen effects
* - Configuring effect properties
* - Managing multiple effects
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Enable effects in the inspector panel using the Feature API
cesdk.feature.enable('ly.img.effect');
// Calculate responsive grid layout for 6 examples
const layout = calculateGridLayout(pageWidth, pageHeight, 6);
const { blockWidth, blockHeight, getPosition } = layout;
// Use sample images for demonstrations
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const blockSize = { width: blockWidth, height: blockHeight };
// Create a Recolor effect to swap red colors to blue
const block1 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block1);
const recolorEffect = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0
}); // Red source color
engine.block.setColor(recolorEffect, 'effect/recolor/toColor', {
r: 0.0,
g: 0.5,
b: 1.0,
a: 1.0
}); // Blue target color
engine.block.appendEffect(block1, recolorEffect);
// Select this block to show the effects panel
engine.block.setSelected(block1, true);
// Configure color matching precision for Recolor effect
const block2 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block2);
const recolorEffect2 = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.6,
b: 0.4,
a: 1.0
}); // Skin tone source
engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', {
r: 0.3,
g: 0.7,
b: 0.3,
a: 1.0
}); // Green tint
// Adjust color match tolerance (0-1, higher = more inclusive)
engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3);
// Adjust brightness match tolerance
engine.block.setFloat(
recolorEffect2,
'effect/recolor/brightnessMatch',
0.2
);
// Adjust edge smoothness
engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1);
engine.block.appendEffect(block2, recolorEffect2);
// Create a Green Screen effect to remove green backgrounds
const block3 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block3);
const greenScreenEffect = engine.block.createEffect('green_screen');
// Specify the color to remove (green)
engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0
});
engine.block.appendEffect(block3, greenScreenEffect);
// Fine-tune Green Screen removal parameters
const block4 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block4);
const greenScreenEffect2 = engine.block.createEffect('green_screen');
engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', {
r: 0.2,
g: 0.8,
b: 0.3,
a: 1.0
}); // Specific green shade
// Adjust color match tolerance
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/colorMatch',
0.4
);
// Adjust edge smoothness for cleaner removal
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/smoothness',
0.2
);
// Reduce color spill from green background
engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5);
engine.block.appendEffect(block4, greenScreenEffect2);
// Demonstrate managing multiple effects on a block
const block5 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block5);
// Add multiple effects to the same block
const recolor1 = engine.block.createEffect('recolor');
engine.block.setColor(recolor1, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0
});
engine.block.setColor(recolor1, 'effect/recolor/toColor', {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0
});
engine.block.appendEffect(block5, recolor1);
const recolor2 = engine.block.createEffect('recolor');
engine.block.setColor(recolor2, 'effect/recolor/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0
});
engine.block.setColor(recolor2, 'effect/recolor/toColor', {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0
});
engine.block.appendEffect(block5, recolor2);
// Get all effects on the block
const effects = engine.block.getEffects(block5);
// eslint-disable-next-line no-console
console.log('Number of effects:', effects.length); // 2
// Disable the first effect without removing it
engine.block.setEffectEnabled(effects[0], false);
// Check if effect is enabled
const isEnabled = engine.block.isEffectEnabled(effects[0]);
// eslint-disable-next-line no-console
console.log('First effect enabled:', isEnabled); // false
// Apply consistent color replacement across multiple blocks
const block6 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block6);
// Find all image blocks in the scene
const allBlocks = engine.block.findByType('//ly.img.ubq/graphic');
// Apply a consistent recolor effect to each block
allBlocks.forEach((blockId) => {
// Skip if block already has effects
if (engine.block.getEffects(blockId).length > 0) {
return;
}
const batchRecolor = engine.block.createEffect('recolor');
engine.block.setColor(batchRecolor, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.7,
b: 0.6,
a: 1.0
});
engine.block.setColor(batchRecolor, 'effect/recolor/toColor', {
r: 0.6,
g: 0.7,
b: 0.9,
a: 1.0
});
engine.block.setFloat(batchRecolor, 'effect/recolor/colorMatch', 0.25);
engine.block.appendEffect(blockId, batchRecolor);
});
// Position all blocks in a grid layout
const blocks = [block1, block2, block3, block4, block5, block6];
blocks.forEach((block, index) => {
const pos = getPosition(index);
engine.block.setPositionX(block, pos.x);
engine.block.setPositionY(block, pos.y);
});
// Zoom to show all blocks
engine.block.setSelected(block1, true);
cesdk.engine.scene.zoomToBlock(page);
}
}
export default Example;
```
This guide covers how to enable the built-in effects panel for interactive editing and how to apply and manage color replacement effects programmatically using the Block API.
## Using the Built-in Effects UI
The CE.SDK editor provides a visual effects panel where users can add and configure Recolor and Green Screen effects interactively. Enable the effects feature using the Feature API, then users can access effects through the inspector panel.
To enable effects in your editor configuration:
```typescript highlight=highlight-enable-effects-panel
// Enable effects in the inspector panel using the Feature API
cesdk.feature.enable('ly.img.effect');
```
With effects enabled, users can:
- Select an image block and open the effects panel
- Add Recolor or Green Screen effects from the available options
- Use visual color pickers to select source and target colors
- Adjust tolerance sliders for color match, brightness match, and smoothness
- See changes applied in real-time on the canvas
## Programmatic Color Replacement
For automation workflows or custom implementations, you can create and apply color replacement effects programmatically using the Block API. Effects are created as blocks, configured with properties, and appended to target blocks.
### Creating a Recolor Effect
The Recolor effect replaces pixels matching a source color with a target color. Use `engine.block.createEffect('recolor')` to create the effect, then set the `fromColor` and `toColor` properties using `engine.block.setColor()`.
```typescript highlight=highlight-create-recolor-effect
// Create a Recolor effect to swap red colors to blue
const block1 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block1);
const recolorEffect = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0
}); // Red source color
engine.block.setColor(recolorEffect, 'effect/recolor/toColor', {
r: 0.0,
g: 0.5,
b: 1.0,
a: 1.0
}); // Blue target color
engine.block.appendEffect(block1, recolorEffect);
// Select this block to show the effects panel
engine.block.setSelected(block1, true);
```
The `fromColor` specifies which color to match in the image, and `toColor` defines the replacement color. Colors use RGBA format with values from 0 to 1.
### Configuring Color Matching Precision
Fine-tune which pixels are affected by adjusting the tolerance parameters with `engine.block.setFloat()`:
```typescript highlight=highlight-configure-recolor-matching
// Configure color matching precision for Recolor effect
const block2 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block2);
const recolorEffect2 = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.6,
b: 0.4,
a: 1.0
}); // Skin tone source
engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', {
r: 0.3,
g: 0.7,
b: 0.3,
a: 1.0
}); // Green tint
// Adjust color match tolerance (0-1, higher = more inclusive)
engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3);
// Adjust brightness match tolerance
engine.block.setFloat(
recolorEffect2,
'effect/recolor/brightnessMatch',
0.2
);
// Adjust edge smoothness
engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1);
engine.block.appendEffect(block2, recolorEffect2);
```
The Recolor effect has three precision parameters:
- **colorMatch** (0-1): Controls hue tolerance. Higher values include more color variations around the source color.
- **brightnessMatch** (0-1): Controls luminance tolerance. Higher values include pixels with different brightness levels.
- **smoothness** (0-1): Controls edge blending. Higher values create softer transitions at the boundaries of affected areas.
### Creating a Green Screen Effect
The Green Screen effect removes pixels matching a specified color, making them transparent. This is commonly used for background removal.
```typescript highlight=highlight-create-green-screen-effect
// Create a Green Screen effect to remove green backgrounds
const block3 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block3);
const greenScreenEffect = engine.block.createEffect('green_screen');
// Specify the color to remove (green)
engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0
});
engine.block.appendEffect(block3, greenScreenEffect);
```
Set the `fromColor` property to specify which color to remove. The effect will make matching pixels transparent.
### Configuring Green Screen Parameters
Control the precision of color removal using these parameters:
```typescript highlight=highlight-configure-green-screen
// Fine-tune Green Screen removal parameters
const block4 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block4);
const greenScreenEffect2 = engine.block.createEffect('green_screen');
engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', {
r: 0.2,
g: 0.8,
b: 0.3,
a: 1.0
}); // Specific green shade
// Adjust color match tolerance
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/colorMatch',
0.4
);
// Adjust edge smoothness for cleaner removal
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/smoothness',
0.2
);
// Reduce color spill from green background
engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5);
engine.block.appendEffect(block4, greenScreenEffect2);
```
The Green Screen effect parameters:
- **colorMatch**: Tolerance for matching the background color
- **smoothness**: Edge softness for cleaner cutouts around subjects
- **spill**: Reduces color bleed from the removed background onto the subject, useful when the background color reflects onto edges
## Managing Multiple Effects
A single block can have multiple effects applied. Use the effect management APIs to list, toggle, and remove effects.
```typescript highlight=highlight-manage-effects
// Demonstrate managing multiple effects on a block
const block5 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block5);
// Add multiple effects to the same block
const recolor1 = engine.block.createEffect('recolor');
engine.block.setColor(recolor1, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0
});
engine.block.setColor(recolor1, 'effect/recolor/toColor', {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0
});
engine.block.appendEffect(block5, recolor1);
const recolor2 = engine.block.createEffect('recolor');
engine.block.setColor(recolor2, 'effect/recolor/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0
});
engine.block.setColor(recolor2, 'effect/recolor/toColor', {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0
});
engine.block.appendEffect(block5, recolor2);
// Get all effects on the block
const effects = engine.block.getEffects(block5);
// eslint-disable-next-line no-console
console.log('Number of effects:', effects.length); // 2
// Disable the first effect without removing it
engine.block.setEffectEnabled(effects[0], false);
// Check if effect is enabled
const isEnabled = engine.block.isEffectEnabled(effects[0]);
// eslint-disable-next-line no-console
console.log('First effect enabled:', isEnabled); // false
```
Key effect management methods:
- `engine.block.getEffects(blockId)`: Returns an array of all effect IDs attached to a block
- `engine.block.setEffectEnabled(effectId, enabled)`: Toggle an effect on/off without removing it
- `engine.block.isEffectEnabled(effectId)`: Check whether an effect is currently active
- `engine.block.removeEffect(blockId, index)`: Remove an effect by its index in the effects array
Stacking multiple Recolor effects enables complex color transformations, such as replacing multiple colors in a single image or creating variations.
## Troubleshooting
**Colors not matching as expected**: Increase the `colorMatch` tolerance for broader selection, or decrease it for more precise matching. Check that your source color closely matches the actual color in the image.
**Harsh edges around replaced areas**: Increase the `smoothness` value to create softer transitions at the boundaries of affected pixels.
**Color spill on Green Screen subjects**: Increase the `spill` value to reduce the green tint that often appears on edges when removing green backgrounds.
**Effect not visible**: Verify that the effect is enabled using `isEffectEnabled()` and that it has been appended to the block using `appendEffect()`.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "System Compatibility"
description: "Learn how device performance and hardware limits affect CE.SDK editing, rendering, and export capabilities."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/compatibility-139ef9/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/sveltekit/compatibility-fef719/) > [System Compatibility](https://img.ly/docs/cesdk/sveltekit/compatibility-139ef9/)
---
## Recommended Hardware
| Platform | Hardware |
| ---------------- | ------------------------------------------------------------------------------ |
| Desktop | A notebook or desktop released in the last 7 years and at least 4GB of memory. |
| Mobile (Apple) | iPhone 8, iPad (6th gen) or newer |
| Mobile (Android) | Phones & tablets released in the last 4 years |
## Video
Our video feature introduces additional requirements and we generally distinguish playback (decoding) and export (encoding) capabilities. On the web, certain browser features directly depend on the host operating system. For video, this currently introduces the following limitations:
- Transparency in H.265 videos is **not supported** on Windows hosts.
- **Chrome on Linux** generally doesn't ship with encoder support for H.264 & AAC, which can cause video exports to fail even though decoding of non-free codecs is supported.
- **Firefox** supports video editing (decoding) starting with version 130 via the WebCodecs API. However, video export is **not supported** because Firefox does not include the patent-encumbered H.264 and AAC codecs required for encoding.
- **Chromium** although technically the base of Chrome doesn't include any codecs for licensing reasons and therefore can't be used for video editing. It does fall back to system-provided media libraries on e.g. macOS, but support is not guaranteed in any way.
- **Linux browsers** generally have limited video support due to codec licensing. Video editing may work if the browser can decode H.264/AAC, but video export typically fails because open-source browser builds do not include the required encoders.
- Video is **not supported** on mobile browsers on any platform due to technical limitations which result in performance issues.
To detect these limitations at runtime, use the `video.decode.checkSupport` and `video.encode.checkSupport` actions, or the `cesdk.utils.supportsVideoDecode()` and `cesdk.utils.supportsVideoEncode()` utilities.
## Export Limitations
The export size is limited by the hardware capabilities of the device, e.g., due to the maximum texture size that can be allocated. The maximum possible export size can be queried via API, see [export guide](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/overview-9ed3a8/).
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Compatibility & Security"
description: "Learn about CE.SDK's compatibility and security features."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/compatibility-fef719/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/sveltekit/compatibility-fef719/)
---
CE.SDK provides robust compatibility and security features across platforms.
Learn about supported browsers, frameworks, file formats, language support,
and how CE.SDK ensures secure operation in your applications.
---
## Related Pages
- [Browser Support](https://img.ly/docs/cesdk/sveltekit/browser-support-28c1b0/) - Find out which browsers and versions fully support CE.SDK features, including editing and video capabilities.
- [System Compatibility](https://img.ly/docs/cesdk/sveltekit/compatibility-139ef9/) - Learn how device performance and hardware limits affect CE.SDK editing, rendering, and export capabilities.
- [File Format Support](https://img.ly/docs/cesdk/sveltekit/file-format-support-3c4b2a/) - See which image, video, audio, font, and template formats CE.SDK supports for import and export.
- [Security](https://img.ly/docs/cesdk/sveltekit/security-777bfd/) - Learn how CE.SDK keeps your data private with client-side processing, secure licensing, and GDPR-compliant practices.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Concepts"
description: "Key concepts and principles of CE.SDK"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/)
---
Key Concepts and principles of CE.SDK.
---
## Related Pages
- [Key Concepts](https://img.ly/docs/cesdk/sveltekit/key-concepts-21a270/) - Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control.
- [Key Capabilities](https://img.ly/docs/cesdk/sveltekit/key-capabilities-dbb5b1/) - Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control.
- [Architecture](https://img.ly/docs/cesdk/sveltekit/concepts/architecture-6ea9b2/) - Understand how CE.SDK is structured around the CreativeEngine—the core runtime with six APIs for scenes, blocks, assets, events, variables, and editor state.
- [Terminology](https://img.ly/docs/cesdk/sveltekit/concepts/terminology-99e82d/) - Definitions for the core terms and concepts used throughout CE.SDK documentation, including Engine, Scene, Block, Fill, Shape, Effect, and more.
- [Editing Workflow](https://img.ly/docs/cesdk/sveltekit/concepts/editing-workflow-032d27/) - Control editing access with Creator, Adopter, Viewer, and Presenter roles using global and block-level scopes for tailored permissions.
- [Blocks](https://img.ly/docs/cesdk/sveltekit/concepts/blocks-90241e/) - Learn how blocks define elements in a scene and how to structure them for rendering in CE.SDK.
- [Scenes](https://img.ly/docs/cesdk/sveltekit/concepts/scenes-e8596d/) - Create, configure, save, and load scenes—the root container for all design elements in CE.SDK.
- [Pages](https://img.ly/docs/cesdk/sveltekit/concepts/pages-7b6bae/) - Pages structure scenes in CE.SDK and must share the same dimensions to ensure consistent rendering.
- [Assets](https://img.ly/docs/cesdk/sveltekit/concepts/assets-a84fdd/) - Learn how assets provide external content to CE.SDK designs and how asset sources make them available programmatically.
- [Editor State](https://img.ly/docs/cesdk/sveltekit/concepts/edit-modes-1f5b6c/) - Control how users interact with content by switching between edit modes like transform, crop, and text.
- [Templating](https://img.ly/docs/cesdk/sveltekit/concepts/templating-f94385/) - Understand how templates work in CE.SDK—reusable designs with variables for dynamic text and placeholders for swappable media.
- [Events](https://img.ly/docs/cesdk/sveltekit/concepts/events-353f97/) - Subscribe to block creation, update, and deletion events to track changes in your CE.SDK scene.
- [Buffers](https://img.ly/docs/cesdk/sveltekit/concepts/buffers-9c565b/) - Use buffers to store temporary, non-serializable data in CE.SDK via the CreativeEngine API.
- [Resources](https://img.ly/docs/cesdk/sveltekit/concepts/resources-a58d71/) - Learn how CE.SDK loads and manages external media files, including preloading for performance, handling transient data, and relocating resources when URLs change.
- [Undo and History](https://img.ly/docs/cesdk/sveltekit/concepts/undo-and-history-99479d/) - Manage undo and redo stacks in CE.SDK using multiple histories, callbacks, and API-based controls.
- [Design Units](https://img.ly/docs/cesdk/sveltekit/concepts/design-units-cc6597/) - Configure design units (pixels, millimeters, inches) and DPI settings for print-ready output in CE.SDK.
- [Headless](https://img.ly/docs/cesdk/sveltekit/concepts/headless-mode/browser-24ab98/) - Run headless CE.SDK's Engine inside a browser-based app.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Architecture"
description: "Understand how CE.SDK is structured around the CreativeEngine—the core runtime with six APIs for scenes, blocks, assets, events, variables, and editor state."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/architecture-6ea9b2/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Architecture](https://img.ly/docs/cesdk/sveltekit/concepts/architecture-6ea9b2/)
---
Understand how CE.SDK is structured around the CreativeEngine and its six interconnected APIs.
CE.SDK is built around the **CreativeEngine**—a single-threaded core runtime that manages state, rendering, and coordination between six specialized APIs. Understanding how these pieces connect helps you navigate the SDK effectively.
## The CreativeEngine
The *Engine* is the central coordinator. All operations—creating content, manipulating blocks, rendering, and exporting—flow through it. Initialize it once and access everything else through its API namespaces.
The *Engine* manages:
- **One active scene** containing all design content
- **Six API namespaces** for different domains of functionality
- **Event dispatching** for reactive state management
- **Resource loading** and caching
- **Rendering** to a canvas element (browser) or headless export (server)
## Content Hierarchy
CE.SDK organizes content in a tree: *Scene* → *Pages* → *Blocks*.
- **Scene**: The root container. One scene per engine instance. Supports both static designs and time-based video editing.
- **Pages**: Containers within a scene. Artboards for static designs, time-based compositions for video editing.
- **Blocks**: The atomic units—graphics, text, audio, video. Everything visible is a block.
The **Scene API** manages this hierarchy. The **Block API** manipulates individual blocks within it. See [Scenes](https://img.ly/docs/cesdk/sveltekit/concepts/scenes-e8596d/), [Pages](https://img.ly/docs/cesdk/sveltekit/concepts/pages-7b6bae/), and [Blocks](https://img.ly/docs/cesdk/sveltekit/concepts/blocks-90241e/) for details.
## The Six APIs
The engine exposes six API namespaces. Here's how they interconnect:
### Scene API (`engine.scene`)
Creates and manages the content hierarchy. Works with the *Block API* to populate scenes with content and the *Event API* to notify when structure changes.
### Block API (`engine.block`)
The most-used API. Creates, modifies, and queries blocks. Every visual element flows through here. Blocks reference *Assets* loaded through the *Asset API* and can contain *Variables* managed by the *Variable API*.
### Asset API (`engine.asset`)
Provides content to the *Block API*. Registers asset sources (images, videos, stickers, templates) and handles queries. When you add an image to a block, the *Asset API* resolves it and the *Block API* applies it.
### Variable API (`engine.variable`)
Enables data-driven designs. Define variables at the scene level; reference them in text blocks with `{{variableName}}` syntax. When variable values change, affected blocks update automatically—coordinated through the *Event API*.
### Editor API (`engine.editor`)
Controls application state: edit modes, undo/redo history, user roles, and permissions. The *Editor API* determines what operations the *Block API* can perform based on current role and scope settings.
### Event API (`engine.event`)
The reactive backbone. Subscribe to changes across all other APIs—block modifications, selection changes, history updates. Build UIs that stay synchronized with engine state.
## How They Connect
A typical flow shows the interconnection:
1. **Scene API** creates the content structure
2. **Asset API** provides images, templates, or other content
3. **Block API** creates blocks and applies assets to them
4. **Variable API** injects dynamic data into text blocks
5. **Editor API** controls what users can modify
6. **Event API** notifies your UI of every change
Each API focuses on one domain but works through the others. The *Engine* coordinates these interactions.
## Scene Capabilities
CE.SDK scenes support a range of capabilities:
- **Static designs**: Social posts, print materials, graphics. Blocks positioned spatially.
- **Time-based content**: Duration, playback time, and animation. Blocks arranged across time.
The scene configuration determines which *Block API* properties and *Editor API* capabilities are available. See [Scenes](https://img.ly/docs/cesdk/sveltekit/concepts/scenes-e8596d/) for details.
## Integration Patterns
CE.SDK runs in two contexts:
- **Browser**: The engine renders to a canvas element. Append `engine.element` to your DOM. Use with the built-in UI or build your own.
- **Headless**: No rendering, just processing. Use for server-side exports, automation, and batch operations. See [Headless Mode](https://img.ly/docs/cesdk/sveltekit/concepts/headless-mode/browser-24ab98/).
Both contexts use the same six APIs—only rendering differs.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Assets"
description: "Learn how assets provide external content to CE.SDK designs and how asset sources make them available programmatically."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/assets-a84fdd/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Assets](https://img.ly/docs/cesdk/sveltekit/concepts/assets-a84fdd/)
---
Understand the asset system—how external media and resources like images, stickers, or videos are handled in CE.SDK.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-assets-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-assets-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-assets-browser/)
Images, videos, audio, fonts, stickers, and templates—every premade resource you can add to a design is what we call an *Asset*. The editor gets access to these Assets through *Asset Sources*. When you apply an Asset, CE.SDK creates or modifies a Block to display that content.
```typescript file=@cesdk_web_examples/guides-concepts-assets-browser/browser.ts reference-only
import type {
AssetQueryData,
AssetResult,
AssetSource,
AssetsQueryResult,
EditorPlugin,
EditorPluginContext
} from '@cesdk/cesdk-js';
import packageJson from './package.json';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
/**
* CE.SDK Plugin: Assets Concepts Guide
*
* Demonstrates the core concepts of the asset system:
* - What assets are and how they differ from blocks
* - Creating and registering asset sources
* - Querying and applying assets
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.print.iso.a6.landscape'
}
});
const engine = cesdk.engine;
// An asset is a content definition with metadata
// It describes content that can be added to designs
const stickerAsset: AssetResult = {
id: 'sticker-smile',
label: 'Smile Sticker',
tags: ['emoji', 'happy'],
groups: ['stickers'],
meta: {
uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg',
thumbUri:
'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg',
blockType: '//ly.img.ubq/graphic',
fillType: '//ly.img.ubq/fill/image',
width: 62,
height: 58,
mimeType: 'image/svg+xml'
}
};
// Asset sources provide assets to the editor
// Each source has an id and a findAssets() method
const customSource: AssetSource = {
id: 'my-assets',
async findAssets(query: AssetQueryData): Promise {
// Return paginated results matching the query
return {
assets: [stickerAsset],
total: 1,
currentPage: query.page,
nextPage: undefined
};
}
};
engine.asset.addSource(customSource);
// Query assets from a source
const results = await engine.asset.findAssets('my-assets', {
page: 0,
perPage: 10
});
console.log('Found assets:', results.total);
// Apply an asset to create a block in the scene
if (results.assets.length > 0) {
const blockId = await engine.asset.apply('my-assets', results.assets[0]);
console.log('Created block:', blockId);
// Center the sticker on the page
const page = engine.scene.getCurrentPage();
if (page && blockId) {
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// SVG is 62x58, scale to fit nicely
const stickerWidth = 62;
const stickerHeight = 58;
engine.block.setWidth(blockId, stickerWidth);
engine.block.setHeight(blockId, stickerHeight);
engine.block.setPositionX(blockId, (pageWidth - stickerWidth) / 2);
engine.block.setPositionY(blockId, (pageHeight - stickerHeight) / 2);
}
}
// Local sources support dynamic add/remove operations
engine.asset.addLocalSource('uploads', ['image/svg+xml', 'image/png']);
engine.asset.addAssetToSource('uploads', {
id: 'uploaded-1',
label: { en: 'Heart Sticker' },
meta: {
uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg',
thumbUri:
'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg',
blockType: '//ly.img.ubq/graphic',
fillType: '//ly.img.ubq/fill/image',
mimeType: 'image/svg+xml'
}
});
// Subscribe to asset source lifecycle events
const unsubscribe = engine.asset.onAssetSourceUpdated((sourceId) => {
console.log('Source updated:', sourceId);
});
// Notify that source contents changed
engine.asset.assetSourceContentsChanged('uploads');
unsubscribe();
}
}
export default Example;
```
This guide covers the core concepts of the Asset system. For detailed instructions on inserting specific media types, see the [Images](https://img.ly/docs/cesdk/sveltekit/insert-media/images-63848a/), [Videos](https://img.ly/docs/cesdk/sveltekit/insert-media/videos-a5fa03/), and [Shapes & Stickers](https://img.ly/docs/cesdk/sveltekit/insert-media/shapes-or-stickers-20ac68/) guides.
## Assets vs Blocks
**Assets** are content definitions with metadata (URIs, dimensions, labels) that exist outside the scene. **Blocks** are the visual elements in the scene tree that display content.
When you apply an asset, CE.SDK creates a block configured according to the asset's properties. Multiple blocks can reference the same asset, and assets can exist without being used in any block.
## The Asset Data Model
An asset describes content that can be added to designs. Each asset has an `id` and optional properties:
```typescript highlight-asset-definition
// An asset is a content definition with metadata
// It describes content that can be added to designs
const stickerAsset: AssetResult = {
id: 'sticker-smile',
label: 'Smile Sticker',
tags: ['emoji', 'happy'],
groups: ['stickers'],
meta: {
uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg',
thumbUri:
'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg',
blockType: '//ly.img.ubq/graphic',
fillType: '//ly.img.ubq/fill/image',
width: 62,
height: 58,
mimeType: 'image/svg+xml'
}
};
```
Key properties include:
- **`id`** — Unique identifier for the asset
- **`label`** — Display name (can be localized)
- **`tags`** — Searchable keywords
- **`groups`** — Categories for filtering
- **`meta`** — Content-specific data including `uri`, `thumbUri`, `blockType`, `fillType`, `width`, `height`, and `mimeType`
> **Note:** See the [Content JSON Schema](https://img.ly/docs/cesdk/sveltekit/import-media/content-json-schema-a7b3d2/) guide for the complete property reference.
## Asset Sources
Asset sources provide assets to the editor. Each source has an `id` and implements a `findAssets()` method that returns paginated results.
```typescript highlight-asset-source
// Asset sources provide assets to the editor
// Each source has an id and a findAssets() method
const customSource: AssetSource = {
id: 'my-assets',
async findAssets(query: AssetQueryData): Promise {
// Return paginated results matching the query
return {
assets: [stickerAsset],
total: 1,
currentPage: query.page,
nextPage: undefined
};
}
};
engine.asset.addSource(customSource);
```
The `findAssets()` callback receives query parameters (`page`, `perPage`, `query`, `tags`, `groups`) and returns a result object with `assets`, `total`, `currentPage`, and `nextPage`.
Sources can also implement optional methods like `getGroups()`, `getSupportedMimeTypes()`, and `applyAsset()` for custom behavior.
## Querying Assets
Search and filter assets from registered sources using `findAssets()`:
```typescript highlight-query-assets
// Query assets from a source
const results = await engine.asset.findAssets('my-assets', {
page: 0,
perPage: 10
});
console.log('Found assets:', results.total);
```
Results include pagination info. Loop through pages until `nextPage` is undefined to retrieve all matching assets.
## Applying Assets
Use `apply()` to create a new block from an asset:
```typescript highlight-apply-asset
// Apply an asset to create a block in the scene
if (results.assets.length > 0) {
const blockId = await engine.asset.apply('my-assets', results.assets[0]);
console.log('Created block:', blockId);
// Center the sticker on the page
const page = engine.scene.getCurrentPage();
if (page && blockId) {
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// SVG is 62x58, scale to fit nicely
const stickerWidth = 62;
const stickerHeight = 58;
engine.block.setWidth(blockId, stickerWidth);
engine.block.setHeight(blockId, stickerHeight);
engine.block.setPositionX(blockId, (pageWidth - stickerWidth) / 2);
engine.block.setPositionY(blockId, (pageHeight - stickerHeight) / 2);
}
}
```
The method returns the new block ID, which you can use to position and configure the block.
## Local Asset Sources
Local sources store assets in memory and support dynamic add/remove operations. Use these for user uploads or runtime-generated content:
```typescript highlight-local-source
// Local sources support dynamic add/remove operations
engine.asset.addLocalSource('uploads', ['image/svg+xml', 'image/png']);
engine.asset.addAssetToSource('uploads', {
id: 'uploaded-1',
label: { en: 'Heart Sticker' },
meta: {
uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg',
thumbUri:
'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg',
blockType: '//ly.img.ubq/graphic',
fillType: '//ly.img.ubq/fill/image',
mimeType: 'image/svg+xml'
}
});
```
## Source Events
Subscribe to asset source lifecycle events for reactive UIs:
```typescript highlight-source-events
// Subscribe to asset source lifecycle events
const unsubscribe = engine.asset.onAssetSourceUpdated((sourceId) => {
console.log('Source updated:', sourceId);
});
// Notify that source contents changed
engine.asset.assetSourceContentsChanged('uploads');
unsubscribe();
```
Call `assetSourceContentsChanged()` after modifying a source to notify subscribers.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Blocks"
description: "Learn how blocks define elements in a scene and how to structure them for rendering in CE.SDK."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/blocks-90241e/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Blocks](https://img.ly/docs/cesdk/sveltekit/concepts/blocks-90241e/)
---
Work with blocks—the fundamental building units for all visual elements in
CE.SDK designs.

> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-blocks-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-blocks-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-blocks-browser/)
Every visual element in CE.SDK—images, text, shapes, and audio—is represented as a block. Blocks are organized in a tree structure within scenes and pages, where parent-child relationships determine rendering order and visibility. Each block has properties you can read and modify, a `Type` that defines its core behavior, and an optional `Kind` for custom categorization.
```typescript file=@cesdk_web_examples/guides-concepts-blocks-browser/browser.ts reference-only
import type { EditorPlugin,EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Blocks Guide
*
* Demonstrates working with blocks in CE.SDK:
* - Block types (graphic, text, audio, page, cutout)
* - Block hierarchy (parent-child relationships)
* - Block lifecycle (create, duplicate, destroy)
* - Block properties and reflection
* - Selection and visibility
* - Block state management
* - Serialization (save/load)
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the current scene and page
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
// Find the page block - pages contain all design elements
const pages = engine.block.findByType('page');
const page = pages[0];
// Query the block type - returns the full type path
const pageType = engine.block.getType(page);
console.log('Page block type:', pageType); // '//ly.img.ubq/page'
// Type is immutable, determined at creation
// Kind is a custom label you can set and change
engine.block.setKind(page, 'main-canvas');
const pageKind = engine.block.getKind(page);
console.log('Page kind:', pageKind); // 'main-canvas'
// Find blocks by kind
const mainCanvasBlocks = engine.block.findByKind('main-canvas');
console.log('Blocks with kind "main-canvas":', mainCanvasBlocks.length);
// Create a graphic block for an image
const graphic = engine.block.create('graphic');
// Duplicate creates a copy with a new UUID
const graphicCopy = engine.block.duplicate(graphic);
// Destroy removes a block - the duplicate is no longer needed
engine.block.destroy(graphicCopy);
// Check if a block ID is still valid after operations
const isOriginalValid = engine.block.isValid(graphic);
const isCopyValid = engine.block.isValid(graphicCopy);
console.log('Original valid:', isOriginalValid); // true
console.log('Copy valid after destroy:', isCopyValid); // false
// Create a rect shape to define the graphic's bounds
const rectShape = engine.block.createShape('rect');
engine.block.setShape(graphic, rectShape);
// Position and size the graphic (centered horizontally on 800px page)
engine.block.setPositionX(graphic, 200);
engine.block.setPositionY(graphic, 100);
engine.block.setWidth(graphic, 400);
engine.block.setHeight(graphic, 300);
// Create an image fill and attach it to the graphic
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(graphic, imageFill);
// Set content fill mode so the image fills the block bounds
engine.block.setEnum(graphic, 'contentFill/mode', 'Cover');
// Blocks form a tree: scene > page > elements
// Append the graphic to the page to make it visible
engine.block.appendChild(page, graphic);
// Query parent-child relationships
const graphicParent = engine.block.getParent(graphic);
console.log('Graphic parent is page:', graphicParent === page); // true
const pageChildren = engine.block.getChildren(page);
console.log('Page has children:', pageChildren.length);
// Create a text block with content
const textBlock = engine.block.create('text');
engine.block.appendChild(page, textBlock);
// Position the text block (centered horizontally on 800px page)
engine.block.setPositionX(textBlock, 200);
engine.block.setPositionY(textBlock, 450);
engine.block.setWidth(textBlock, 400);
engine.block.setHeight(textBlock, 80);
// Set text content
engine.block.setString(
textBlock,
'text/text',
'Blocks are the building units of CE.SDK designs'
);
// Set font size to 72pt
engine.block.setFloat(textBlock, 'text/fontSize', 72);
// Center-align the text
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
// Check the text block type
const textType = engine.block.getType(textBlock);
console.log('Text block type:', textType); // '//ly.img.ubq/text'
// Use reflection to discover available properties
const graphicProperties = engine.block.findAllProperties(graphic);
console.log('Graphic block has', graphicProperties.length, 'properties');
// Get property type information
const opacityType = engine.block.getPropertyType('opacity');
console.log('Opacity property type:', opacityType); // 'Float'
// Check if properties are readable/writable
const isOpacityReadable = engine.block.isPropertyReadable('opacity');
const isOpacityWritable = engine.block.isPropertyWritable('opacity');
console.log(
'Opacity readable:',
isOpacityReadable,
'writable:',
isOpacityWritable
);
// Use type-specific getters and setters
// Float properties
engine.block.setFloat(graphic, 'opacity', 0.9);
const opacity = engine.block.getFloat(graphic, 'opacity');
console.log('Graphic opacity:', opacity);
// Bool properties
engine.block.setBool(page, 'page/marginEnabled', false);
const marginEnabled = engine.block.getBool(page, 'page/marginEnabled');
console.log('Page margin enabled:', marginEnabled);
// Enum properties - get allowed values first
const blendModes = engine.block.getEnumValues('blend/mode');
console.log(
'Available blend modes:',
blendModes.slice(0, 3).join(', '),
'...'
);
engine.block.setEnum(graphic, 'blend/mode', 'Multiply');
const blendMode = engine.block.getEnum(graphic, 'blend/mode');
console.log('Graphic blend mode:', blendMode);
// Each block has a stable UUID across save/load cycles
const graphicUUID = engine.block.getUUID(graphic);
console.log('Graphic UUID:', graphicUUID);
// Block names are mutable labels for organization
engine.block.setName(graphic, 'Hero Image');
engine.block.setName(textBlock, 'Caption');
const graphicName = engine.block.getName(graphic);
console.log('Graphic name:', graphicName); // 'Hero Image'
// Select a block programmatically
engine.block.select(graphic); // Selects graphic, deselects others
// Check selection state
const isGraphicSelected = engine.block.isSelected(graphic);
console.log('Graphic is selected:', isGraphicSelected); // true
// Add to selection without deselecting others
engine.block.setSelected(textBlock, true);
// Get all selected blocks
const selectedBlocks = engine.block.findAllSelected();
console.log('Selected blocks count:', selectedBlocks.length); // 2
// Subscribe to selection changes
const unsubscribeSelection = engine.block.onSelectionChanged(() => {
const selected = engine.block.findAllSelected();
console.log(
'Selection changed, now selected:',
selected.length,
'blocks'
);
});
// Control block visibility
engine.block.setVisible(graphic, true);
const isVisible = engine.block.isVisible(graphic);
console.log('Graphic is visible:', isVisible);
// Control export inclusion
engine.block.setIncludedInExport(graphic, true);
const inExport = engine.block.isIncludedInExport(graphic);
console.log('Graphic included in export:', inExport);
// Control clipping behavior
engine.block.setClipped(graphic, false);
const isClipped = engine.block.isClipped(graphic);
console.log('Graphic is clipped:', isClipped);
// Query block state - indicates loading status
const graphicState = engine.block.getState(graphic);
console.log('Graphic state:', graphicState.type); // 'Ready', 'Pending', or 'Error'
// Subscribe to state changes (useful for loading indicators)
const unsubscribeState = engine.block.onStateChanged(
[graphic],
(changedBlocks) => {
for (const blockId of changedBlocks) {
const state = engine.block.getState(blockId);
console.log(`Block ${blockId} state changed to:`, state.type);
if (state.type === 'Pending' && state.progress !== undefined) {
console.log(
'Loading progress:',
Math.round(state.progress * 100) + '%'
);
}
}
}
);
// Save blocks to a string for persistence
// Include 'bundle' scheme to allow serialization of blocks with bundled fonts
const savedString = await engine.block.saveToString(
[graphic, textBlock],
['buffer', 'http', 'https', 'bundle']
);
console.log('Blocks saved to string, length:', savedString.length);
// Alternatively, blocks can also be saved with their assets to an archive
// const savedBlocksArchive = await engine.block.saveToArchive([
// graphic,
// textBlock
// ]);
// Load blocks from string (creates new blocks, not attached to scene)
const loadedBlocks = await engine.block.loadFromString(savedString);
console.log('Loaded blocks from string:', loadedBlocks.length);
// Alternatively, blocks can also be loaded from an archive
// const loadedBlocks = await engine.block.loadFromArchiveURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1_blocks.zip'
// );
// console.log('Loaded blocks from archive URL:', loadedBlocks.length);
// Alternatively, blocks can be loaded from an extracted zip file created with block.saveToArchive
// const loadedBlocks = await engine.block.loadFromURL(
// 'https://cdn.img.ly/assets/v6/ly.img.text.components/box/blocks.blocks'
// );
// console.log('Loaded blocks from URL:', loadedBlocks.length);
// Loaded blocks must be parented to appear in the scene
// For demo purposes, we won't add them to avoid duplicates
for (const block of loadedBlocks) {
engine.block.destroy(block);
}
// Clean up subscriptions when done
// In a real application, you'd keep these active as needed
unsubscribeSelection();
unsubscribeState();
console.log('Blocks guide initialized successfully.');
console.log('Created graphic block with image fill and text block.');
console.log(
'Demonstrated: types, hierarchy, properties, selection, state, and serialization.'
);
}
}
export default Example;
```
This guide covers block types and their uses, how to create and manage blocks programmatically, how to work with block properties using the reflection system, and how to handle selection, visibility, and state changes.
## Block Types
CE.SDK provides several block types, each designed for specific content:
- **graphic** (`//ly.img.ubq/graphic`): Visual blocks for images, shapes, and graphics
- **text** (`//ly.img.ubq/text`): Text content with typography controls
- **audio** (`//ly.img.ubq/audio`): Audio content for video scenes
- **page** (`//ly.img.ubq/page`): Container blocks representing canvases or artboards
- **cutout** (`//ly.img.ubq/cutout`): Blocks for masking operations
We query a block's type using `getType()` and find blocks of a specific type with `findByType()`:
```typescript highlight-block-types
// Get the current scene and page
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
// Find the page block - pages contain all design elements
const pages = engine.block.findByType('page');
const page = pages[0];
// Query the block type - returns the full type path
const pageType = engine.block.getType(page);
console.log('Page block type:', pageType); // '//ly.img.ubq/page'
```
Block types are immutable—once created, a block's type cannot change. This distinguishes type from kind.
## Type vs Kind
Type and kind serve different purposes. The **type** is determined at creation and defines core behavior. The **kind** is a custom string label you assign for application-specific categorization.
```typescript highlight-type-vs-kind
// Type is immutable, determined at creation
// Kind is a custom label you can set and change
engine.block.setKind(page, 'main-canvas');
const pageKind = engine.block.getKind(page);
console.log('Page kind:', pageKind); // 'main-canvas'
// Find blocks by kind
const mainCanvasBlocks = engine.block.findByKind('main-canvas');
console.log('Blocks with kind "main-canvas":', mainCanvasBlocks.length);
```
Use kind to tag blocks for your application's logic. You can set it with `setKind()`, query it with `getKind()`, and find blocks by kind with `findByKind()`.
## Block Hierarchy
Blocks form a tree structure where scenes contain pages, and pages contain design elements.
```typescript highlight-block-hierarchy
// Blocks form a tree: scene > page > elements
// Append the graphic to the page to make it visible
engine.block.appendChild(page, graphic);
// Query parent-child relationships
const graphicParent = engine.block.getParent(graphic);
console.log('Graphic parent is page:', graphicParent === page); // true
const pageChildren = engine.block.getChildren(page);
console.log('Page has children:', pageChildren.length);
```
Only blocks that are direct or indirect children of a page block are rendered. A scene without any page children won't display content in the editor. Use `appendChild()` to add blocks to parents, `getParent()` to query a block's parent, and `getChildren()` to get a block's children.
## Block Lifecycle
Create new blocks with `create()`, duplicate existing blocks with `duplicate()`, and remove blocks with `destroy()`. After destroying a block, `isValid()` returns `false` for that block ID.
```typescript highlight-block-lifecycle
// Create a graphic block for an image
const graphic = engine.block.create('graphic');
// Duplicate creates a copy with a new UUID
const graphicCopy = engine.block.duplicate(graphic);
// Destroy removes a block - the duplicate is no longer needed
engine.block.destroy(graphicCopy);
// Check if a block ID is still valid after operations
const isOriginalValid = engine.block.isValid(graphic);
const isCopyValid = engine.block.isValid(graphicCopy);
console.log('Original valid:', isOriginalValid); // true
console.log('Copy valid after destroy:', isCopyValid); // false
```
When duplicating a block, all children are included, and the duplicate receives a new UUID.
## Working with Fills
Graphic blocks display content through fills. We create a fill, attach it to a block, and configure its source.
```typescript highlight-fill
// Create a rect shape to define the graphic's bounds
const rectShape = engine.block.createShape('rect');
engine.block.setShape(graphic, rectShape);
// Position and size the graphic (centered horizontally on 800px page)
engine.block.setPositionX(graphic, 200);
engine.block.setPositionY(graphic, 100);
engine.block.setWidth(graphic, 400);
engine.block.setHeight(graphic, 300);
// Create an image fill and attach it to the graphic
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(graphic, imageFill);
// Set content fill mode so the image fills the block bounds
engine.block.setEnum(graphic, 'contentFill/mode', 'Cover');
```
CE.SDK supports several fill types including image, video, color, and gradient fills. See the [Fills guide](https://img.ly/docs/cesdk/sveltekit/filters-and-effects/gradients-0ff079/) for details on available fill types.
## Creating Text Blocks
Text blocks display formatted text content. We create a text block, position it, and set its content.
```typescript highlight-text-block
// Create a text block with content
const textBlock = engine.block.create('text');
engine.block.appendChild(page, textBlock);
// Position the text block (centered horizontally on 800px page)
engine.block.setPositionX(textBlock, 200);
engine.block.setPositionY(textBlock, 450);
engine.block.setWidth(textBlock, 400);
engine.block.setHeight(textBlock, 80);
// Set text content
engine.block.setString(
textBlock,
'text/text',
'Blocks are the building units of CE.SDK designs'
);
// Set font size to 72pt
engine.block.setFloat(textBlock, 'text/fontSize', 72);
// Center-align the text
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
// Check the text block type
const textType = engine.block.getType(textBlock);
console.log('Text block type:', textType); // '//ly.img.ubq/text'
```
Text blocks support extensive typography controls covered in the [Text guides](https://img.ly/docs/cesdk/sveltekit/text-8a993a/).
## Block Properties
The reflection system lets you discover and manipulate any block property dynamically. Use `findAllProperties()` to get all available properties for a block—they're prefixed by category like `shape/star/points` or `text/fontSize`.
```typescript highlight-block-properties
// Use reflection to discover available properties
const graphicProperties = engine.block.findAllProperties(graphic);
console.log('Graphic block has', graphicProperties.length, 'properties');
// Get property type information
const opacityType = engine.block.getPropertyType('opacity');
console.log('Opacity property type:', opacityType); // 'Float'
// Check if properties are readable/writable
const isOpacityReadable = engine.block.isPropertyReadable('opacity');
const isOpacityWritable = engine.block.isPropertyWritable('opacity');
console.log(
'Opacity readable:',
isOpacityReadable,
'writable:',
isOpacityWritable
);
```
Query property types with `getPropertyType()`. Returns include `Bool`, `Int`, `Float`, `Double`, `String`, `Color`, `Enum`, or `Struct`. For enum properties, use `getEnumValues()` to get allowed values.
### Property Accessors
Use type-specific getters and setters matching the property type:
```typescript highlight-property-accessors
// Use type-specific getters and setters
// Float properties
engine.block.setFloat(graphic, 'opacity', 0.9);
const opacity = engine.block.getFloat(graphic, 'opacity');
console.log('Graphic opacity:', opacity);
// Bool properties
engine.block.setBool(page, 'page/marginEnabled', false);
const marginEnabled = engine.block.getBool(page, 'page/marginEnabled');
console.log('Page margin enabled:', marginEnabled);
// Enum properties - get allowed values first
const blendModes = engine.block.getEnumValues('blend/mode');
console.log(
'Available blend modes:',
blendModes.slice(0, 3).join(', '),
'...'
);
engine.block.setEnum(graphic, 'blend/mode', 'Multiply');
const blendMode = engine.block.getEnum(graphic, 'blend/mode');
console.log('Graphic blend mode:', blendMode);
```
Using the wrong accessor type for a property will cause an error. Always check `getPropertyType()` if you're unsure which accessor to use.
## UUID, Names, and Identity
Each block has a UUID that remains stable across save and load operations. Block names are mutable labels for organization.
```typescript highlight-uuid-identity
// Each block has a stable UUID across save/load cycles
const graphicUUID = engine.block.getUUID(graphic);
console.log('Graphic UUID:', graphicUUID);
// Block names are mutable labels for organization
engine.block.setName(graphic, 'Hero Image');
engine.block.setName(textBlock, 'Caption');
const graphicName = engine.block.getName(graphic);
console.log('Graphic name:', graphicName); // 'Hero Image'
```
Use `getUUID()` when you need a persistent identifier for a block. Names are useful for user-facing labels and can be changed freely with `setName()`.
## Selection
Control which blocks are selected programmatically. Use `select()` to select a single block (deselecting others) or `setSelected()` to modify selection without affecting other blocks.
```typescript highlight-selection
// Select a block programmatically
engine.block.select(graphic); // Selects graphic, deselects others
// Check selection state
const isGraphicSelected = engine.block.isSelected(graphic);
console.log('Graphic is selected:', isGraphicSelected); // true
// Add to selection without deselecting others
engine.block.setSelected(textBlock, true);
// Get all selected blocks
const selectedBlocks = engine.block.findAllSelected();
console.log('Selected blocks count:', selectedBlocks.length); // 2
// Subscribe to selection changes
const unsubscribeSelection = engine.block.onSelectionChanged(() => {
const selected = engine.block.findAllSelected();
console.log(
'Selection changed, now selected:',
selected.length,
'blocks'
);
});
```
Subscribe to selection changes with `onSelectionChanged()` to update your UI when the selection state changes.
## Visibility
Control whether blocks appear on the canvas and are included in exports.
```typescript highlight-visibility
// Control block visibility
engine.block.setVisible(graphic, true);
const isVisible = engine.block.isVisible(graphic);
console.log('Graphic is visible:', isVisible);
// Control export inclusion
engine.block.setIncludedInExport(graphic, true);
const inExport = engine.block.isIncludedInExport(graphic);
console.log('Graphic included in export:', inExport);
```
A block with `isVisible()` returning true may still not appear if it hasn't been added to a parent, the parent is hidden, or another block obscures it.
### Clipping
Clipping determines whether a block's content is constrained to its parent's bounds. When `setClipped(block, true)` is set, any portion of the block extending beyond its parent's boundaries is hidden. When clipping is disabled, the block renders fully even if it overflows its parent container.
```typescript highlight-clipping
// Control clipping behavior
engine.block.setClipped(graphic, false);
const isClipped = engine.block.isClipped(graphic);
console.log('Graphic is clipped:', isClipped);
```
## Block State
Blocks track loading progress and error conditions through a state system with three possible states:
- **Ready**: Normal state, no pending operations
- **Pending**: Operation in progress with optional progress value (0-1)
- **Error**: Operation failed (`ImageDecoding`, `VideoDecoding`, `FileFetch`, `AudioDecoding`, `Unknown`)
```typescript highlight-block-state
// Query block state - indicates loading status
const graphicState = engine.block.getState(graphic);
console.log('Graphic state:', graphicState.type); // 'Ready', 'Pending', or 'Error'
// Subscribe to state changes (useful for loading indicators)
const unsubscribeState = engine.block.onStateChanged(
[graphic],
(changedBlocks) => {
for (const blockId of changedBlocks) {
const state = engine.block.getState(blockId);
console.log(`Block ${blockId} state changed to:`, state.type);
if (state.type === 'Pending' && state.progress !== undefined) {
console.log(
'Loading progress:',
Math.round(state.progress * 100) + '%'
);
}
}
}
);
```
Subscribe to state changes with `onStateChanged()` to show loading indicators or handle errors in your UI.
## Serialization
Save blocks to strings for persistence and restore them later.
```typescript highlight-serialization
// Save blocks to a string for persistence
// Include 'bundle' scheme to allow serialization of blocks with bundled fonts
const savedString = await engine.block.saveToString(
[graphic, textBlock],
['buffer', 'http', 'https', 'bundle']
);
console.log('Blocks saved to string, length:', savedString.length);
// Alternatively, blocks can also be saved with their assets to an archive
// const savedBlocksArchive = await engine.block.saveToArchive([
// graphic,
// textBlock
// ]);
// Load blocks from string (creates new blocks, not attached to scene)
const loadedBlocks = await engine.block.loadFromString(savedString);
console.log('Loaded blocks from string:', loadedBlocks.length);
// Alternatively, blocks can also be loaded from an archive
// const loadedBlocks = await engine.block.loadFromArchiveURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1_blocks.zip'
// );
// console.log('Loaded blocks from archive URL:', loadedBlocks.length);
// Alternatively, blocks can be loaded from an extracted zip file created with block.saveToArchive
// const loadedBlocks = await engine.block.loadFromURL(
// 'https://cdn.img.ly/assets/v6/ly.img.text.components/box/blocks.blocks'
// );
// console.log('Loaded blocks from URL:', loadedBlocks.length);
// Loaded blocks must be parented to appear in the scene
// For demo purposes, we won't add them to avoid duplicates
for (const block of loadedBlocks) {
engine.block.destroy(block);
}
```
Use `saveToString()` for lightweight serialization or `saveToArchive()` to include all referenced assets.
Blocks can be loaded with `loadFromString()`, `loadFromArchiveURL()`, or `loadFromURL()`.
For `loadFromArchiveURL()`, the URL should point to the zipped archive file previously saved with `saveToArchive()`,
whereas for `loadFromURL()`, it should point to a blocks file within an unzipped archive directory.
Loaded blocks are not automatically attached to the scene—you must parent them with `appendChild()` to make them visible.
## Troubleshooting
**Block not visible**: Ensure the block is a child of a page that's a child of the scene.
**Property setter fails**: Verify the property type matches the setter method used. Use `getPropertyType()` to check.
**Block ID invalid after destroy**: Use `isValid()` before operations on potentially destroyed blocks.
**State stuck in Pending**: Check network connectivity for remote resources or use state change events to monitor progress.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Buffers"
description: "Use buffers to store temporary, non-serializable data in CE.SDK via the CreativeEngine API."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/buffers-9c565b/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Buffers](https://img.ly/docs/cesdk/sveltekit/concepts/buffers-9c565b/)
---
Store and manage temporary binary data directly in memory using CE.SDK's buffer API for dynamically generated content.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-buffers-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-buffers-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-buffers-browser/)
Buffers are in-memory containers for binary data referenced via `buffer://` URIs. Unlike external files that require network or file I/O, buffers exist only during the current session and are not serialized when saving scenes. This makes them ideal for procedural audio, real-time image data, or streaming content that doesn't need to persist beyond the current editing session.
```typescript file=@cesdk_web_examples/guides-concepts-buffers-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
// Helper function to create a WAV file from audio samples
function createWavFile(
samples: Float32Array,
sampleRate: number,
numChannels: number
): Uint8Array {
const bytesPerSample = 2; // 16-bit audio
const blockAlign = numChannels * bytesPerSample;
const byteRate = sampleRate * blockAlign;
const dataSize = samples.length * bytesPerSample;
const fileSize = 44 + dataSize; // WAV header is 44 bytes
const buffer = new ArrayBuffer(fileSize);
const view = new DataView(buffer);
// Write WAV header
// "RIFF" chunk descriptor
writeString(view, 0, 'RIFF');
view.setUint32(4, fileSize - 8, true); // File size minus RIFF header
writeString(view, 8, 'WAVE');
// "fmt " sub-chunk
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true); // Subchunk1Size (16 for PCM)
view.setUint16(20, 1, true); // AudioFormat (1 = PCM)
view.setUint16(22, numChannels, true); // NumChannels
view.setUint32(24, sampleRate, true); // SampleRate
view.setUint32(28, byteRate, true); // ByteRate
view.setUint16(32, blockAlign, true); // BlockAlign
view.setUint16(34, bytesPerSample * 8, true); // BitsPerSample
// "data" sub-chunk
writeString(view, 36, 'data');
view.setUint32(40, dataSize, true); // Subchunk2Size
// Write audio samples as 16-bit PCM
let offset = 44;
for (let i = 0; i < samples.length; i++) {
// Convert float (-1 to 1) to 16-bit integer
const sample = Math.max(-1, Math.min(1, samples[i]));
const intSample = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
view.setInt16(offset, intSample, true);
offset += 2;
}
return new Uint8Array(buffer);
}
function writeString(view: DataView, offset: number, str: string): void {
for (let i = 0; i < str.length; i++) {
view.setUint8(offset + i, str.charCodeAt(i));
}
}
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.instagram.story'
}
});
const engine = cesdk.engine;
// Get the page (first container in video scenes)
const pages = engine.block.findByType('page');
const page = pages[0];
// Add a centered text block to explain the example
const textBlock = engine.block.create('text');
engine.block.setString(
textBlock,
'text/text',
'The audio track in this scene lives in a buffer.'
);
engine.block.setFloat(textBlock, 'text/fontSize', 108);
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
engine.block.setHeightMode(textBlock, 'Auto');
// Set text color to white
engine.block.setColor(textBlock, 'fill/solid/color', {
r: 1,
g: 1,
b: 1,
a: 1
});
// Get page dimensions and position with 10% horizontal margin
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const horizontalMargin = pageWidth * 0.1;
const textWidth = pageWidth - horizontalMargin * 2;
engine.block.setWidth(textBlock, textWidth);
engine.block.setPositionX(textBlock, horizontalMargin);
// Append to page first so layout can be computed
engine.block.appendChild(page, textBlock);
// Force layout computation and get the actual frame height
const textHeight = engine.block.getFrameHeight(textBlock);
engine.block.setPositionY(textBlock, (pageHeight - textHeight) / 2);
// Set duration to match the scene
engine.block.setDuration(textBlock, 2);
// Create a buffer and get its URI
const bufferUri = engine.editor.createBuffer();
console.log('Buffer URI:', bufferUri);
// Generate sine wave audio samples
const sampleRate = 44100;
const duration = 2; // 2 seconds
const frequency = 440; // A4 note
const numChannels = 2; // Stereo
// Create Float32Array for audio samples (interleaved stereo)
const numSamples = sampleRate * duration * numChannels;
const samples = new Float32Array(numSamples);
// Generate a 440 Hz sine wave
for (let i = 0; i < numSamples; i += numChannels) {
const sampleIndex = i / numChannels;
const time = sampleIndex / sampleRate;
const value = Math.sin(2 * Math.PI * frequency * time) * 0.5; // 50% amplitude
// Write to both left and right channels
samples[i] = value; // Left channel
samples[i + 1] = value; // Right channel
}
// Convert samples to WAV format and write to buffer
const wavData = createWavFile(samples, sampleRate, numChannels);
engine.editor.setBufferData(bufferUri, 0, wavData);
// Verify the buffer length
const bufferLength = engine.editor.getBufferLength(bufferUri);
console.log('Buffer length:', bufferLength, 'bytes');
// Create an audio block
const audioBlock = engine.block.create('audio');
// Assign the buffer URI to the audio block
engine.block.setString(audioBlock, 'audio/fileURI', bufferUri);
// Set audio duration to match the generated samples
engine.block.setDuration(audioBlock, duration);
// Append the audio block to the page
engine.block.appendChild(page, audioBlock);
// Demonstrate reading buffer data back
const readData = engine.editor.getBufferData(bufferUri, 0, 100);
console.log('First 100 bytes of buffer data:', readData);
// Demonstrate resizing a buffer with a separate demo buffer
const demoBuffer = engine.editor.createBuffer();
engine.editor.setBufferData(
demoBuffer,
0,
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
);
const demoLength = engine.editor.getBufferLength(demoBuffer);
console.log('Demo buffer length before resize:', demoLength);
engine.editor.setBufferLength(demoBuffer, demoLength / 2);
console.log(
'Demo buffer length after resize:',
engine.editor.getBufferLength(demoBuffer)
);
engine.editor.destroyBuffer(demoBuffer);
// Find all transient resources (including our buffer)
const transientResources = engine.editor.findAllTransientResources();
console.log('Transient resources in scene:');
for (const resource of transientResources) {
console.log(` URL: ${resource.URL}, Size: ${resource.size} bytes`);
}
// Demonstrate persisting buffer data using a Blob URL
// In production, you would upload to CDN/cloud storage instead
const bufferData = engine.editor.getBufferData(bufferUri, 0, bufferLength);
const blob = new Blob([new Uint8Array(bufferData)], { type: 'audio/wav' });
const persistentUrl = URL.createObjectURL(blob);
// Update all references from buffer:// to the new URL
engine.editor.relocateResource(bufferUri, persistentUrl);
console.log('Buffer relocated to:', persistentUrl);
console.log('Buffers example loaded successfully');
console.log(
'Note: Audio playback requires user interaction in most browsers'
);
}
}
export default Example;
```
This guide covers how to create and manage buffers, write and read binary data, assign buffers to block properties like audio sources, and handle transient resources when saving scenes.
## Setting Up the Scene
We first create a scene and set up a page for our audio composition.
```typescript highlight-create-video-scene
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.instagram.story'
}
});
```
## Creating and Managing Buffers
We use `engine.editor.createBuffer()` to allocate a new buffer and receive its URI. This URI follows the `buffer://` scheme and uniquely identifies the buffer within the engine instance.
```typescript highlight-create-buffer
// Create a buffer and get its URI
const bufferUri = engine.editor.createBuffer();
console.log('Buffer URI:', bufferUri);
```
Buffers persist in memory until you explicitly destroy them with `engine.editor.destroyBuffer()` or the engine instance is disposed. For large buffers or long editing sessions, you should destroy buffers when they're no longer needed to free memory.
## Writing Data to Buffers
To populate a buffer with binary data, we use `engine.editor.setBufferData()`. This method takes the buffer URI, an offset in bytes, and a `Uint8Array` containing the data to write.
In this example, we generate a 440 Hz sine wave as stereo PCM audio samples. We create a `Float32Array` for the sample values that will be converted to a valid audio format.
```typescript highlight-generate-samples
// Generate sine wave audio samples
const sampleRate = 44100;
const duration = 2; // 2 seconds
const frequency = 440; // A4 note
const numChannels = 2; // Stereo
// Create Float32Array for audio samples (interleaved stereo)
const numSamples = sampleRate * duration * numChannels;
const samples = new Float32Array(numSamples);
// Generate a 440 Hz sine wave
for (let i = 0; i < numSamples; i += numChannels) {
const sampleIndex = i / numChannels;
const time = sampleIndex / sampleRate;
const value = Math.sin(2 * Math.PI * frequency * time) * 0.5; // 50% amplitude
// Write to both left and right channels
samples[i] = value; // Left channel
samples[i + 1] = value; // Right channel
}
```
When using buffers for audio, the data must be in a recognized audio format like WAV. We convert the raw samples to a WAV file by adding the appropriate headers, then write the complete file to the buffer.
```typescript highlight-write-buffer
// Convert samples to WAV format and write to buffer
const wavData = createWavFile(samples, sampleRate, numChannels);
engine.editor.setBufferData(bufferUri, 0, wavData);
// Verify the buffer length
const bufferLength = engine.editor.getBufferLength(bufferUri);
console.log('Buffer length:', bufferLength, 'bytes');
```
## Reading Data from Buffers
To read data back from a buffer, we use `engine.editor.getBufferData()` with the buffer URI, a starting offset, and the number of bytes to read. We first query the buffer length with `engine.editor.getBufferLength()` to determine how much data is available.
```typescript highlight-read-buffer
// Demonstrate reading buffer data back
const readData = engine.editor.getBufferData(bufferUri, 0, 100);
console.log('First 100 bytes of buffer data:', readData);
```
This returns a `Uint8Array` that you can convert back to other typed arrays as needed. Partial reads are supported—you can read any range within the buffer bounds.
## Resizing Buffers
You can change a buffer's size at any time with `engine.editor.setBufferLength()`. Increasing the size allocates additional space, while decreasing it truncates the data. Here we demonstrate resizing with a separate demo buffer to avoid truncating our audio data.
```typescript highlight-resize-buffer
// Demonstrate resizing a buffer with a separate demo buffer
const demoBuffer = engine.editor.createBuffer();
engine.editor.setBufferData(
demoBuffer,
0,
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
);
const demoLength = engine.editor.getBufferLength(demoBuffer);
console.log('Demo buffer length before resize:', demoLength);
engine.editor.setBufferLength(demoBuffer, demoLength / 2);
console.log(
'Demo buffer length after resize:',
engine.editor.getBufferLength(demoBuffer)
);
engine.editor.destroyBuffer(demoBuffer);
```
Keep in mind that truncating a buffer permanently discards data beyond the new length. Always query the current length first if you need to preserve the original size, or create a copy before resizing.
## Assigning Buffers to Blocks
Buffer URIs work like any other resource URI in CE.SDK. We assign them to block properties using `engine.block.setString()`. For audio blocks, we set the `audio/fileURI` property.
```typescript highlight-create-audio-block
// Create an audio block
const audioBlock = engine.block.create('audio');
// Assign the buffer URI to the audio block
engine.block.setString(audioBlock, 'audio/fileURI', bufferUri);
// Set audio duration to match the generated samples
engine.block.setDuration(audioBlock, duration);
// Append the audio block to the page
engine.block.appendChild(page, audioBlock);
```
The same approach works for other resource properties:
- **Audio blocks**: `audio/fileURI`
- **Image fills**: `fill/image/imageFileURI`
- **Video fills**: `fill/video/fileURI`
Any property that accepts a URI can reference a buffer.
## Transient Resources and Scene Serialization
Buffers are transient resources—the URI gets serialized when you save a scene, but the actual binary data does not persist. This means a saved scene will contain references to `buffer://` URIs that won't resolve when the scene is loaded again.
We use `engine.editor.findAllTransientResources()` to discover all transient resources in the current scene, including buffers. Each resource includes its URL and size in bytes.
```typescript highlight-find-transient
// Find all transient resources (including our buffer)
const transientResources = engine.editor.findAllTransientResources();
console.log('Transient resources in scene:');
for (const resource of transientResources) {
console.log(` URL: ${resource.URL}, Size: ${resource.size} bytes`);
}
```
> **Note:** **Limitations**Buffers are intended for temporary data only.* Buffer data is not part of scene serialization
> * Changes to buffers can't be undone using the history system
Note that `engine.scene.saveToString()` does NOT include `buffer://` in its default allowed resource schemes, while `engine.block.saveToString()` does include it. You may need to configure the allowed schemes depending on your serialization needs.
## Persisting Buffer Data
To permanently save buffer content, you must extract the data, upload it to persistent storage, then update the block references to point to the new URL. This example demonstrates the pattern using a Blob URL—in production, you would upload to a CDN or cloud storage instead.
```typescript highlight-persist-buffer
// Demonstrate persisting buffer data using a Blob URL
// In production, you would upload to CDN/cloud storage instead
const bufferData = engine.editor.getBufferData(bufferUri, 0, bufferLength);
const blob = new Blob([new Uint8Array(bufferData)], { type: 'audio/wav' });
const persistentUrl = URL.createObjectURL(blob);
// Update all references from buffer:// to the new URL
engine.editor.relocateResource(bufferUri, persistentUrl);
console.log('Buffer relocated to:', persistentUrl);
```
We read the buffer data, create a persistent URL from it, then use `engine.editor.relocateResource()` to update all references to the old buffer URI throughout the scene. After relocation, you can save the scene and the new persistent URLs will be serialized.
## Troubleshooting
**Buffer data not appearing in exported scene**
Buffers are transient and don't persist with scene saves. Use `findAllTransientResources()` to identify buffers, then relocate them to persistent storage before exporting.
**Memory usage growing unexpectedly**
Call `engine.editor.destroyBuffer()` when buffers are no longer needed. Unlike external resources that can be garbage collected, buffers remain in memory until explicitly destroyed.
**Data corruption when writing**
Ensure the offset plus data length doesn't exceed the intended buffer bounds. Resize the buffer first with `setBufferLength()` if you need more space.
**Buffer URI not recognized by block**
Verify the buffer was created in the same engine instance. Buffer URIs are not portable between different engine instances or sessions.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Design Units"
description: "Configure design units (pixels, millimeters, inches) and DPI settings for print-ready output in CE.SDK."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/design-units-cc6597/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Design Units](https://img.ly/docs/cesdk/sveltekit/concepts/design-units-cc6597/)
---
Control measurement systems for precise physical dimensions—create print-ready
documents with millimeter or inch units and configurable DPI for export quality.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-design-units-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-design-units-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-design-units-browser/)
Design units determine the coordinate system for all layout values in CE.SDK—positions, sizes, and margins. The engine supports three unit types: **Pixel** for screen-based designs, **Millimeter** for metric print dimensions, and **Inch** for imperial print formats.
```typescript file=@cesdk_web_examples/guides-concepts-design-units-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Design Units Guide
*
* Demonstrates working with design units in CE.SDK:
* - Understanding unit types (Pixel, Millimeter, Inch)
* - Getting and setting the design unit
* - Configuring DPI for print output
* - Setting up print-ready dimensions
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 210, height: 297, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the current scene
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
// Get the current design unit
const currentUnit = engine.scene.getDesignUnit();
// eslint-disable-next-line no-console
console.log('Current design unit:', currentUnit); // 'Pixel' by default
// Set design unit to Millimeter for print workflow
engine.scene.setDesignUnit('Millimeter');
// Verify the change
const newUnit = engine.scene.getDesignUnit();
// eslint-disable-next-line no-console
console.log('Design unit changed to:', newUnit); // 'Millimeter'
// Set DPI to 300 for print-quality exports
// Higher DPI produces higher resolution output
engine.block.setFloat(scene, 'scene/dpi', 300);
// Verify the DPI setting
const dpi = engine.block.getFloat(scene, 'scene/dpi');
// eslint-disable-next-line no-console
console.log('DPI set to:', dpi); // 300
// Get the page and set A4 dimensions (210 x 297 mm)
const page = engine.block.findByType('page')[0];
// Verify dimensions
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// eslint-disable-next-line no-console
console.log(`Page dimensions: ${pageWidth}mm x ${pageHeight}mm`);
// Create a text block with millimeter dimensions
const textBlock = engine.block.create('text');
engine.block.appendChild(page, textBlock);
// Position text at 20mm from left, 30mm from top
engine.block.setPositionX(textBlock, 20);
engine.block.setPositionY(textBlock, 30);
// Set text block size to 170mm x 50mm
engine.block.setWidth(textBlock, 170);
engine.block.setHeight(textBlock, 50);
// Add content to the text block
engine.block.setString(
textBlock,
'text/text',
'This A4 document uses millimeter units with 300 DPI for print-ready output.'
);
// Demonstrate unit comparison
// At 300 DPI: 1 inch = 300 pixels, 1 mm = ~11.81 pixels
// eslint-disable-next-line no-console
console.log('Unit comparison at 300 DPI:');
// eslint-disable-next-line no-console
console.log(
'- A4 width (210mm) will export as',
210 * (300 / 25.4),
'pixels'
);
// eslint-disable-next-line no-console
console.log(
'- A4 height (297mm) will export as',
297 * (300 / 25.4),
'pixels'
);
// eslint-disable-next-line no-console
console.log(
'Design units guide initialized. Scene configured for A4 print output.'
);
}
}
export default Example;
```
This guide covers how to get and set design units, configure DPI for export quality, and set up scenes for specific physical dimensions like A4 paper.
## Understanding Design Units
### Supported Unit Types
CE.SDK supports three design unit types, each suited for different output scenarios:
- **Pixel** — Default unit, ideal for screen-based designs, web graphics, and video content. One unit equals one pixel in the design coordinate space.
- **Millimeter** — For print designs targeting metric dimensions (A4, A5, business cards). One unit equals one millimeter at the scene's DPI setting.
- **Inch** — For print designs targeting imperial dimensions (letter, legal, US business cards). One unit equals one inch at the scene's DPI setting.
### Design Unit and DPI Relationship
DPI (dots per inch) determines how physical units convert to pixels during export. At 300 DPI, a 1-inch block exports as 300 pixels wide. Higher DPI values produce higher-resolution exports suitable for professional printing.
For pixel-based scenes, DPI primarily affects font size conversions since font sizes are always specified in points.
## Getting the Current Design Unit
Use `engine.scene.getDesignUnit()` to retrieve the current scene's design unit. This returns one of three values: `'Pixel'`, `'Millimeter'`, or `'Inch'`.
```typescript highlight-get-design-unit
// Get the current scene
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
// Get the current design unit
const currentUnit = engine.scene.getDesignUnit();
// eslint-disable-next-line no-console
console.log('Current design unit:', currentUnit); // 'Pixel' by default
```
## Setting the Design Unit
Use `engine.scene.setDesignUnit()` to change the measurement system. When you change the design unit, CE.SDK automatically converts existing layout values to maintain visual appearance.
```typescript highlight-set-design-unit
// Set design unit to Millimeter for print workflow
engine.scene.setDesignUnit('Millimeter');
// Verify the change
const newUnit = engine.scene.getDesignUnit();
// eslint-disable-next-line no-console
console.log('Design unit changed to:', newUnit); // 'Millimeter'
```
## Configuring DPI
Access DPI through the scene's `scene/dpi` property. For print workflows, 300 DPI is the standard for high-quality output.
```typescript highlight-configure-dpi
// Set DPI to 300 for print-quality exports
// Higher DPI produces higher resolution output
engine.block.setFloat(scene, 'scene/dpi', 300);
// Verify the DPI setting
const dpi = engine.block.getFloat(scene, 'scene/dpi');
// eslint-disable-next-line no-console
console.log('DPI set to:', dpi); // 300
```
DPI affects different aspects depending on the design unit:
- **Physical units (mm, in)**: DPI determines the pixel resolution of exported files
- **Pixel units**: DPI only affects the conversion of font sizes from points to pixels
## Setting Up Print-Ready Designs
For print workflows, combine `setDesignUnit()` with appropriate DPI and page dimensions. Here's how to set up an A4 document ready for print export:
```typescript highlight-set-page-dimensions
// Get the page and set A4 dimensions (210 x 297 mm)
const page = engine.block.findByType('page')[0];
// Verify dimensions
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// eslint-disable-next-line no-console
console.log(`Page dimensions: ${pageWidth}mm x ${pageHeight}mm`);
```
## Font Sizes and Design Units
Font sizes are always specified in points (`pt`), regardless of the scene's design unit. The DPI setting affects how points convert to pixels for rendering.
```typescript highlight-create-text-block
// Create a text block with millimeter dimensions
const textBlock = engine.block.create('text');
engine.block.appendChild(page, textBlock);
// Position text at 20mm from left, 30mm from top
engine.block.setPositionX(textBlock, 20);
engine.block.setPositionY(textBlock, 30);
// Set text block size to 170mm x 50mm
engine.block.setWidth(textBlock, 170);
engine.block.setHeight(textBlock, 50);
// Add content to the text block
engine.block.setString(
textBlock,
'text/text',
'This A4 document uses millimeter units with 300 DPI for print-ready output.'
);
```
When DPI changes, text blocks automatically adjust their rendered size to maintain visual consistency.
## Understanding Export Resolution
The relationship between design units and export resolution is important for print workflows:
```typescript highlight-compare-units
// Demonstrate unit comparison
// At 300 DPI: 1 inch = 300 pixels, 1 mm = ~11.81 pixels
// eslint-disable-next-line no-console
console.log('Unit comparison at 300 DPI:');
// eslint-disable-next-line no-console
console.log(
'- A4 width (210mm) will export as',
210 * (300 / 25.4),
'pixels'
);
// eslint-disable-next-line no-console
console.log(
'- A4 height (297mm) will export as',
297 * (300 / 25.4),
'pixels'
);
```
At 300 DPI:
- An A4 page (210 × 297 mm) exports as 2480 × 3508 pixels
- A letter page (8.5 × 11 in) exports as 2550 × 3300 pixels
## Troubleshooting
### Exported Dimensions Don't Match Expected Size
Verify that DPI is set correctly for physical units. At 300 DPI, 1 inch becomes 300 pixels. Check that your design unit matches your target output format.
### Text Appears Wrong Size After Unit Change
Font sizes in points auto-adjust based on DPI. If text looks incorrect, verify the DPI setting matches your workflow requirements.
### Blocks Shift Position After Changing Units
CE.SDK preserves visual appearance during unit conversion. If positions seem unexpected, check the original coordinate values—the numeric values change but visual positions should remain stable.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Editor State"
description: "Control how users interact with content by switching between edit modes like transform, crop, and text."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/edit-modes-1f5b6c/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Editor State](https://img.ly/docs/cesdk/sveltekit/concepts/edit-modes-1f5b6c/)
---
Editor state determines how users interact with content on the canvas by controlling which editing mode is active and tracking cursor behavior. This guide covers edit modes, state change subscriptions, cursor state, and interaction detection.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-editor-state-browser/)
Edit modes define what type of content users can currently modify. Each mode enables different interaction behaviors—Transform mode for moving and resizing, Crop mode for adjusting content within frames, Text mode for inline text editing, and so on. The engine maintains the current edit mode as part of its state and notifies subscribers when it changes.
```typescript file=@cesdk_web_examples/guides-concepts-editor-state-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Editor State Guide
*
* Demonstrates working with editor state in CE.SDK:
* - Understanding edit modes and switching between them
* - Subscribing to state changes
* - Reading cursor type and rotation
* - Tracking text cursor position
* - Detecting active interactions
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
// Add an image block to demonstrate Crop mode
const imageBlock = engine.block.create('graphic');
engine.block.appendChild(page, imageBlock);
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
engine.block.setWidth(imageBlock, 350);
engine.block.setHeight(imageBlock, 250);
engine.block.setPositionX(imageBlock, 50);
engine.block.setPositionY(imageBlock, 175);
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
// Add a text block to demonstrate Text mode
const textBlock = engine.block.create('text');
engine.block.appendChild(page, textBlock);
engine.block.replaceText(textBlock, 'Edit this text');
engine.block.setTextFontSize(textBlock, 48);
engine.block.setTextColor(textBlock, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 });
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setPositionX(textBlock, 450);
engine.block.setPositionY(textBlock, 275);
// Subscribe to state changes to track mode transitions
// The returned function can be called to unsubscribe when no longer needed
const unsubscribeFromStateChanges = engine.editor.onStateChanged(() => {
const currentMode = engine.editor.getEditMode();
console.log('Edit mode changed to:', currentMode);
// Also log cursor state when state changes
const cursorType = engine.editor.getCursorType();
console.log('Current cursor type:', cursorType);
});
console.log('State change subscription active');
// Example: Unsubscribe after a delay (in a real app, call when component unmounts)
setTimeout(() => {
unsubscribeFromStateChanges();
console.log('Unsubscribed from state changes');
}, 10000);
// Get the current edit mode (default is Transform)
const initialMode = engine.editor.getEditMode();
console.log('Initial edit mode:', initialMode);
// Select the image block and switch to Crop mode
engine.block.select(imageBlock);
engine.editor.setEditMode('Crop');
console.log('Switched to Crop mode on image block');
// After a moment, switch to Transform mode
engine.editor.setEditMode('Transform');
console.log('Switched back to Transform mode');
// Create a custom edit mode that inherits from Crop behavior
engine.editor.setEditMode('MyCustomCropMode', 'Crop');
console.log(
'Created custom mode based on Crop:',
engine.editor.getEditMode()
);
// Switch back to Transform for the demo
engine.editor.setEditMode('Transform');
// Get the cursor type to display the appropriate mouse cursor
const cursorType = engine.editor.getCursorType();
console.log('Cursor type:', cursorType);
// Returns: 'Arrow', 'Move', 'MoveNotPermitted', 'Resize', 'Rotate', or 'Text'
// Get cursor rotation for directional cursors like resize handles
const cursorRotation = engine.editor.getCursorRotation();
console.log('Cursor rotation (radians):', cursorRotation);
// Apply to cursor element: transform: rotate(${cursorRotation}rad)
// Select the text block and switch to Text mode to get cursor position
engine.block.select(textBlock);
engine.editor.setEditMode('Text');
// Get text cursor position in screen space
const textCursorX = engine.editor.getTextCursorPositionInScreenSpaceX();
const textCursorY = engine.editor.getTextCursorPositionInScreenSpaceY();
console.log('Text cursor position:', { x: textCursorX, y: textCursorY });
// Use these coordinates to position a floating toolbar near the text cursor
// Check if a user interaction is currently in progress
const isInteracting = engine.editor.unstable_isInteractionHappening();
console.log('Is interaction happening:', isInteracting);
// Use this to defer expensive operations during drag/resize operations
if (!isInteracting) {
console.log('Safe to perform heavy updates');
}
// Switch back to Transform mode and select the image for the hero screenshot
engine.editor.setEditMode('Transform');
engine.block.select(imageBlock);
// Zoom to fit the page
engine.scene.enableZoomAutoFit(page, 'Both');
console.log('Editor State guide initialized successfully.');
console.log('Try clicking on blocks to see edit modes change.');
console.log('Double-click on the text block to enter Text mode.');
console.log('Select the image and use the crop handle to enter Crop mode.');
}
}
export default Example;
```
This guide covers:
- Understanding the built-in edit modes (Transform, Crop, Text, Trim, Playback, Vector)
- Switching edit modes programmatically
- Creating custom edit modes that inherit from built-in modes
- Subscribing to state changes for UI synchronization
- Reading cursor type and rotation for custom cursors
- Tracking text cursor position for overlays
- Detecting active user interactions
## Edit Modes
CE.SDK supports five built-in edit modes, each designed for a specific type of interaction with canvas content.
### Transform Mode
Transform is the default mode that allows users to move, resize, and rotate blocks on the canvas. When a block is selected in Transform mode, control handles appear for manipulation.
```typescript highlight=highlight-get-edit-mode
// Get the current edit mode (default is Transform)
const initialMode = engine.editor.getEditMode();
console.log('Initial edit mode:', initialMode);
```
Query the current mode using `engine.editor.getEditMode()`. The initial mode is always `'Transform'`.
### Switching Edit Modes
Use `engine.editor.setEditMode()` to change the current editing mode. The mode determines what interactions are available on selected blocks.
```typescript highlight=highlight-set-edit-mode
// Select the image block and switch to Crop mode
engine.block.select(imageBlock);
engine.editor.setEditMode('Crop');
console.log('Switched to Crop mode on image block');
// After a moment, switch to Transform mode
engine.editor.setEditMode('Transform');
console.log('Switched back to Transform mode');
```
Available modes include:
- **Transform**: Move, resize, and rotate blocks (default)
- **Crop**: Adjust media content within block frames
- **Text**: Edit text content inline
- **Trim**: Adjust clip start and end points (video scenes)
- **Playback**: Play video or audio content (limited interactions)
- **Vector**: Edit vector path anchor points and bezier handles
### Custom Edit Modes
You can create custom modes that inherit behavior from a built-in base mode. Pass an optional second parameter to `setEditMode()` specifying the base mode.
```typescript highlight=highlight-custom-edit-mode
// Create a custom edit mode that inherits from Crop behavior
engine.editor.setEditMode('MyCustomCropMode', 'Crop');
console.log(
'Created custom mode based on Crop:',
engine.editor.getEditMode()
);
// Switch back to Transform for the demo
engine.editor.setEditMode('Transform');
```
Custom modes are useful when you need to track application-specific states while maintaining standard editing behavior. For example, you might use a custom mode to indicate that a specific tool is active in your UI while still allowing Transform interactions.
## Subscribing to State Changes
The engine notifies subscribers whenever the editor state changes, including mode switches and cursor updates.
### Using onStateChanged
Subscribe to state changes using `engine.editor.onStateChanged()`. The callback fires at the end of each engine update where state changed. The subscription returns an unsubscribe function for cleanup.
```typescript highlight=highlight-on-state-changed
// Subscribe to state changes to track mode transitions
// The returned function can be called to unsubscribe when no longer needed
const unsubscribeFromStateChanges = engine.editor.onStateChanged(() => {
const currentMode = engine.editor.getEditMode();
console.log('Edit mode changed to:', currentMode);
// Also log cursor state when state changes
const cursorType = engine.editor.getCursorType();
console.log('Current cursor type:', cursorType);
});
console.log('State change subscription active');
// Example: Unsubscribe after a delay (in a real app, call when component unmounts)
setTimeout(() => {
unsubscribeFromStateChanges();
console.log('Unsubscribed from state changes');
}, 10000);
```
Common use cases include:
- Updating toolbar UI to reflect the current mode
- Showing mode-specific panels or controls
- Disabling certain actions during Playback mode
- Logging state transitions for analytics
Always call the unsubscribe function when your component unmounts or when you no longer need updates. This prevents memory leaks and unnecessary callback invocations.
## Cursor State
The engine tracks what cursor type should be displayed based on the current context and hovered element. Use this information to display the appropriate mouse cursor in your custom UI.
### Reading Cursor Type
Use `engine.editor.getCursorType()` to get the cursor type to display.
```typescript highlight=highlight-cursor-type
// Get the cursor type to display the appropriate mouse cursor
const cursorType = engine.editor.getCursorType();
console.log('Cursor type:', cursorType);
// Returns: 'Arrow', 'Move', 'MoveNotPermitted', 'Resize', 'Rotate', or 'Text'
```
The method returns one of these values:
- **Arrow**: Default pointer cursor
- **Move**: Indicates the element can be moved
- **MoveNotPermitted**: Element cannot be moved in the current context
- **Resize**: Resize handle is hovered
- **Rotate**: Rotation handle is hovered
- **Text**: Text editing cursor
- **Cell**: Crosshair cursor for precise placement (vector editing)
### Reading Cursor Rotation
For directional cursors like resize handles, use `engine.editor.getCursorRotation()` to get the rotation angle in radians.
```typescript highlight=highlight-cursor-rotation
// Get cursor rotation for directional cursors like resize handles
const cursorRotation = engine.editor.getCursorRotation();
console.log('Cursor rotation (radians):', cursorRotation);
// Apply to cursor element: transform: rotate(${cursorRotation}rad)
```
Apply this rotation to your cursor image for correct visual feedback. For example, when hovering over a corner resize handle at 45 degrees, the rotation value reflects that angle so your cursor points in the correct direction.
## Text Cursor Position
When in Text edit mode, you can track the text cursor (caret) position for rendering custom overlays or toolbars near the insertion point.
### Screen Space Coordinates
Use `engine.editor.getTextCursorPositionInScreenSpaceX()` and `engine.editor.getTextCursorPositionInScreenSpaceY()` to get the cursor position in screen pixels.
```typescript highlight=highlight-text-cursor-position
// Select the text block and switch to Text mode to get cursor position
engine.block.select(textBlock);
engine.editor.setEditMode('Text');
// Get text cursor position in screen space
const textCursorX = engine.editor.getTextCursorPositionInScreenSpaceX();
const textCursorY = engine.editor.getTextCursorPositionInScreenSpaceY();
console.log('Text cursor position:', { x: textCursorX, y: textCursorY });
// Use these coordinates to position a floating toolbar near the text cursor
```
These values update as the user moves through text. Use them to position floating toolbars, formatting menus, or other UI elements relative to where the user is editing.
## Detecting Active Interactions
Determine whether the user is currently in the middle of an interaction like dragging or resizing.
### Using unstable\_isInteractionHappening
Call `engine.editor.unstable_isInteractionHappening()` to check if a user interaction is in progress.
```typescript highlight=highlight-interaction-happening
// Check if a user interaction is currently in progress
const isInteracting = engine.editor.unstable_isInteractionHappening();
console.log('Is interaction happening:', isInteracting);
// Use this to defer expensive operations during drag/resize operations
if (!isInteracting) {
console.log('Safe to perform heavy updates');
}
```
This is useful for:
- Deferring expensive operations until after the interaction completes
- Showing different UI states during drag operations
- Optimizing performance by batching updates
Note that this API is marked unstable and may change in future releases.
## Troubleshooting
### Mode Doesn't Change Visually
Ensure a block is selected that supports the target mode. For example, switching to Crop mode requires an image or video block to be selected. Switching to Text mode requires a text block.
### State Change Callback Not Firing
Verify the subscription is active before the operation that changes state. If you subscribe after the state change occurs, you won't receive the initial notification.
### Cursor Type Always Arrow
Check that the mouse is over an interactive element and the element supports the current edit mode. The cursor type only changes when hovering over actionable areas like handles or selectable content.
### Text Cursor Position is 0,0
Confirm the editor is in Text mode with an active text selection. The text cursor position is only valid when actively editing text content.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Editing Workflow"
description: "Control editing access with Creator, Adopter, Viewer, and Presenter roles using global and block-level scopes for tailored permissions."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/editing-workflow-032d27/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Editing Workflow](https://img.ly/docs/cesdk/sveltekit/concepts/editing-workflow-032d27/)
---
CE.SDK controls editing access through roles and scopes, enabling template workflows where designers create locked layouts and end-users customize only permitted elements.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-editing-workflow-browser/)
CE.SDK uses a two-tier permission system: **roles** define user types with preset permissions, while **scopes** control specific capabilities. This enables workflows where templates can be prepared by designers and safely customized by end-users.
```typescript file=@cesdk_web_examples/guides-concepts-editing-workflow-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* Demonstrates CE.SDK's role-based permission system with scopes.
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.print.iso.a6.landscape'
}
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
// Roles define user types: 'Creator', 'Adopter', 'Viewer', 'Presenter'
const role = engine.editor.getRole();
console.log('Current role:', role); // 'Creator'
// Configure scopes when role changes (role change resets to defaults)
engine.editor.onRoleChanged(() => {
// Set global scopes to 'Defer' so block-level scopes take effect
engine.editor.setGlobalScope('editor/select', 'Defer');
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('text/edit', 'Defer');
engine.editor.setGlobalScope('lifecycle/destroy', 'Defer');
});
// Get page dimensions for centering
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Create a locked text block (brand element)
const lockedText = engine.block.create('text');
engine.block.replaceText(lockedText, 'Locked Text');
engine.block.setTextFontSize(lockedText, 40);
engine.block.setEnum(lockedText, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(lockedText, pageWidth);
engine.block.setHeightMode(lockedText, 'Auto');
engine.block.setPositionX(lockedText, 0);
engine.block.setPositionY(lockedText, pageHeight / 2 - 50);
engine.block.appendChild(page, lockedText);
// Lock the block - Adopters cannot select, edit, or move it
engine.block.setScopeEnabled(lockedText, 'editor/select', false);
engine.block.setScopeEnabled(lockedText, 'text/edit', false);
engine.block.setScopeEnabled(lockedText, 'layer/move', false);
engine.block.setScopeEnabled(lockedText, 'lifecycle/destroy', false);
// Create an editable text block (user content)
const editableText = engine.block.create('text');
engine.block.replaceText(editableText, 'Editable Text');
engine.block.setTextFontSize(editableText, 40);
engine.block.setEnum(editableText, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(editableText, pageWidth);
engine.block.setHeightMode(editableText, 'Auto');
engine.block.setPositionX(editableText, 0);
engine.block.setPositionY(editableText, pageHeight / 2 + 10);
engine.block.appendChild(page, editableText);
// Center both texts vertically as a group
const lockedHeight = engine.block.getFrameHeight(lockedText);
const editableHeight = engine.block.getFrameHeight(editableText);
const gap = 20;
const totalHeight = lockedHeight + gap + editableHeight;
const topMargin = (pageHeight - totalHeight) / 2;
engine.block.setPositionY(lockedText, topMargin);
engine.block.setPositionY(editableText, topMargin + lockedHeight + gap);
// Editable block - enable selection and editing
engine.block.setScopeEnabled(editableText, 'editor/select', true);
engine.block.setScopeEnabled(editableText, 'text/edit', true);
engine.block.setScopeEnabled(editableText, 'layer/move', true);
// Check resolved permissions (role + global + block scopes)
const canEditLocked = engine.block.isAllowedByScope(
lockedText,
'text/edit'
);
const canEditEditable = engine.block.isAllowedByScope(
editableText,
'text/edit'
);
// As Creator: both return true (Creators bypass restrictions)
console.log(
'Can edit locked:',
canEditLocked,
'Can edit editable:',
canEditEditable
);
// Switch to Adopter to apply restrictions
engine.editor.setRole('Adopter');
// Select the editable block to show it's interactive
engine.block.select(editableText);
}
}
export default Example;
```
This guide covers:
- The four user roles and their purposes
- How scopes control editing capabilities
- The permission resolution hierarchy
- Common template workflow patterns
## Roles
Roles define user types with different default permissions:
| Role | Purpose | Default Access |
|------|---------|----------------|
| **Creator** | Designers building templates | Full access to all operations |
| **Adopter** | End-users customizing templates | Limited by block-level scopes |
| **Viewer** | Preview-only users | Read-only access |
| **Presenter** | Slideshow/video presenters | Read-only with playback controls |
Creators set the block-level scopes that constrain what Adopters can do. This separation enables brand consistency while allowing personalization.
```typescript highlight-roles
// Roles define user types: 'Creator', 'Adopter', 'Viewer', 'Presenter'
const role = engine.editor.getRole();
console.log('Current role:', role); // 'Creator'
```
## Scopes
Scopes define specific capabilities organized into categories:
- **Text**: Editing content and character formatting
- **Fill/Stroke**: Changing colors and shapes
- **Layer**: Moving, resizing, rotating, cropping
- **Appearance**: Filters, effects, shadows, animations
- **Lifecycle**: Deleting and duplicating elements
- **Editor**: Adding new elements and selecting
## Global vs Block-Level Scopes
**Global scopes** apply editor-wide and determine whether block-level settings are checked:
- `'Allow'` — Always permit the operation
- `'Deny'` — Always block the operation
- `'Defer'` — Check block-level scope settings
**Block-level scopes** control permissions on individual blocks. These settings only take effect when the corresponding global scope is set to `'Defer'`.
```typescript highlight-global-scopes
// Configure scopes when role changes (role change resets to defaults)
engine.editor.onRoleChanged(() => {
// Set global scopes to 'Defer' so block-level scopes take effect
engine.editor.setGlobalScope('editor/select', 'Defer');
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('text/edit', 'Defer');
engine.editor.setGlobalScope('lifecycle/destroy', 'Defer');
});
```
To lock a specific block, disable its scopes:
```typescript highlight-block-scopes
// Lock the block - Adopters cannot select, edit, or move it
engine.block.setScopeEnabled(lockedText, 'editor/select', false);
engine.block.setScopeEnabled(lockedText, 'text/edit', false);
engine.block.setScopeEnabled(lockedText, 'layer/move', false);
engine.block.setScopeEnabled(lockedText, 'lifecycle/destroy', false);
```
## Permission Resolution
Permissions resolve in this order:
1. **Role defaults** — Each role has preset global scope values
2. **Global scope** — If `'Allow'` or `'Deny'`, this is the final answer
3. **Block-level scope** — If global is `'Defer'`, check the block's settings
Use `isAllowedByScope()` to check the final computed permission for any block and scope combination:
```typescript highlight-check-permissions
// Check resolved permissions (role + global + block scopes)
const canEditLocked = engine.block.isAllowedByScope(
lockedText,
'text/edit'
);
const canEditEditable = engine.block.isAllowedByScope(
editableText,
'text/edit'
);
// As Creator: both return true (Creators bypass restrictions)
console.log(
'Can edit locked:',
canEditLocked,
'Can edit editable:',
canEditEditable
);
```
## Template Workflow Pattern
A typical template workflow:
1. **Designer (Creator)** creates the template layout
2. **Designer** locks brand elements using block scopes
3. **Designer** keeps personalization fields editable
4. **End-user (Adopter)** opens the template
5. **End-user** edits only permitted elements
6. **End-user** exports the personalized result
This pattern ensures brand consistency while enabling personalization.
## Implementation Guides
For detailed implementation, see these guides:
[Lock Design Elements](https://img.ly/docs/cesdk/sveltekit/create-templates/lock-131489/) — Step-by-step instructions for locking specific elements in templates
[Set Editing Constraints](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) — Configure which properties users can modify
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Events"
description: "Subscribe to block creation, update, and deletion events to track changes in your CE.SDK scene."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/events-353f97/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Events](https://img.ly/docs/cesdk/sveltekit/concepts/events-353f97/)
---
Monitor and react to block changes in real time by subscribing to creation,
update, and destruction events in your CE.SDK scene.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-events-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-events-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-events-browser/)
Events enable real-time monitoring of block changes in CE.SDK. When blocks are created, modified, or destroyed, the engine delivers these changes through callback subscriptions at the end of each update cycle. This push-based notification system eliminates the need for polling and enables efficient reactive architectures.
```typescript file=@cesdk_web_examples/guides-concepts-events-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Events Guide
*
* Demonstrates working with block lifecycle events in CE.SDK:
* - Subscribing to all block events
* - Filtering events to specific blocks
* - Processing Created, Updated, and Destroyed event types
* - Event batching and deduplication behavior
* - Safe handling of destroyed blocks
* - Proper unsubscription for cleanup
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Subscribe to events from all blocks in the scene
// Pass an empty array to receive events from every block
const unsubscribeAll = engine.event.subscribe([], (events) => {
for (const event of events) {
console.log(
`[All Blocks] ${event.type} event for block ${event.block}`
);
}
});
// Get the current page to add blocks to
const pages = engine.block.findByType('page');
const page = pages[0];
// Create a graphic block - this triggers a Created event
const graphic = engine.block.create('graphic');
// Set up the graphic with a shape and fill
const rectShape = engine.block.createShape('rect');
engine.block.setShape(graphic, rectShape);
// Position and size the graphic
engine.block.setPositionX(graphic, 200);
engine.block.setPositionY(graphic, 150);
engine.block.setWidth(graphic, 400);
engine.block.setHeight(graphic, 300);
// Add an image fill
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(graphic, imageFill);
engine.block.setEnum(graphic, 'contentFill/mode', 'Cover');
// Append to page to make it visible
engine.block.appendChild(page, graphic);
console.log('Created graphic block:', graphic);
// Subscribe to events for specific blocks only
// This is more efficient when you only care about certain blocks
const unsubscribeSpecific = engine.event.subscribe([graphic], (events) => {
for (const event of events) {
console.log(
`[Specific Block] ${event.type} event for block ${event.block}`
);
}
});
// Modify the block - this triggers Updated events
// Due to deduplication, multiple rapid changes result in one Updated event
engine.block.setRotation(graphic, 0.1); // Rotate slightly
engine.block.setFloat(graphic, 'opacity', 0.9); // Adjust opacity
console.log('Modified graphic block - rotation and opacity changed');
// Process events by checking the type property
const unsubscribeProcess = engine.event.subscribe([], (events) => {
for (const event of events) {
switch (event.type) {
case 'Created': {
// Block was just created - safe to use Block API
const blockType = engine.block.getType(event.block);
console.log(`Block created with type: ${blockType}`);
break;
}
case 'Updated': {
// Block property changed - safe to use Block API
console.log(`Block ${event.block} was updated`);
break;
}
case 'Destroyed': {
// Block was destroyed - must check validity before using Block API
const isValid = engine.block.isValid(event.block);
console.log(
`Block ${event.block} destroyed, still valid: ${isValid}`
);
break;
}
}
}
});
// When handling Destroyed events, always check block validity
// The block ID is no longer valid after destruction
const unsubscribeDestroyed = engine.event.subscribe([], (events) => {
for (const event of events) {
if (event.type === 'Destroyed') {
// IMPORTANT: Check validity before any Block API calls
if (engine.block.isValid(event.block)) {
// Block is still valid (this shouldn't happen for Destroyed events)
console.log('Block is unexpectedly still valid');
} else {
// Block is invalid - expected for Destroyed events
// Clean up any references to this block ID
console.log(
`Block ${event.block} has been destroyed and is invalid`
);
}
}
}
});
// Create a second block to demonstrate destruction
const textBlock = engine.block.create('text');
engine.block.appendChild(page, textBlock);
engine.block.setPositionX(textBlock, 200);
engine.block.setPositionY(textBlock, 500);
engine.block.setWidth(textBlock, 400);
engine.block.setHeight(textBlock, 50);
engine.block.setString(textBlock, 'text/text', 'Events Demo');
engine.block.setFloat(textBlock, 'text/fontSize', 48);
console.log('Created text block:', textBlock);
// Destroy the text block - this triggers a Destroyed event
engine.block.destroy(textBlock);
console.log('Destroyed text block');
// After destruction, the block ID is no longer valid
const isTextBlockValid = engine.block.isValid(textBlock);
console.log('Text block still valid after destroy:', isTextBlockValid); // false
// Clean up subscriptions when no longer needed
// This prevents memory leaks and reduces engine overhead
unsubscribeAll();
unsubscribeSpecific();
unsubscribeProcess();
unsubscribeDestroyed();
console.log('Unsubscribed from all event listeners');
// Re-subscribe with a single listener for the demo UI
engine.event.subscribe([], (events) => {
for (const event of events) {
console.log(`Event: ${event.type} - Block: ${event.block}`);
}
});
console.log('Events guide initialized successfully.');
console.log(
'Demonstrated: subscribing, event types, processing, and cleanup.'
);
}
}
export default Example;
```
This guide covers subscribing to block lifecycle events, processing the three event types (`Created`, `Updated`, `Destroyed`), filtering events to specific blocks, understanding batching and deduplication behavior, and properly cleaning up subscriptions.
## Event Types
CE.SDK provides three event types that capture the block lifecycle:
- **`Created`**: Fires when a new block is added to the scene
- **`Updated`**: Fires when any property of a block changes
- **`Destroyed`**: Fires when a block is removed from the scene
Each event contains a `block` property with the block ID and a `type` property indicating which event occurred.
## Subscribing to All Blocks
Use `engine.event.subscribe()` to register a callback that receives batched events. Pass an empty array to receive events from all blocks in the scene:
```typescript highlight-subscribe-all
// Subscribe to events from all blocks in the scene
// Pass an empty array to receive events from every block
const unsubscribeAll = engine.event.subscribe([], (events) => {
for (const event of events) {
console.log(
`[All Blocks] ${event.type} event for block ${event.block}`
);
}
});
```
The callback receives an array of events at the end of each engine update cycle. The function returns an unsubscribe function you should store for cleanup.
## Subscribing to Specific Blocks
For better performance when you only care about certain blocks, pass an array of block IDs to filter events:
```typescript highlight-subscribe-specific
// Subscribe to events for specific blocks only
// This is more efficient when you only care about certain blocks
const unsubscribeSpecific = engine.event.subscribe([graphic], (events) => {
for (const event of events) {
console.log(
`[Specific Block] ${event.type} event for block ${event.block}`
);
}
});
```
This reduces overhead since the engine only needs to prepare events for the blocks you're tracking.
## Creating Blocks and Handling `Created` Events
When you create a block, the engine fires a `Created` event. You can safely use Block API methods on the block ID since the block is valid:
```typescript highlight-event-created
// Create a graphic block - this triggers a Created event
const graphic = engine.block.create('graphic');
// Set up the graphic with a shape and fill
const rectShape = engine.block.createShape('rect');
engine.block.setShape(graphic, rectShape);
// Position and size the graphic
engine.block.setPositionX(graphic, 200);
engine.block.setPositionY(graphic, 150);
engine.block.setWidth(graphic, 400);
engine.block.setHeight(graphic, 300);
// Add an image fill
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(graphic, imageFill);
engine.block.setEnum(graphic, 'contentFill/mode', 'Cover');
// Append to page to make it visible
engine.block.appendChild(page, graphic);
console.log('Created graphic block:', graphic);
```
Use `Created` events to initialize tracking, update UI state, or set up additional subscriptions for the new block.
## Updating Blocks and Handling `Updated` Events
Modifying any property of a block triggers an `Updated` event. Due to deduplication, you receive at most one `Updated` event per block per engine update cycle, regardless of how many properties changed:
```typescript highlight-event-updated
// Modify the block - this triggers Updated events
// Due to deduplication, multiple rapid changes result in one Updated event
engine.block.setRotation(graphic, 0.1); // Rotate slightly
engine.block.setFloat(graphic, 'opacity', 0.9); // Adjust opacity
console.log('Modified graphic block - rotation and opacity changed');
```
Multiple rapid changes to the same block result in a single `Updated` event, making event handling efficient even during complex operations.
## Processing Events by Type
Handle each event type appropriately by checking the `type` property. For `Created` and `Updated` events, you can safely use Block API methods. For `Destroyed` events, the block ID is no longer valid:
```typescript highlight-process-events
// Process events by checking the type property
const unsubscribeProcess = engine.event.subscribe([], (events) => {
for (const event of events) {
switch (event.type) {
case 'Created': {
// Block was just created - safe to use Block API
const blockType = engine.block.getType(event.block);
console.log(`Block created with type: ${blockType}`);
break;
}
case 'Updated': {
// Block property changed - safe to use Block API
console.log(`Block ${event.block} was updated`);
break;
}
case 'Destroyed': {
// Block was destroyed - must check validity before using Block API
const isValid = engine.block.isValid(event.block);
console.log(
`Block ${event.block} destroyed, still valid: ${isValid}`
);
break;
}
}
}
});
```
## Handling `Destroyed` Events Safely
When a block is destroyed, the block ID becomes invalid. Calling Block API methods on a destroyed block throws an exception. Always check validity with `engine.block.isValid()` before operations:
```typescript highlight-destroyed-safety
// When handling Destroyed events, always check block validity
// The block ID is no longer valid after destruction
const unsubscribeDestroyed = engine.event.subscribe([], (events) => {
for (const event of events) {
if (event.type === 'Destroyed') {
// IMPORTANT: Check validity before any Block API calls
if (engine.block.isValid(event.block)) {
// Block is still valid (this shouldn't happen for Destroyed events)
console.log('Block is unexpectedly still valid');
} else {
// Block is invalid - expected for Destroyed events
// Clean up any references to this block ID
console.log(
`Block ${event.block} has been destroyed and is invalid`
);
}
}
}
});
```
After verifying the block is invalid, you can safely clean up any local references. The destroy operation itself triggers the `Destroyed` event:
```typescript highlight-event-destroyed
// Destroy the text block - this triggers a Destroyed event
engine.block.destroy(textBlock);
console.log('Destroyed text block');
// After destruction, the block ID is no longer valid
const isTextBlockValid = engine.block.isValid(textBlock);
console.log('Text block still valid after destroy:', isTextBlockValid); // false
```
Use `isValid()` to clean up any references to destroyed blocks in your application state.
## Unsubscribing from Events
The `subscribe()` method returns an unsubscribe function. Call it when you no longer need events to prevent memory leaks and reduce engine overhead:
```typescript highlight-unsubscribe
// Clean up subscriptions when no longer needed
// This prevents memory leaks and reduces engine overhead
unsubscribeAll();
unsubscribeSpecific();
unsubscribeProcess();
unsubscribeDestroyed();
console.log('Unsubscribed from all event listeners');
```
Always unsubscribe when your component unmounts, the editor closes, or you no longer need to track changes. Keeping unnecessary subscriptions active forces the engine to prepare event lists for each subscriber at every update.
## Event Batching and Deduplication
Events are collected during an engine update and delivered together at the end. The engine deduplicates events, so you receive at most one `Updated` event per block per update cycle. Event order in the callback does not reflect the actual order of changes within the update.
This batching behavior means:
- Multiple property changes to a single block result in one `Updated` event
- You cannot determine which specific property changed from the event alone
- If you need to track specific property changes, compare against cached values
## Use Cases
Events support various reactive patterns in CE.SDK applications:
- **Syncing external state**: Keep state management systems (Redux, MobX, Zustand) synchronized with scene changes
- **Building reactive UIs**: Update UI components when blocks change without polling
- **Tracking changes for undo/redo**: Monitor all block changes for custom history implementations
- **Validating scene constraints**: React to block creation or property changes to enforce design rules
## Troubleshooting
**Events not firing**: Ensure you haven't unsubscribed prematurely. Verify the blocks you're filtering to still exist.
**Exception on `Destroyed` event**: Never call Block API methods on a destroyed block without first checking `engine.block.isValid()`.
**Missing events**: Events are deduplicated—multiple rapid changes to the same property result in one `Updated` event.
**Memory leaks**: Store the unsubscribe function and call it during cleanup. Forgetting to unsubscribe keeps listeners active.
**Event order confusion**: Don't rely on event array order within a single callback—it doesn't reflect chronological order of changes.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Headless"
description: "Run headless CE.SDK's Engine inside a browser-based app."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/headless-mode/browser-24ab98/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Headless Mode](https://img.ly/docs/cesdk/sveltekit/concepts/headless-mode/browser-24ab98/)
---
CreativeEditor SDK (CE.SDK) **Headless Mode** allows your web app to control the Engine API without loading the built-in user interface.
Instead of starting the full editor with panels and toolbars, your browser code talks directly to the Engine. That keeps the experience within your own design system—ideal for automation running on the client.
## Common Use Cases
- **Automating workflows:** Merge data into templates or generate variations with no user input.
- **Automated exports:** Render custom images or videos in the browser and hand them off to storage or your backend.
- **Custom interfaces:** Embed the Engine beneath your own UI components.
- **Background actions:** Run batch jobs or event-driven edits without overlaying the default UI.
### When to Use Headless Mode
## How Headless Mode Works
The standard editor wires UI events (button clicks, drag interactions) to the Engine under the hood. In headless mode, you call the same Engine APIs yourself.
### Features Available in Headless Mode
In headless mode, you keep access to the CE.SDK features while staying entirely within your app’s DOM lifecycle, such as:
- Scene management
- Asset manipulation
- Rendering and export
- Templates and dynamic content
### Requirements to Run the CreativeEngine in the Client
CE.SDK’s Engine needs a **WebGL-capable environment**. When you ship a headless build in the browser:
- Point the `baseURL` in your config at a publicly reachable asset directory (CDN or self-hosted) so the browser can load fonts and other Engine files—even when you install the npm package.
- Only start the Engine **after the DOM is available** (for example, inside `useEffect`, `mounted`, or `DOMContentLoaded`).
- **Dispose** of the Engine during tear down with `engine.dispose()` to release WebGL resources.
- Ensure iframes or workers that boot the Engine expose `document` and a WebGL context.
## Set Up Headless Mode in a Web App
1. Install the Engine package:
```bash
npm install @cesdk/engine
```
````
2. Configure your license and asset location. The global CDN works for prototypes; production apps commonly self-host the asset bundle for faster loads.
```ts
const config = {
license: '',
baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/$UBQ_VERSION$/assets'
};
````
3. Start the Engine from client-side code and reuse the instance across interactions.
> **Tip:** The CreativeEngine doesn’t attach a canvas automatically. If you want a live preview, append `engine.element` onto your own layout.
## Quick Start: Initialize the Engine Headlessly
When you import `@cesdk/engine`, you get the same runtime that powers the full editor. Using the CreativeEngine instead of the CreativeEditor allows you to expose the Engine interface while skipping all UI bootstrapping that comes with the editor.
```js
import CreativeEngine from '@cesdk/engine';
const config = {
license: '',
baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/$UBQ_VERSION$/assets'
};
let engine;
export async function getEngine() {
if (!engine) {
engine = await CreativeEngine.init(config);
}
return engine;
}
```
Once started, you can trigger Engine operations inside your app logic from:
- Buttons
- Keyboard shortcuts
- Background tasks
## Example: Build and Export a Scene on a Single Click
The workflow below wires the Engine to a button, so the user automatically downloads an edited PNG when clicking on a button:
1. Copy paste the following code (adjust to your stack as needed):
```js
// Grab the button element
const downloadButton = document.querySelector('#download-headless');
// Register an async click handler
downloadButton?.addEventListener('click', async () => {
// Wait for the Engine
const engine = await getEngine();
// Build a fresh scene
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 600);
// Attach the page to the scene
engine.block.appendChild(scene, page);
// Add an image layer
const imageBlock = engine.block.create('graphic');
engine.block.setShape(imageBlock, engine.block.createShape('rect'));
const imageFill = engine.block.createFill('image');
// Load an image fill from the CDN
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
// Set position and dimensions
engine.block.setPosition(imageBlock, 100, 100);
engine.block.setWidth(imageBlock, 300);
engine.block.setHeight(imageBlock, 300);
// Append the image to the page
engine.block.appendChild(page, imageBlock);
// Add a text layer
const textBlock = engine.block.create('text');
// Set the text content
engine.block.setString(textBlock, 'text/text', 'Hello from Headless Mode!');
// Set position and dimensions
engine.block.setPosition(textBlock, 100, 450);
engine.block.setWidth(textBlock, 600);
// Append the text to the page
engine.block.appendChild(page, textBlock);
// Export the page as a PNG
const exportResult = await engine.block.export(page, 'image/png');
// Normalize the result to a blob
const blob =
exportResult instanceof Blob
? exportResult
: new Blob([exportResult], { type: 'image/png' });
triggerDownload(blob, 'headless-output.png');
});
function triggerDownload(blob, filename) {
// Build a temporary object URL
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
// Start the download on click
link.click();
// Revoke the link and URL to release resources
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
```
2. Add a clickable element with the matching ID somewhere in your page’s markup. For example:
```js
```
If you’re in a component-based setup (React, Vue, Svelte, etc.), include the same button inside the component’s render/template:
```js
return (
);
```
Once that element is in the DOM, the snippet’s querySelector('#download-headless') finds it and wires up the click handler that runs the export.
See full module
```js
import CreativeEngine from '@cesdk/engine';
const config = {
license: '',
baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/$UBQ_VERSION$/assets'
};
let engine;
export async function getEngine() {
if (!engine) {
engine = await CreativeEngine.init(config);
}
return engine;
}
const downloadButton = document.querySelector('#download-headless');
downloadButton?.addEventListener('click', async () => {
const engine = await getEngine();
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 600);
engine.block.appendChild(scene, page);
const imageBlock = engine.block.create('graphic');
engine.block.setShape(imageBlock, engine.block.createShape('rect'));
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setPosition(imageBlock, 100, 100);
engine.block.setWidth(imageBlock, 300);
engine.block.setHeight(imageBlock, 300);
engine.block.appendChild(page, imageBlock);
const textBlock = engine.block.create('text');
engine.block.setString(textBlock, 'text/text', 'Hello from Headless Mode!');
engine.block.setPosition(textBlock, 100, 450);
engine.block.setWidth(textBlock, 600);
engine.block.appendChild(page, textBlock);
const exportResult = await engine.block.export(page, 'image/png');
const blob =
exportResult instanceof Blob
? exportResult
: new Blob([exportResult], { type: 'image/png' });
triggerDownload(blob, 'headless-output.png');
});
function triggerDownload(blob, filename) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
```
After you add the script to your app and run the dev server:
1. Click the `#download-headless` button in your interface.
2. The Engine assembles the scene in memory and exports it as a PNG.
3. The PNG downloads locally (or you can upload it to your backend).
## Go Further
Need a hybrid approach? [Engine interface guides](https://img.ly/docs/cesdk/sveltekit/engine-interface-6fb7cf/) show how to combine headless logic with the standard editor UI.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Pages"
description: "Pages structure scenes in CE.SDK and must share the same dimensions to ensure consistent rendering."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/pages-7b6bae/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Pages](https://img.ly/docs/cesdk/sveltekit/concepts/pages-7b6bae/)
---
Pages define the format of your designs—every graphic block, text element, and media file lives inside a page. This guide covers how pages fit into the scene hierarchy, their properties like margins and title templates, and how to configure page dimensions for different layout modes.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-pages-browser/)
Pages provide the canvas and frame for your designs. Whether you're building a multi-page document, a social media carousel, or a video composition, understanding how pages work will help you with structuring your content correctly.
```typescript file=@cesdk_web_examples/guides-concepts-pages-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Pages Guide
*
* Demonstrates working with pages in CE.SDK:
* - Understanding the scene hierarchy (Scene → Pages → Blocks)
* - Creating and managing multiple pages
* - Setting page dimensions at the scene level
* - Configuring page properties (margins, title templates, fills)
* - Navigating between pages
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
// Create a scene with VerticalStack layout for multi-page designs
engine.scene.create('VerticalStack');
// Get the stack container to configure spacing
const [stack] = engine.block.findByType('stack');
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
// Get the scene to set page dimensions
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
// Set page dimensions at the scene level (all pages share these dimensions)
engine.block.setFloat(scene, 'scene/pageDimensions/width', 800);
engine.block.setFloat(scene, 'scene/pageDimensions/height', 600);
// Create the first page and set its dimensions
const firstPage = engine.block.create('page');
engine.block.setWidth(firstPage, 800);
engine.block.setHeight(firstPage, 600);
engine.block.appendChild(stack, firstPage);
// Create the second page with the same dimensions
const secondPage = engine.block.create('page');
engine.block.setWidth(secondPage, 800);
engine.block.setHeight(secondPage, 600);
engine.block.appendChild(stack, secondPage);
// Add an image block to the first page
const imageBlock = engine.block.create('graphic');
engine.block.appendChild(firstPage, imageBlock);
// Create a rect shape for the graphic block
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
// Configure size and position after appending to the page
engine.block.setWidth(imageBlock, 400);
engine.block.setHeight(imageBlock, 300);
engine.block.setPositionX(imageBlock, 200);
engine.block.setPositionY(imageBlock, 150);
// Create and configure the image fill
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
// Add a text block to the second page
const textBlock = engine.block.create('text');
engine.block.appendChild(secondPage, textBlock);
// Configure text properties after appending to the page
engine.block.replaceText(textBlock, 'Page 2');
engine.block.setTextFontSize(textBlock, 48);
engine.block.setTextColor(textBlock, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 });
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
// Center the text on the page
const textWidth = engine.block.getFrameWidth(textBlock);
const textHeight = engine.block.getFrameHeight(textBlock);
engine.block.setPositionX(textBlock, (800 - textWidth) / 2);
engine.block.setPositionY(textBlock, (600 - textHeight) / 2);
// Configure page properties on the first page
// Enable and set margins for print bleed
engine.block.setBool(firstPage, 'page/marginEnabled', true);
engine.block.setFloat(firstPage, 'page/margin/top', 10);
engine.block.setFloat(firstPage, 'page/margin/bottom', 10);
engine.block.setFloat(firstPage, 'page/margin/left', 10);
engine.block.setFloat(firstPage, 'page/margin/right', 10);
// Set a custom title template for the first page
engine.block.setString(firstPage, 'page/titleTemplate', 'Cover');
// Set a custom title template for the second page
engine.block.setString(secondPage, 'page/titleTemplate', 'Content');
// Set a background fill on the second page
const colorFill = engine.block.createFill('color');
engine.block.setColor(colorFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 1.0,
a: 1.0
});
engine.block.setFill(secondPage, colorFill);
// Demonstrate finding pages
const allPages = engine.scene.getPages();
console.log('All pages:', allPages);
console.log('Number of pages:', allPages.length);
// Get the current page (nearest to viewport center or containing selection)
const currentPage = engine.scene.getCurrentPage();
console.log('Current page:', currentPage);
// Alternative: Find pages using block API
const pagesByType = engine.block.findByType('page');
console.log('Pages found by type:', pagesByType);
// Select the first page and zoom to fit
engine.block.select(firstPage);
engine.scene.enableZoomAutoFit(firstPage, 'Both');
console.log('Pages guide initialized with a 2-page design.');
}
}
export default Example;
```
This guide covers:
- Understanding the scene hierarchy: Scene → Pages → Blocks
- Creating and managing multiple pages
- Setting page dimensions at the scene level
- Configuring page properties like margins and title templates
- Navigating between pages programmatically
## Pages in the Scene Hierarchy
In CE.SDK, content follows a strict hierarchy: a **scene** contains **pages**, and pages contain **content blocks**. Only blocks attached to a page are rendered on the canvas.
```typescript highlight=highlight-create-scene
// Create a scene with VerticalStack layout for multi-page designs
engine.scene.create('VerticalStack');
// Get the stack container to configure spacing
const [stack] = engine.block.findByType('stack');
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
```
When you create a scene with a layout mode like `VerticalStack`, pages are automatically arranged according to that mode. Create pages using `engine.block.create('page')`, set their dimensions with `setWidth()` and `setHeight()`, then attach them to the scene (or its stack container) with `engine.block.appendChild()`.
```typescript highlight=highlight-create-pages
// Create the first page and set its dimensions
const firstPage = engine.block.create('page');
engine.block.setWidth(firstPage, 800);
engine.block.setHeight(firstPage, 600);
engine.block.appendChild(stack, firstPage);
// Create the second page with the same dimensions
const secondPage = engine.block.create('page');
engine.block.setWidth(secondPage, 800);
engine.block.setHeight(secondPage, 600);
engine.block.appendChild(stack, secondPage);
```
Content blocks must be added as children of a page to render. For graphic blocks, set both a shape and a fill for content to display. Append blocks to the page before configuring their properties.
```typescript highlight=highlight-add-content
// Add an image block to the first page
const imageBlock = engine.block.create('graphic');
engine.block.appendChild(firstPage, imageBlock);
// Create a rect shape for the graphic block
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
// Configure size and position after appending to the page
engine.block.setWidth(imageBlock, 400);
engine.block.setHeight(imageBlock, 300);
engine.block.setPositionX(imageBlock, 200);
engine.block.setPositionY(imageBlock, 150);
// Create and configure the image fill
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
// Add a text block to the second page
const textBlock = engine.block.create('text');
engine.block.appendChild(secondPage, textBlock);
// Configure text properties after appending to the page
engine.block.replaceText(textBlock, 'Page 2');
engine.block.setTextFontSize(textBlock, 48);
engine.block.setTextColor(textBlock, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 });
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
// Center the text on the page
const textWidth = engine.block.getFrameWidth(textBlock);
const textHeight = engine.block.getFrameHeight(textBlock);
engine.block.setPositionX(textBlock, (800 - textWidth) / 2);
engine.block.setPositionY(textBlock, (600 - textHeight) / 2);
```
## Page Dimensions and Consistency
The CE.SDK engine supports pages with different dimensions. When using stacked layout modes (VerticalStack, HorizontalStack), the Editor UI expects all pages to share the same size. However, with the `Free` layout mode, you can set different dimensions for each page in the UI.
```typescript highlight=highlight-set-dimensions
// Get the scene to set page dimensions
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
// Set page dimensions at the scene level (all pages share these dimensions)
engine.block.setFloat(scene, 'scene/pageDimensions/width', 800);
engine.block.setFloat(scene, 'scene/pageDimensions/height', 600);
```
You can set default page dimensions at the scene level using `engine.block.setFloat()` with `scene/pageDimensions/width` and `scene/pageDimensions/height`. The `scene/aspectRatioLock` property controls whether changing one dimension automatically adjusts the other. Individual pages can also have their dimensions set directly with `setWidth()` and `setHeight()`.
## Finding and Navigating Pages
CE.SDK provides several methods to locate and navigate between pages in your scene.
```typescript highlight=highlight-find-pages
// Demonstrate finding pages
const allPages = engine.scene.getPages();
console.log('All pages:', allPages);
console.log('Number of pages:', allPages.length);
// Get the current page (nearest to viewport center or containing selection)
const currentPage = engine.scene.getCurrentPage();
console.log('Current page:', currentPage);
// Alternative: Find pages using block API
const pagesByType = engine.block.findByType('page');
console.log('Pages found by type:', pagesByType);
```
Use these methods based on your needs:
- `engine.scene.getPages()` returns all pages in sorted order
- `engine.scene.getCurrentPage()` returns the page containing the current selection, or the page nearest to the viewport center
- `engine.block.findByType('page')` finds all page blocks in the scene
- `engine.scene.findNearestToViewPortCenterByType('page')` returns pages sorted by their distance from the viewport center
## Page Properties
Each page has its own properties that control its appearance and behavior. These are set on the page block itself, not on the scene.
### Margins
Page margins define bleed areas useful for print designs. Enable margins and configure each side individually:
```typescript highlight=highlight-page-properties
// Configure page properties on the first page
// Enable and set margins for print bleed
engine.block.setBool(firstPage, 'page/marginEnabled', true);
engine.block.setFloat(firstPage, 'page/margin/top', 10);
engine.block.setFloat(firstPage, 'page/margin/bottom', 10);
engine.block.setFloat(firstPage, 'page/margin/left', 10);
engine.block.setFloat(firstPage, 'page/margin/right', 10);
// Set a custom title template for the first page
engine.block.setString(firstPage, 'page/titleTemplate', 'Cover');
// Set a custom title template for the second page
engine.block.setString(secondPage, 'page/titleTemplate', 'Content');
```
Set `page/marginEnabled` to `true` to enable margins, then use `page/margin/top`, `page/margin/bottom`, `page/margin/left`, and `page/margin/right` to configure each side.
### Title Template
The `page/titleTemplate` property defines the display label shown for each page. It supports template variables like `{{ubq.page_index}}` for dynamic numbering.
The default value is `"Page {{ubq.page_index}}"`. You can customize this to show labels like "Slide 1", "Cover", or any custom text.
### Fill and Background
Pages support fills for background colors or images using the standard fill system.
```typescript highlight=highlight-page-background
// Set a background fill on the second page
const colorFill = engine.block.createFill('color');
engine.block.setColor(colorFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 1.0,
a: 1.0
});
engine.block.setFill(secondPage, colorFill);
```
Create a fill using `engine.block.createFill('color')` or `engine.block.createFill('image')`, configure its properties, then apply it to the page with `engine.block.setFill(page, fill)`.
## Page Layout Modes
The scene's layout mode controls how multiple pages are arranged. Set this using `engine.block.setEnum()` on the scene with the `scene/layout` property:
- **VerticalStack** (default): Pages stack vertically, one below the other
- **HorizontalStack**: Pages arrange horizontally, side by side
- **DepthStack**: Pages overlay each other, typically used for video editing
- **Free**: Pages can be positioned freely without automatic arrangement
## Pages for Static Designs vs. Video Editing
Page behavior varies depending on how the scene is used.
### Static Designs
For static designs, pages act like artboards. Each page is a separate canvas ideal for multi-page documents, social media posts, or print layouts. Pages exist side by side and don't have time-based properties.
### Video Editing
For video editing, pages represent time-based compositions that transition sequentially during playback. Each page has playback properties:
- `playback/duration` controls how long the page appears (in seconds)
- `playback/time` tracks the current playback position
## Troubleshooting
### Content Not Visible
If content blocks aren't appearing, check these common causes:
- Verify the block is attached to a page with `engine.block.appendChild(page, block)`
- For graphic blocks, ensure both a shape and fill are set
- Append blocks to the page before setting their size and position
### Dimension Inconsistencies
If pages appear at unexpected sizes when using stacked layouts, ensure all pages have consistent dimensions. With `Free` layout mode, pages can have different sizes. Set dimensions on individual pages using `setWidth()` and `setHeight()`.
### Page Not Found
If `engine.scene.getPages()` returns an empty array, ensure a scene is loaded first. In headless mode, you must create both the scene and pages manually before querying them.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Resources"
description: "Learn how CE.SDK loads and manages external media files, including preloading for performance, handling transient data, and relocating resources when URLs change."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/resources-a58d71/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Resources](https://img.ly/docs/cesdk/sveltekit/concepts/resources-a58d71/)
---
Manage external media files—images, videos, audio, and fonts—that blocks
reference via URIs in CE.SDK.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-resources-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-resources-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-resources-browser/)
Resources are external media files that blocks reference through URI properties like `fill/image/imageFileURI` or `fill/video/fileURI`. CE.SDK loads resources automatically when needed, but you can preload them for better performance. When working with temporary data like buffers or blobs, you need to persist them before saving. If resource URLs change (such as during CDN migration), you can update the mappings without modifying scene data.
```typescript file=@cesdk_web_examples/guides-concepts-resources-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Resources Guide
*
* Demonstrates resource management in CE.SDK:
* - On-demand resource loading
* - Preloading resources with forceLoadResources()
* - Preloading audio/video with forceLoadAVResource()
* - Finding transient resources
* - Persisting transient resources during save
* - Relocating resources when URLs change
* - Finding all media URIs in a scene
* - Detecting MIME types
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.instagram.story'
}
});
const engine = cesdk.engine;
// Get the current scene and page
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
const pages = engine.block.findByType('page');
const page = pages[0];
// Layout configuration: two blocks with equal margins
const margin = 30;
const gap = 20;
const blockWidth = 300;
const blockHeight = 200;
// Set page dimensions to hug the blocks
const pageWidth = margin + blockWidth + gap + blockWidth + margin;
const pageHeight = margin + blockHeight + margin;
engine.block.setWidth(page, pageWidth);
engine.block.setHeight(page, pageHeight);
// Create a graphic block with an image fill
// Resources are loaded on-demand when the engine renders the block
const imageBlock = engine.block.create('graphic');
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
engine.block.setPositionX(imageBlock, margin);
engine.block.setPositionY(imageBlock, margin);
engine.block.setWidth(imageBlock, blockWidth);
engine.block.setHeight(imageBlock, blockHeight);
// Create an image fill - the image loads when the block is rendered
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_4.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setEnum(imageBlock, 'contentFill/mode', 'Cover');
engine.block.appendChild(page, imageBlock);
console.log('Created image block - resource loads on-demand when rendered');
// Preload all resources in the scene before rendering
// This ensures resources are cached and ready for display
console.log('Preloading all resources in the scene...');
await engine.block.forceLoadResources([scene]);
console.log('All resources preloaded successfully');
// Preload specific blocks only (useful for optimizing load order)
await engine.block.forceLoadResources([imageBlock]);
console.log('Image block resources preloaded');
// Create a second graphic block for video
const videoBlock = engine.block.create('graphic');
const videoShape = engine.block.createShape('rect');
engine.block.setShape(videoBlock, videoShape);
engine.block.setPositionX(videoBlock, margin + blockWidth + gap);
engine.block.setPositionY(videoBlock, margin);
engine.block.setWidth(videoBlock, blockWidth);
engine.block.setHeight(videoBlock, blockHeight);
// Create a video fill
const videoFill = engine.block.createFill('video');
engine.block.setString(
videoFill,
'fill/video/fileURI',
'https://img.ly/static/ubq_video_samples/bbb.mp4'
);
engine.block.setFill(videoBlock, videoFill);
engine.block.setEnum(videoBlock, 'contentFill/mode', 'Cover');
engine.block.appendChild(page, videoBlock);
// Preload video resource to query its properties
console.log('Preloading video resource...');
await engine.block.forceLoadAVResource(videoFill);
console.log('Video resource preloaded');
// Now we can query video properties
const videoDuration = engine.block.getAVResourceTotalDuration(videoFill);
const videoWidth = engine.block.getVideoWidth(videoFill);
const videoHeight = engine.block.getVideoHeight(videoFill);
console.log(
`Video properties - Duration: ${videoDuration}s, Size: ${videoWidth}x${videoHeight}`
);
// Find all transient resources that need persistence before export
// Transient resources include buffers and blobs that won't survive serialization
const transientResources = engine.editor.findAllTransientResources();
console.log(`Found ${transientResources.length} transient resources`);
for (const resource of transientResources) {
console.log(
`Transient: URL=${resource.URL}, Size=${resource.size} bytes`
);
}
// Get all media URIs referenced in the scene
// Useful for pre-fetching or validating resource availability
const mediaURIs = engine.editor.findAllMediaURIs();
console.log(`Scene contains ${mediaURIs.length} media URIs:`);
for (const uri of mediaURIs) {
console.log(` - ${uri}`);
}
// Detect the MIME type of a resource
// This downloads the resource if not already cached
const imageUri = 'https://img.ly/static/ubq_samples/sample_4.jpg';
const mimeType = await engine.editor.getMimeType(imageUri);
console.log(`MIME type of ${imageUri}: ${mimeType}`);
// Relocate a resource when its URL changes
// This updates the internal cache mapping without modifying scene data
const oldUrl = 'https://example.com/old-location/image.jpg';
const newUrl = 'https://cdn.example.com/new-location/image.jpg';
// In a real scenario, you would relocate after uploading to a new location:
// engine.editor.relocateResource(oldUrl, newUrl);
console.log(`Resource relocation example: ${oldUrl} -> ${newUrl}`);
console.log('Use relocateResource() after uploading to a CDN');
// When saving, use onDisallowedResourceScheme to handle transient resources
// This callback is called for each resource with a disallowed scheme (like buffer: or blob:)
const sceneString = await engine.block.saveToString(
[scene],
['http', 'https'], // Only allow http and https URLs
async (url: string) => {
// In a real app, upload the resource and return the permanent URL
// const response = await uploadToCDN(url);
// return response.permanentUrl;
// For this example, we'll just log the URL
console.log(`Would upload transient resource: ${url}`);
// Return the original URL since we're not actually uploading
return url;
}
);
console.log(`Scene saved to string (${sceneString.length} characters)`);
// Set playback time to show video content in the scene
engine.block.setPlaybackTime(page, 2);
console.log('Resources guide initialized successfully.');
console.log(
'Demonstrated: on-demand loading, preloading, transient resources, and relocation.'
);
}
}
export default Example;
```
This guide covers on-demand and preloaded resource loading, identifying and persisting transient resources, relocating resources when URLs change, and discovering all media URIs in a scene.
## On-Demand Loading
The engine fetches resources automatically when rendering blocks or preparing exports. This approach requires no extra code but may delay the initial render while resources download.
```typescript highlight-on-demand-loading
// Get the current scene and page
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
const pages = engine.block.findByType('page');
const page = pages[0];
// Layout configuration: two blocks with equal margins
const margin = 30;
const gap = 20;
const blockWidth = 300;
const blockHeight = 200;
// Set page dimensions to hug the blocks
const pageWidth = margin + blockWidth + gap + blockWidth + margin;
const pageHeight = margin + blockHeight + margin;
engine.block.setWidth(page, pageWidth);
engine.block.setHeight(page, pageHeight);
// Create a graphic block with an image fill
// Resources are loaded on-demand when the engine renders the block
const imageBlock = engine.block.create('graphic');
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
engine.block.setPositionX(imageBlock, margin);
engine.block.setPositionY(imageBlock, margin);
engine.block.setWidth(imageBlock, blockWidth);
engine.block.setHeight(imageBlock, blockHeight);
// Create an image fill - the image loads when the block is rendered
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_4.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setEnum(imageBlock, 'contentFill/mode', 'Cover');
engine.block.appendChild(page, imageBlock);
console.log('Created image block - resource loads on-demand when rendered');
```
When you create a block with an image fill, the image doesn't load immediately. The engine fetches it when the block first renders on the canvas.
## Preloading Resources
Load resources before they're needed with `forceLoadResources()`. Pass block IDs to load resources for those blocks and their children. Preloading eliminates render delays and is useful when you want the scene fully ready before displaying it.
```typescript highlight-preload-resources
// Preload all resources in the scene before rendering
// This ensures resources are cached and ready for display
console.log('Preloading all resources in the scene...');
await engine.block.forceLoadResources([scene]);
console.log('All resources preloaded successfully');
// Preload specific blocks only (useful for optimizing load order)
await engine.block.forceLoadResources([imageBlock]);
console.log('Image block resources preloaded');
```
Pass the scene to preload all resources in the entire design, or pass specific blocks to load only what you need.
## Preloading Audio and Video
Audio and video resources require `forceLoadAVResource()` for full metadata access. The engine needs to download and parse media files before you can query properties like duration or dimensions.
```typescript highlight-preload-av
// Create a second graphic block for video
const videoBlock = engine.block.create('graphic');
const videoShape = engine.block.createShape('rect');
engine.block.setShape(videoBlock, videoShape);
engine.block.setPositionX(videoBlock, margin + blockWidth + gap);
engine.block.setPositionY(videoBlock, margin);
engine.block.setWidth(videoBlock, blockWidth);
engine.block.setHeight(videoBlock, blockHeight);
// Create a video fill
const videoFill = engine.block.createFill('video');
engine.block.setString(
videoFill,
'fill/video/fileURI',
'https://img.ly/static/ubq_video_samples/bbb.mp4'
);
engine.block.setFill(videoBlock, videoFill);
engine.block.setEnum(videoBlock, 'contentFill/mode', 'Cover');
engine.block.appendChild(page, videoBlock);
// Preload video resource to query its properties
console.log('Preloading video resource...');
await engine.block.forceLoadAVResource(videoFill);
console.log('Video resource preloaded');
// Now we can query video properties
const videoDuration = engine.block.getAVResourceTotalDuration(videoFill);
const videoWidth = engine.block.getVideoWidth(videoFill);
const videoHeight = engine.block.getVideoHeight(videoFill);
console.log(
`Video properties - Duration: ${videoDuration}s, Size: ${videoWidth}x${videoHeight}`
);
```
Without preloading, properties like `getAVResourceTotalDuration()` or `getVideoWidth()` may return zero or incomplete values.
## Finding Transient Resources
Transient resources are temporary data stored in buffers or blobs that won't survive scene serialization. Use `findAllTransientResources()` to discover them before saving.
```typescript highlight-find-transient
// Find all transient resources that need persistence before export
// Transient resources include buffers and blobs that won't survive serialization
const transientResources = engine.editor.findAllTransientResources();
console.log(`Found ${transientResources.length} transient resources`);
for (const resource of transientResources) {
console.log(
`Transient: URL=${resource.URL}, Size=${resource.size} bytes`
);
}
```
Each entry includes the resource URL and its size in bytes. Common transient resources include images from clipboard paste operations, camera captures, or programmatically generated content.
## Finding Media URIs
Get all media file URIs referenced in a scene with `findAllMediaURIs()`. This returns a deduplicated list of URIs from image fills, video fills, audio blocks, and other media sources.
```typescript highlight-find-media-uris
// Get all media URIs referenced in the scene
// Useful for pre-fetching or validating resource availability
const mediaURIs = engine.editor.findAllMediaURIs();
console.log(`Scene contains ${mediaURIs.length} media URIs:`);
for (const uri of mediaURIs) {
console.log(` - ${uri}`);
}
```
Use this for pre-fetching resources, validating availability, or building a manifest of all assets in a design.
## Detecting MIME Types
Determine a resource's content type with `getMimeType()`. The engine downloads the resource if it's not already cached.
```typescript highlight-detect-mime-type
// Detect the MIME type of a resource
// This downloads the resource if not already cached
const imageUri = 'https://img.ly/static/ubq_samples/sample_4.jpg';
const mimeType = await engine.editor.getMimeType(imageUri);
console.log(`MIME type of ${imageUri}: ${mimeType}`);
```
Common return values include `image/jpeg`, `image/png`, `video/mp4`, and `audio/mpeg`. This is useful when you need to verify resource types or make format-dependent decisions.
## Relocating Resources
Update URL mappings when resources move with `relocateResource()`. This modifies the internal cache without changing scene data.
```typescript highlight-relocate-resource
// Relocate a resource when its URL changes
// This updates the internal cache mapping without modifying scene data
const oldUrl = 'https://example.com/old-location/image.jpg';
const newUrl = 'https://cdn.example.com/new-location/image.jpg';
// In a real scenario, you would relocate after uploading to a new location:
// engine.editor.relocateResource(oldUrl, newUrl);
console.log(`Resource relocation example: ${oldUrl} -> ${newUrl}`);
console.log('Use relocateResource() after uploading to a CDN');
```
Use relocation after uploading resources to a CDN or when migrating assets between storage locations. The scene continues to reference the original URL, but the engine fetches from the new location.
## Persisting Transient Resources
Handle transient resources during save with the `onDisallowedResourceScheme` callback in `saveToString()`. The callback receives each resource URL with a disallowed scheme (like `buffer:` or `blob:`) and returns the permanent URL after uploading.
```typescript highlight-persist-transient
// When saving, use onDisallowedResourceScheme to handle transient resources
// This callback is called for each resource with a disallowed scheme (like buffer: or blob:)
const sceneString = await engine.block.saveToString(
[scene],
['http', 'https'], // Only allow http and https URLs
async (url: string) => {
// In a real app, upload the resource and return the permanent URL
// const response = await uploadToCDN(url);
// return response.permanentUrl;
// For this example, we'll just log the URL
console.log(`Would upload transient resource: ${url}`);
// Return the original URL since we're not actually uploading
return url;
}
);
console.log(`Scene saved to string (${sceneString.length} characters)`);
```
This pattern lets you intercept temporary resources, upload them to permanent storage, and save the scene with stable URLs that will work when reloaded.
## Troubleshooting
**Slow initial render**: Preload resources with `forceLoadResources()` before displaying the scene.
**Export fails with missing resources**: Check `findAllTransientResources()` and persist any temporary resources before export.
**Video duration returns 0**: Ensure the video resource is loaded with `forceLoadAVResource()` before querying properties.
**Resources not found after reload**: Transient resources (buffers, blobs) are not serialized—relocate them to persistent URLs before saving.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Scenes"
description: "Create, configure, save, and load scenes—the root container for all design elements in CE.SDK."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/scenes-e8596d/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Scenes](https://img.ly/docs/cesdk/sveltekit/concepts/scenes-e8596d/)
---
Scenes are the root container for all designs in CE.SDK. They hold pages,
blocks, and the camera that controls what you see in the canvas—and the engine
manages only one active scene at a time.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-scenes-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-scenes-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-scenes-browser/)
Every design you create starts with a scene. Scenes contain pages, and pages contain the visible design elements—text, images, shapes, and other blocks. Understanding how scenes work is essential for building, saving, and restoring user designs.
```typescript file=@cesdk_web_examples/guides-concepts-scenes-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Scenes Guide
*
* Demonstrates the complete scene lifecycle in CE.SDK:
* - Creating scenes with different layouts
* - Managing pages within scenes
* - Configuring scene properties
* - Saving and loading scenes
* - Camera control and zoom
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
// Create a new design scene with VerticalStack layout
// The layout controls how pages are arranged in the canvas
engine.scene.create('VerticalStack');
// Get the stack container and add spacing between pages
const stack = engine.block.findByType('stack')[0];
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
// Create the first page
const page1 = engine.block.create('page');
engine.block.setWidth(page1, 800);
engine.block.setHeight(page1, 600);
engine.block.appendChild(stack, page1);
// Create a second page
const page2 = engine.block.create('page');
engine.block.setWidth(page2, 800);
engine.block.setHeight(page2, 600);
engine.block.appendChild(stack, page2);
// Add a shape to the first page
const graphic1 = engine.block.create('graphic');
engine.block.setShape(graphic1, engine.block.createShape('rect'));
const fill1 = engine.block.createFill('color');
engine.block.setColor(fill1, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1
});
engine.block.setFill(graphic1, fill1);
engine.block.setWidth(graphic1, 400);
engine.block.setHeight(graphic1, 300);
engine.block.setPositionX(graphic1, 200);
engine.block.setPositionY(graphic1, 150);
engine.block.appendChild(page1, graphic1);
// Add a different shape to the second page
const graphic2 = engine.block.create('graphic');
engine.block.setShape(graphic2, engine.block.createShape('ellipse'));
const fill2 = engine.block.createFill('color');
engine.block.setColor(fill2, 'fill/color/value', {
r: 0.9,
g: 0.3,
b: 0.2,
a: 1
});
engine.block.setFill(graphic2, fill2);
engine.block.setWidth(graphic2, 350);
engine.block.setHeight(graphic2, 350);
engine.block.setPositionX(graphic2, 225);
engine.block.setPositionY(graphic2, 125);
engine.block.appendChild(page2, graphic2);
// Query scene properties
const currentUnit = engine.scene.getDesignUnit();
// eslint-disable-next-line no-console
console.log('Scene design unit:', currentUnit);
// Get the scene layout
const layout = engine.scene.getLayout();
// eslint-disable-next-line no-console
console.log('Scene layout:', layout);
// Access pages within the scene
const pages = engine.scene.getPages();
// eslint-disable-next-line no-console
console.log('Number of pages:', pages.length);
// Get the current page (nearest to viewport center)
const currentPage = engine.scene.getCurrentPage();
// eslint-disable-next-line no-console
console.log('Current page ID:', currentPage);
// Zoom to show all pages in the scene
const scene = engine.scene.get();
if (scene) {
await engine.scene.zoomToBlock(scene, { padding: 50 });
}
// Get the current zoom level
const zoomLevel = engine.scene.getZoomLevel();
// eslint-disable-next-line no-console
console.log('Current zoom level:', zoomLevel);
// Save the scene to a string for persistence
const sceneString = await engine.scene.saveToString();
// eslint-disable-next-line no-console
console.log('Scene saved successfully. String length:', sceneString.length);
// Demonstrate loading the scene from the saved string
// This replaces the current scene with the saved version
await engine.scene.loadFromString(sceneString);
// eslint-disable-next-line no-console
console.log('Scene loaded from saved string');
// Zoom to show all loaded pages
const loadedScene = engine.scene.get();
if (loadedScene) {
await engine.scene.zoomToBlock(loadedScene, { padding: 50 });
}
// eslint-disable-next-line no-console
console.log('Scenes guide initialized successfully.');
}
}
export default Example;
```
This guide covers how to create scenes from scratch, manage pages within scenes, configure scene properties, save and load designs, and control the camera's zoom and position.
## Scene Hierarchy
Scenes form the root of CE.SDK's design structure. The hierarchy works as follows:
- **Scene** — The root container holding all design content
- **Pages** — Direct children of scenes, arranged according to the scene's layout
- **Blocks** — Design elements (text, images, shapes) that belong to pages
Only blocks attached to pages within the active scene are rendered in the canvas. Use `engine.scene.get()` to retrieve the current scene and `engine.scene.getPages()` to access its pages.
## Creating Scenes
### Creating an Empty Scene
Use `engine.scene.create()` to create a new design scene with a configurable page layout. The layout parameter controls how pages are arranged in the canvas.
```typescript highlight-create-scene
// Create a new design scene with VerticalStack layout
// The layout controls how pages are arranged in the canvas
engine.scene.create('VerticalStack');
// Get the stack container and add spacing between pages
const stack = engine.block.findByType('stack')[0];
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
```
Available layouts include:
- `VerticalStack` — Pages stacked vertically
- `HorizontalStack` — Pages arranged horizontally
- `DepthStack` — Pages layered on top of each other
- `Free` — Manual positioning
### Adding Pages
After creating a scene, add pages using `engine.block.create('page')`. Configure the page dimensions and append it to the scene's stack container.
```typescript highlight-create-page
// Create the first page
const page1 = engine.block.create('page');
engine.block.setWidth(page1, 800);
engine.block.setHeight(page1, 600);
engine.block.appendChild(stack, page1);
// Create a second page
const page2 = engine.block.create('page');
engine.block.setWidth(page2, 800);
engine.block.setHeight(page2, 600);
engine.block.appendChild(stack, page2);
```
### Adding Blocks
With pages in place, add design elements like shapes, text, or images. Create a graphic block, configure its shape and fill, then append it to a page.
```typescript highlight-create-block
// Add a shape to the first page
const graphic1 = engine.block.create('graphic');
engine.block.setShape(graphic1, engine.block.createShape('rect'));
const fill1 = engine.block.createFill('color');
engine.block.setColor(fill1, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1
});
engine.block.setFill(graphic1, fill1);
engine.block.setWidth(graphic1, 400);
engine.block.setHeight(graphic1, 300);
engine.block.setPositionX(graphic1, 200);
engine.block.setPositionY(graphic1, 150);
engine.block.appendChild(page1, graphic1);
// Add a different shape to the second page
const graphic2 = engine.block.create('graphic');
engine.block.setShape(graphic2, engine.block.createShape('ellipse'));
const fill2 = engine.block.createFill('color');
engine.block.setColor(fill2, 'fill/color/value', {
r: 0.9,
g: 0.3,
b: 0.2,
a: 1
});
engine.block.setFill(graphic2, fill2);
engine.block.setWidth(graphic2, 350);
engine.block.setHeight(graphic2, 350);
engine.block.setPositionX(graphic2, 225);
engine.block.setPositionY(graphic2, 125);
engine.block.appendChild(page2, graphic2);
```
## Scene Properties
### Design Units
Query or configure how measurements are interpreted using `engine.scene.getDesignUnit()` and `engine.scene.setDesignUnit()`. This is useful for print workflows where precise physical dimensions matter.
```typescript highlight-scene-properties
// Query scene properties
const currentUnit = engine.scene.getDesignUnit();
// eslint-disable-next-line no-console
console.log('Scene design unit:', currentUnit);
// Get the scene layout
const layout = engine.scene.getLayout();
// eslint-disable-next-line no-console
console.log('Scene layout:', layout);
```
Supported units are `'Pixel'`, `'Millimeter'`, and `'Inch'`. For more details, see the [Design Units](https://img.ly/docs/cesdk/sveltekit/concepts/design-units-cc6597/) guide.
### Scene Layout
Control how pages are arranged using `engine.scene.getLayout()` and `engine.scene.setLayout()`. The layout affects how users navigate between pages in multi-page designs.
## Page Navigation
Access pages within your scene using these methods:
```typescript highlight-page-navigation
// Access pages within the scene
const pages = engine.scene.getPages();
// eslint-disable-next-line no-console
console.log('Number of pages:', pages.length);
// Get the current page (nearest to viewport center)
const currentPage = engine.scene.getCurrentPage();
// eslint-disable-next-line no-console
console.log('Current page ID:', currentPage);
```
The `getCurrentPage()` method returns the page nearest to the viewport center—useful for determining which page the user is currently viewing.
## Camera and Zoom
### Zoom to Block
Use `engine.scene.zoomToBlock()` to frame a specific block in the viewport with padding. Pass the scene block to show all pages:
```typescript highlight-camera-zoom
// Zoom to show all pages in the scene
const scene = engine.scene.get();
if (scene) {
await engine.scene.zoomToBlock(scene, { padding: 50 });
}
// Get the current zoom level
const zoomLevel = engine.scene.getZoomLevel();
// eslint-disable-next-line no-console
console.log('Current zoom level:', zoomLevel);
```
### Zoom Level
Get and set the zoom level directly with `engine.scene.getZoomLevel()` and `engine.scene.setZoomLevel()`. A zoom level of 1.0 means one design unit equals one screen pixel.
### Auto-Fit Zoom
For continuous auto-framing, use `engine.scene.enableZoomAutoFit()` to automatically keep a block centered as the viewport resizes.
## Saving Scenes
### Saving to String
Use `engine.scene.saveToString()` to serialize the current scene. This captures the complete scene structure—pages, blocks, and their properties—as a string you can store.
```typescript highlight-save-scene
// Save the scene to a string for persistence
const sceneString = await engine.scene.saveToString();
// eslint-disable-next-line no-console
console.log('Scene saved successfully. String length:', sceneString.length);
```
The serialized string references external assets by URL rather than embedding them. For complete portability including assets, use `engine.scene.saveToArchive()`.
## Loading Scenes
### Loading from String
Use `engine.scene.loadFromString()` to restore a scene from a saved string:
```typescript highlight-load-scene
// Demonstrate loading the scene from the saved string
// This replaces the current scene with the saved version
await engine.scene.loadFromString(sceneString);
// eslint-disable-next-line no-console
console.log('Scene loaded from saved string');
// Zoom to show all loaded pages
const loadedScene = engine.scene.get();
if (loadedScene) {
await engine.scene.zoomToBlock(loadedScene, { padding: 50 });
}
```
Loading a new scene replaces any existing scene. The engine only holds one active scene at a time.
### Loading from URL
Use `engine.scene.loadFromURL()` to load a scene directly from a remote location:
```typescript
await engine.scene.loadFromURL('https://example.com/design.scene');
```
## Troubleshooting
### Blocks Not Visible
Ensure blocks are attached to pages, and pages are attached to the scene. Orphaned blocks that aren't part of the scene hierarchy won't render.
### Scene Not Loading
Check that the scene URL or string is valid. If assets fail to load, consider using the `waitForResources` option to ensure everything loads before rendering.
### Zoom Not Working
Verify the scene has a valid camera. Some UI configurations may override programmatic zoom controls.
## Scene Type
Represents the scene and its global properties.
This section describes the properties available for the **Scene Type** (`//ly.img.ubq/scene`) block type.
| Property | Type | Default | Description |
| ------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `alwaysOnBottom` | `Bool` | `false` | If true, this element's global sorting order is automatically adjusted to be lower than all other siblings. |
| `alwaysOnTop` | `Bool` | `false` | If true, this element's global sorting order is automatically adjusted to be higher than all other siblings. |
| `blend/mode` | `Enum` | `"Normal"` | The blend mode to use when compositing the block., Possible values: `"PassThrough"`, `"Normal"`, `"Darken"`, `"Multiply"`, `"ColorBurn"`, `"LinearBurn"`, `"DarkenColor"`, `"Lighten"`, `"Screen"`, `"ColorDodge"`, `"LinearDodge"`, `"LightenColor"`, `"Overlay"`, `"SoftLight"`, `"HardLight"`, `"VividLight"`, `"LinearLight"`, `"PinLight"`, `"HardMix"`, `"Difference"`, `"Exclusion"`, `"Subtract"`, `"Divide"`, `"Hue"`, `"Saturation"`, `"Color"`, `"Luminosity"` |
| `clipped` | `Bool` | `false` | This component is used to identify elements whose contents and children should be clipped to their bounds. |
| `contentFill/mode` | `Enum` | `"Cover"` | Defines how content should be resized to fit its container (e.g., Crop, Cover, Contain)., Possible values: `"Crop"`, `"Cover"`, `"Contain"` |
| `flip/horizontal` | `Bool` | `"-"` | Whether the block is flipped horizontally. |
| `flip/vertical` | `Bool` | `"-"` | Whether the block is flipped vertically. |
| `globalBoundingBox/height` | `Float` | `"-"` | The height of the block's axis-aligned bounding box in world coordinates., *(read-only)* |
| `globalBoundingBox/width` | `Float` | `"-"` | The width of the block's axis-aligned bounding box in world coordinates., *(read-only)* |
| `globalBoundingBox/x` | `Float` | `"-"` | The x-coordinate of the block's axis-aligned bounding box in world coordinates., *(read-only)* |
| `globalBoundingBox/y` | `Float` | `"-"` | The y-coordinate of the block's axis-aligned bounding box in world coordinates., *(read-only)* |
| `height` | `Float` | `0` | The height of the block's frame. |
| `height/mode` | `Enum` | `"Auto"` | A mode describing how the height dimension may be interpreted (Absolute, Percent, Auto)., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` |
| `highlightEnabled` | `Bool` | `true` | Show highlighting when selected or hovered |
| `lastFrame/height` | `Float` | `"-"` | The height of the block's frame from the previous layout pass., *(read-only)* |
| `lastFrame/width` | `Float` | `"-"` | The width of the block's frame from the previous layout pass., *(read-only)* |
| `lastFrame/x` | `Float` | `"-"` | The x-coordinate of the block's frame from the previous layout pass., *(read-only)* |
| `lastFrame/y` | `Float` | `"-"` | The y-coordinate of the block's frame from the previous layout pass., *(read-only)* |
| `placeholder/enabled` | `Bool` | `false` | Whether the placeholder behavior is enabled or not. |
| `playback/playing` | `Bool` | `false` | A tag that can be set on elements for their playback time to be progressed. |
| `playback/soloPlaybackEnabled` | `Bool` | `false` | A tag for blocks where playback should progress while the scene is paused. |
| `playback/time` | `Double` | `0` | The current playback time of the block contents in seconds. |
| `position/x` | `Float` | `0` | The x-coordinate of the block's origin. |
| `position/x/mode` | `Enum` | `"Absolute"` | A mode describing how the x-position may be interpreted., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` |
| `position/y` | `Float` | `0` | The y-coordinate of the block's origin. |
| `position/y/mode` | `Enum` | `"Absolute"` | A mode describing how the y-position may be interpreted., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` |
| `rotation` | `Float` | `0` | The rotation of the block in radians. |
| `scene/aspectRatioLock` | `Bool` | `true` | Whether the ratio of the pageDimensions' width and height should remain constant when changing either dimension. |
| `scene/designUnit` | `Enum` | `"Pixel"` | The unit type in which the page values (size, distances, etc.) are defined., Possible values: `"Pixel"`, `"Millimeter"`, `"Inch"` |
| `scene/dpi` | `Float` | `300` | The DPI value to use when exporting and when converting between pixels and inches or millimeter units. |
| `scene/layout` | `Enum` | `"Free"` | A value describing how the scene's children are laid out., Possible values: `"Free"`, `"VerticalStack"`, `"HorizontalStack"`, `"DepthStack"` |
| `scene/mode` | `Enum` | `"Video"` | The mode of this scene and all elements inside of it., *(read-only)*, Possible values: `"Design"`, `"Video"` |
| `scene/pageDimensions/height` | `Float` | `1` | The height of all pages in this scene. |
| `scene/pageDimensions/width` | `Float` | `1` | The width of all pages in this scene. |
| `scene/pageFormatId` | `String` | `""` | The identifier of the page format configuration that was most recently selected for the pages in this scene. |
| `scene/pixelScaleFactor` | `Float` | `1` | A scale factor that is applied to the final export resolution if the design unit is Pixel. |
| `selected` | `Bool` | `false` | Indicates if the block is currently selected. |
| `transformLocked` | `Bool` | `false` | DesignBlocks with this tag can't be transformed (moved, rotated, scaled, cropped, or flipped). |
| `visible` | `Bool` | `true` | If the block is visible in the editor. |
| `width` | `Float` | `0` | The width of the block's frame. |
| `width/mode` | `Enum` | `"Auto"` | A mode describing how the width dimension may be interpreted (Absolute, Percent, Auto)., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Templating"
description: "Understand how templates work in CE.SDK—reusable designs with variables for dynamic text and placeholders for swappable media."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/templating-f94385/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Templating](https://img.ly/docs/cesdk/sveltekit/concepts/templating-f94385/)
---
Templates transform static designs into dynamic, data-driven content. They combine reusable layouts with variable text and placeholder media, enabling personalization at scale.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-templating-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-templating-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-templating-browser/)
A template is a regular CE.SDK scene that contains **variable tokens** in text and **placeholder blocks** for media. When you load a template, you can populate the variables with data and swap placeholder content—producing personalized designs without modifying the underlying layout.
```typescript file=@cesdk_web_examples/guides-concepts-templating-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Templating Concepts
*
* Demonstrates the core template concepts in CE.SDK:
* - Loading a template from URL
* - Discovering and setting variables
* - Discovering placeholders
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
// Load a postcard template from URL
// Templates are scenes containing variable tokens and placeholder blocks
const templateUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene';
await engine.scene.loadFromURL(templateUrl);
// Zoom to show the full page in the viewport
const page = engine.scene.getCurrentPage();
if (page) {
await engine.scene.zoomToBlock(page, { padding: 40 });
}
// Discover what variables this template expects
// Variables are named slots that can be populated with data
const variableNames = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log('Template variables:', variableNames);
// Set variable values to personalize the template
// These values replace {{variableName}} tokens in text blocks
engine.variable.setString('Name', 'Jane');
engine.variable.setString('Greeting', 'Wish you were here!');
// eslint-disable-next-line no-console
console.log('Variables set successfully.');
// Discover placeholder blocks in the template
// Placeholders mark content slots for user or automation replacement
const placeholders = engine.block.findAllPlaceholders();
// eslint-disable-next-line no-console
console.log('Template placeholders:', placeholders.length);
// eslint-disable-next-line no-console
console.log('Templating guide completed successfully.');
}
}
export default Example;
```
This guide explains the core concepts. For implementation details, see the guides linked in each section.
## What Makes a Template
Any CE.SDK scene can become a template by adding dynamic elements:
| Element | Purpose | Example |
|---------|---------|---------|
| **Variables** | Dynamic text replacement | `Hello, {{firstName}}!` |
| **Placeholders** | Swappable media slots | Profile photo, product image |
| **Editing Constraints** | Protected design elements | Locked logo, fixed layout |
Templates separate **design** (created once by designers) from **content** (populated at runtime with data). This enables workflows like batch generation, form-based customization, and user personalization.
## Variables
Variables enable dynamic text without modifying the design structure. Text blocks contain `{{variableName}}` tokens that CE.SDK resolves at render time.
```typescript highlight-set-variables
// Set variable values to personalize the template
// These values replace {{variableName}} tokens in text blocks
engine.variable.setString('Name', 'Jane');
engine.variable.setString('Greeting', 'Wish you were here!');
// eslint-disable-next-line no-console
console.log('Variables set successfully.');
```
**How variables work:**
- Define variables with `engine.variable.setString('name', 'value')`
- Reference them in text: `Welcome, {{name}}!`
- CE.SDK automatically updates all text blocks using that variable
- Tokens are case-sensitive; unmatched tokens render as literal text
Variables are scene-scoped and persist when you save the template. Use `engine.variable.findAll()` to discover what variables a template expects.
[Learn more about text variables →](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/)
## Placeholders
Placeholders mark blocks as content slots that users or automation can replace. When you enable placeholder behavior on an image block, it displays an overlay pattern and replacement button in the editor.
**How placeholders work:**
- Enable with `engine.block.setPlaceholderEnabled(block, true)`
- Add visual UI with `engine.block.setPlaceholderBehaviorEnabled(fill, true)`
- Users in Adopter mode can select and replace placeholder content
- Other design elements remain locked
Use `engine.block.findAllPlaceholders()` to discover all placeholder blocks in a loaded template.
[Learn more about placeholders →](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/placeholders-d9ba8a/)
## Template Workflows
Templates support several common workflows:
### Form-Based Customization
Load a template, present a form for variable values, and let users customize text while the design stays consistent. The editor UI handles placeholder replacement through drag-and-drop.
### Batch Generation
Load a template programmatically, iterate through data records, set variables for each record, and export personalized designs. This powers use cases like certificates, badges, and personalized marketing.
### Design Systems
Create template libraries where designers maintain approved layouts and end users customize within defined boundaries using variables and placeholders.
## Loading and Applying Templates
CE.SDK provides two approaches for working with templates:
**Load a template** with `engine.scene.loadFromURL()` to replace the current scene entirely, including page dimensions:
```typescript highlight-load-template
// Load a postcard template from URL
// Templates are scenes containing variable tokens and placeholder blocks
const templateUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene';
await engine.scene.loadFromURL(templateUrl);
// Zoom to show the full page in the viewport
const page = engine.scene.getCurrentPage();
if (page) {
await engine.scene.zoomToBlock(page, { padding: 40 });
}
```
**Apply a template** with `engine.scene.applyTemplateFromURL()` to merge template content into an existing scene while preserving current page dimensions.
[Learn more about importing templates →](https://img.ly/docs/cesdk/sveltekit/create-templates/import-e50084/)
## Creating Templates
Build templates by adding variable tokens to text blocks and configuring placeholder behavior on media blocks. Save with `engine.scene.saveToString()` or `engine.scene.saveToArchive()`.
[Learn more about creating templates →](https://img.ly/docs/cesdk/sveltekit/create-templates/from-scratch-663cda/)
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Terminology"
description: "Definitions for the core terms and concepts used throughout CE.SDK documentation, including Engine, Scene, Block, Fill, Shape, Effect, and more."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/terminology-99e82d/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Terminology](https://img.ly/docs/cesdk/sveltekit/concepts/terminology-99e82d/)
---
A reference guide to the core terms and concepts used throughout CE.SDK documentation.
CE.SDK uses consistent terminology across all platforms. Understanding what we call things helps you navigate the API, read documentation efficiently, and communicate effectively with other developers working on CE.SDK integration.
## Core Architecture
### Engine
All operations—creating scenes, manipulating blocks, rendering, and exporting—go through the *Engine*. Initialize it once and use it throughout your application's lifecycle.
### Scene
The root container for all design content. A *Scene* contains *Pages*, which contain *Blocks*. Only one *Scene* can be active per *Engine* instance. You can create a *Scene* programmatically or load one from a file.
*Scenes* support both static designs (social posts, print materials, graphics) and time-based content (duration, playback time, animation).
See [Scenes](https://img.ly/docs/cesdk/sveltekit/concepts/scenes-e8596d/) for details.
### Page
*Pages* are containers within a *Scene* that hold content *Blocks* (see below) and define working area dimensions.
For static designs, pages are individual artboards. For video editing, pages are time-based compositions where *Blocks* are arranged across time. See [Pages](https://img.ly/docs/cesdk/sveltekit/concepts/pages-7b6bae/) for details.
### Block
The fundamental building unit in CE.SDK. Everything visible in a design is a *Block*—images, text, shapes, graphics, audio, video—and even *Pages* themselves. *Blocks* form a parent-child hierarchy.
Each *Block* has two identifiers:
- **DesignBlockId**: A numeric handle (integer) used in API calls
- **UUID**: A stable string identifier that persists across save and load operations
See [Blocks](https://img.ly/docs/cesdk/sveltekit/concepts/blocks-90241e/) for details.
## Block Anatomy
Modify a *Block's* appearance and behavior by attaching *Fills*, *Shapes*, and *Effects*. Most of these modifiers must be created separately and then attached to a *Block*.
### Fill
*Fills* cover the surface of a *Block's* shape:
- **Color Fill**: Solid color
- **Gradient Fill**: Linear, radial, or conical gradients
- **Image Fill**: Image content
- **Video Fill**: Video content
See the [Color Fills](https://img.ly/docs/cesdk/sveltekit/fills/color-7129cd/), [Gradient Fills](https://img.ly/docs/cesdk/sveltekit/filters-and-effects/gradients-0ff079/), [Image Fills](https://img.ly/docs/cesdk/sveltekit/fills/image-e9cb5c/), and [Video Fills](https://img.ly/docs/cesdk/sveltekit/fills/video-ec7f9f/) guides.
### Shape
*Shapes* define a *Block's* outline and dimensions, determining the silhouette and how the *Fill* is clipped. *Shape* types include:
- **Rect**: Rectangles and squares
- **Ellipse**: Circles and ovals
- **Polygon**: Multi-sided shapes
- **Star**: Star shapes with configurable points
- **Line**: Straight lines
- **Vector Path**: Custom vector shapes
Like *Fills*, *Shapes* are created separately and attached to *Blocks*. See [Shapes](https://img.ly/docs/cesdk/sveltekit/shapes-9f1b2c/) for details.
### Effect
*Effects* are non-destructive visual modifications applied to a *Block*. Multiple *Effects* can be stacked. *Effect* categories include:
- **Adjustments**: Brightness, contrast, saturation, and other image corrections
- **Filters**: LUT-based color grading, duotone
- **Stylization**: Pixelize, posterize, half-tone, dot pattern, linocut, outliner
- **Distortion**: Liquid, mirror, shifter, cross-cut, extrude blur
- **Focus**: Tilt-shift, vignette
- **Color**: Recolor, green screen (chroma key)
- **Other**: Glow, TV glitch
The order determines how multiple effects attached to a single block interact. See [Filters and Effects](https://img.ly/docs/cesdk/sveltekit/filters-and-effects-6f88ac/) for details.
### Blur
A modifier that reduces sharpness. *Blur* types include:
- **Uniform Blur**: Even blur across the entire block
- **Radial Blur**: Circular blur from a center point
- **Mirrored Blur**: Blur with reflection
> **Note:** **Blur has a dedicated API because it composites differently than other effects.** While most effects like brightness or saturation operate only on a block's own pixels, blur needs to sample pixels from the surrounding area to calculate the blurred result. This means blur interacts with the scene's layering and transparency in ways other effects don't—when you blur a partially transparent block, the engine must handle how that blur blends with whatever content sits behind it.
See [Blur](https://img.ly/docs/cesdk/sveltekit/filters-and-effects/blur-71d642/) for details.
### Drop Shadow
A built-in block property (not an *Effect*) that renders a shadow beneath blocks. *Drop Shadow* has dedicated API methods for enabling, color, offset, and blur radius.
> **Warning:** Unlike effects, drop shadow is configured directly on the block rather than created and attached separately.
## Block Handling
These terms describe how *Blocks* are categorized and identified.
### Type
The built-in *Type* defines a *Block's* core behavior and available properties. *Type* is immutable—you choose it when creating the *Block*.
- `//ly.img.ubq/graphic` — Visual block for images, shapes, and graphics
- `//ly.img.ubq/text` — Text content
- `//ly.img.ubq/audio` — Audio content
- `//ly.img.ubq/page` — Page container
- `//ly.img.ubq/scene` — Root scene container
- `//ly.img.ubq/track` — Video timeline track
- `//ly.img.ubq/stack` — Stack container for layering
- `//ly.img.ubq/group` — Group container for organizing blocks
- `//ly.img.ubq/camera` — Camera for scene viewing
- `//ly.img.ubq/cutout` — Cutout/mask block
- `//ly.img.ubq/caption` — Caption/subtitle block
- `//ly.img.ubq/captionTrack` — Track for captions
The *Type* determines which properties and capabilities a *Block* has.
### Kind
A custom string label you assign to categorize *Blocks* for your application. Unlike *Type*, *Kind* is mutable and application-defined. Changing the *Kind* has no effect on appearance or behavior at the engine level. You can query and search for *Blocks* by *Kind*. Common uses:
- Categorizing template elements ("logo", "headline", "background")
- Filtering blocks for custom UI
- Automation workflows that process blocks by purpose
### Property
A configurable attribute of a *Block*. *Properties* have types (`Bool`, `Int`, `Float`, `String`, `Color`, `Enum`) and paths like `text/fontSize` or `fill/image/imageFileURI`.
Access *Properties* using type-specific getter and setter methods. Each *Block* type exposes different properties, which you can discover programmatically. See [Blocks](https://img.ly/docs/cesdk/sveltekit/concepts/blocks-90241e/) for details.
## Assets and Resources
### Asset
Think of *Assets* as media items that you can provide to your users: images, videos, audio files, fonts, stickers, or templates—anything that can be added to a design. *Assets* have metadata including:
- **ID**: Unique identifier within an asset source
- **Label**: Display name
- **Meta**: Custom metadata (URI, dimensions, format)
- **Thumbnail URI**: Preview image URL
*Assets* are provided by *Asset Sources* and added through the UI or programmatically.
### Asset Source
A provider of *Assets*. *Asset Sources* can be built-in (like the default sticker library) or custom. *Asset Sources* implement a query interface returning paginated results with search and filtering.
- **Local Asset Source**: Assets defined in JSON, loaded at initialization
- **Remote Asset Source**: Custom implementation fetching from external APIs
Register *Asset Sources* with the *Engine* to make *Assets* available throughout your application.
### Resource
Loaded data from an *Asset* URI. When you reference an image or video URL in a *Block*, the *Engine* fetches and caches the *Resource*. *Resources* include binary data and metadata for rendering. See [Resources](https://img.ly/docs/cesdk/sveltekit/concepts/resources-a58d71/) for details.
### Buffer
A resizable container for arbitrary binary data. *Buffers* are useful for dynamically generated content that doesn't come from a URL, such as synthesized audio or programmatically created images.
Create a *Buffer*, write data to it, and reference it by URI in *Block* properties. *Buffer* data is not serialized with scenes and changes cannot be undone. See [Buffers](https://img.ly/docs/cesdk/sveltekit/concepts/buffers-9c565b/) for details.
## Templating and Automation
These terms describe dynamic content and reusable designs.
### Template
A reusable design with predefined structure and styling. *Templates* typically contain *Placeholders* and *Variables* that users customize while maintaining overall layout and branding.
*Templates* are scenes saved in a format that can be loaded and modified.
### Placeholder
A *Block* marked for content replacement. When a *Block's* placeholder property is enabled, it signals that the *Block* expects user-provided content—an image drop zone or editable text field.
*Placeholders* indicate which parts of a design should be customized versus fixed. See [Placeholders](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/placeholders-d9ba8a/) for details.
### Variable
A named value referenced in text blocks using `{{variableName}}` syntax. *Variables* enable data-driven design generation by populating templates with dynamic content.
Define *Variables* at the scene level and reference them in text blocks. When a *Variable* value changes, all referencing text blocks update automatically. See [Text Variables](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/) for details.
## Permissions and Scopes
These terms relate to controlling what operations are allowed.
### Scope
A permission setting controlling whether specific operations are allowed on a *Block*. *Scopes* enable fine-grained control over what users can modify—essential for template workflows where some elements should be editable and others locked.
Common scopes:
- `layer/move` — Allow or prevent moving
- `layer/resize` — Allow or prevent resizing
- `layer/rotate` — Allow or prevent rotation
- `layer/visibility` — Allow or prevent hiding
- `lifecycle/destroy` — Allow or prevent deletion
- `editor/select` — Allow or prevent selection
Enable or disable *Scopes* per *Block* to create controlled editing experiences. See [Lock Design Elements](https://img.ly/docs/cesdk/sveltekit/create-templates/lock-131489/) for details.
### Role
A preset collection of *Scope* settings. CE.SDK defines two built-in *Roles*:
- **Creator**: Full access to all operations, for template authors
- **Adopter**: Restricted access for end-users customizing templates
*Roles* provide a convenient way to apply consistent permission sets.
## Layout and Units
These terms relate to positioning and measurement.
### Design Unit
The measurement unit for dimensions in a *Scene*. The choice affects how positions, sizes, and exports are interpreted. Options:
- **Pixel**: Screen pixels, default for digital designs
- **Millimeter**: Metric measurement for print
- **Inch**: Imperial measurement for print
Set the design unit at the scene level—all dimension values are interpreted in that unit. See [Design Units](https://img.ly/docs/cesdk/sveltekit/concepts/design-units-cc6597/) for details.
### DPI (Dots Per Inch)
Resolution setting affecting export quality and unit conversion. Higher DPI produces larger exports with more detail. The default is 300 DPI, suitable for print-quality output.
DPI matters when working with physical units (millimeters, inches) as it determines how measurements translate to pixel dimensions during export.
## Operating Modes
These terms describe how CE.SDK runs.
### Scene Capabilities
Every *Scene* supports the full range of features:
- **Static designs**: Content arranged spatially on pages.
- **Video editing**: Blocks can have duration, time offset, playback time, and animation properties.
See [Scenes](https://img.ly/docs/cesdk/sveltekit/concepts/scenes-e8596d/) for details.
### Headless Mode
Running CE.SDK without the built-in UI. Used for:
- Server-side rendering and export
- Automation pipelines
- Custom UI implementations
- Batch processing
In *Headless Mode*, you work directly with *Engine* APIs without the visual editor. See [Headless Mode](https://img.ly/docs/cesdk/sveltekit/concepts/headless-mode/browser-24ab98/) for setup.
## Events and State
These terms relate to monitoring changes.
### Event / Subscription
A callback mechanism for reacting to changes in the *Engine*. Subscribe to events and receive notifications when state changes. Common events:
- Selection changes
- Block state changes
- History (undo/redo) changes
Subscriptions return an unsubscribe function to call when you no longer need notifications. See [Events](https://img.ly/docs/cesdk/sveltekit/concepts/events-353f97/) for details.
### Block State
The current status of a *Block* indicating readiness or issues:
- **Ready**: Normal state, no pending operations
- **Pending**: Operation in progress, with optional progress value (0-1)
- **Error**: Operation failed, with error type (`ImageDecoding`, `VideoDecoding`, `FileFetch`, etc.)
*Block State* reflects the combined status of the *Block* and its attached *Fill*, *Shape*, and *Effects*.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Undo and History"
description: "Manage undo and redo stacks in CE.SDK using multiple histories, callbacks, and API-based controls."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/concepts/undo-and-history-99479d/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/sveltekit/concepts-c9ff51/) > [Undo and History](https://img.ly/docs/cesdk/sveltekit/concepts/undo-and-history-99479d/)
---
Implement undo/redo functionality and manage multiple history stacks to track editing operations.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-undo-and-history-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-undo-and-history-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-undo-and-history-browser/)
CE.SDK automatically tracks editing operations, enabling users to undo and redo changes. The engine creates undo steps for most operations automatically. You can also create multiple independent history stacks to isolate different editing contexts, such as separate histories for a main canvas and an overlay editing panel.
```typescript file=@cesdk_web_examples/guides-concepts-undo-and-history-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0]!;
// Subscribe to history updates to track state changes
const unsubscribe = engine.editor.onHistoryUpdated(() => {
const canUndo = engine.editor.canUndo();
const canRedo = engine.editor.canRedo();
console.log('History updated:', { canUndo, canRedo });
});
// Create a triangle shape and add an undo step to record it in history
const block = engine.block.create('graphic');
engine.block.setPositionX(block, 140);
engine.block.setPositionY(block, 95);
engine.block.setWidth(block, 265);
engine.block.setHeight(block, 265);
const triangleShape = engine.block.createShape('polygon');
engine.block.setInt(triangleShape, 'shape/polygon/sides', 3);
engine.block.setShape(block, triangleShape);
const triangleFill = engine.block.createFill('color');
engine.block.setColor(triangleFill, 'fill/color/value', {
r: 0.2,
g: 0.5,
b: 0.9,
a: 1
});
engine.block.setFill(block, triangleFill);
engine.block.appendChild(page, block);
// Commit the block creation to history so it can be undone
engine.editor.addUndoStep();
// Log current state - canUndo should now be true
console.log('Block created. canUndo:', engine.editor.canUndo());
// Undo the block creation
if (engine.editor.canUndo()) {
engine.editor.undo();
console.log(
'After undo - canUndo:',
engine.editor.canUndo(),
'canRedo:',
engine.editor.canRedo()
);
}
// Redo to restore the block
if (engine.editor.canRedo()) {
engine.editor.redo();
console.log(
'After redo - canUndo:',
engine.editor.canUndo(),
'canRedo:',
engine.editor.canRedo()
);
}
// Create a second history stack for isolated operations
const secondaryHistory = engine.editor.createHistory();
const primaryHistory = engine.editor.getActiveHistory();
console.log(
'Created secondary history. Primary:',
primaryHistory,
'Secondary:',
secondaryHistory
);
// Switch to the secondary history
engine.editor.setActiveHistory(secondaryHistory);
console.log(
'Switched to secondary history. Active:',
engine.editor.getActiveHistory()
);
// Operations in secondary history are isolated from the primary history
const secondBlock = engine.block.create('graphic');
engine.block.setPositionX(secondBlock, 440);
engine.block.setPositionY(secondBlock, 95);
engine.block.setWidth(secondBlock, 220);
engine.block.setHeight(secondBlock, 220);
const circleShape = engine.block.createShape('ellipse');
engine.block.setShape(secondBlock, circleShape);
const circleFill = engine.block.createFill('color');
engine.block.setColor(circleFill, 'fill/color/value', {
r: 0.9,
g: 0.3,
b: 0.3,
a: 1
});
engine.block.setFill(secondBlock, circleFill);
engine.block.appendChild(page, secondBlock);
// Commit changes to the secondary history
engine.editor.addUndoStep();
console.log(
'Block added in secondary history. canUndo:',
engine.editor.canUndo()
);
// Switch back to primary history
engine.editor.setActiveHistory(primaryHistory);
console.log(
'Switched back to primary history. canUndo:',
engine.editor.canUndo()
);
// Clean up the secondary history when no longer needed
engine.editor.destroyHistory(secondaryHistory);
console.log('Secondary history destroyed');
// Manually add an undo step after custom operations
engine.block.setPositionX(block, 190);
engine.editor.addUndoStep();
console.log('Manual undo step added. canUndo:', engine.editor.canUndo());
// Remove the most recent undo step if needed
if (engine.editor.canUndo()) {
engine.editor.removeUndoStep();
console.log('Most recent undo step removed');
}
// Reset block position to its original location
engine.block.setPositionX(block, 140);
// Add instruction text at the end (after all demo operations)
const instructionText = engine.block.create('text');
engine.block.setPositionX(instructionText, 50);
engine.block.setPositionY(instructionText, 430);
engine.block.setWidth(instructionText, 700);
engine.block.setHeight(instructionText, 120);
engine.block.replaceText(
instructionText,
'Open the browser console to see logs of the undo and redo operations in this example.'
);
engine.block.setFloat(instructionText, 'text/fontSize', 90);
engine.block.setEnum(instructionText, 'text/horizontalAlignment', 'Center');
engine.block.appendChild(page, instructionText);
// Clean up subscription when done (in a real app, call this on cleanup)
// unsubscribe();
void unsubscribe;
}
}
export default Example;
```
This guide covers how to use the built-in undo/redo UI controls, perform undo and redo operations programmatically, subscribe to history changes, manually manage undo steps, and work with multiple history stacks.
## Setup
We start by initializing the CE.SDK editor and creating a design scene. The engine automatically creates a history stack when the editor is initialized.
```typescript highlight=highlight-setup
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0]!;
```
## Using the Built-in Undo/Redo UI
The CE.SDK editor includes undo and redo buttons in the navigation bar. When users make changes, the undo button becomes active. After undoing, the redo button allows restoring changes. The UI automatically reflects the current history state, disabling buttons when no operations are available.
## Automatic Undo Step Creation
Most editing operations automatically create undo steps. When we add a shape to the scene, the engine records this operation in the history stack.
```typescript highlight=highlight-create-block
// Create a triangle shape and add an undo step to record it in history
const block = engine.block.create('graphic');
engine.block.setPositionX(block, 140);
engine.block.setPositionY(block, 95);
engine.block.setWidth(block, 265);
engine.block.setHeight(block, 265);
const triangleShape = engine.block.createShape('polygon');
engine.block.setInt(triangleShape, 'shape/polygon/sides', 3);
engine.block.setShape(block, triangleShape);
const triangleFill = engine.block.createFill('color');
engine.block.setColor(triangleFill, 'fill/color/value', {
r: 0.2,
g: 0.5,
b: 0.9,
a: 1
});
engine.block.setFill(block, triangleFill);
engine.block.appendChild(page, block);
// Commit the block creation to history so it can be undone
engine.editor.addUndoStep();
```
After creating the shape, `canUndo()` returns `true` since the operation has been recorded as an undoable step.
## Performing Undo and Redo Operations
We use `engine.editor.undo()` and `engine.editor.redo()` to programmatically revert or restore changes. Before calling these methods, check availability with `engine.editor.canUndo()` and `engine.editor.canRedo()` to prevent errors.
```typescript highlight=highlight-undo
// Undo the block creation
if (engine.editor.canUndo()) {
engine.editor.undo();
console.log(
'After undo - canUndo:',
engine.editor.canUndo(),
'canRedo:',
engine.editor.canRedo()
);
}
```
The undo operation reverts the most recent change. After undoing, `canRedo()` returns `true` since there's now a step available to restore.
```typescript highlight=highlight-redo
// Redo to restore the block
if (engine.editor.canRedo()) {
engine.editor.redo();
console.log(
'After redo - canUndo:',
engine.editor.canUndo(),
'canRedo:',
engine.editor.canRedo()
);
}
```
The redo operation restores the most recently undone change. After redoing, `canRedo()` returns `false` (unless there are more undo steps to restore).
## Subscribing to History Changes
We use `engine.editor.onHistoryUpdated()` to receive notifications when the history state changes. The callback fires after any undo, redo, or new operation. This enables synchronizing custom UI elements with the current history state.
```typescript highlight=highlight-subscribe-history
// Subscribe to history updates to track state changes
const unsubscribe = engine.editor.onHistoryUpdated(() => {
const canUndo = engine.editor.canUndo();
const canRedo = engine.editor.canRedo();
console.log('History updated:', { canUndo, canRedo });
});
```
The subscription returns an unsubscribe function. Call it when you no longer need notifications, such as when unmounting a component.
## Managing Undo Steps Manually
Most editing operations automatically create undo steps. However, some custom operations may require manual checkpoint creation using `engine.editor.addUndoStep()`. This is useful when you make multiple related changes that should be undone as a single unit.
```typescript highlight=highlight-manual-undo-step
// Manually add an undo step after custom operations
engine.block.setPositionX(block, 190);
engine.editor.addUndoStep();
console.log('Manual undo step added. canUndo:', engine.editor.canUndo());
// Remove the most recent undo step if needed
if (engine.editor.canUndo()) {
engine.editor.removeUndoStep();
console.log('Most recent undo step removed');
}
// Reset block position to its original location
engine.block.setPositionX(block, 140);
```
We use `engine.editor.removeUndoStep()` to remove the most recent undo step. Always check `canUndo()` before calling this method to ensure an undo step is available. This can be useful when you need to discard changes without affecting the redo stack.
## Working with Multiple History Stacks
CE.SDK supports multiple independent history stacks for isolated editing contexts. This is useful when you need separate undo/redo histories for different parts of your application, such as a main canvas and an overlay editor.
### Creating and Switching History Stacks
We create a new history stack using `engine.editor.createHistory()`. Use `engine.editor.setActiveHistory()` to switch between stacks. Only the active history responds to undo/redo operations.
```typescript highlight=highlight-multiple-histories
// Create a second history stack for isolated operations
const secondaryHistory = engine.editor.createHistory();
const primaryHistory = engine.editor.getActiveHistory();
console.log(
'Created secondary history. Primary:',
primaryHistory,
'Secondary:',
secondaryHistory
);
// Switch to the secondary history
engine.editor.setActiveHistory(secondaryHistory);
console.log(
'Switched to secondary history. Active:',
engine.editor.getActiveHistory()
);
// Operations in secondary history are isolated from the primary history
const secondBlock = engine.block.create('graphic');
engine.block.setPositionX(secondBlock, 440);
engine.block.setPositionY(secondBlock, 95);
engine.block.setWidth(secondBlock, 220);
engine.block.setHeight(secondBlock, 220);
const circleShape = engine.block.createShape('ellipse');
engine.block.setShape(secondBlock, circleShape);
const circleFill = engine.block.createFill('color');
engine.block.setColor(circleFill, 'fill/color/value', {
r: 0.9,
g: 0.3,
b: 0.3,
a: 1
});
engine.block.setFill(secondBlock, circleFill);
engine.block.appendChild(page, secondBlock);
// Commit changes to the secondary history
engine.editor.addUndoStep();
console.log(
'Block added in secondary history. canUndo:',
engine.editor.canUndo()
);
// Switch back to primary history
engine.editor.setActiveHistory(primaryHistory);
console.log(
'Switched back to primary history. canUndo:',
engine.editor.canUndo()
);
```
Operations performed while a history is active only affect that history. When you switch back to the primary history, its undo/redo state remains unchanged by operations performed in the secondary history.
### Cleaning Up History Stacks
We destroy unused history stacks with `engine.editor.destroyHistory()` to free resources. Always clean up secondary histories when they're no longer needed.
```typescript highlight=highlight-destroy-history
// Clean up the secondary history when no longer needed
engine.editor.destroyHistory(secondaryHistory);
console.log('Secondary history destroyed');
```
## Troubleshooting
Common issues when working with undo/redo functionality:
- **Undo step not recorded**: Ensure changes occur after the history subscription is active. The engine only tracks operations that happen while the history is being monitored.
- **Redo not available**: Performing any new action after undo clears the redo stack. This is standard behavior to prevent branching history states.
- **Wrong history active**: Always verify the correct history is set with `getActiveHistory()` before performing undo/redo operations when using multiple stacks.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Configuration"
description: "Learn how to configure CE.SDK to match your application's functional, visual, and performance requirements."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/configuration-2c1c3d/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Configuration](https://img.ly/docs/cesdk/sveltekit/configuration-2c1c3d/)
---
Set up CE.SDK with license keys, asset base URLs, user IDs, and runtime configuration options to match your application requirements.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-configuration-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-configuration-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-configuration-browser/)
`CreativeEditorSDK.create()` initializes the full CE.SDK editor with UI components. The configuration object controls license validation, asset loading, user tracking, and UI behavior.
```typescript file=@cesdk_web_examples/guides-configuration-browser/index.ts reference-only
import CreativeEditorSDK from '@cesdk/cesdk-js';
import Example from './browser';
const config = {
// License key removes watermarks from exports
// Get a free trial at https://img.ly/forms/free-trial
// license: 'YOUR_CESDK_LICENSE_KEY',
// User ID for accurate MAU tracking across devices
userId: 'guides-user',
// Custom logger for debugging and monitoring
logger: (message: string, level?: string) => {
console.log(`[CE.SDK ${level ?? 'Info'}] ${message}`);
},
// Enable developer mode for diagnostics
devMode: false,
// Accessibility settings
a11y: {
headingsHierarchyStart: 1 as const
},
// Location of core engine assets (WASM, data files)
// Default: IMG.LY CDN. For production, host assets yourself.
// baseURL: 'https://your-cdn.com/cesdk-assets/',
// Use local assets when developing with local packages
...(import.meta.env.CESDK_USE_LOCAL && {
baseURL: import.meta.env.VITE_CESDK_ASSETS_BASE_URL
})
};
CreativeEditorSDK.create('#cesdk_container', config)
.then(async (cesdk: CreativeEditorSDK) => {
// Expose cesdk for debugging and hero screenshot generation
(window as any).cesdk = cesdk;
// Load the example plugin
await cesdk.addPlugin(new Example());
})
.catch((error: Error) => {
// eslint-disable-next-line no-console
console.error('Failed to initialize CE.SDK:', error);
});
```
```typescript file=@cesdk_web_examples/guides-configuration-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
// Create a Scene
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const pages = engine.block.findByType('page');
const page = pages[0];
// ========================================
// Setup: Gradient Background with Title
// ========================================
// Create gradient background
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { r: 0.15, g: 0.1, b: 0.35, a: 1.0 }, stop: 0 },
{ color: { r: 0.4, g: 0.2, b: 0.5, a: 1.0 }, stop: 0.5 },
{ color: { r: 0.6, g: 0.3, b: 0.4, a: 1.0 }, stop: 1 }
]);
engine.block.setFill(page, gradientFill);
// Add centered title text
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const titleText = engine.block.create('text');
engine.block.replaceText(titleText, 'Configure your Editor');
engine.block.setFloat(titleText, 'text/fontSize', 12);
engine.block.setTextColor(titleText, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
engine.block.setWidthMode(titleText, 'Auto');
engine.block.setHeightMode(titleText, 'Auto');
engine.block.appendChild(page, titleText);
// Add IMG.LY subtext
const subtitleText = engine.block.create('text');
engine.block.replaceText(subtitleText, 'Powered by IMG.LY');
engine.block.setFloat(subtitleText, 'text/fontSize', 6);
engine.block.setTextColor(subtitleText, { r: 0.9, g: 0.9, b: 0.9, a: 0.8 });
engine.block.setWidthMode(subtitleText, 'Auto');
engine.block.setHeightMode(subtitleText, 'Auto');
engine.block.appendChild(page, subtitleText);
// Center both texts
const titleWidth = engine.block.getFrameWidth(titleText);
const titleHeight = engine.block.getFrameHeight(titleText);
const subtitleWidth = engine.block.getFrameWidth(subtitleText);
const subtitleHeight = engine.block.getFrameHeight(subtitleText);
const spacing = 12;
const totalHeight = titleHeight + spacing + subtitleHeight;
const startY = (pageHeight - totalHeight) / 2;
engine.block.setPositionX(titleText, (pageWidth - titleWidth) / 2);
engine.block.setPositionY(titleText, startY);
engine.block.setPositionX(subtitleText, (pageWidth - subtitleWidth) / 2);
engine.block.setPositionY(subtitleText, startY + titleHeight + spacing);
// ========================================
// Runtime Configuration: Theme
// ========================================
cesdk.ui.setTheme('light');
const currentTheme = cesdk.ui.getTheme();
console.log('Current theme:', currentTheme);
// ========================================
// Runtime Configuration: Scale
// ========================================
cesdk.ui.setScale('modern');
const currentScale = cesdk.ui.getScale();
console.log('Current scale:', currentScale);
// ========================================
// Runtime Configuration: Actions
// ========================================
cesdk.actions.register('customSave', async () => {
const sceneBlob = await engine.scene.saveToArchive();
await cesdk.utils.downloadFile(sceneBlob, 'application/zip');
});
// ========================================
// Built-in Actions
// ========================================
// Add built-in export and import actions to the navigation bar
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, {
id: 'ly.img.actions.navigationBar',
children: [
'ly.img.saveScene.navigationBar',
'ly.img.exportImage.navigationBar',
'ly.img.exportPDF.navigationBar',
'ly.img.exportScene.navigationBar',
'ly.img.exportArchive.navigationBar',
'ly.img.importScene.navigationBar',
'ly.img.importArchive.navigationBar'
]
});
// ========================================
// Engine Settings
// ========================================
engine.editor.setSetting('doubleClickToCropEnabled', true);
engine.editor.setSetting('highlightColor', { r: 0, g: 0.5, b: 1, a: 1 });
const cropEnabled = engine.editor.getSetting('doubleClickToCropEnabled');
console.log('Double-click crop enabled:', cropEnabled);
// ========================================
// Internationalization: Locale
// ========================================
cesdk.i18n.setLocale('en');
const currentLocale = cesdk.i18n.getLocale();
console.log('Current locale:', currentLocale);
// ========================================
// Internationalization: Translations
// ========================================
cesdk.i18n.setTranslations({
en: {
'common.back': 'Go Back',
'common.apply': 'Apply Changes'
}
});
// Enable Auto-Fit Zoom
engine.scene.zoomToBlock(page);
engine.scene.enableZoomAutoFit(page, 'Horizontal', 40, 40);
}
}
export default Example;
```
## Required Configuration
The `license` property is the only required configuration. All other properties have sensible defaults.
| Property | Type | Purpose |
|----------|------|---------|
| `license` | `string` | License key to remove export watermarks |
The license key validates your CE.SDK subscription and removes watermarks from exports. Get a free trial license at [https://img.ly/forms/free-trial](https://img.ly/forms/free-trial).
## Optional Configuration
These properties customize engine behavior and are all optional.
### Engine Properties
| Property | Type | Purpose |
|----------|------|---------|
| `baseURL` | `string` | Location of core engine assets (WASM, data files) |
| `userId` | `string` | User identifier for MAU tracking |
| `logger` | `function` | Custom logging function |
| `role` | `'Creator'` | `'Adopter'` | `'Viewer'` | `'Presenter'` | User role for feature access |
| `featureFlags` | `object` | Experimental feature toggles |
### Editor Properties
| Property | Type | Purpose |
|----------|------|---------|
| `devMode` | `boolean` | Enable developer diagnostics |
| `a11y` | `object` | Accessibility settings |
| `ui` | `object` | User interface customization |
## Configuration Properties
### License Key
The license key validates your CE.SDK subscription and removes watermarks from exports. Without a valid license, exports include a watermark.
```typescript highlight=highlight-license
// License key removes watermarks from exports
// Get a free trial at https://img.ly/forms/free-trial
// license: 'YOUR_CESDK_LICENSE_KEY',
```
### User ID
Provide a unique user identifier for accurate Monthly Active User (MAU) tracking. This helps count users correctly when the same person accesses your application from multiple devices.
```typescript highlight=highlight-userId
// User ID for accurate MAU tracking across devices
userId: 'guides-user',
```
### Custom Logger
Replace the default console logging with a custom logger function. The logger receives a message string and an optional log level (`'Info'`, `'Warning'`, or `'Error'`).
```typescript highlight=highlight-logger
// Custom logger for debugging and monitoring
logger: (message: string, level?: string) => {
console.log(`[CE.SDK ${level ?? 'Info'}] ${message}`);
},
```
### Developer Mode
Enable developer mode to get additional diagnostics and debugging information in the console.
```typescript highlight=highlight-devMode
// Enable developer mode for diagnostics
devMode: false,
```
### Accessibility Settings
Configure accessibility options like heading hierarchy for screen readers. The `headingsHierarchyStart` property sets which heading level (1-6) the editor should start from.
```typescript highlight=highlight-a11y
// Accessibility settings
a11y: {
headingsHierarchyStart: 1 as const
},
```
### Asset Base URL
The `baseURL` property specifies the location of core engine assets, including WASM files, data files, and JavaScript workers. By default, these load from the IMG.LY CDN. For production deployments, host these assets yourself by copying the `assets` folder from `node_modules/@cesdk/engine/assets` to your server.
Content assets like stickers and filters are loaded separately via asset source plugins (imported from `@cesdk/cesdk-js/plugins`), each of which accepts its own `baseURL` option defaulting to `https://cdn.img.ly/assets/v4`.
```typescript highlight=highlight-baseURL
// Location of core engine assets (WASM, data files)
// Default: IMG.LY CDN. For production, host assets yourself.
// baseURL: 'https://your-cdn.com/cesdk-assets/',
```
### Initialization
Pass the configuration object to `CreativeEditorSDK.create()` along with a container element selector.
```typescript highlight=highlight-create
CreativeEditorSDK.create('#cesdk_container', config)
.then(async (cesdk: CreativeEditorSDK) => {
```
## Runtime Configuration
After initialization, use dedicated APIs to modify settings dynamically.
### Internationalization
#### Locale
Change the UI language using `cesdk.i18n.setLocale()`.
```typescript highlight=highlight-locale
cesdk.i18n.setLocale('en');
const currentLocale = cesdk.i18n.getLocale();
console.log('Current locale:', currentLocale);
```
#### Translations
Add or override UI text strings using `cesdk.i18n.setTranslations()`.
```typescript highlight=highlight-translations
cesdk.i18n.setTranslations({
en: {
'common.back': 'Go Back',
'common.apply': 'Apply Changes'
}
});
```
> **Note:** For complete localization including custom translations and RTL support, see [Localization](https://img.ly/docs/cesdk/sveltekit/user-interface/localization-508e20/).
### Theme
Set the UI theme using `cesdk.ui.setTheme()`. Options: `'light'`, `'dark'`, or `'system'`.
```typescript highlight=highlight-theme
cesdk.ui.setTheme('light');
const currentTheme = cesdk.ui.getTheme();
console.log('Current theme:', currentTheme);
```
> **Note:** For advanced theming including custom CSS variables and color schemes, see [Theming](https://img.ly/docs/cesdk/sveltekit/user-interface/appearance/theming-4b0938/).
### Actions
Register custom actions for user interactions like save and export.
```typescript highlight=highlight-actions
cesdk.actions.register('customSave', async () => {
const sceneBlob = await engine.scene.saveToArchive();
await cesdk.utils.downloadFile(sceneBlob, 'application/zip');
});
```
### Built-in Actions
CE.SDK provides built-in actions for common operations like saving, exporting, and importing. Add them to the navigation bar using `insertOrderComponent()`:
```typescript highlight=highlight-builtin-actions
// Add built-in export and import actions to the navigation bar
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, {
id: 'ly.img.actions.navigationBar',
children: [
'ly.img.saveScene.navigationBar',
'ly.img.exportImage.navigationBar',
'ly.img.exportPDF.navigationBar',
'ly.img.exportScene.navigationBar',
'ly.img.exportArchive.navigationBar',
'ly.img.importScene.navigationBar',
'ly.img.importArchive.navigationBar'
]
});
```
**Available built-in actions:**
| Action ID | Purpose |
|-----------|---------|
| `ly.img.saveScene.navigationBar` | Save scene to cloud |
| `ly.img.exportImage.navigationBar` | Export as image (PNG/JPEG) |
| `ly.img.exportPDF.navigationBar` | Export as PDF |
| `ly.img.exportScene.navigationBar` | Export scene file |
| `ly.img.exportArchive.navigationBar` | Export as archive (ZIP) |
| `ly.img.importScene.navigationBar` | Import scene file |
| `ly.img.importArchive.navigationBar` | Import archive (ZIP) |
> **Note:** For detailed navigation bar customization including adding buttons and rearranging elements, see [Navigation Bar](https://img.ly/docs/cesdk/sveltekit/user-interface/customization/navigation-bar-4e5d39/).
> **Note:** For a complete guide on registering and managing actions, see [Actions](https://img.ly/docs/cesdk/sveltekit/actions-6ch24x/).
### Scale
Adjust UI scale for different device types. Options: `'normal'`, `'large'`, or `'modern'`.
```typescript highlight=highlight-scale
cesdk.ui.setScale('modern');
const currentScale = cesdk.ui.getScale();
console.log('Current scale:', currentScale);
```
> **Note:** For advanced scale configuration including responsive callbacks, see [Theming](https://img.ly/docs/cesdk/sveltekit/user-interface/appearance/theming-4b0938/).
### Engine Settings
Configure engine behavior using `engine.editor.setSetting()`.
```typescript highlight=highlight-settings
engine.editor.setSetting('doubleClickToCropEnabled', true);
engine.editor.setSetting('highlightColor', { r: 0, g: 0.5, b: 1, a: 1 });
const cropEnabled = engine.editor.getSetting('doubleClickToCropEnabled');
console.log('Double-click crop enabled:', cropEnabled);
```
> **Note:** For a complete reference of available engine settings, see [Engine Interface](https://img.ly/docs/cesdk/sveltekit/engine-interface-6fb7cf/).
## API Reference
| Method | Purpose |
|--------|---------|
| `CreativeEditorSDK.create()` | Initialize editor |
| `cesdk.ui.setTheme()` | Set UI theme |
| `cesdk.i18n.setLocale()` | Set UI locale |
| `cesdk.i18n.setTranslations()` | Add translations |
| `cesdk.ui.setScale()` | Set UI scale |
| `cesdk.actions.register()` | Register custom actions |
| `cesdk.ui.insertOrderComponent()` | Add built-in actions to navigation bar |
| `cesdk.utils.downloadFile()` | Download blob as file |
| `engine.editor.setSetting()` | Set engine setting |
## Next Steps
- [Headless Mode](https://img.ly/docs/cesdk/sveltekit/concepts/headless-mode/browser-24ab98/) - Use CE.SDK without the UI
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Conversion"
description: "Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/conversion-c3fbb3/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/sveltekit/conversion-c3fbb3/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/sveltekit/conversion/overview-44dc58/) - Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools.
- [To Base64](https://img.ly/docs/cesdk/sveltekit/conversion/to-base64-39ff25/) - Convert CE.SDK exports to Base64-encoded strings for embedding in URLs, storing in databases, or transmitting via APIs.
- [To PNG](https://img.ly/docs/cesdk/sveltekit/conversion/to-png-f1660c/) - Export designs and images to PNG format with compression settings and target dimensions using CE.SDK.
- [To PDF](https://img.ly/docs/cesdk/sveltekit/conversion/to-pdf-eb937f/) - Convert your design or document into a high-quality, print-ready PDF format.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/conversion/overview-44dc58/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/sveltekit/conversion-c3fbb3/) > [Overview](https://img.ly/docs/cesdk/sveltekit/conversion/overview-44dc58/)
---
## Supported Input and Output Formats
CE.SDK accepts a range of input formats when working with designs, including:
When it comes to exporting or converting designs, the SDK supports the following output formats:
Each format serves different use cases, giving you the flexibility to adapt designs for your application’s needs.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To Base64"
description: "Convert CE.SDK exports to Base64-encoded strings for embedding in URLs, storing in databases, or transmitting via APIs."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/conversion/to-base64-39ff25/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/sveltekit/conversion-c3fbb3/) > [To Base64](https://img.ly/docs/cesdk/sveltekit/conversion/to-base64-39ff25/)
---
Convert CE.SDK exports to Base64-encoded strings for embedding in HTML, storing in databases, or transmitting via JSON APIs.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-conversion-to-base64-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-conversion-to-base64-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-conversion-to-base64-browser/)
Base64 encoding transforms binary image data into ASCII text, enabling you to embed images directly in HTML, store them in text-only databases, or transmit them through JSON APIs without binary handling.
```typescript file=@cesdk_web_examples/guides-conversion-to-base64-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) throw new Error('CE.SDK instance is required');
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.scene.getCurrentPage()!;
await engine.scene.zoomToBlock(page);
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, {
id: 'ly.img.actions.navigationBar',
children: [
{
id: 'ly.img.action.navigationBar',
onClick: async () => {
const currentPage = engine.scene.getCurrentPage()!;
const blob = await engine.block.export(currentPage, {
mimeType: 'image/png'
});
const base64 = await this.blobToBase64(blob);
await cesdk.utils.downloadFile(blob, 'image/png');
cesdk.ui.showNotification({
message: `Base64: ${(base64.length / 1024).toFixed(0)} KB`,
type: 'success'
});
},
key: 'export-base64',
label: 'To Base64',
icon: '@imgly/Save'
}
]
});
cesdk.actions.register('exportDesign', async () => {
const currentPage = engine.scene.getCurrentPage()!;
const blob = await engine.block.export(currentPage, {
mimeType: 'image/png'
});
const base64 = await this.blobToBase64(blob);
await cesdk.utils.downloadFile(blob, 'image/png');
});
}
private blobToBase64(blob: Blob): Promise {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(blob);
});
}
}
export default Example;
```
## Export a Block to Base64
Use `engine.block.export()` to export a design block as a Blob, then convert it to a Base64 data URI.
```typescript
const currentPage = engine.scene.getCurrentPage()!;
const blob = await engine.block.export(currentPage, {
mimeType: 'image/png'
});
const base64 = await blobToBase64(blob);
```
The export returns a Blob containing the rendered image. You then convert this Blob to a Base64 data URI using the browser's `FileReader` API. The resulting string includes the MIME type prefix (`data:image/png;base64,...`), making it ready for immediate use as an image source.
## Convert Blob to Base64
Convert the exported Blob into a Base64 data URI using the browser's `FileReader` API.
```typescript highlight=highlight-convert-base64
private blobToBase64(blob: Blob): Promise {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(blob);
});
}
```
The `readAsDataURL()` method returns a complete data URI including the MIME type prefix (`data:image/png;base64,...`). This wrapper converts the callback-based FileReader into a Promise for cleaner async/await usage.
## Customize the Built-in Export Action
Override the default `exportDesign` action to integrate Base64 conversion into CE.SDK's built-in export flow.
```typescript highlight=highlight-custom-action
cesdk.actions.register('exportDesign', async () => {
const currentPage = engine.scene.getCurrentPage()!;
const blob = await engine.block.export(currentPage, {
mimeType: 'image/png'
});
const base64 = await this.blobToBase64(blob);
await cesdk.utils.downloadFile(blob, 'image/png');
});
```
When registered, this action replaces the default export behavior. Any UI component or keyboard shortcut that triggers `exportDesign` will use your custom handler instead.
## Download the Export
Use `cesdk.utils.downloadFile()` to save the exported Blob to the user's device. The method accepts a Blob and MIME type, triggering a browser download with the appropriate file extension.
## When to Use Base64
Base64 encoding works well for:
- Embedding images directly in HTML or CSS without additional HTTP requests
- Storing images in text-only databases like Redis or localStorage
- Transmitting images through JSON APIs that don't support binary data
- Generating data URIs for email templates
> **Note:** Base64 increases file size by approximately 33%. For images larger than 100KB, consider binary storage or direct URL references instead.
## Troubleshooting
**Base64 string too long** — Use JPEG or WebP formats with lower quality settings. Reduce dimensions with `targetWidth` and `targetHeight` export options.
**Image not displaying** — Verify the data URI includes the correct MIME type prefix. Check that the string wasn't truncated during storage or transmission.
**Performance issues** — FileReader operations are asynchronous but encoding large images can still block the UI. Consider Web Workers for images over 1MB.
## API Reference
| Method | Description |
|--------|-------------|
| `engine.block.export(block, options)` | Export a block to a Blob with format options (`mimeType`, `jpegQuality`, `webpQuality`, `targetWidth`, `targetHeight`) |
| `engine.scene.getCurrentPage()` | Get the currently active page block |
| `FileReader.readAsDataURL(blob)` | Convert Blob to Base64 data URI (Browser API) |
| `cesdk.utils.downloadFile(blob, mimeType)` | Download a Blob as a file |
| `cesdk.actions.register(name, handler)` | Register or override an action |
| `cesdk.ui.showNotification(options)` | Display a notification to the user |
## Next Steps
- [Export Options](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/overview-9ed3a8/) — Explore all available export formats and configuration
- [Export to PDF](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/to-pdf-95e04b/) — Generate PDFs for print and document workflows
- [Partial Export](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/partial-export-89aaf6/) — Export specific regions or individual elements
- [Size Limits](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To PDF"
description: "Convert your design or document into a high-quality, print-ready PDF format."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/conversion/to-pdf-eb937f/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/sveltekit/conversion-c3fbb3/) > [To PDF](https://img.ly/docs/cesdk/sveltekit/conversion/to-pdf-eb937f/)
---
The CE.SDK allows you to convert JPEG, PNG, WebP, BMP and SVG images into PDFs directly in the browser—no server-side processing required. You can perform this conversion programmatically or through the user interface.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-conversion-to-pdf-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-conversion-to-pdf-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-conversion-to-pdf-browser/)
The CE.SDK supports converting single or multiple images to PDF while allowing transformations such as cropping, rotating, and adding text before exporting. You can also customize PDF output settings, including resolution, compatibility and underlayer.
```typescript file=@cesdk_web_examples/guides-conversion-to-pdf-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: To PDF Guide
*
* This example demonstrates:
* - Exporting designs as PDF documents
* - Configuring PDF output settings (DPI, compatibility, underlayer)
* - Adding a custom PDF export button to the navigation bar
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) throw new Error('CE.SDK instance is required');
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
// Load a template scene
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.scene.getCurrentPage()!;
await engine.scene.zoomToBlock(page);
// Add PDF export buttons to the navigation bar
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, {
id: 'ly.img.actions.navigationBar',
children: [
{
id: 'ly.img.action.navigationBar',
key: 'export-pdf',
label: 'PDF',
icon: '@imgly/Download',
onClick: async () => {
// Export scene as PDF (includes all pages)
const scene = engine.scene.get()!;
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf'
});
// Download using CE.SDK utils
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
cesdk.ui.showNotification({
message: 'PDF exported successfully',
type: 'success'
});
}
},
{
id: 'ly.img.action.navigationBar',
key: 'export-high-compat',
label: 'High Compat',
icon: '@imgly/Download',
onClick: async () => {
const scene = engine.scene.get()!;
// Enable high compatibility mode for consistent rendering across PDF viewers
// This rasterizes complex elements like gradients with transparency at scene DPI
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true
});
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
cesdk.ui.showNotification({
message: 'High compatibility PDF exported',
type: 'success'
});
}
},
{
id: 'ly.img.action.navigationBar',
key: 'export-underlayer',
label: 'Underlayer',
icon: '@imgly/Download',
onClick: async () => {
const scene = engine.scene.get()!;
// Define the underlayer spot color before export
// RGB values (0.8, 0.8, 0.8) provide a preview representation
engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8);
// Export with underlayer for special media printing
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true,
exportPdfWithUnderlayer: true,
underlayerSpotColorName: 'RDG_WHITE',
// Negative offset shrinks underlayer to prevent visible edges
underlayerOffset: -2.0
});
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
cesdk.ui.showNotification({
message: 'PDF with underlayer exported',
type: 'success'
});
}
},
{
id: 'ly.img.action.navigationBar',
key: 'export-dpi',
label: 'Custom DPI',
icon: '@imgly/Download',
onClick: async () => {
const scene = engine.scene.get()!;
// Adjust the scene DPI for print-ready output
// Higher DPI = better quality but larger file size
engine.block.setFloat(scene, 'scene/dpi', 150);
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf'
});
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
cesdk.ui.showNotification({
message: 'PDF exported at 150 DPI',
type: 'success'
});
}
}
]
});
}
}
export default Example;
```
This guide covers exporting designs as PDF documents, configuring output settings like DPI and compatibility, adding underlayers for specialty printing, and integrating PDF export into the user interface.
## Convert to PDF Programmatically
You can use the CE.SDK to load an image, apply basic edits, and export it as a PDF programmatically. The following examples demonstrate how to convert a single image and how to merge multiple images into a single PDF.
### Convert a Single Image to PDF
The example below loads an image, applies transformations, and exports it as a PDF.
```ts
// Prepare an image URL
const imageURL = 'https://img.ly/static/ubq_samples/sample_4.jpg';
// Create a new scene by loading the image immediately
await cesdk.createFromImage(imageURL);
// Find the automatically added graphic block with an image fill
const block = engine.block.findByType('graphic')[0];
// Apply crop with a scale ratio of 2.0
engine.block.setCropScaleRatio(block, 2.0);
// Export as PDF Blob
const page = engine.scene.getCurrentPage()!;
const blob = await engine.block.export(page, { mimeType: 'application/pdf' });
// You can now save it or display it in your application
```
### Combine Multiple Images into a Single PDF
The example below demonstrates how to merge multiple images into a single PDF document.
```ts
// Prepare image URLs
const images = [
'https://img.ly/static/ubq_samples/sample_1.jpg',
'https://img.ly/static/ubq_samples/sample_2.jpg',
'https://img.ly/static/ubq_samples/sample_3.jpg',
];
// Create an empty scene with a 'VerticalStack' layout
const scene = engine.scene.create('VerticalStack');
const [stack] = engine.block.findByType('stack');
// Load all images as pages
for (const image of images) {
// Append the new page to the stack
const page = engine.block.create('page');
engine.block.appendChild(stack, page);
// Set the image as the fill of the page
const imageFill = engine.block.createFill('image');
engine.block.setString(imageFill, 'fill/image/imageFileURI', image);
engine.block.setFill(page, imageFill);
}
// Export all images as a single PDF blob
const blob = await engine.block.export(scene, { mimeType: 'application/pdf' });
// You can now save it or display it in your application
```
## Export a Page as PDF
Use `engine.block.export()` to export a design block as a PDF. The method accepts a block ID and export options including the MIME type.
```typescript highlight=highlight-export-pdf
// Export scene as PDF (includes all pages)
const scene = engine.scene.get()!;
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf'
});
```
Export returns a Blob containing the PDF data. You can export a single page for a single-page PDF, or export the entire scene to include all pages in a multi-page PDF document.
## Download the PDF
Use `cesdk.utils.downloadFile()` to save the exported PDF to the user's device.
```typescript highlight=highlight-download
// Download using CE.SDK utils
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
```
The utility handles creating a download link and triggering the browser's save dialog with the appropriate file extension.
## PDF Conversion via the User Interface
The CE.SDK allows you to enable PDF conversion directly from the user interface. You can customize the UI to include a "Convert to PDF" button, allowing users to trigger conversion to PDF after they [upload images](https://img.ly/docs/cesdk/sveltekit/insert-media/images-63848a/) and perform any edits or adjustments.
### Add a PDF Export Button
Integrate PDF export into the CE.SDK interface by adding a custom button to the navigation bar.
```typescript highlight=highlight-export-button
// Add PDF export buttons to the navigation bar
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, {
id: 'ly.img.actions.navigationBar',
children: [
{
id: 'ly.img.action.navigationBar',
key: 'export-pdf',
label: 'PDF',
icon: '@imgly/Download',
onClick: async () => {
// Export scene as PDF (includes all pages)
const scene = engine.scene.get()!;
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf'
});
// Download using CE.SDK utils
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
cesdk.ui.showNotification({
message: 'PDF exported successfully',
type: 'success'
});
}
},
{
id: 'ly.img.action.navigationBar',
key: 'export-high-compat',
label: 'High Compat',
icon: '@imgly/Download',
onClick: async () => {
const scene = engine.scene.get()!;
// Enable high compatibility mode for consistent rendering across PDF viewers
// This rasterizes complex elements like gradients with transparency at scene DPI
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true
});
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
cesdk.ui.showNotification({
message: 'High compatibility PDF exported',
type: 'success'
});
}
},
{
id: 'ly.img.action.navigationBar',
key: 'export-underlayer',
label: 'Underlayer',
icon: '@imgly/Download',
onClick: async () => {
const scene = engine.scene.get()!;
// Define the underlayer spot color before export
// RGB values (0.8, 0.8, 0.8) provide a preview representation
engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8);
// Export with underlayer for special media printing
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true,
exportPdfWithUnderlayer: true,
underlayerSpotColorName: 'RDG_WHITE',
// Negative offset shrinks underlayer to prevent visible edges
underlayerOffset: -2.0
});
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
cesdk.ui.showNotification({
message: 'PDF with underlayer exported',
type: 'success'
});
}
},
{
id: 'ly.img.action.navigationBar',
key: 'export-dpi',
label: 'Custom DPI',
icon: '@imgly/Download',
onClick: async () => {
const scene = engine.scene.get()!;
// Adjust the scene DPI for print-ready output
// Higher DPI = better quality but larger file size
engine.block.setFloat(scene, 'scene/dpi', 150);
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf'
});
await cesdk.utils.downloadFile(pdfBlob, 'application/pdf');
cesdk.ui.showNotification({
message: 'PDF exported at 150 DPI',
type: 'success'
});
}
}
]
});
```
The button triggers the export workflow when clicked, providing users with a convenient way to download their designs as PDF documents.
### Alternative: Register a Custom Component
You can also use `ui.registerComponent` to create a more customized button with full control over styling and behavior.
```ts
// Register a custom button component
cesdk.ui.registerComponent(
'convert.nav',
({ builder: { Button }, engine }) => {
Button('convert-to-pdf', {
label: 'Convert To PDF',
icon: '@imgly/Download',
color: 'accent',
onClick: async () => {
// Export the current scene as a PDF blob
const scene = engine.scene.get()!;
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
});
// Trigger download of the PDF blob
const element = document.createElement('a');
element.setAttribute('href', window.URL.createObjectURL(blob));
element.setAttribute('download', 'converted.pdf');
element.style.display = 'none';
element.click();
element.remove();
},
});
},
);
// Add the custom button at the end of the navigation bar
cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [
...cesdk.ui.getComponentOrder({ in: 'ly.img.navigation.bar' }),
'convert.nav',
]);
```
For more details on customizing the UI, see the [User Interface Configuration Guide](https://img.ly/docs/cesdk/sveltekit/user-interface/customization-72b2f8/).
## Configure PDF Output Settings
The SDK provides various options for customizing PDF exports. You can control resolution, compatibility, and underlayer settings.
### Available PDF Output Settings
- **Resolution:** Adjust the DPI (dots per inch) to create print-ready PDFs with the desired level of detail.
- **Page Size:** Define custom dimensions in pixels for the output PDF. If specified, the block will scale to fully cover the target size while maintaining its aspect ratio.
- **Compatibility:** Enable this setting to improve compatibility with various PDF viewers. When enabled, images and effects are rasterized based on the scene's DPI instead of being embedded as vector elements.
- **Underlayer:** Add an underlayer beneath the image content to optimize printing on non-white or specialty media (e.g., fabric, glass). The ink type is defined in `ExportOptions` using a spot color. You can also apply a positive or negative offset, in design units, to adjust the underlayer's scale.
### Adjust DPI for Print Quality
Control the output resolution by setting the scene's DPI property before export.
```typescript highlight=highlight-dpi
// Adjust the scene DPI for print-ready output
// Higher DPI = better quality but larger file size
engine.block.setFloat(scene, 'scene/dpi', 150);
```
Higher DPI values produce better quality output but result in larger file sizes. The default is 300 DPI, which is suitable for most print applications.
### Enable High Compatibility Mode
Enable high compatibility mode for consistent rendering across different PDF viewers.
```typescript highlight=highlight-high-compatibility
// Enable high compatibility mode for consistent rendering across PDF viewers
// This rasterizes complex elements like gradients with transparency at scene DPI
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true
});
```
When enabled, complex elements like gradients with transparency are rasterized at the scene's DPI setting instead of being embedded as native PDF objects. This ensures consistent appearance in viewers like Safari and macOS Preview but increases file size.
### PDF Performance Optimization
The `exportPdfWithHighCompatibility` flag significantly impacts PDF export performance, especially for high-DPI content:
**When `true` (default - safer but slower):**
- Rasterizes images and gradients at the scene's DPI setting
- Maximum compatibility with all PDF viewers including Safari and macOS Preview
- Slower performance (4-10x slower for high-DPI content)
- Larger file sizes
**When `false` (faster but needs testing):**
- Embeds images and gradients directly as native PDF objects
- 6-15x faster export performance for high-DPI content
- Smaller file sizes (typically 30-40% reduction)
- May have rendering issues in Safari/macOS Preview with gradients that use transparency
```typescript
const scene = engine.scene.get()!;
// For maximum performance (test with your print workflow first)
engine.block.setFloat(scene, 'scene/dpi', 150); // Reduce from default 300
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: false, // Much faster
});
```
**Before using `exportPdfWithHighCompatibility: false` in production:**
- Test generated PDFs with your actual print vendor/equipment
- Verify rendering in Safari and macOS Preview if end-users will view PDFs in those applications
- Check that gradients with transparency render correctly
- Confirm your content renders properly in Adobe Acrobat and Chrome (these typically work fine)
**Safe to use `false` when:**
- PDFs go directly to professional printing (not viewed in Safari/Preview)
- Content is primarily photos and solid colors (minimal gradients with transparency)
- Performance is critical for batch processing workflows
**Keep `true` when:**
- Users view PDFs in Safari or macOS Preview
- Maximum compatibility is required
- Content has complex gradients with transparency
- You cannot test with your print workflow before production
### Add an Underlayer for Specialty Printing
Add an underlayer for printing on non-white or transparent media like fabric or glass.
```typescript highlight=highlight-spot-color
// Define the underlayer spot color before export
// RGB values (0.8, 0.8, 0.8) provide a preview representation
engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8);
```
First define the spot color that will be used for the underlayer. The RGB values provide a preview representation in the editor.
```typescript highlight=highlight-underlayer
// Export with underlayer for special media printing
const pdfBlob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true,
exportPdfWithUnderlayer: true,
underlayerSpotColorName: 'RDG_WHITE',
// Negative offset shrinks underlayer to prevent visible edges
underlayerOffset: -2.0
});
```
The underlayer creates a solid background behind your design content. The negative offset shrinks the underlayer slightly to prevent visible edges around the printed output.
### Customizing PDF Output
You can configure all PDF settings together when exporting.
```ts
const scene = engine.scene.get()!;
// Adjust the DPI to 72
engine.block.setFloat(scene, 'scene/dpi', 72);
// Set spot color to be used as underlayer
engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8);
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
// Set target width and height in pixels
targetWidth: 800,
targetHeight: 600,
// Increase compatibility with different PDF viewers
exportPdfWithHighCompatibility: true,
// Add an underlayer beneath the image content
exportPdfWithUnderlayer: true,
underlayerSpotColorName: 'RDG_WHITE',
underlayerOffset: -2.0,
});
```
## Troubleshooting
**PDF file size too large** — Reduce the scene DPI or disable high compatibility mode. Use JPEG compression for embedded images where quality loss is acceptable.
**Gradients look different in some viewers** — Enable `exportPdfWithHighCompatibility` to rasterize gradients at the scene's DPI setting for consistent appearance across all PDF viewers.
**Underlayer not visible in print** — Verify the spot color name matches your print vendor's configuration exactly. Check that the PDF wasn't flattened during post-processing.
## API Reference
| Method | Description |
|--------|-------------|
| `engine.block.export(block, options)` | Export a block to a Blob with format options (`mimeType`, `exportPdfWithHighCompatibility`, `exportPdfWithUnderlayer`, `underlayerSpotColorName`, `underlayerOffset`, `targetWidth`, `targetHeight`) |
| `engine.block.setFloat(block, property, value)` | Set a float property on a block (use `scene/dpi` to control PDF resolution) |
| `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color for underlayer printing |
| `engine.scene.get()` | Get the current scene block ID |
| `engine.scene.getCurrentPage()` | Get the currently active page block |
| `cesdk.utils.downloadFile(blob, mimeType)` | Download a Blob as a file |
## Next Steps
- [Export Options](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/overview-9ed3a8/) — Explore all available export formats and configuration
- [Size Limits](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints
- [Export for Printing](https://img.ly/docs/cesdk/sveltekit/export-save-publish/for-printing-bca896/) — Learn more about print-specific export settings
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To PNG"
description: "Export designs and images to PNG format with compression settings and target dimensions using CE.SDK."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/conversion/to-png-f1660c/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/sveltekit/conversion-c3fbb3/) > [To PNG](https://img.ly/docs/cesdk/sveltekit/conversion/to-png-f1660c/)
---
Export designs to PNG format with lossless quality and optional transparency support.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-conversion-to-png-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-conversion-to-png-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-conversion-to-png-browser/)
PNG is a lossless image format that preserves image quality and supports transparency. It's ideal for designs requiring pixel-perfect fidelity, logos, graphics with transparent backgrounds, and any content where quality cannot be compromised.
```typescript file=@cesdk_web_examples/guides-conversion-to-png-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.scene.getCurrentPage();
if (!page) throw new Error('No page found');
await engine.scene.zoomToBlock(page, { padding: 40 });
// Export programmatically using the engine API
const exportProgrammatically = async () => {
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
await cesdk.utils.downloadFile(blob, 'image/png');
};
// Export with compression level (0-9)
// Higher values produce smaller files but take longer
const exportWithCompression = async () => {
const blob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9
});
await cesdk.utils.downloadFile(blob, 'image/png');
};
// Export with target dimensions
// The block scales to fill the target while maintaining aspect ratio
const exportWithDimensions = async () => {
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1920,
targetHeight: 1080
});
await cesdk.utils.downloadFile(blob, 'image/png');
};
// Trigger the built-in export action
const triggerExportAction = async () => {
await cesdk.actions.run('exportDesign', {
mimeType: 'image/png'
});
};
// Override the default export action to customize behavior
cesdk.actions.register('exportDesign', async (options) => {
// Use the utils API to export with a loading dialog
const { blobs, options: exportOptions } =
await cesdk.utils.export(options);
// Custom logic: log the export details
console.log(
`Exported ${blobs.length} file(s) as ${exportOptions.mimeType}`
);
// Download the exported file
await cesdk.utils.downloadFile(blobs[0], exportOptions.mimeType);
});
// Add export dropdown to navigation bar
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, {
id: 'ly.img.actions.navigationBar',
children: [
{
id: 'ly.img.action.navigationBar',
key: 'export-png',
label: 'Export PNG',
icon: '@imgly/Save',
onClick: exportProgrammatically
},
{
id: 'ly.img.action.navigationBar',
key: 'export-png-action',
label: 'Export PNG (action)',
icon: '@imgly/Save',
onClick: triggerExportAction
},
{
id: 'ly.img.action.navigationBar',
key: 'export-png-compressed',
label: 'Export PNG (compressed)',
icon: '@imgly/Save',
onClick: exportWithCompression
},
{
id: 'ly.img.action.navigationBar',
key: 'export-png-hd',
label: 'Export PNG (HD)',
icon: '@imgly/Save',
onClick: exportWithDimensions
}
]
});
}
}
export default Example;
```
This guide covers how to export designs to PNG, configure export options, and integrate with the built-in export action.
## Export to PNG
Use `engine.block.export()` to export a design block to PNG. The method returns a Blob containing the image data.
```typescript highlight=highlight-export-programmatic
// Export programmatically using the engine API
const exportProgrammatically = async () => {
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
await cesdk.utils.downloadFile(blob, 'image/png');
};
```
## Compression Level
Control the file size versus export speed tradeoff using `pngCompressionLevel`. Valid values are 0-9, where higher values produce smaller files but take longer to export. Since PNG is lossless, image quality remains unchanged.
```typescript highlight=highlight-options-compression
// Export with compression level (0-9)
// Higher values produce smaller files but take longer
const exportWithCompression = async () => {
const blob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9
});
await cesdk.utils.downloadFile(blob, 'image/png');
};
```
The default compression level is 5, providing a good balance between file size and export speed.
## Target Dimensions
Resize the output by setting `targetWidth` and `targetHeight`. The block scales to fill the target dimensions while maintaining its aspect ratio.
```typescript highlight=highlight-options-dimensions
// Export with target dimensions
// The block scales to fill the target while maintaining aspect ratio
const exportWithDimensions = async () => {
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1920,
targetHeight: 1080
});
await cesdk.utils.downloadFile(blob, 'image/png');
};
```
## Trigger the Export Action
The built-in `exportDesign` action triggers the default export workflow with a loading dialog and automatically downloads the file.
```typescript highlight=highlight-export-action
// Trigger the built-in export action
const triggerExportAction = async () => {
await cesdk.actions.run('exportDesign', {
mimeType: 'image/png'
});
};
```
## Override the Export Action
Register a custom handler for the `exportDesign` action to customize behavior. This allows you to add custom logic such as uploading to a server or processing the exported file.
```typescript highlight=highlight-override-action
// Override the default export action to customize behavior
cesdk.actions.register('exportDesign', async (options) => {
// Use the utils API to export with a loading dialog
const { blobs, options: exportOptions } =
await cesdk.utils.export(options);
// Custom logic: log the export details
console.log(
`Exported ${blobs.length} file(s) as ${exportOptions.mimeType}`
);
// Download the exported file
await cesdk.utils.downloadFile(blobs[0], exportOptions.mimeType);
});
```
The `cesdk.utils.export()` method handles the export with a loading dialog, while `cesdk.utils.downloadFile()` triggers the browser download.
## API Reference
| API | Description |
| --- | --- |
| `engine.block.export(block, options)` | Exports a block to a Blob with the specified options |
| `cesdk.actions.run('exportDesign', options)` | Triggers the default export workflow |
| `cesdk.actions.register('exportDesign', handler)` | Overrides the default export action |
| `cesdk.utils.export(options)` | Exports with a loading dialog, returns `{ blobs, options }` |
| `cesdk.utils.downloadFile(blob, mimeType)` | Downloads a Blob as a file |
## Next Steps
- [Conversion Overview](https://img.ly/docs/cesdk/sveltekit/conversion/overview-44dc58/) - Learn about other export formats
- [Export Overview](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export/overview-9ed3a8/) - Understand the full export workflow
- [To PDF](https://img.ly/docs/cesdk/sveltekit/conversion/to-pdf-eb937f/) - Export designs to PDF format
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Audio"
description: "Create audio in videos"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-audio/audio-2f700b/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/sveltekit/create-audio/audio-2f700b/)
---
The **CreativeEditor (CE.SDK)** provides different **audio features** you can leverage in web-based apps. This section covers how to **add audio blocks**, **extract** audio from videos, control **playback**, generate **waveforms**, and manage **multi-track audio**.
## Use Cases
Use the CE.SDK audio features when you need to create:
- Background music
- Voice-overs
- Sound effects
- Podcasts
## How Audio Works in the CE.SDK
The CE.SDK represents audio as audio blocks.
Each block has:
- Source (file or extracted track)
- Playback properties (time, speed, volume, mute)
- Time-based properties (offset, duration, trim length)
- Optional waveform thumbnails for UI visualization
### What Are the Time-Based Properties
Each audio block has properties that determine when and how much of the sound plays:
- **Offset:** the delay before an audio block begins playing inside the scene.
- **Trim length**: cuts the audio to keep only a specific part of it.
- **Duration**: defines how long the audio plays.
### What Are Waveforms
Waveforms are **visual representations** of the audio signal over time. They show the amplitude (volume level) of the sound at each moment, using peaks and valleys.
The CE.SDK can generate sampled waveform data that you can render in your UI. This is especially helpful for editing tools.
### When to Create VS. Extract Audio
The CE.SDK allows you to either create a blank audio block or extract the audio from a video.
- **Create an empty audio block** when you want to add external or standalone audio that doesn’t come from a video.
- **Extract the audio** when it comes from a video block already in your scene.
## CE.SDK Audio Features Overview
The table below summarizes the main audio-related capabilities in CE.SDK.\
Each feature is related to an example further down the page.
| **Category** | **Action** | **API Name** | **Notes** |
|--------------------------|-----------------------------|---------------------------------------|-----------|
| **Create Audio Blocks** | Create empty audio block | `create` | Creates an audio block with no source. |
| | Extract audio from video | `createAudioFromVideo` | Requires a video block ID and track index. |
| **Playback Control** | Set playback position | `setPlaybackTime` | Time in seconds. |
| | Volume | `setVolume` | Range `0.0–1.0`. |
| | Mute | `setMuted` | Boolean. |
| | Playback speed | `setPlaybackSpeed` | Range `0.25–3.0`. |
| **Time Management** | Offset | `setTimeOffset` | To move the playback starting point in the scene. |
| | Duration | `setDuration` | Total length (seconds). |
| | Trim length | `setTrimLength` | Cuts content to a defined length. |
| **Replace Audio Source** | Reload edited scene | `scene.loadFromString` | Used when replacing audio at runtime. |
| **Waveforms** | Generate thumbnails | `generateAudioThumbnailSequence` | Produces waveform sample data for UI. |
| **Export Audio** | Export WAV | `exportAudio` | MIME type: `audio/wav`. |
| | Export MP4 | `exportAudio` | MIME type: `audio/mp4`. |
## Examples
Find in the following list of examples different API calls listed in the preceding table.
### Create Audio
To create an empty audio block, use:
```ts
const blockId = engine.block.create('audio');
```
Use `engine.block.createAudioFromVideo(blockId, trackIndex: number);`.
This example:
1. Extracts the first audio track (1) from a video.
2. Appends it to the page for further manipulation.
> **Note:** `videoBlockId` and `pageId` must refer to existing blocks.
```ts
const audioBlockId = engine.block.createAudioFromVideo(videoBlockId, 0);
// Attach to the page so it’s part of the scene
engine.block.appendChild(pageId, audioBlockId);
```
This example:
1. Creates an audio block.
2. Attaches the audio block to the page.
3. Sets the source for the audio from a remote URL.
> **Note:** `pageId` must refer to an existing page.
```ts
// Create an audio block
const audioBlockId = engine.block.create('audio');
await engine.block.appendChild(pageId, audioBlockId);
engine.block.setString(
audioBlockId,
'audio/fileURI',
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a'
);
```
For details on loading sources, check [the dedicated guide](https://img.ly/docs/cesdk/sveltekit/import-media/from-remote-source/unsplash-8f31f0/).
This example exports the audio block in **mp4** format:
> **Note:** `blockId` must refer to an existing audio block.
```ts
engine.block.exportAudio(blockId, {
mymeType: 'audio/mp4'
}
);
```
This example exports the audio in **wav** format:
```ts
const audioData = await engine.block.exportAudio(blockId, {
mimeType: 'audio/wav'
}
);
```
### Control Audio Playback
Use `engine.block.setPlaybackTime(blockId, time: number)`.
This example sets the current playback at 3 seconds:
```ts
engine.block.setPlaybackTime(blockId, 3)
```
Use `engine.block.setVolume(blockId, volume: number);`.
This example sets the volume at 1 (max value):
```ts
engine.block.setVolume(blockId, 1);
```
Use `engine.block.setMuted(blockId, muted: boolean);`.
This example mutes the audio of the block:
```ts
engine.block.setMuted(blockId, true);
```
Use `engine.block.setPlaybackSpeed(blockId, speed: number);`.
This example multiplies the speed by 0.25:
```ts
engine.block.setPlaybackSpeed(blockId, 0.25);
```
### Manage Audio Timing
If the audio has an offset of:
- 0 s → It plays immediately when the scene starts.
- 2 s → The CE.SDK waits 2 seconds before playing it.
- 10 s → The audio only starts at the 10-second mark.
Use `engine.block.setTimeOffset(blockId, offset: number)`.
This example starts the audio at 2 s in the composition:
```ts
engine.block.setTimeOffset(blockId, 2);
```
Use `engine.block.setDuration(blockId, duration: number)`.
This example sets the audio duration for 300 seconds:
```ts
engine.block.setDuration(blockId, 300)
```
Use `engine.block.setTrimLength(blockId, length: number);`.
This example creates a new trim that:
1. Starts at the second 2 of the audio content.
2. Plays for 10 seconds.
```ts
engine.block.setTrimOffset(blockId, 2)
engine.block.setTrimLength(blockId, 10);
```
Use:
```ts
engine.block.generateAudioThumbnailSequence(
blockId,
samplesPerChunk: number,
timeBegin: number,
timeEnd: number,
numberOfSamples: number,
numberOfChannels: number)
```
This example generates 1 audio sample that:
- Produces 3 chunks of this sample.
- Start at second 8.
- End at second 18.
- Is stereo audio (1 for mono, 2 for stereo)
> **Note:** `audioBlockId` must refer to an existing block.
```ts
const audioThumbnail = engine.block.generateAudioThumbnailSequence(
audioBlockId,
3, // samplesPerChunk
8, // timeBegin
18, // timeEnd
1, // numberOfSamples
2, // numberOfChannels
// Return the result
(chunkIndex, result) => {
if (result instanceof Error) {
console.error('Thumbnail chunk failed', result);
audioThumbnail();
return;
}
console.log(`Chunk ${chunkIndex}`, result);
}
);
```
Once generated, integrate the waveform into your UI.
## Next Steps
For each feature’s detailed instructions and options:
- Explore the [CE.SDK API options](https://img.ly/docs/cesdk/sveltekit/api-reference/overview-8f24e1/).
- Check the dedicated guides in the audio section.
---
## Related Pages
- [Add Sound Effects](https://img.ly/docs/cesdk/sveltekit/create-audio/audio/add-sound-effects-9e984e/) - Learn how to use buffers with arbitrary data to generate sound effects programmatically
- [Add Music](https://img.ly/docs/cesdk/sveltekit/create-audio/audio/add-music-5b182c/) - Add background music and audio tracks to video projects using CE.SDK's audio block system.
- [Adjust Audio Volume](https://img.ly/docs/cesdk/sveltekit/create-video/audio/adjust-volume-7ecc4a/) - Learn how to adjust audio volume in CE.SDK to control playback levels, mute audio, and balance multiple audio sources in video projects.
- [Adjust Audio Playback Speed](https://img.ly/docs/cesdk/sveltekit/create-video/audio/adjust-speed-908d57/) - Learn how to adjust audio playback speed in CE.SDK to create slow-motion, time-stretched, and fast-forward audio effects.
- [Loop Audio](https://img.ly/docs/cesdk/sveltekit/create-audio/audio/loop-937be7/) - Create seamless repeating audio playback for background music and sound effects using CE.SDK's audio looping system.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add Music"
description: "Add background music and audio tracks to video projects using CE.SDK's audio block system."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-audio/audio/add-music-5b182c/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/sveltekit/create-audio/audio-2f700b/) > [Add Music](https://img.ly/docs/cesdk/sveltekit/create-audio/audio/add-music-5b182c/)
---
Add background music and audio tracks to video projects using CE.SDK's audio
block system for rich multimedia experiences.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-add-music-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-add-music-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-add-music-browser/)
Audio blocks are standalone time-based blocks that play alongside video content, independent of video fills. You can add music from the built-in asset library or from custom URLs, position tracks in the composition, configure volume levels, and layer multiple audio tracks for complex soundscapes.
```typescript file=@cesdk_web_examples/guides-create-audio-add-music-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Add Music Guide
*
* Demonstrates adding background music to video projects:
* - Creating audio blocks programmatically
* - Setting audio source URIs
* - Configuring timeline position and duration
* - Adjusting audio volume
* - Querying audio assets from the library
* - Managing audio blocks
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: { width: 1920, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.scene.getCurrentPage();
if (!page) {
throw new Error('No page found in scene');
}
// Set page duration for timeline
engine.block.setDuration(page, 30);
// Enable audio and timeline features for the UI
cesdk.feature.enable('ly.img.video.timeline');
cesdk.feature.enable('ly.img.video.audio');
cesdk.feature.enable('ly.img.video.controls.playback');
// Create an audio block for background music
const audioBlock = engine.block.create('audio');
// Set the audio source file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a';
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Append audio to the page (makes it part of the timeline)
engine.block.appendChild(page, audioBlock);
// Wait for audio to load to get duration
await engine.block.forceLoadAVResource(audioBlock);
// Get the total duration of the audio file
const totalDuration = engine.block.getAVResourceTotalDuration(audioBlock);
console.log('Audio total duration:', totalDuration, 'seconds');
// Set when the audio starts on the timeline (0 = beginning)
engine.block.setTimeOffset(audioBlock, 0);
// Set how long the audio plays (use full duration or page duration)
const playbackDuration = Math.min(totalDuration, 30);
engine.block.setDuration(audioBlock, playbackDuration);
// Set the audio volume (0.0 = mute, 1.0 = full volume)
engine.block.setVolume(audioBlock, 0.8);
// Get current volume
const currentVolume = engine.block.getVolume(audioBlock);
console.log('Audio volume:', currentVolume);
// Query available audio tracks from the asset library
const audioAssets = await engine.asset.findAssets('ly.img.audio', {
page: 0,
perPage: 10
});
console.log('Available audio assets:', audioAssets.assets.length);
// Log metadata for each audio asset
audioAssets.assets.forEach((asset) => {
console.log('Audio asset:', {
id: asset.id,
label: asset.label,
duration: asset.meta?.duration,
uri: asset.meta?.uri
});
});
// Find all audio blocks in the scene
const allAudioBlocks = engine.block.findByType('audio');
console.log('Total audio blocks:', allAudioBlocks.length);
// Get information about each audio block
allAudioBlocks.forEach((block, index) => {
const uri = engine.block.getString(block, 'audio/fileURI');
const timeOffset = engine.block.getTimeOffset(block);
const duration = engine.block.getDuration(block);
const volume = engine.block.getVolume(block);
console.log(`Audio block ${index + 1}:`, {
uri: uri.split('/').pop(), // Just filename
timeOffset: `${timeOffset}s`,
duration: `${duration}s`,
volume: `${(volume * 100).toFixed(0)}%`
});
});
// Example: Remove the second audio block if it exists
if (allAudioBlocks.length > 1) {
const blockToRemove = allAudioBlocks[1];
// Destroy the block to remove it and free resources
engine.block.destroy(blockToRemove);
console.log('Removed second audio block');
}
console.log(
'Add Music guide initialized. Open the timeline to see audio tracks.'
);
}
}
export default Example;
```
This guide covers how to add music using the built-in audio UI and how to create and configure audio blocks programmatically using the Block API.
## Using the Built-in Audio UI
### Enable Audio Features
Audio blocks require timeline support. Enable the audio and timeline features to give users access to audio capabilities through the UI.
```typescript highlight-enable-audio-features
// Enable audio and timeline features for the UI
cesdk.feature.enable('ly.img.video.timeline');
cesdk.feature.enable('ly.img.video.audio');
cesdk.feature.enable('ly.img.video.controls.playback');
```
These features control:
- `ly.img.video.timeline` - Shows the timeline for positioning audio tracks
- `ly.img.video.audio` - Enables the audio library in the dock
- `ly.img.video.controls.playback` - Adds playback controls for previewing audio
### User Workflow
With audio features enabled, users can add music through the interface:
1. **Open the dock** - Access the asset library panel
2. **Select audio category** - Browse available music tracks
3. **Preview tracks** - Listen to audio before adding
4. **Drag to timeline** - Add audio to the project
5. **Position on timeline** - Adjust when audio starts and ends
6. **Adjust volume** - Use the inspector to set volume levels
## Programmatic Audio Creation
### Create Audio Block
We create audio blocks using `engine.block.create('audio')` and set the source file using the `audio/fileURI` property. The audio block must be appended to a page to become part of the composition.
```typescript highlight-create-audio-block
// Create an audio block for background music
const audioBlock = engine.block.create('audio');
// Set the audio source file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a';
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Append audio to the page (makes it part of the timeline)
engine.block.appendChild(page, audioBlock);
```
Audio blocks support common formats including M4A, MP3, and WAV. The source URI can point to any accessible URL or local file.
### Configure Time Position
Audio blocks have time-based properties that control when and how long they play. We use `setTimeOffset()` to set the start time and `setDuration()` to control playback length.
```typescript highlight-configure-timeline
// Wait for audio to load to get duration
await engine.block.forceLoadAVResource(audioBlock);
// Get the total duration of the audio file
const totalDuration = engine.block.getAVResourceTotalDuration(audioBlock);
console.log('Audio total duration:', totalDuration, 'seconds');
// Set when the audio starts on the timeline (0 = beginning)
engine.block.setTimeOffset(audioBlock, 0);
// Set how long the audio plays (use full duration or page duration)
const playbackDuration = Math.min(totalDuration, 30);
engine.block.setDuration(audioBlock, playbackDuration);
```
The `forceLoadAVResource()` method ensures the audio file is loaded before we access its duration. This is important when you need to know the total length of the audio file for timing calculations.
### Configure Volume
Volume is set using `setVolume()` with values from 0.0 (mute) to 1.0 (full volume). This volume level is applied during export and affects the final rendered output.
```typescript highlight-configure-volume
// Set the audio volume (0.0 = mute, 1.0 = full volume)
engine.block.setVolume(audioBlock, 0.8);
// Get current volume
const currentVolume = engine.block.getVolume(audioBlock);
console.log('Audio volume:', currentVolume);
```
## Working with Audio Assets
### Query Audio Library
CE.SDK provides a demo audio library that you can query using the Asset API. This allows you to build custom audio selection interfaces or programmatically add tracks based on metadata.
```typescript highlight-query-audio-assets
// Query available audio tracks from the asset library
const audioAssets = await engine.asset.findAssets('ly.img.audio', {
page: 0,
perPage: 10
});
console.log('Available audio assets:', audioAssets.assets.length);
// Log metadata for each audio asset
audioAssets.assets.forEach((asset) => {
console.log('Audio asset:', {
id: asset.id,
label: asset.label,
duration: asset.meta?.duration,
uri: asset.meta?.uri
});
});
```
Each asset includes metadata such as duration, file URI, and thumbnail URL, which you can use to display track information or make programmatic selections.
## Managing Audio Blocks
### List Audio Blocks
Use `findByType('audio')` to retrieve all audio blocks in the scene. This is useful for building audio management interfaces or batch operations.
```typescript highlight-list-audio-blocks
// Find all audio blocks in the scene
const allAudioBlocks = engine.block.findByType('audio');
console.log('Total audio blocks:', allAudioBlocks.length);
// Get information about each audio block
allAudioBlocks.forEach((block, index) => {
const uri = engine.block.getString(block, 'audio/fileURI');
const timeOffset = engine.block.getTimeOffset(block);
const duration = engine.block.getDuration(block);
const volume = engine.block.getVolume(block);
console.log(`Audio block ${index + 1}:`, {
uri: uri.split('/').pop(), // Just filename
timeOffset: `${timeOffset}s`,
duration: `${duration}s`,
volume: `${(volume * 100).toFixed(0)}%`
});
});
```
### Remove Audio
To remove an audio block, call `destroy()` which removes it from the scene and frees its resources.
```typescript highlight-remove-audio
// Example: Remove the second audio block if it exists
if (allAudioBlocks.length > 1) {
const blockToRemove = allAudioBlocks[1];
// Destroy the block to remove it and free resources
engine.block.destroy(blockToRemove);
console.log('Removed second audio block');
}
```
Always destroy blocks that are no longer needed to prevent memory leaks, especially when working with multiple audio files.
## API Reference
| Method | Description |
| -------------------------------------------------- | ----------------------------------- |
| `feature.enable('ly.img.video.timeline')` | Show timeline for audio positioning |
| `feature.enable('ly.img.video.audio')` | Enable audio library in dock |
| `feature.enable('ly.img.video.controls.playback')` | Add playback controls |
| `block.create('audio')` | Create a new audio block |
| `block.setString(id, 'audio/fileURI', uri)` | Set the audio source file |
| `block.setTimeOffset(id, seconds)` | Set when audio starts playing |
| `block.setDuration(id, seconds)` | Set audio playback duration |
| `block.setVolume(id, volume)` | Set volume (0.0 to 1.0) |
| `block.getVolume(id)` | Get current volume level |
| `block.getAVResourceTotalDuration(id)` | Get total audio file duration |
| `block.forceLoadAVResource(id)` | Force load audio resource |
| `block.findByType('audio')` | Find all audio blocks in scene |
| `asset.findAssets(sourceId, query)` | Query audio assets |
## Audio Type
A block for playing audio content.
This section describes the properties available for the **Audio Type** (`//ly.img.ubq/audio`) block type.
| Property | Type | Default | Description |
| ------------------------------ | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `audio/fileURI` | `String` | `""` | A URI referencing an audio file. |
| `audio/totalDuration` | `Double` | `"-"` | The total duration of the audio file., *(read-only)* |
| `contentFill/mode` | `Enum` | `"Cover"` | Defines how content should be resized to fit its container (e.g., Crop, Cover, Contain)., Possible values: `"Crop"`, `"Cover"`, `"Contain"` |
| `playback/duration` | `Double` | `null` | The duration in seconds for which this block should be visible. |
| `playback/looping` | `Bool` | `false` | Whether the medium should start from the beginning again or should stop. |
| `playback/muted` | `Bool` | `false` | Whether the audio is muted. |
| `playback/playing` | `Bool` | `false` | A tag that can be set on elements for their playback time to be progressed. |
| `playback/soloPlaybackEnabled` | `Bool` | `false` | A tag for blocks where playback should progress while the scene is paused. |
| `playback/speed` | `Float` | `1` | The playback speed multiplier. |
| `playback/time` | `Double` | `0` | The current playback time of the block contents in seconds. |
| `playback/timeOffset` | `Double` | `0` | The time in seconds relative to its parent at which this block should first appear. |
| `playback/trimLength` | `Double` | `"-"` | The relative duration of the clip for playback. |
| `playback/trimOffset` | `Double` | `"-"` | The time within the clip at which playback should begin, in seconds. |
| `playback/volume` | `Float` | `1` | Audio volume with a range of \[0, 1]. |
| `selected` | `Bool` | `false` | Indicates if the block is currently selected. |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add Sound Effects"
description: "Learn how to use buffers with arbitrary data to generate sound effects programmatically"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-audio/audio/add-sound-effects-9e984e/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/sveltekit/create-audio/audio-2f700b/) > [Add Sound Effects](https://img.ly/docs/cesdk/sveltekit/create-audio/audio/add-sound-effects-9e984e/)
---
Generate sound effects programmatically using buffers with arbitrary audio data. Create notification chimes, alert tones, and melodies without external files.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-add-sound-effects-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-add-sound-effects-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-add-sound-effects-browser/)
CE.SDK lets you create audio from code using buffers. This approach generates sound effects dynamically without external files—useful for notification tones, procedural audio, or any scenario where you need to synthesize audio at runtime.
```typescript file=@cesdk_web_examples/guides-create-audio-add-sound-effects-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
/**
* Creates a WAV file buffer from audio parameters and a sample generator function.
*
* @param sampleRate - Sample rate in Hz (e.g., 48000)
* @param durationSeconds - Duration of the audio in seconds
* @param generator - Function that generates sample values (-1.0 to 1.0) for each time point
* @returns Uint8Array containing a stereo WAV file
*/
function createWavBuffer(
sampleRate: number,
durationSeconds: number,
/* eslint-disable-next-line no-unused-vars -- Parameter name documents callback signature */
generator: (time: number) => number
): Uint8Array {
const bitsPerSample = 16;
const channels = 2; // Stereo output
const numSamples = Math.floor(durationSeconds * sampleRate);
const dataSize = numSamples * channels * (bitsPerSample / 8);
// Create WAV file buffer (44-byte header + audio data)
const wavBuffer = new ArrayBuffer(44 + dataSize);
const view = new DataView(wavBuffer);
// RIFF chunk descriptor
view.setUint32(0, 0x52494646, false); // "RIFF"
view.setUint32(4, 36 + dataSize, true); // File size - 8
view.setUint32(8, 0x57415645, false); // "WAVE"
// fmt sub-chunk
view.setUint32(12, 0x666d7420, false); // "fmt "
view.setUint32(16, 16, true); // Sub-chunk size (16 for PCM)
view.setUint16(20, 1, true); // Audio format (1 = PCM)
view.setUint16(22, channels, true); // Number of channels
view.setUint32(24, sampleRate, true); // Sample rate
view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true);
view.setUint16(32, channels * (bitsPerSample / 8), true); // Block align
view.setUint16(34, bitsPerSample, true); // Bits per sample
// data sub-chunk
view.setUint32(36, 0x64617461, false); // "data"
view.setUint32(40, dataSize, true); // Data size
// Generate audio samples
let offset = 44;
for (let i = 0; i < numSamples; i++) {
const time = i / sampleRate;
// Generate mono sample and duplicate to both channels
const value = generator(time);
const sample = Math.max(-32768, Math.min(32767, Math.round(value * 32767)));
view.setInt16(offset, sample, true); // Left channel
view.setInt16(offset + 2, sample, true); // Right channel
offset += 4;
}
return new Uint8Array(wavBuffer);
}
/**
* Calculates an ADSR (Attack-Decay-Sustain-Release) envelope value for a note.
* The envelope shapes the volume over time, creating natural-sounding tones.
*
* @param time - Current time in seconds
* @param noteStart - When the note starts (seconds)
* @param noteDuration - Total note duration including release (seconds)
* @param attack - Time to reach peak volume (seconds)
* @param decay - Time to fall from peak to sustain level (seconds)
* @param sustain - Held volume level (0.0 to 1.0)
* @param release - Time to fade to silence (seconds)
* @returns Envelope amplitude (0.0 to 1.0)
*/
function adsr(
time: number,
noteStart: number,
noteDuration: number,
attack: number,
decay: number,
sustain: number,
release: number
): number {
const t = time - noteStart;
if (t < 0) return 0;
const noteEnd = noteDuration - release;
if (t < attack) {
// Attack phase: ramp up from 0 to 1
return t / attack;
} else if (t < attack + decay) {
// Decay phase: ramp down from 1 to sustain level
return 1 - ((t - attack) / decay) * (1 - sustain);
} else if (t < noteEnd) {
// Sustain phase: hold at sustain level
return sustain;
} else if (t < noteDuration) {
// Release phase: ramp down from sustain to 0
return sustain * (1 - (t - noteEnd) / release);
}
return 0;
}
// Musical note frequencies (Hz) for the 4th and 5th octaves
const NOTE_FREQUENCIES = {
C4: 261.63,
D4: 293.66,
E4: 329.63,
F4: 349.23,
G4: 392.0,
A4: 440.0,
B4: 493.88,
C5: 523.25,
D5: 587.33,
E5: 659.25,
F5: 698.46,
G5: 783.99,
A5: 880.0,
B5: 987.77,
C6: 1046.5
};
// Sound effect 1: Ascending "success" fanfare (2 seconds)
// Creates a triumphant feeling with overlapping notes building to a chord
const SUCCESS_CHIME = {
notes: [
// Quick ascending arpeggio
{ freq: NOTE_FREQUENCIES.C4, start: 0.0, duration: 0.3 },
{ freq: NOTE_FREQUENCIES.E4, start: 0.1, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.G4, start: 0.2, duration: 0.5 },
// Sustained major chord
{ freq: NOTE_FREQUENCIES.C5, start: 0.35, duration: 1.65 },
{ freq: NOTE_FREQUENCIES.E5, start: 0.4, duration: 1.6 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.45, duration: 1.55 }
],
totalDuration: 2.0
};
// Sound effect 2: Gentle notification melody (2 seconds)
// A musical phrase that resolves pleasantly
const NOTIFICATION_MELODY = {
notes: [
{ freq: NOTE_FREQUENCIES.E5, start: 0.0, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.25, duration: 0.5 },
{ freq: NOTE_FREQUENCIES.A5, start: 0.6, duration: 0.3 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.85, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.E5, start: 1.15, duration: 0.85 }
],
totalDuration: 2.0
};
// Sound effect 3: Alert/warning tone (2 seconds)
// Descending pattern that grabs attention
const ALERT_TONE = {
notes: [
// Attention-grabbing high notes
{ freq: NOTE_FREQUENCIES.A5, start: 0.0, duration: 0.25 },
{ freq: NOTE_FREQUENCIES.A5, start: 0.3, duration: 0.25 },
// Descending resolution
{ freq: NOTE_FREQUENCIES.F5, start: 0.6, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.D5, start: 0.9, duration: 0.5 },
{ freq: NOTE_FREQUENCIES.A4, start: 1.3, duration: 0.7 }
],
totalDuration: 2.0
};
/**
* CE.SDK Plugin: Add Sound Effects Guide
*
* This example demonstrates:
* - Creating audio buffers with arbitrary data
* - Generating sound effects programmatically (chimes, notifications)
* - Using WAV format for in-memory audio
* - Positioning sound effects on the timeline
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
// Create a video scene (audio blocks require timeline support)
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.instagram.story'
}
});
const engine = cesdk.engine;
// Get the page (timeline)
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Calculate total duration: 3 effects × 2s + 2 gaps × 0.5s = 7s
const effectDuration = 2.0;
const gapDuration = 0.5;
const totalDuration = 3 * effectDuration + 2 * gapDuration; // 7 seconds
// Set page duration to match total effects length
engine.block.setDuration(page, totalDuration);
// Add a centered title text to the canvas
const text = engine.block.create('text');
engine.block.appendChild(page, text);
engine.block.replaceText(text, 'Sound Effects Demo');
engine.block.setTextColor(text, { r: 1, g: 1, b: 1, a: 1 });
engine.block.setFloat(text, 'text/fontSize', 48);
// Center the text on the canvas
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
engine.block.setWidth(text, pageWidth);
engine.block.setHeight(text, 70);
engine.block.setPositionX(text, 0);
engine.block.setPositionY(text, pageHeight / 2 - 35);
engine.block.setEnum(text, 'text/horizontalAlignment', 'Center');
// Make text visible for the entire duration
engine.block.setTimeOffset(text, 0);
engine.block.setDuration(text, totalDuration);
const sampleRate = 48000;
// Create the "success chime" sound effect
const chimeBuffer = engine.editor.createBuffer();
// Generate the chime using our helper function
const chimeWav = createWavBuffer(
sampleRate,
SUCCESS_CHIME.totalDuration,
(time) => {
let sample = 0;
// Mix all notes together
for (const note of SUCCESS_CHIME.notes) {
// Calculate envelope for this note
const envelope = adsr(
time,
note.start,
note.duration,
0.02, // Soft attack (20ms)
0.08, // Gentle decay (80ms)
0.7, // Sustain at 70%
0.25 // Smooth release (250ms)
);
if (envelope > 0) {
// Generate sine wave with slight harmonics for richness
const fundamental = Math.sin(2 * Math.PI * note.freq * time);
const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.25;
const harmonic3 = Math.sin(6 * Math.PI * note.freq * time) * 0.1;
sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.3;
}
}
return sample;
}
);
// Write WAV data to the buffer
engine.editor.setBufferData(chimeBuffer, 0, chimeWav);
// Create audio block for the chime (starts at 0s)
const chimeBlock = engine.block.create('audio');
engine.block.appendChild(page, chimeBlock);
engine.block.setString(chimeBlock, 'audio/fileURI', chimeBuffer);
engine.block.setTimeOffset(chimeBlock, 0);
engine.block.setDuration(chimeBlock, SUCCESS_CHIME.totalDuration);
engine.block.setVolume(chimeBlock, 0.8);
// Create the "notification melody" sound effect
const melodyBuffer = engine.editor.createBuffer();
const melodyWav = createWavBuffer(
sampleRate,
NOTIFICATION_MELODY.totalDuration,
(time) => {
let sample = 0;
for (const note of NOTIFICATION_MELODY.notes) {
const envelope = adsr(
time,
note.start,
note.duration,
0.01, // Soft attack (10ms)
0.06, // Gentle decay (60ms)
0.6, // Sustain at 60%
0.2 // Smooth release (200ms)
);
if (envelope > 0) {
// Pure sine wave with light 2nd harmonic for gentle tone
const fundamental = Math.sin(2 * Math.PI * note.freq * time);
const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.15;
sample += (fundamental + harmonic2) * envelope * 0.4;
}
}
return sample;
}
);
engine.editor.setBufferData(melodyBuffer, 0, melodyWav);
// Starts at 2.5s (after 2s effect + 0.5s gap)
const melodyBlock = engine.block.create('audio');
engine.block.appendChild(page, melodyBlock);
engine.block.setString(melodyBlock, 'audio/fileURI', melodyBuffer);
engine.block.setTimeOffset(melodyBlock, effectDuration + gapDuration); // 2.5s
engine.block.setDuration(melodyBlock, NOTIFICATION_MELODY.totalDuration);
engine.block.setVolume(melodyBlock, 0.8);
// Create the "alert" sound effect
const alertBuffer = engine.editor.createBuffer();
const alertWav = createWavBuffer(
sampleRate,
ALERT_TONE.totalDuration,
(time) => {
let sample = 0;
for (const note of ALERT_TONE.notes) {
const envelope = adsr(
time,
note.start,
note.duration,
0.005, // Sharp attack (5ms)
0.05, // Quick decay (50ms)
0.5, // Sustain at 50%
0.15 // Medium release (150ms)
);
if (envelope > 0) {
// Slightly brighter tone for alert
const fundamental = Math.sin(2 * Math.PI * note.freq * time);
const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.2;
const harmonic3 = Math.sin(6 * Math.PI * note.freq * time) * 0.15;
sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.35;
}
}
return sample;
}
);
engine.editor.setBufferData(alertBuffer, 0, alertWav);
// Starts at 5s (after 2 effects + 2 gaps)
const alertBlock = engine.block.create('audio');
engine.block.appendChild(page, alertBlock);
engine.block.setString(alertBlock, 'audio/fileURI', alertBuffer);
engine.block.setTimeOffset(alertBlock, 2 * (effectDuration + gapDuration)); // 5s
engine.block.setDuration(alertBlock, ALERT_TONE.totalDuration);
engine.block.setVolume(alertBlock, 0.75);
// Select the chime block to show it in the UI
engine.block.select(chimeBlock);
// Zoom to fit the timeline
await cesdk.actions.run('zoom.toPage', { autoFit: true });
// eslint-disable-next-line no-console
console.log(
`Sound effects: Success (0s), Melody (2.5s), Alert (5s) - each 2s, total ${totalDuration}s`
);
}
}
export default Example;
```
This guide covers working with buffers to create audio data and position it in the composition.
## Working with Buffers
CE.SDK provides a buffer API for creating and managing arbitrary binary data in memory. Use buffers when you need to generate content programmatically rather than loading from files.
### Creating a Buffer
Create a buffer with `createBuffer()`, which returns a URI you can use to reference the buffer:
```typescript highlight-buffer-create
// Create the "notification melody" sound effect
const melodyBuffer = engine.editor.createBuffer();
```
### Writing Data
Write data to a buffer using `setBufferData()`. The offset parameter specifies where to start writing:
```typescript highlight-buffer-write
engine.editor.setBufferData(melodyBuffer, 0, melodyWav);
```
### Reading Data
Read data back from a buffer:
```typescript
const length = engine.editor.getBufferLength(buffer);
const data = engine.editor.getBufferData(buffer, 0, length);
```
### Adding an Audio Track
Create an audio block and assign the buffer URI to its `audio/fileURI` property. Append it to the page to add it to the composition:
```typescript highlight-audio-track
// Starts at 2.5s (after 2s effect + 0.5s gap)
const melodyBlock = engine.block.create('audio');
engine.block.appendChild(page, melodyBlock);
engine.block.setString(melodyBlock, 'audio/fileURI', melodyBuffer);
```
### Cleanup
Destroy buffers when no longer needed (buffers are also cleaned up automatically with the scene):
```typescript
engine.editor.destroyBuffer(buffer);
```
## Generating Audio Data
To use buffers for audio, you need valid audio data. The WAV format is straightforward to generate: a 44-byte header followed by raw PCM samples.
```typescript highlight-wav-body
const bitsPerSample = 16;
const channels = 2; // Stereo output
const numSamples = Math.floor(durationSeconds * sampleRate);
const dataSize = numSamples * channels * (bitsPerSample / 8);
// Create WAV file buffer (44-byte header + audio data)
const wavBuffer = new ArrayBuffer(44 + dataSize);
const view = new DataView(wavBuffer);
// RIFF chunk descriptor
view.setUint32(0, 0x52494646, false); // "RIFF"
view.setUint32(4, 36 + dataSize, true); // File size - 8
view.setUint32(8, 0x57415645, false); // "WAVE"
// fmt sub-chunk
view.setUint32(12, 0x666d7420, false); // "fmt "
view.setUint32(16, 16, true); // Sub-chunk size (16 for PCM)
view.setUint16(20, 1, true); // Audio format (1 = PCM)
view.setUint16(22, channels, true); // Number of channels
view.setUint32(24, sampleRate, true); // Sample rate
view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true);
view.setUint16(32, channels * (bitsPerSample / 8), true); // Block align
view.setUint16(34, bitsPerSample, true); // Bits per sample
// data sub-chunk
view.setUint32(36, 0x64617461, false); // "data"
view.setUint32(40, dataSize, true); // Data size
// Generate audio samples
let offset = 44;
for (let i = 0; i < numSamples; i++) {
const time = i / sampleRate;
// Generate mono sample and duplicate to both channels
const value = generator(time);
const sample = Math.max(-32768, Math.min(32767, Math.round(value * 32767)));
view.setInt16(offset, sample, true); // Left channel
view.setInt16(offset + 2, sample, true); // Right channel
offset += 4;
}
return new Uint8Array(wavBuffer);
```
This code builds a stereo WAV file by writing the RIFF header, format chunk, and data chunk, then iterating through time to generate samples from a generator function that returns values between -1.0 and 1.0.
## Creating a Sound Effect
Combine the buffer API with the WAV helper to create a complete sound effect. This example generates a notification melody by mixing multiple notes with harmonics:
```typescript highlight-generate-melody
const melodyWav = createWavBuffer(
sampleRate,
NOTIFICATION_MELODY.totalDuration,
(time) => {
let sample = 0;
for (const note of NOTIFICATION_MELODY.notes) {
const envelope = adsr(
time,
note.start,
note.duration,
0.01, // Soft attack (10ms)
0.06, // Gentle decay (60ms)
0.6, // Sustain at 60%
0.2 // Smooth release (200ms)
);
if (envelope > 0) {
// Pure sine wave with light 2nd harmonic for gentle tone
const fundamental = Math.sin(2 * Math.PI * note.freq * time);
const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.15;
sample += (fundamental + harmonic2) * envelope * 0.4;
}
}
return sample;
}
);
```
The generator function mixes overlapping notes, each with its own start time and duration. The `adsr()` function shapes each note's volume over time (attack, decay, sustain, release), preventing harsh clicks. Adding a second harmonic at 15% creates a warmer tone than a pure sine wave.
## Positioning in Time
Position audio blocks using time offset (when it starts) and duration (how long it plays):
```typescript highlight-timeline-position
engine.block.setTimeOffset(melodyBlock, effectDuration + gapDuration); // 2.5s
engine.block.setDuration(melodyBlock, NOTIFICATION_MELODY.totalDuration);
```
This example spaces three sound effects with 0.5-second gaps:
```
Timeline: |----|----|----|----|----|----|----|
0s 1s 2s 3s 4s 5s 6s 7s
Success: |====|
^ 0s (2s)
Melody: |====|
^ 2.5s (2s)
Alert: |====|
^ 5s (2s)
```
Each effect is 2 seconds with 0.5-second gaps between them, for a total duration of 7 seconds.
## Troubleshooting
### No Sound
- **Check scene setup** - Ensure the audio block is attached to a page in the scene
- **Verify duration** - Ensure the audio block's duration is greater than 0
- **Check buffer data** - The buffer must contain valid WAV data
### Audio Sounds Wrong
- **Clipping** - Reduce sample values if they exceed -1.0 to 1.0
- **Clicking** - Add attack/release envelope to avoid pops
- **Wrong pitch** - Verify frequency calculations and sample rate (48 kHz)
### Buffer Errors
- **Invalid WAV** - Ensure header size fields match actual data size
- **Format mismatch** - Use 16-bit PCM, stereo, 48 kHz for best compatibility
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Loop Audio"
description: "Create seamless repeating audio playback for background music and sound effects using CE.SDK's audio looping system."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-audio/audio/loop-937be7/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/sveltekit/create-audio/audio-2f700b/) > [Loop](https://img.ly/docs/cesdk/sveltekit/create-audio/audio/loop-937be7/)
---
Create seamless repeating audio playback for background music, sound effects,
and rhythmic elements using CE.SDK's audio looping system.

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-loop-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-loop-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-audio-loop-browser/)
Audio looping allows media to play continuously by restarting from the beginning when it reaches the end. When you set a block's duration longer than the audio length and enable looping, CE.SDK automatically repeats the audio to fill the entire duration. This makes looping perfect for background music, ambient soundscapes, and repeating sound effects.
```typescript file=@cesdk_web_examples/guides-create-audio-audio-loop-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Audio Loop Guide
*
* Demonstrates audio looping capabilities in CE.SDK:
* - Creating audio blocks with looping enabled
* - Controlling looping behavior with duration
* - Querying looping state
* - Disabling looping for one-time playback
* - Understanding loop and duration interaction
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: { width: 1280, height: 720, unit: 'Pixel' }
});
const engine = cesdk.engine;
const scene = engine.scene.get()!;
const pages = engine.block.findByType('page');
const page = pages.length > 0 ? pages[0] : scene;
engine.block.setDuration(page, 30); // 30 second timeline
// Use sample audio from demo assets
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a';
// Create a basic audio block
const audioBlock = engine.block.create('audio')!;
engine.block.appendChild(page, audioBlock);
// Set the audio source URI
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Load the audio resource to access metadata like duration
await engine.block.forceLoadAVResource(audioBlock);
// Get the total audio duration
const audioDuration = engine.block.getDouble(
audioBlock,
'audio/totalDuration'
);
// eslint-disable-next-line no-console
console.log('Audio duration:', audioDuration, 'seconds');
// Enable looping for this audio block
engine.block.setLooping(audioBlock, true);
// Set the block duration longer than the audio length
// The audio will loop to fill the entire duration
engine.block.setDuration(audioBlock, 15);
// eslint-disable-next-line no-console
console.log('Looping enabled - audio will repeat to fill 15 seconds');
// Check if an audio block is set to loop
const isLooping = engine.block.isLooping(audioBlock);
// eslint-disable-next-line no-console
console.log('Is looping:', isLooping); // true
// Create a second audio block to demonstrate non-looping behavior
const nonLoopingAudio = engine.block.create('audio')!;
engine.block.appendChild(page, nonLoopingAudio);
engine.block.setString(nonLoopingAudio, 'audio/fileURI', audioUri);
await engine.block.forceLoadAVResource(nonLoopingAudio);
// Set time offset so it doesn't overlap with first audio
engine.block.setTimeOffset(nonLoopingAudio, 16);
// Disable looping (or leave it at default false)
engine.block.setLooping(nonLoopingAudio, false);
// Set duration longer than audio length
// Audio will play once and stop (no looping)
engine.block.setDuration(nonLoopingAudio, 12);
// eslint-disable-next-line no-console
console.log('Looping disabled - audio plays once and stops');
// Create a third audio block demonstrating looping with trim
const trimmedLoopAudio = engine.block.create('audio')!;
engine.block.appendChild(page, trimmedLoopAudio);
engine.block.setString(trimmedLoopAudio, 'audio/fileURI', audioUri);
await engine.block.forceLoadAVResource(trimmedLoopAudio);
// Trim to a 2-second segment
engine.block.setTrimOffset(trimmedLoopAudio, 1.0);
engine.block.setTrimLength(trimmedLoopAudio, 2.0);
// Enable looping and set duration longer than trim length
engine.block.setLooping(trimmedLoopAudio, true);
engine.block.setDuration(trimmedLoopAudio, 8.0);
// Position in timeline
engine.block.setTimeOffset(trimmedLoopAudio, 29);
// eslint-disable-next-line no-console
console.log('Trimmed loop - 2s segment will loop 4 times to fill 8s');
// Select the first audio block to show it in timeline
engine.block.setSelected(audioBlock, true);
// eslint-disable-next-line no-console
console.log('Audio looping guide initialized successfully');
}
}
export default Example;
```
This guide covers how to enable and disable audio looping, control looping behavior with duration settings, and loop trimmed audio segments.
## Understanding Audio Looping
When looping is enabled on an audio block, CE.SDK repeats the audio content from the beginning each time it reaches the end. This continues until the block's duration is filled. For example, a 5-second audio clip with looping enabled and a 15-second duration will play three complete times.
The loop transitions are seamless—CE.SDK jumps immediately from the end back to the beginning without gaps or clicks. The audio content itself determines how smooth the loop sounds. Audio files designed for looping (with matching start and end points) create perfectly seamless loops, while non-looping audio may have audible transitions.
## Creating Audio Blocks
### Adding Audio Content
Audio blocks use file URIs to reference audio sources. We create the block, add it to the page, and set the audio source.
```typescript highlight-create-audio-block
// Create a basic audio block
const audioBlock = engine.block.create('audio')!;
engine.block.appendChild(page, audioBlock);
// Set the audio source URI
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
```
The `audio/fileURI` property points to the audio file. CE.SDK supports common audio formats including MP3, M4A, WAV, and AAC.
## Enabling Audio Looping
### Loading Audio Resources
Before working with audio properties like duration or trim, we load the audio resource to ensure metadata is available.
```typescript highlight-load-audio-resource
// Load the audio resource to access metadata like duration
await engine.block.forceLoadAVResource(audioBlock);
// Get the total audio duration
const audioDuration = engine.block.getDouble(
audioBlock,
'audio/totalDuration'
);
// eslint-disable-next-line no-console
console.log('Audio duration:', audioDuration, 'seconds');
```
Loading the resource provides access to the total audio duration, which helps calculate how many times the audio will loop given a specific block duration.
### Setting Looping State
We enable looping by calling `setLooping()` with `true`. When combined with a block duration longer than the audio length, the audio repeats to fill the full duration.
```typescript highlight-enable-looping
// Enable looping for this audio block
engine.block.setLooping(audioBlock, true);
// Set the block duration longer than the audio length
// The audio will loop to fill the entire duration
engine.block.setDuration(audioBlock, 15);
// eslint-disable-next-line no-console
console.log('Looping enabled - audio will repeat to fill 15 seconds');
```
In this example, if the audio is 5 seconds long and the block duration is 15 seconds, the audio loops three times (5 seconds × 3 = 15 seconds total).
## Querying and Controlling Looping
### Checking Looping State
We can check whether an audio block has looping enabled at any time.
```typescript highlight-query-looping-state
// Check if an audio block is set to loop
const isLooping = engine.block.isLooping(audioBlock);
// eslint-disable-next-line no-console
console.log('Is looping:', isLooping); // true
```
This is useful when managing complex compositions with multiple audio tracks, allowing us to query and update looping states dynamically.
### Disabling Looping
To play audio once without repeating, we set looping to `false`.
```typescript highlight-non-looping-audio
const nonLoopingAudio = engine.block.create('audio')!;
engine.block.appendChild(page, nonLoopingAudio);
engine.block.setString(nonLoopingAudio, 'audio/fileURI', audioUri);
await engine.block.forceLoadAVResource(nonLoopingAudio);
// Set time offset so it doesn't overlap with first audio
engine.block.setTimeOffset(nonLoopingAudio, 16);
// Disable looping (or leave it at default false)
engine.block.setLooping(nonLoopingAudio, false);
// Set duration longer than audio length
// Audio will play once and stop (no looping)
engine.block.setDuration(nonLoopingAudio, 12);
// eslint-disable-next-line no-console
console.log('Looping disabled - audio plays once and stops');
```
With looping disabled and a duration longer than the audio length, the audio plays once and then stops, leaving silence for the remaining duration.
## Looping with Trim Settings
### Trimming Looped Audio
We can combine trimming with looping to create short repeating segments from longer audio files.
```typescript highlight-looping-with-trim
// Create a third audio block demonstrating looping with trim
const trimmedLoopAudio = engine.block.create('audio')!;
engine.block.appendChild(page, trimmedLoopAudio);
engine.block.setString(trimmedLoopAudio, 'audio/fileURI', audioUri);
await engine.block.forceLoadAVResource(trimmedLoopAudio);
// Trim to a 2-second segment
engine.block.setTrimOffset(trimmedLoopAudio, 1.0);
engine.block.setTrimLength(trimmedLoopAudio, 2.0);
// Enable looping and set duration longer than trim length
engine.block.setLooping(trimmedLoopAudio, true);
engine.block.setDuration(trimmedLoopAudio, 8.0);
// Position in timeline
engine.block.setTimeOffset(trimmedLoopAudio, 29);
// eslint-disable-next-line no-console
console.log('Trimmed loop - 2s segment will loop 4 times to fill 8s');
```
This trims the audio to a 2-second segment (from 1.0s to 3.0s of the source), then loops that segment four times to fill an 8-second duration. This technique is powerful for creating rhythmic loops or extracting repeatable portions from longer audio files.
### Choosing Loop Points
For seamless loops, choose trim points where the audio content flows naturally from end to beginning. Audio with consistent rhythm, tone, and volume at trim boundaries creates the smoothest loops. Abrupt changes in content or volume at loop boundaries create noticeable transitions.
## API Reference
| Method | Description | Parameters | Returns |
| ------------------------------- | ---------------------------------- | ----------------------------------------- | --------------- |
| `create(type)` | Create an audio block | `type: 'audio'` | `DesignBlockId` |
| `setString(id, property, value)`| Set audio source URI | `id: DesignBlockId, property: string, value: string` | `void` |
| `setLooping(id, enabled)` | Enable or disable audio looping | `id: DesignBlockId, enabled: boolean` | `void` |
| `isLooping(id)` | Check if audio is set to loop | `id: DesignBlockId` | `boolean` |
| `setDuration(id, duration)` | Set block playback duration | `id: DesignBlockId, duration: number` | `void` |
| `getDuration(id)` | Get block duration | `id: DesignBlockId` | `number` |
| `setTrimOffset(id, offset)` | Set trim start point | `id: DesignBlockId, offset: number` | `void` |
| `setTrimLength(id, length)` | Set trim length | `id: DesignBlockId, length: number` | `void` |
| `forceLoadAVResource(id)` | Load audio resource with metadata | `id: DesignBlockId` | `Promise` |
| `getDouble(id, property)` | Get audio property value | `id: DesignBlockId, property: string` | `number` |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Compositions"
description: "Combine and arrange multiple elements to create complex, multi-page, or layered design compositions."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/sveltekit/create-composition/overview-5b19c5/) - Combine and arrange multiple elements to create complex, multi-page, or layered design compositions.
- [Multi-Page Layouts](https://img.ly/docs/cesdk/sveltekit/create-composition/multi-page-4d2b50/) - Create and manage multi-page designs in CE.SDK for documents like brochures, presentations, and catalogs with multiple pages in a single scene.
- [Create a Collage](https://img.ly/docs/cesdk/sveltekit/create-composition/collage-f7d28d/) - Combine images into a collage using the CE.SDK.
- [Design a Layout](https://img.ly/docs/cesdk/sveltekit/create-composition/layout-b66311/) - Create structured compositions using scene layouts, positioning systems, and hierarchical block organization for collages, magazines, and multi-page documents.
- [Add a Background](https://img.ly/docs/cesdk/sveltekit/create-composition/add-background-375a47/) - Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks.
- [Positioning and Alignment](https://img.ly/docs/cesdk/sveltekit/insert-media/position-and-align-cc6b6a/) - Precisely position, align, and distribute objects using guides, snapping, and alignment tools.
- [Group and Ungroup Objects](https://img.ly/docs/cesdk/sveltekit/create-composition/group-and-ungroup-62565a/) - Group multiple design elements together so they move, scale, and transform as a single unit; ungroup to edit them individually.
- [Layer Management](https://img.ly/docs/cesdk/sveltekit/create-composition/layer-management-18f07a/) - Organize design elements using a layer stack for precise control over stacking and visibility.
- [Lock Design](https://img.ly/docs/cesdk/sveltekit/create-composition/lock-design-0a81de/) - Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. Control which properties users can edit at both global and block levels.
- [Blend Modes](https://img.ly/docs/cesdk/sveltekit/create-composition/blend-modes-ad3519/) - Apply blend modes to elements to control how colors and layers interact visually.
- [Programmatic Creation](https://img.ly/docs/cesdk/sveltekit/create-composition/programmatic-a688bf/) - Documentation for Programmatic Creation
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add a Background"
description: "Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/add-background-375a47/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Add a Background](https://img.ly/docs/cesdk/sveltekit/create-composition/add-background-375a47/)
---
Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-add-background-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-add-background-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-add-background-browser/)
CE.SDK provides two distinct approaches for adding backgrounds to design elements. Understanding when to use each approach ensures your designs render correctly and efficiently.
```typescript file=@cesdk_web_examples/guides-create-composition-add-background-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Add a Background Guide
*
* This example demonstrates:
* - Applying gradient fills to pages
* - Adding background colors to text blocks
* - Applying image fills to shapes
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
// Create a design scene and get the page
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Check if the page supports fill, then apply a pastel gradient
if (engine.block.supportsFill(page)) {
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { r: 0.85, g: 0.75, b: 0.95, a: 1.0 }, stop: 0 },
{ color: { r: 0.7, g: 0.9, b: 0.95, a: 1.0 }, stop: 1 }
]);
engine.block.setFill(page, gradientFill);
}
// Create header text (dark, no background)
const headerText = engine.block.create('text');
engine.block.setString(headerText, 'text/text', 'Learn cesdk');
engine.block.setFloat(headerText, 'text/fontSize', 56);
engine.block.setWidth(headerText, 350);
engine.block.setHeightMode(headerText, 'Auto');
engine.block.setPositionX(headerText, 50);
engine.block.setPositionY(headerText, 230);
engine.block.setColor(headerText, 'fill/solid/color', {
r: 0.15,
g: 0.15,
b: 0.2,
a: 1.0
});
engine.block.appendChild(page, headerText);
// Create "Backgrounds" text with white background
const featuredText = engine.block.create('text');
engine.block.setString(featuredText, 'text/text', 'Backgrounds');
engine.block.setFloat(featuredText, 'text/fontSize', 48);
engine.block.setWidth(featuredText, 280);
engine.block.setHeightMode(featuredText, 'Auto');
// Offset X by paddingLeft (16) so background aligns with header at X=50
engine.block.setPositionX(featuredText, 66);
engine.block.setPositionY(featuredText, 280);
engine.block.setColor(featuredText, 'fill/solid/color', {
r: 0.2,
g: 0.2,
b: 0.25,
a: 1.0
});
engine.block.appendChild(page, featuredText);
// Add white background color to the featured text block
if (engine.block.supportsBackgroundColor(featuredText)) {
engine.block.setBackgroundColorEnabled(featuredText, true);
engine.block.setColor(featuredText, 'backgroundColor/color', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
engine.block.setFloat(featuredText, 'backgroundColor/paddingLeft', 16);
engine.block.setFloat(featuredText, 'backgroundColor/paddingRight', 16);
engine.block.setFloat(featuredText, 'backgroundColor/paddingTop', 10);
engine.block.setFloat(featuredText, 'backgroundColor/paddingBottom', 10);
engine.block.setFloat(featuredText, 'backgroundColor/cornerRadius', 8);
}
// Create an image block on the right side
const imageBlock = engine.block.create('graphic');
const imageShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, imageShape);
engine.block.setFloat(imageShape, 'shape/rect/cornerRadiusTL', 16);
engine.block.setFloat(imageShape, 'shape/rect/cornerRadiusTR', 16);
engine.block.setFloat(imageShape, 'shape/rect/cornerRadiusBL', 16);
engine.block.setFloat(imageShape, 'shape/rect/cornerRadiusBR', 16);
engine.block.setWidth(imageBlock, 340);
engine.block.setHeight(imageBlock, 400);
engine.block.setPositionX(imageBlock, 420);
engine.block.setPositionY(imageBlock, 100);
// Check if the block supports fill, then apply an image fill
if (engine.block.supportsFill(imageBlock)) {
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
}
engine.block.appendChild(page, imageBlock);
// Create IMG.LY logo (bottom left)
const logoBlock = engine.block.create('graphic');
const logoShape = engine.block.createShape('rect');
engine.block.setShape(logoBlock, logoShape);
engine.block.setWidth(logoBlock, 100);
engine.block.setHeight(logoBlock, 40);
engine.block.setPositionX(logoBlock, 50);
engine.block.setPositionY(logoBlock, 530);
if (engine.block.supportsFill(logoBlock)) {
const logoFill = engine.block.createFill('image');
engine.block.setString(
logoFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/imgly_logo.jpg'
);
engine.block.setFill(logoBlock, logoFill);
}
engine.block.appendChild(page, logoBlock);
// Check feature support on different blocks
const pageSupportsFill = engine.block.supportsFill(page);
const textSupportsBackground =
engine.block.supportsBackgroundColor(featuredText);
const imageSupportsFill = engine.block.supportsFill(imageBlock);
console.log('Page supports fill:', pageSupportsFill);
console.log('Text supports backgroundColor:', textSupportsBackground);
console.log('Image supports fill:', imageSupportsFill);
// Zoom to fit the page
await engine.scene.zoomToBlock(page, {
padding: { left: 40, top: 40, right: 40, bottom: 40 }
});
}
}
export default Example;
```
## Setup
Create a design scene and get a reference to the page where we'll apply backgrounds.
```typescript highlight=highlight-setup
// Create a design scene and get the page
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
```
## Fills
Fills are visual content applied to pages and graphic blocks. Supported fill types include solid colors, linear gradients, radial gradients, and images.
### Check Fill Support
Before applying a fill, verify the block supports it with `supportsFill()`. Pages and graphic blocks typically support fills, while text blocks handle their content differently.
```typescript highlight=highlight-check-support
// Check feature support on different blocks
const pageSupportsFill = engine.block.supportsFill(page);
const textSupportsBackground =
engine.block.supportsBackgroundColor(featuredText);
const imageSupportsFill = engine.block.supportsFill(imageBlock);
console.log('Page supports fill:', pageSupportsFill);
console.log('Text supports backgroundColor:', textSupportsBackground);
console.log('Image supports fill:', imageSupportsFill);
```
### Apply a Gradient Fill
Create a fill with `createFill()` specifying the type, configure its properties, then apply it with `setFill()`. The example below creates a linear gradient with two color stops.
```typescript highlight=highlight-page-fill
// Check if the page supports fill, then apply a pastel gradient
if (engine.block.supportsFill(page)) {
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { r: 0.85, g: 0.75, b: 0.95, a: 1.0 }, stop: 0 },
{ color: { r: 0.7, g: 0.9, b: 0.95, a: 1.0 }, stop: 1 }
]);
engine.block.setFill(page, gradientFill);
}
```
The gradient transitions from a pastel purple at the start to a light cyan at the end.
### Apply an Image Fill
Image fills display images within the block's shape bounds. Create an image fill, set its URI, and apply it to a graphic block.
```typescript highlight=highlight-shape-fill
// Check if the block supports fill, then apply an image fill
if (engine.block.supportsFill(imageBlock)) {
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
}
```
The shape's corner radius creates rounded corners on the image. Image fills automatically scale to cover the shape area.
## Background Color
Background color is a dedicated property available specifically on text blocks. Unlike fills, background colors include configurable padding and corner radius, creating highlighted text effects without additional graphic blocks.
### Check Background Color Support
Use `supportsBackgroundColor()` to verify a block supports this feature. Currently, only text blocks support background colors.
```typescript highlight=highlight-check-support
// Check feature support on different blocks
const pageSupportsFill = engine.block.supportsFill(page);
const textSupportsBackground =
engine.block.supportsBackgroundColor(featuredText);
const imageSupportsFill = engine.block.supportsFill(imageBlock);
console.log('Page supports fill:', pageSupportsFill);
console.log('Text supports backgroundColor:', textSupportsBackground);
console.log('Image supports fill:', imageSupportsFill);
```
### Apply Background Color
Enable the background color with `setBackgroundColorEnabled()`, then configure its appearance using property paths for color, padding, and corner radius.
```typescript highlight=highlight-background-color
// Add white background color to the featured text block
if (engine.block.supportsBackgroundColor(featuredText)) {
engine.block.setBackgroundColorEnabled(featuredText, true);
engine.block.setColor(featuredText, 'backgroundColor/color', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
engine.block.setFloat(featuredText, 'backgroundColor/paddingLeft', 16);
engine.block.setFloat(featuredText, 'backgroundColor/paddingRight', 16);
engine.block.setFloat(featuredText, 'backgroundColor/paddingTop', 10);
engine.block.setFloat(featuredText, 'backgroundColor/paddingBottom', 10);
engine.block.setFloat(featuredText, 'backgroundColor/cornerRadius', 8);
}
```
The padding properties (`backgroundColor/paddingLeft`, `backgroundColor/paddingRight`, `backgroundColor/paddingTop`, `backgroundColor/paddingBottom`) control the space between the text and the background edge. The `backgroundColor/cornerRadius` property rounds the corners.
## Troubleshooting
### Fill Not Visible
If a fill doesn't appear:
- Ensure all color components (r, g, b) are between 0 and 1
- Check that the alpha component is greater than 0
- Verify the block supports fills with `supportsFill()`
### Background Color Not Appearing
If a background color doesn't appear:
- Confirm the block supports it with `supportsBackgroundColor()`
- Verify `setBackgroundColorEnabled(block, true)` was called
- Check that the color's alpha value is greater than 0
### Image Not Loading
If an image fill doesn't display:
- Verify the image URI is accessible
- Check browser console for CORS or network errors
- Ensure the image format is supported (PNG, JPEG, WebP)
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.supportsFill(block)` | Check if a block supports fills |
| `engine.block.createFill(type)` | Create a fill (color, gradient/linear, gradient/radial, image) |
| `engine.block.setFill(block, fill)` | Apply a fill to a block |
| `engine.block.getFill(block)` | Get the fill applied to a block |
| `engine.block.setGradientColorStops(fill, property, stops)` | Set gradient color stops |
| `engine.block.supportsBackgroundColor(block)` | Check if a block supports background color |
| `engine.block.setBackgroundColorEnabled(block, enabled)` | Enable or disable background color |
| `engine.block.isBackgroundColorEnabled(block)` | Check if background color is enabled |
| `engine.block.setColor(block, property, color)` | Set color properties |
| `engine.block.setFloat(block, property, value)` | Set float properties (padding, radius) |
## Next Steps
Explore related topics:
- [Apply Colors](https://img.ly/docs/cesdk/sveltekit/colors/apply-2211e3/) - Work with RGB, CMYK, and spot colors
- [Fills Overview](https://img.ly/docs/cesdk/sveltekit/fills/overview-3895ee/) - Learn about all fill types in depth
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Blend Modes"
description: "Apply blend modes to elements to control how colors and layers interact visually."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/blend-modes-ad3519/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Blend Modes](https://img.ly/docs/cesdk/sveltekit/create-composition/blend-modes-ad3519/)
---
Control how design blocks visually blend with underlying layers using CE.SDK's
blend mode system for professional layered compositions.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-blend-modes-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-blend-modes-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-blend-modes-browser/)
Blend modes control how a block's colors combine with underlying layers, similar to blend modes in Photoshop or other design tools. CE.SDK provides 27 blend modes organized into categories: Normal, Darken, Lighten, Contrast, Inversion, and Component. Each category serves different compositing needs—darken modes make images darker, lighten modes make them brighter, and contrast modes increase midtone contrast.
```typescript file=@cesdk_web_examples/guides-create-composition-blend-modes-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Blend Modes Guide
*
* This example demonstrates:
* - Checking if a block supports blend modes
* - Setting blend modes on overlay layers
* - Getting the current blend mode of a block
* - Working with opacity values
* - Available blend mode values
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.print.iso.a6.landscape'
}
});
const page = engine.block.findByType('page')[0];
// Grid configuration: 3 columns x 2 rows
const cols = 3;
const rows = 2;
const cellWidth = 280;
const cellHeight = 210;
const padding = 20;
const pageWidth = cols * cellWidth + (cols + 1) * padding;
const pageHeight = rows * cellHeight + (rows + 1) * padding;
// Set page dimensions
engine.block.setWidth(page, pageWidth);
engine.block.setHeight(page, pageHeight);
// Base and overlay image URLs
const baseImageUrl = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const overlayImageUrl = 'https://img.ly/static/ubq_samples/sample_2.jpg';
// Six commonly used blend modes to demonstrate
const blendModes: Array<
'Multiply' | 'Screen' | 'Overlay' | 'Darken' | 'Lighten' | 'ColorBurn'
> = ['Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorBurn'];
// Create 6 image pairs in a grid layout
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const index = row * cols + col;
const x = padding + col * (cellWidth + padding);
const y = padding + row * (cellHeight + padding);
// Create a background image block as the base layer
const backgroundBlock = engine.block.create('graphic');
const backgroundShape = engine.block.createShape('rect');
engine.block.setShape(backgroundBlock, backgroundShape);
engine.block.setWidth(backgroundBlock, cellWidth);
engine.block.setHeight(backgroundBlock, cellHeight);
engine.block.setPositionX(backgroundBlock, x);
engine.block.setPositionY(backgroundBlock, y);
// Set the image fill for the background
const backgroundFill = engine.block.createFill('image');
engine.block.setString(
backgroundFill,
'fill/image/imageFileURI',
baseImageUrl
);
engine.block.setFill(backgroundBlock, backgroundFill);
engine.block.setContentFillMode(backgroundBlock, 'Cover');
engine.block.appendChild(page, backgroundBlock);
// Create a second image block on top for blending
const overlayBlock = engine.block.create('graphic');
const overlayShape = engine.block.createShape('rect');
engine.block.setShape(overlayBlock, overlayShape);
engine.block.setWidth(overlayBlock, cellWidth);
engine.block.setHeight(overlayBlock, cellHeight);
engine.block.setPositionX(overlayBlock, x);
engine.block.setPositionY(overlayBlock, y);
// Set a different image fill for the overlay
const overlayFill = engine.block.createFill('image');
engine.block.setString(
overlayFill,
'fill/image/imageFileURI',
overlayImageUrl
);
engine.block.setFill(overlayBlock, overlayFill);
engine.block.setContentFillMode(overlayBlock, 'Cover');
engine.block.appendChild(page, overlayBlock);
// Check if the block supports blend modes before applying
if (engine.block.supportsBlendMode(overlayBlock)) {
// Apply a different blend mode to each overlay
const blendMode = blendModes[index];
engine.block.setBlendMode(overlayBlock, blendMode);
// Retrieve and log the current blend mode
const currentMode = engine.block.getBlendMode(overlayBlock);
// eslint-disable-next-line no-console
console.log(`Cell ${index + 1} blend mode:`, currentMode);
}
// Check if the block supports opacity
if (engine.block.supportsOpacity(overlayBlock)) {
// Set the opacity to 80% for clear visibility
engine.block.setOpacity(overlayBlock, 0.8);
}
// Retrieve and log the opacity value
const opacity = engine.block.getOpacity(overlayBlock);
// eslint-disable-next-line no-console
console.log(`Cell ${index + 1} opacity:`, opacity);
}
}
// Zoom to fit the composition
await engine.scene.zoomToBlock(page, {
padding: {
left: 40,
top: 40,
right: 40,
bottom: 40
}
});
}
}
export default Example;
```
This guide covers how to check blend mode support, apply blend modes programmatically, understand the available blend mode options, and combine blend modes with opacity for fine control over layer compositing.
## Checking Blend Mode Support
Before applying a blend mode, verify that the block supports it using `supportsBlendMode()`. Most graphic blocks support blend modes, but always check to avoid errors.
```typescript highlight-check-support
// Check if the block supports blend modes before applying
if (engine.block.supportsBlendMode(overlayBlock)) {
```
Blend mode support is available for graphic blocks with image or video fills, shape blocks, and text blocks. Page blocks and scene blocks typically do not support blend modes directly.
## Setting and Getting Blend Modes
Apply a blend mode with `setBlendMode()` and retrieve the current mode with `getBlendMode()`. The default blend mode is `'Normal'`, which displays the block without any blending effect.
```typescript highlight-set-blend-mode
// Apply a different blend mode to each overlay
const blendMode = blendModes[index];
engine.block.setBlendMode(overlayBlock, blendMode);
```
After setting a blend mode, you can confirm the change by retrieving the current value:
```typescript highlight-get-blend-mode
// Retrieve and log the current blend mode
const currentMode = engine.block.getBlendMode(overlayBlock);
// eslint-disable-next-line no-console
console.log(`Cell ${index + 1} blend mode:`, currentMode);
```
## Available Blend Modes
CE.SDK provides 27 blend modes organized into categories, each producing different visual results:
### Normal Modes
- **`PassThrough`** - Allows children of a group to blend with layers below the group
- **`Normal`** - Default mode with no blending effect
### Darken Modes
These modes darken the result by comparing the base and blend colors:
- **`Darken`** - Selects the darker of the base and blend colors
- **`Multiply`** - Multiplies colors, producing darker results (great for shadows)
- **`ColorBurn`** - Darkens base color by increasing contrast
- **`LinearBurn`** - Darkens base color by decreasing brightness
- **`DarkenColor`** - Selects the darker color based on luminosity
### Lighten Modes
These modes lighten the result by comparing colors:
- **`Lighten`** - Selects the lighter of the base and blend colors
- **`Screen`** - Multiplies the inverse of colors, producing lighter results (great for highlights)
- **`ColorDodge`** - Lightens base color by decreasing contrast
- **`LinearDodge`** - Lightens base color by increasing brightness
- **`LightenColor`** - Selects the lighter color based on luminosity
### Contrast Modes
These modes increase midtone contrast:
- **`Overlay`** - Combines Multiply and Screen based on the base color
- **`SoftLight`** - Similar to Overlay but with a softer effect
- **`HardLight`** - Similar to Overlay but based on the blend color
- **`VividLight`** - Burns or dodges colors based on the blend color
- **`LinearLight`** - Increases or decreases brightness based on blend color
- **`PinLight`** - Replaces colors based on the blend color
- **`HardMix`** - Reduces colors to white, black, or primary colors
### Inversion Modes
These modes create inverted or subtracted effects:
- **`Difference`** - Subtracts the darker from the lighter color
- **`Exclusion`** - Similar to Difference with lower contrast
- **`Subtract`** - Subtracts blend color from base color
- **`Divide`** - Divides base color by blend color
### Component Modes
These modes affect specific color components:
- **`Hue`** - Uses the hue of the blend color with base saturation and luminosity
- **`Saturation`** - Uses the saturation of the blend color
- **`Color`** - Uses the hue and saturation of the blend color
- **`Luminosity`** - Uses the luminosity of the blend color
## Combining Blend Modes with Opacity
For finer control over compositing, combine blend modes with opacity. Opacity reduces overall visibility while the blend mode affects color interaction with underlying layers.
```typescript highlight-set-opacity
// Check if the block supports opacity
if (engine.block.supportsOpacity(overlayBlock)) {
// Set the opacity to 80% for clear visibility
engine.block.setOpacity(overlayBlock, 0.8);
}
```
You can retrieve the current opacity value to confirm changes or read existing state:
```typescript highlight-get-opacity
// Retrieve and log the opacity value
const opacity = engine.block.getOpacity(overlayBlock);
// eslint-disable-next-line no-console
console.log(`Cell ${index + 1} opacity:`, opacity);
```
> **Tip:** Start with full opacity (1.0) when experimenting with blend modes, then reduce
> opacity to soften the effect. Common values are 0.5-0.7 for subtle blending
> effects.
## Troubleshooting
### Blend Mode Has No Visible Effect
If a blend mode doesn't produce visible changes:
- Ensure there are underlying layers for the block to blend with. Blend modes only affect compositing with content below.
- Verify the blend mode is applied to the correct block using `getBlendMode()`.
- Check that the block has visible content (fill or image) to blend.
### Cannot Set Blend Mode
If `setBlendMode()` throws an error:
- Check that `supportsBlendMode()` returns `true` for the block.
- Verify the block ID is valid and the block exists in the scene.
- Ensure you're passing a valid blend mode string from the available options.
### Unexpected Blending Results
If the visual result doesn't match expectations:
- Verify the blend mode category matches your intent (darken vs lighten vs contrast).
- Check the stacking order of blocks—blend modes affect content below the block.
- Experiment with different blend modes from the same category to find the best visual match.
## API Reference
| Method | Description |
| ---------------------------------------- | ------------------------------------------------- |
| `engine.block.supportsBlendMode(id)` | Check if a block supports blend modes |
| `engine.block.setBlendMode(id, mode)` | Set the blend mode for a block |
| `engine.block.getBlendMode(id)` | Get the current blend mode of a block |
| `engine.block.supportsOpacity(id)` | Check if a block supports opacity |
| `engine.block.setOpacity(id, opacity)` | Set the opacity for a block (0-1) |
| `engine.block.getOpacity(id)` | Get the current opacity of a block |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create a Collage"
description: "Combine images into a collage using the CE.SDK."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/collage-f7d28d/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Create a Collage](https://img.ly/docs/cesdk/sveltekit/create-composition/collage-f7d28d/)
---
This guide shows you how to add a **Collage** feature in your web app with the help of the CE.SDK. A collage allows you to reuse images and assets as you change the layout of the scene.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/CaseComponent.jsx)
>
> - [Open in StackBlitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/showcase-layouts?title=IMG.LY+CE.SDK%3A+Layouts\&file=src%2Fcomponents%2Fcase%2FCaseComponent.jsx)
>
> - [Live demo](https://img.ly/showcases/cesdk/layouts/web)
**Layouts** in CE.SDK are predefined templates that dictate how to arrange images and assets within a single composition. They allow you to create visually appealing collages by specifying each image’s:
- Position
- Size
- Orientation relative to other assets on the page
Layouts instantly create visuals and allow you to skip manually arranging each asset.
## What You’ll Learn
In this guide, you will learn how to:
- Set up the layout panel in the CE.SDK UI.
- Use a layout file for collages.
- Use TypeScript to apply layouts in your custom UI.
## When to Use Layouts
The CE.SDK **Layout** feature is ideal for:
- Photo collages
- Grid layouts
- Magazine spreads
- Social media posts
## Difference Between Layouts and Templates
Layouts are [custom assets](https://img.ly/docs/cesdk/sveltekit/import-media/concepts-5e6197/) that differ from templates:
- **Templates:** Load **new assets** and replace the existing scene.
- **Layouts:** Keep existing assets but change their arrangement on the page.
Preserving assets while changing the layout **isn’t a native CE.SDK feature**. The following sections explain how to leverage the CE.SDK to do it in your app using JavaScript.
## How Collages Work
When you choose a collage layout, the app needs to:
1. Load a new layout file.
2. Extract content from your current design.
3. Map content to new layout positions.
4. Preserve images and text in visual order.
You trigger this workflow when a user selects a layout from the **Layouts** panel in the CE.SDK UI.
## Add a Layouts Panel to the CE.SDK UI
The CE.SDK allows you to add [custom panels to the UI](https://img.ly/docs/cesdk/sveltekit/user-interface/customization/panel-7ce1ee/). To add a **Layouts** panel for collage selection, you need to create it and load custom assets to configure its appearance and behavior.
For this action, you need:
1. A layout file defining the collage structure (use [this one](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/CustomLayouts.json) to get started).
2. Thumbnail images for preview (find a collection [here](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/showcase-layouts/public/cases/layouts)).
### 1. Add a Layouts Option to the CE.SDK Menu
To **customize the CE.SDK UI** and create a **Layouts** panel, use the CE.SDK UserInterfaceAPI. Add a Layout button with `ui.setComponentOrder({ in: 'ly.img.dock' }, order)`, using the following properties:
- `id`
- `key`
- `label`
- `icon`
- `entries`
For example:
```ts
cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [
{
id: 'ly.img.assetLibrary.dock',
key: 'layouts-dock-entry',
label: 'Layouts',
icon: '@imgly/Layout',
entries: ['layouts'],
},
]);
```
### 2. Customize the Layouts Panel Appearance
To configure how the **Layouts** panel looks and behaves, use the `ui.addAssetLibraryEntry()` helper with the following options:
- `id`: Unique identifier for the panel.
- `sourceIds`: Which assets to display.
- `previewLength`: How many preview items to display in the panel.
- `gridColumns`: How many columns to contain the previews in the panel.
- `gridItemHeight`: The shape of each tile in the panel grid.
- `previewBackgroundType`: How to fit the previews inside their tiles (cover/contain).
- `gridBackgroundType`: How to display the panel background (cover/contain).
For example, the demo uses this configuration:
```jsx title="CustomCase.jsx"
instance.ui.addAssetLibraryEntry({
id: 'ly.img.layouts', // Referenced in the dock entry in Step 1
sourceIds: ['ly.img.layouts'], // Points to our custom layout asset source
previewLength: 2, // Number of preview items int the compact panel
gridColumns: 2, // Organize tiles in 2 columns
gridItemHeight: 'square', // Square tiles
previewBackgroundType: 'contain', // Fit compact panel background
gridBackgroundType: 'contain' // Fit panel background
});
```
To learn more about panel customization, check the [Panel Customization guide](https://img.ly/docs/cesdk/sveltekit/user-interface/customization/panel-7ce1ee/).
### 3. Load Custom Assets
The demo uses a [helper function](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/lib/loadAssetSourceFromContentJSON.ts) to load a custom layout asset source from a JSON file. This file defines the available layouts and their metadata. You can reuse this logic by defining both:
- A `ContentJSON` object (like `CustomLayouts.json`)
- A `baseURL` for your custom assets that replaces the `{{base_url}}`.
```jsx title="CustomCase.jsx"
import { createApplyLayoutAsset } from './lib/createApplyLayoutAsset';
import loadAssetSourceFromContentJSON from './lib/loadAssetSourceFromContentJSON';
// ...
const caseAssetPath = (path, caseId = 'layouts') =>
`${process.env.NEXT_PUBLIC_URL_HOSTNAME}${process.env.NEXT_PUBLIC_URL}/cases/${caseId}${path}`;
// Call the helper to load the layout assets
loadAssetSourceFromContentJSON(
instance.engine, // Pss the CE.SDK engine to load assets into
LAYOUT_ASSETS, // Pass the JSON bundle
caseAssetPath(''), // Base URL for assets
createApplyLayoutAsset(instance.engine) // Callback to createApplyLayoutAsset.js helper
);
await instance.loadFromURL(caseAssetPath('/custom-layouts.scene')); // Load the scene
// ...
```
In the previous example, the helper accepts an optional **applyAsset callback**, so:
1. A user picks a layout from the library.
2. The engine invokes the callback to apply it.
3. The engine replaces the current page’s structure with the layout while keeping the user’s images/text.
Asset preservation isn't a CE.SDK native feature. It’s handled in the `createApplyLayoutAsset.js` helper, which you can find in [the repository](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/lib/createApplyLayoutAsset.js).
## Apply the Collage
When applying a collage, the following actions need to be implemented:
1. Changing the structure of the design.
2. Transferring the existing content to the new structure.
3. Deleting the previous scene.
You can find this workflow in the `createApplyLayoutAsset()` helper from the demo. Follow these steps to replicate it:
### 1. Prepare and Allow changes
- Allow block deletion with `editor.setGlobalScope('lifecycle/destroy', 'Allow')`.
- Clear the selected blocks with `block.setSelected(block, false)`.
```js title="createApplyLayoutAsset.js"
const scopeBefore = engine.editor.getGlobalScope('lifecycle/destroy');
engine.editor.setGlobalScope('lifecycle/destroy', 'Allow');
const page = engine.scene.getCurrentPage();
engine.block.findAllSelected().forEach((block) => engine.block.setSelected(block, false));
```
### 2. Load the New Layout
- Load the new layout with `block.loadFromString()`.
- Return the first page from the loaded blocks as the layout page.
```js title="createApplyLayoutAsset.js"
const sceneString = await fetch(asset.meta.uri).then((response) => response.text());
const blocks = await engine.block.loadFromString(sceneString);
const layoutPage = blocks[0];
```
### 3. Backup Current Page
- Duplicate the current page with `block.duplicate()` to keep a backup of the existing content.
- The CE.SDK uses this backup to transfer images and text to the new layout.
- Clear the current page structure by destroying all its children.
```js title="createApplyLayoutAsset.js"
const oldPage = engine.block.duplicate(page);
engine.block.getChildren(page).forEach((child) => {
engine.block.destroy(child);
});
engine.block.getChildren(layoutPage).forEach((child) => {
engine.block.insertChild(page, child, engine.block.getChildren(page).length);
});
```
### 4. Transfer Content
Copy user text and images onto the new layout:
```js title="createApplyLayoutAsset.js"
copyAssets(engine, oldPage, page);
```
### 5. Sort Blocks Visually and Pair Content
Grab text and image blocks from both pages in visual order (top to bottom, left to right):
```js title="createApplyLayoutAsset.js"
const fromChildren = visuallySortBlocks(engine, getChildrenTree(engine, fromPageId).flat());
const textsOnFromPage = fromChildren.filter((childId) => engine.block.getType(childId).includes('text'));
const imagesOnFromPage = fromChildren.filter((childId) => engine.block.getKind(childId) === 'image');
// same for toPageId -> textsOnToPage, imagesOnToPage
```
Then apply the content from the old page to the new layout by looping through the blocks:
```js title="createApplyLayoutAsset.js"
const fromText = engine.block.getString(fromBlock, 'text/text');
const fromFontFileUri = engine.block.getString(fromBlock, 'text/fontFileUri');
const fromTypeface = engine.block.getTypeface(fromBlock);
engine.block.setFont(toBlock, fromFontFileUri, fromTypeface);
const fromTextFillColor = engine.block.getColor(fromBlock, 'fill/solid/color');
engine.block.setString(toBlock, 'text/text', fromText);
engine.block.setColor(toBlock, 'fill/solid/color', fromTextFillColor);
```
```js title="createApplyLayoutAsset.js"
const fromImageFill = engine.block.getFill(fromBlock);
const toImageFill = engine.block.getFill(toBlock);
const fromImageFileUri = engine.block.getString(fromImageFill, 'fill/image/imageFileURI');
engine.block.setString(toImageFill, 'fill/image/imageFileURI', fromImageFileUri);
const fromImageSourceSets = engine.block.getSourceSet(fromImageFill, 'fill/image/sourceSet');
engine.block.setSourceSet(toImageFill, 'fill/image/sourceSet', fromImageSourceSets);
if (engine.block.supportsPlaceholderBehavior(fromBlock)) {
engine.block.setPlaceholderBehaviorEnabled(
toBlock,
engine.block.isPlaceholderBehaviorEnabled(fromBlock)
);
}
engine.block.resetCrop(toBlock);
```
### 6. Cleanup and Restore State
Cleanup temporary blocks with `block.destroy()` and restore global scope:
```js title="createApplyLayoutAsset.js"
engine.block.destroy(oldPage);
engine.block.destroy(layoutPage);
engine.editor.setGlobalScope('lifecycle/destroy', scopeBefore);
if (config.addUndoStep) {
engine.editor.addUndoStep();
}
return page;
```
This keeps the editor stable and predictable after the layout changes and prevents:
- Stray selections.
- Ghost placeholders.
- Unused assets left at the scene.
## Advanced Collage Techniques
The CE.SDK BlockAPI eases the transfer of [placeholder behavior](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/placeholders-d9ba8a/) when moving images between blocks. You can use it to:
1. Checks if the block supports placeholder behavior with `supportsPlaceholderBehavior`.
2. Apply the same setting to the target image block with:
- `isPlaceholderBehaviorEnabled()`
- `setPlaceholderBehaviorEnabled()`
```js title="createApplyLayoutAsset.js"
if (engine.block.supportsPlaceholderBehavior(fromBlock)) {
engine.block.setPlaceholderBehaviorEnabled(
toBlock,
engine.block.isPlaceholderBehaviorEnabled(fromBlock),
);
}
```
This maintains the behavior of current placeholders after the layout swap.
To ensure the CE.SDK uses all the existing images when applying a layout, you can:
1. Handle overflow when more images than slots:
```js title="createApplyLayoutAsset.js"
for (
let index = 0;
index < imagesOnToPage.length && index < imagesOnFromPage.length;
index++
) { ... }
```
2. Fill empty slots with defaults or placeholders:
```js title="createApplyLayoutAsset.js"
// loop condition ends when sources run out, leaving remaining target slots unchanged
index < imagesOnToPage.length && index < imagesOnFromPage.length;
```
3. List content by importance or metadata:
```js title="createApplyLayoutAsset.js"
const visuallySortBlocks = (engine, blocks) => {
const blocksWithCoordinates = blocks
.map((block) => ({
block,
coordinates: [
Math.round(engine.block.getPositionX(block)),
Math.round(engine.block.getPositionY(block))
]
}))
.sort(({ coordinates: [X1, Y1] }, { coordinates: [X2, Y2] }) => {
if (Y1 === Y2) return X1 - X2;
return Y1 - Y2;
});
return blocksWithCoordinates.map(({ block }) => block);
};
```
For a better user experience, consider adding animations when applying layouts:
```tsx title="LoadingSpinner.tsx"
const LoadingSpinner = () => {
return ;
};
```
You can code more customizations to incorporate these effects into collage transitions:
- Fading
- Sliding
- Morphing
## Optimize the Layout Workflow
For a better user experience when creating collages, consider these optimizations:
| Topic | Strategies |
| --- | --- |
| **Asset Loading** | ・ Lazy load thumbnails and cache scene files ・ Preload common layouts and minimize file sizes ・ Use CDN for efficient asset delivery |
| **Content Transfer** | ・ Batch block operations to reduce engine calls ・ Optimize sorting algorithms and memory usage ・ Handle large page structures efficiently |
| **Visual Cues** | ・ Show loading states and support undo/redo ・ Add error recovery and prevent accidental changes |
## Troubleshooting
| ❌ Issue | ✅ Solutions |
| --- | --- |
| Layout not applying | ・ Verify asset source registration and callback connection ・ Check browser console for scene files or CORS errors ・ Check the validity of scene file URLs |
| Content lost during layout change | ・ Debug visual sorting with console logs ・ Verify block type filtering is correct ・ Test with different content settings|
| Incorrect visual order | ・ Adjust coordinate rounding tolerance in sorting ・ Flatten complex hierarchies before sorting ・ Test with well-defined layout structures |
| Performance degradation | ・ Optimize scene file sizes ・ Batch engine operations ・ Profile and optimize content transfer |
| Undo not working | ・ Ensure `addUndoStep()` called after all changes complete ・ Verify undo configuration in engine setup |
## Next Steps
Now that know how to create collages with layouts, explore these related guides to expand your CE.SDK knowledge:
- [Apply Templates](https://img.ly/docs/cesdk/sveltekit/create-templates/overview-4ebe30/) - Work with templates instead of layouts
- [Create Custom Asset Sources](https://img.ly/docs/cesdk/sveltekit/import-media/asset-panel/basics-f29078/) - Import custom assets
- [Customize UI Panels](https://img.ly/docs/cesdk/sveltekit/user-interface/customization/panel-7ce1ee/) - Advanced UI customization
- [Work with Images](https://img.ly/docs/cesdk/sveltekit/insert-media/images-63848a/) - Manage image blocks and fills
- [Scene Management](https://img.ly/docs/cesdk/sveltekit/open-the-editor/load-scene-478833/) - Load and save scenes
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Group and Ungroup Objects"
description: "Group multiple design elements together so they move, scale, and transform as a single unit; ungroup to edit them individually."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/group-and-ungroup-62565a/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Group and Ungroup Objects](https://img.ly/docs/cesdk/sveltekit/create-composition/group-and-ungroup-62565a/)
---
Group multiple blocks to move, scale, and transform them as a single unit; ungroup to edit them individually.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-grouping-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-grouping-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-grouping-browser/)
Groups let you treat multiple blocks as a cohesive unit. Grouped blocks move, scale, and rotate together while maintaining their relative positions. Groups can contain other groups, enabling hierarchical compositions.
> **Note:** Groups are not currently available when editing videos.
```typescript file=@cesdk_web_examples/guides-create-composition-grouping-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Group and Ungroup Objects Guide
*
* This example demonstrates:
* - Creating multiple graphic blocks
* - Checking if blocks can be grouped
* - Grouping blocks together
* - Navigating into and out of groups
* - Ungrouping blocks
* - Finding and inspecting groups
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
// Create a design scene and get the page
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Create a graphic block with a colored rectangle shape
const block1 = engine.block.create('graphic');
const shape1 = engine.block.createShape('rect');
engine.block.setShape(block1, shape1);
engine.block.setWidth(block1, 120);
engine.block.setHeight(block1, 120);
engine.block.setPositionX(block1, 200);
engine.block.setPositionY(block1, 240);
const fill1 = engine.block.createFill('color');
engine.block.setColor(fill1, 'fill/color/value', {
r: 0.4,
g: 0.6,
b: 0.9,
a: 1.0
});
engine.block.setFill(block1, fill1);
engine.block.appendChild(page, block1);
// Create two more blocks for grouping
const block2 = engine.block.create('graphic');
const shape2 = engine.block.createShape('rect');
engine.block.setShape(block2, shape2);
engine.block.setWidth(block2, 120);
engine.block.setHeight(block2, 120);
engine.block.setPositionX(block2, 340);
engine.block.setPositionY(block2, 240);
const fill2 = engine.block.createFill('color');
engine.block.setColor(fill2, 'fill/color/value', {
r: 0.9,
g: 0.5,
b: 0.4,
a: 1.0
});
engine.block.setFill(block2, fill2);
engine.block.appendChild(page, block2);
const block3 = engine.block.create('graphic');
const shape3 = engine.block.createShape('rect');
engine.block.setShape(block3, shape3);
engine.block.setWidth(block3, 120);
engine.block.setHeight(block3, 120);
engine.block.setPositionX(block3, 480);
engine.block.setPositionY(block3, 240);
const fill3 = engine.block.createFill('color');
engine.block.setColor(fill3, 'fill/color/value', {
r: 0.5,
g: 0.8,
b: 0.5,
a: 1.0
});
engine.block.setFill(block3, fill3);
engine.block.appendChild(page, block3);
// Check if the blocks can be grouped together
const canGroup = engine.block.isGroupable([block1, block2, block3]);
console.log('Blocks can be grouped:', canGroup);
// Group the blocks together
if (canGroup) {
const groupId = engine.block.group([block1, block2, block3]);
console.log('Created group with ID:', groupId);
// Select the group to show it in the UI
engine.block.setSelected(groupId, true);
// Enter the group to select individual members
engine.block.enterGroup(groupId);
// Select a specific member within the group
engine.block.setSelected(block2, true);
console.log('Selected member inside group');
// Exit the group to return selection to the parent group
engine.block.exitGroup(block2);
console.log('Exited group, group is now selected');
// Find all groups in the scene
const allGroups = engine.block.findByType('group');
console.log('Number of groups in scene:', allGroups.length);
// Check the type of the group block
const groupType = engine.block.getType(groupId);
console.log('Group block type:', groupType);
// Get the members of the group
const members = engine.block.getChildren(groupId);
console.log('Group has', members.length, 'members');
// Ungroup the blocks to make them independent again
engine.block.ungroup(groupId);
console.log('Ungrouped blocks');
// Verify blocks are no longer in a group
const groupsAfterUngroup = engine.block.findByType('group');
console.log('Groups after ungrouping:', groupsAfterUngroup.length);
// Re-group for the final display
const finalGroup = engine.block.group([block1, block2, block3]);
engine.block.setSelected(finalGroup, true);
}
// Enable auto-fit zoom to keep the page centered
engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40);
}
}
export default Example;
```
This guide covers how to check if blocks can be grouped, create and dissolve groups, navigate into groups to select individual members, and find existing groups in a scene.
## Understanding Groups
Groups are blocks with type `'group'` that contain child blocks as members. Transformations applied to a group affect all members proportionally—position, scale, and rotation cascade to all children.
Groups can be nested, meaning a group can contain other groups. This enables complex hierarchical structures where multiple logical units can be combined and manipulated together.
> **Note:** **What cannot be grouped*** Scene blocks cannot be grouped
> * Blocks already part of a group cannot be grouped again until ungrouped
## Create the Blocks
We first create several graphic blocks that we'll group together. Each block has a different color fill to make them visually distinct.
```typescript highlight=highlight-create-blocks
// Create a graphic block with a colored rectangle shape
const block1 = engine.block.create('graphic');
const shape1 = engine.block.createShape('rect');
engine.block.setShape(block1, shape1);
engine.block.setWidth(block1, 120);
engine.block.setHeight(block1, 120);
engine.block.setPositionX(block1, 200);
engine.block.setPositionY(block1, 240);
const fill1 = engine.block.createFill('color');
engine.block.setColor(fill1, 'fill/color/value', {
r: 0.4,
g: 0.6,
b: 0.9,
a: 1.0
});
engine.block.setFill(block1, fill1);
engine.block.appendChild(page, block1);
```
## Check If Blocks Can Be Grouped
Before grouping, verify that the selected blocks can be grouped using `engine.block.isGroupable()`. This method returns `true` if all blocks can be grouped together, or `false` if any block is a scene or already belongs to a group.
```typescript highlight=highlight-check-groupable
// Check if the blocks can be grouped together
const canGroup = engine.block.isGroupable([block1, block2, block3]);
console.log('Blocks can be grouped:', canGroup);
```
## Create a Group
Use `engine.block.group()` to combine multiple blocks into a new group. The method returns the ID of the newly created group block. The group inherits the combined bounding box of its members.
```typescript highlight=highlight-create-group
// Group the blocks together
if (canGroup) {
const groupId = engine.block.group([block1, block2, block3]);
console.log('Created group with ID:', groupId);
// Select the group to show it in the UI
engine.block.setSelected(groupId, true);
```
## Navigate Group Selection
CE.SDK provides methods to navigate into and out of groups while editing.
### Enter a Group
When a group is selected, use `engine.block.enterGroup()` to enter editing mode for that group. This allows you to select and modify individual members within the group.
```typescript highlight=highlight-enter-group
// Enter the group to select individual members
engine.block.enterGroup(groupId);
// Select a specific member within the group
engine.block.setSelected(block2, true);
console.log('Selected member inside group');
```
### Exit a Group
When editing a member inside a group, use `engine.block.exitGroup()` to return selection to the parent group. This method takes a member block ID and selects its parent group.
```typescript highlight=highlight-exit-group
// Exit the group to return selection to the parent group
engine.block.exitGroup(block2);
console.log('Exited group, group is now selected');
```
## Find and Inspect Groups
Discover groups in a scene and inspect their contents using `engine.block.findByType()`, `engine.block.getType()`, and `engine.block.getChildren()`.
```typescript highlight=highlight-find-groups
// Find all groups in the scene
const allGroups = engine.block.findByType('group');
console.log('Number of groups in scene:', allGroups.length);
// Check the type of the group block
const groupType = engine.block.getType(groupId);
console.log('Group block type:', groupType);
// Get the members of the group
const members = engine.block.getChildren(groupId);
console.log('Group has', members.length, 'members');
```
Use `engine.block.findByType('group')` to get all group blocks in the current scene. Use `engine.block.getType()` to check if a specific block is a group (returns `'//ly.img.ubq/group'`). Use `engine.block.getChildren()` to get the member blocks of a group.
## Ungroup Blocks
Use `engine.block.ungroup()` to dissolve a group and release its children back to the parent container. The children maintain their current positions in the scene.
```typescript highlight=highlight-ungroup
// Ungroup the blocks to make them independent again
engine.block.ungroup(groupId);
console.log('Ungrouped blocks');
// Verify blocks are no longer in a group
const groupsAfterUngroup = engine.block.findByType('group');
console.log('Groups after ungrouping:', groupsAfterUngroup.length);
```
## Troubleshooting
### Blocks Cannot Be Grouped
If `engine.block.isGroupable()` returns `false`:
- Check if any of the blocks is a scene block (scenes cannot be grouped)
- Check if any block is already part of a group (use `engine.block.getParent()` to verify)
- Ensure all block IDs are valid
### Enter Group Has No Effect
If `engine.block.enterGroup()` doesn't change selection:
- Verify the block is a group using `engine.block.getType()`
- Ensure the `'editor/select'` scope is enabled
### Group Not Visible After Creation
If a newly created group is not visible:
- Check that the member blocks were visible before grouping
- Verify the group's opacity using `engine.block.getOpacity()`
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.isGroupable(ids)` | Check if blocks can be grouped together |
| `engine.block.group(ids)` | Create a group from multiple blocks |
| `engine.block.ungroup(id)` | Dissolve a group and release its children |
| `engine.block.enterGroup(id)` | Enter group editing mode (select member) |
| `engine.block.exitGroup(id)` | Exit group editing mode (select parent group) |
| `engine.block.findByType(type)` | Find all blocks of a specific type |
| `engine.block.getType(id)` | Get the type string of a block |
| `engine.block.getParent(id)` | Get the parent block |
| `engine.block.getChildren(id)` | Get child blocks of a container |
## Next Steps
Explore related topics:
- [Layer Management](https://img.ly/docs/cesdk/sveltekit/create-composition/layer-management-18f07a/) - Control z-order and visibility of blocks
- [Position and Align](https://img.ly/docs/cesdk/sveltekit/insert-media/position-and-align-cc6b6a/) - Arrange blocks precisely on the canvas
- [Lock Design](https://img.ly/docs/cesdk/sveltekit/create-composition/lock-design-0a81de/) - Prevent modifications to specific elements
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Layer Management"
description: "Organize design elements using a layer stack for precise control over stacking and visibility."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/layer-management-18f07a/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Layers](https://img.ly/docs/cesdk/sveltekit/create-composition/layer-management-18f07a/)
---
Organize design elements in CE.SDK using a hierarchical layer stack to control stacking order, visibility, and element relationships.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-layer-management-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-layer-management-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-layer-management-browser/)
Design elements in CE.SDK are organized in a hierarchical parent-child structure. Children of a block are rendered in order, with the last child appearing on top. This layer stack model gives you precise control over how elements overlap and interact visually.
```typescript file=@cesdk_web_examples/guides-create-composition-layer-management-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Layer Management Guide
*
* This example demonstrates:
* - Navigating parent-child hierarchy
* - Adding and positioning blocks in the layer stack
* - Changing z-order (bring to front, send to back)
* - Controlling visibility
* - Duplicating and removing blocks
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0]!;
// Create a colored rectangle
const redRect = engine.block.create('graphic');
engine.block.setShape(redRect, engine.block.createShape('rect'));
const redFill = engine.block.createFill('color');
engine.block.setFill(redRect, redFill);
engine.block.setColor(redFill, 'fill/color/value', {
r: 0.9,
g: 0.2,
b: 0.2,
a: 1
});
engine.block.setWidth(redRect, 180);
engine.block.setHeight(redRect, 180);
engine.block.setPositionX(redRect, 220);
engine.block.setPositionY(redRect, 120);
// Create additional rectangles to demonstrate layer ordering
const greenRect = engine.block.create('graphic');
engine.block.setShape(greenRect, engine.block.createShape('rect'));
const greenFill = engine.block.createFill('color');
engine.block.setFill(greenRect, greenFill);
engine.block.setColor(greenFill, 'fill/color/value', {
r: 0.2,
g: 0.8,
b: 0.2,
a: 1
});
engine.block.setWidth(greenRect, 180);
engine.block.setHeight(greenRect, 180);
engine.block.setPositionX(greenRect, 280);
engine.block.setPositionY(greenRect, 180);
const blueRect = engine.block.create('graphic');
engine.block.setShape(blueRect, engine.block.createShape('rect'));
const blueFill = engine.block.createFill('color');
engine.block.setFill(blueRect, blueFill);
engine.block.setColor(blueFill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1
});
engine.block.setWidth(blueRect, 180);
engine.block.setHeight(blueRect, 180);
engine.block.setPositionX(blueRect, 340);
engine.block.setPositionY(blueRect, 240);
// Add blocks to the page - last appended is on top
engine.block.appendChild(page, redRect);
engine.block.appendChild(page, greenRect);
engine.block.appendChild(page, blueRect);
// Get the parent of a block
const parent = engine.block.getParent(redRect);
console.log('Parent of red rectangle:', parent);
// Get all children of the page
const children = engine.block.getChildren(page);
console.log('Page children (in render order):', children);
// Insert a new block at a specific position (index 0 = back)
const yellowRect = engine.block.create('graphic');
engine.block.setShape(yellowRect, engine.block.createShape('rect'));
const yellowFill = engine.block.createFill('color');
engine.block.setFill(yellowRect, yellowFill);
engine.block.setColor(yellowFill, 'fill/color/value', {
r: 0.95,
g: 0.85,
b: 0.2,
a: 1
});
engine.block.setWidth(yellowRect, 180);
engine.block.setHeight(yellowRect, 180);
engine.block.setPositionX(yellowRect, 160);
engine.block.setPositionY(yellowRect, 60);
engine.block.insertChild(page, yellowRect, 0);
// Bring the red rectangle to the front
engine.block.bringToFront(redRect);
console.log('Red rectangle brought to front');
// Send the blue rectangle to the back
engine.block.sendToBack(blueRect);
console.log('Blue rectangle sent to back');
// Move the green rectangle forward one layer
engine.block.bringForward(greenRect);
console.log('Green rectangle moved forward');
// Move the yellow rectangle backward one layer
engine.block.sendBackward(yellowRect);
console.log('Yellow rectangle moved backward');
// Check and toggle visibility
const isVisible = engine.block.isVisible(blueRect);
console.log('Blue rectangle visible:', isVisible);
// Hide the blue rectangle temporarily
engine.block.setVisible(blueRect, false);
console.log('Blue rectangle hidden');
// Show it again for the final composition
engine.block.setVisible(blueRect, true);
console.log('Blue rectangle shown again');
// Duplicate a block
const duplicateGreen = engine.block.duplicate(greenRect);
engine.block.setPositionX(duplicateGreen, 400);
engine.block.setPositionY(duplicateGreen, 300);
// Change the duplicate's color to purple
const purpleFill = engine.block.createFill('color');
engine.block.setFill(duplicateGreen, purpleFill);
engine.block.setColor(purpleFill, 'fill/color/value', {
r: 0.6,
g: 0.2,
b: 0.8,
a: 1
});
console.log('Green rectangle duplicated');
// Check if a block is valid before operations
const isValidBefore = engine.block.isValid(yellowRect);
console.log('Yellow rectangle valid before destroy:', isValidBefore);
// Remove a block from the scene
engine.block.destroy(yellowRect);
console.log('Yellow rectangle destroyed');
// Check validity after destruction
const isValidAfter = engine.block.isValid(yellowRect);
console.log('Yellow rectangle valid after destroy:', isValidAfter);
engine.scene.zoomToBlock(page, 40, 40, 40, 40);
}
}
export default Example;
```
This guide covers how to navigate the block hierarchy, reorder elements in the layer stack, toggle visibility, and manage block lifecycles through duplication and deletion.
## Creating Visual Blocks
To demonstrate layer ordering, we create colored rectangles that overlap on the canvas. Each block is created using `engine.block.create()` and configured with a shape, fill color, dimensions, and position.
```typescript highlight=highlight-create-block
// Create a colored rectangle
const redRect = engine.block.create('graphic');
engine.block.setShape(redRect, engine.block.createShape('rect'));
const redFill = engine.block.createFill('color');
engine.block.setFill(redRect, redFill);
engine.block.setColor(redFill, 'fill/color/value', {
r: 0.9,
g: 0.2,
b: 0.2,
a: 1
});
engine.block.setWidth(redRect, 180);
engine.block.setHeight(redRect, 180);
engine.block.setPositionX(redRect, 220);
engine.block.setPositionY(redRect, 120);
```
## Navigating the Block Hierarchy
CE.SDK organizes blocks in a parent-child tree structure. Every block can have one parent and multiple children. Understanding this hierarchy is essential for programmatic layer management.
### Getting a Block's Parent
We retrieve the parent of any block using `engine.block.getParent()`. This returns the parent's block ID, or null if the block has no parent (such as the scene root).
```typescript highlight=highlight-get-parent
// Get the parent of a block
const parent = engine.block.getParent(redRect);
console.log('Parent of red rectangle:', parent);
```
Knowing a block's parent helps you understand where it sits in the hierarchy and enables operations like reparenting or finding sibling blocks.
### Listing Child Blocks
We get all direct children of a block using `engine.block.getChildren()`. Children are returned sorted in their rendering order, where the last child renders in front of other children.
```typescript highlight=highlight-get-children
// Get all children of the page
const children = engine.block.getChildren(page);
console.log('Page children (in render order):', children);
```
This method is useful for iterating through all elements on a page or within a group.
## Adding and Positioning Blocks
When you create a new block, it exists independently until you add it to the hierarchy. There are two ways to attach blocks to a parent: appending to the end or inserting at a specific position.
### Appending Blocks
We add a block as the last child of a parent using `engine.block.appendChild()`. Since the last child renders on top, the appended block becomes the topmost element.
```typescript highlight=highlight-append-child
// Add blocks to the page - last appended is on top
engine.block.appendChild(page, redRect);
engine.block.appendChild(page, greenRect);
engine.block.appendChild(page, blueRect);
```
When you append multiple blocks in sequence, each new block appears in front of the previous ones.
### Inserting at a Specific Position
We insert a block at a specific index in the layer stack using `engine.block.insertChild()`. Index 0 places the block at the back, behind all other children.
```typescript highlight=highlight-insert-child
// Insert a new block at a specific position (index 0 = back)
const yellowRect = engine.block.create('graphic');
engine.block.setShape(yellowRect, engine.block.createShape('rect'));
const yellowFill = engine.block.createFill('color');
engine.block.setFill(yellowRect, yellowFill);
engine.block.setColor(yellowFill, 'fill/color/value', {
r: 0.95,
g: 0.85,
b: 0.2,
a: 1
});
engine.block.setWidth(yellowRect, 180);
engine.block.setHeight(yellowRect, 180);
engine.block.setPositionX(yellowRect, 160);
engine.block.setPositionY(yellowRect, 60);
engine.block.insertChild(page, yellowRect, 0);
```
This gives you precise control over where new elements appear in the stacking order.
### Reparenting Blocks
When you add a block to a new parent using `appendChild()` or `insertChild()`, it is automatically removed from its previous parent. This makes reparenting operations straightforward without needing to manually detach blocks first.
## Changing Z-Order
Once blocks are in the hierarchy, you can change their stacking order without removing and re-adding them. CE.SDK provides four methods for z-order manipulation.
### Bring to Front
We move an element to the top of its siblings using `engine.block.bringToFront()`. This gives the block the highest stacking order among its siblings.
```typescript highlight=highlight-bring-to-front
// Bring the red rectangle to the front
engine.block.bringToFront(redRect);
console.log('Red rectangle brought to front');
```
### Send to Back
We move an element behind all its siblings using `engine.block.sendToBack()`. This gives the block the lowest stacking order among its siblings.
```typescript highlight=highlight-send-to-back
// Send the blue rectangle to the back
engine.block.sendToBack(blueRect);
console.log('Blue rectangle sent to back');
```
### Move Forward One Layer
We move an element one position forward using `engine.block.bringForward()`. This swaps the block with its immediate sibling in front.
```typescript highlight=highlight-bring-forward
// Move the green rectangle forward one layer
engine.block.bringForward(greenRect);
console.log('Green rectangle moved forward');
```
### Move Backward One Layer
We move an element one position backward using `engine.block.sendBackward()`. This swaps the block with its immediate sibling behind.
```typescript highlight=highlight-send-backward
// Move the yellow rectangle backward one layer
engine.block.sendBackward(yellowRect);
console.log('Yellow rectangle moved backward');
```
These incremental operations are useful for fine-tuning the layer order without jumping to extremes.
## Controlling Visibility
Visibility allows you to temporarily hide elements without removing them from the scene. Hidden elements remain in the hierarchy and preserve their properties, but are not rendered.
### Checking and Toggling Visibility
We query the current visibility state using `engine.block.isVisible()` and change it using `engine.block.setVisible()`.
```typescript highlight=highlight-visibility
// Check and toggle visibility
const isVisible = engine.block.isVisible(blueRect);
console.log('Blue rectangle visible:', isVisible);
// Hide the blue rectangle temporarily
engine.block.setVisible(blueRect, false);
console.log('Blue rectangle hidden');
// Show it again for the final composition
engine.block.setVisible(blueRect, true);
console.log('Blue rectangle shown again');
```
Visibility is useful for creating before/after comparisons, hiding elements during editing, or implementing show/hide functionality in your application.
## Managing Block Lifecycle
CE.SDK provides methods for duplicating blocks to create copies and destroying blocks to remove them permanently.
### Duplicating Blocks
We create a copy of a block and all its children using `engine.block.duplicate()`. By default, the duplicate is attached to the same parent as the original.
```typescript highlight=highlight-duplicate
// Duplicate a block
const duplicateGreen = engine.block.duplicate(greenRect);
engine.block.setPositionX(duplicateGreen, 400);
engine.block.setPositionY(duplicateGreen, 300);
// Change the duplicate's color to purple
const purpleFill = engine.block.createFill('color');
engine.block.setFill(duplicateGreen, purpleFill);
engine.block.setColor(purpleFill, 'fill/color/value', {
r: 0.6,
g: 0.2,
b: 0.8,
a: 1
});
console.log('Green rectangle duplicated');
```
The duplicated block is positioned at the same location as the original. You typically want to reposition it to make it visible as a separate element.
### Checking Block Validity
Before performing operations on a block, we can verify it still exists using `engine.block.isValid()`. A block becomes invalid after it has been destroyed.
```typescript highlight=highlight-is-valid
// Check if a block is valid before operations
const isValidBefore = engine.block.isValid(yellowRect);
console.log('Yellow rectangle valid before destroy:', isValidBefore);
```
### Removing Blocks
We permanently remove a block and all its children from the scene using `engine.block.destroy()`. This operation cannot be undone programmatically.
```typescript highlight=highlight-destroy
// Remove a block from the scene
engine.block.destroy(yellowRect);
console.log('Yellow rectangle destroyed');
// Check validity after destruction
const isValidAfter = engine.block.isValid(yellowRect);
console.log('Yellow rectangle valid after destroy:', isValidAfter);
```
After destruction, any references to the block become invalid. Attempting to use an invalid block ID will result in errors.
## Framing the Result
After making layer changes, we zoom to fit the page in the viewport so the composition is clearly visible.
```typescript highlight=highlight-zoom
engine.scene.zoomToBlock(page, 40, 40, 40, 40);
```
## Troubleshooting
**Block not visible after appendChild**: The block may be behind other elements. Use `engine.block.bringToFront()` or adjust the insert index to control stacking order.
**getParent returns null**: The block is not attached to any parent. Use `engine.block.appendChild()` or `engine.block.insertChild()` to attach it to a page or container.
**Changes not reflected**: The block handle may be invalid. Check with `engine.block.isValid()` before performing operations.
**Z-order not updating**: Verify you're operating on the correct block ID and that the block is in the expected parent context.
**Duplicate not appearing**: If `attachToParent` is set to false, the duplicate won't be attached automatically. Set it to true or manually attach the duplicate to a parent.
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.getParent(id)` | Get the parent block of a given block |
| `engine.block.getChildren(id)` | Get all child blocks in rendering order |
| `engine.block.appendChild(parent, child)` | Append a block as the last child |
| `engine.block.insertChild(parent, child, index)` | Insert a block at a specific position |
| `engine.block.bringToFront(id)` | Bring a block to the front of its siblings |
| `engine.block.sendToBack(id)` | Send a block to the back of its siblings |
| `engine.block.bringForward(id)` | Move a block one position forward |
| `engine.block.sendBackward(id)` | Move a block one position backward |
| `engine.block.isVisible(id)` | Check if a block is visible |
| `engine.block.setVisible(id, visible)` | Set the visibility of a block |
| `engine.block.duplicate(id, attachToParent?)` | Duplicate a block and its children |
| `engine.block.destroy(id)` | Remove a block and its children |
| `engine.block.isValid(id)` | Check if a block handle is valid |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Design a Layout"
description: "Create structured compositions using scene layouts, positioning systems, and hierarchical block organization for collages, magazines, and multi-page documents."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/layout-b66311/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Design a Layout](https://img.ly/docs/cesdk/sveltekit/create-composition/layout-b66311/)
---
Create structured compositions using stack layouts that automatically arrange pages vertically or horizontally with consistent spacing.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-layout-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-layout-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-layout-browser/)
Stack layouts arrange pages automatically with consistent spacing. Vertical stacks arrange pages top-to-bottom, while horizontal stacks arrange them left-to-right. This eliminates manual positioning for compositions like photo collages, product catalogs, or social media carousels.
```typescript file=@cesdk_web_examples/guides-create-composition-layout-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) throw new Error('CE.SDK instance is required');
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
// Create a scene with vertical stack layout
// Pages arrange top-to-bottom automatically
engine.scene.create('VerticalStack');
// Get the stack container created with the scene
const [stack] = engine.block.findByType('stack');
// Create two pages that will stack vertically
const page1 = engine.block.create('page');
engine.block.setWidth(page1, 400);
engine.block.setHeight(page1, 300);
engine.block.appendChild(stack, page1);
const page2 = engine.block.create('page');
engine.block.setWidth(page2, 400);
engine.block.setHeight(page2, 300);
engine.block.appendChild(stack, page2);
// Configure spacing between stacked pages
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
// Add image content to page 1
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const block1 = await engine.block.addImage(imageUri, {
size: { width: 350, height: 250 }
});
engine.block.setPositionX(block1, 25);
engine.block.setPositionY(block1, 25);
engine.block.appendChild(page1, block1);
// Add a colored rectangle to page 2
const block2 = engine.block.create('graphic');
const shape2 = engine.block.createShape('rect');
engine.block.setShape(block2, shape2);
engine.block.setWidth(block2, 350);
engine.block.setHeight(block2, 250);
engine.block.setPositionX(block2, 25);
engine.block.setPositionY(block2, 25);
const fill2 = engine.block.createFill('color');
engine.block.setColor(fill2, 'fill/color/value', {
r: 0.3,
g: 0.6,
b: 0.9,
a: 1.0
});
engine.block.setFill(block2, fill2);
engine.block.appendChild(page2, block2);
// Switch to horizontal stack layout
// Pages now arrange left-to-right
engine.scene.setLayout('HorizontalStack');
// Verify the layout type
const currentLayout = engine.scene.getLayout();
console.log('Current layout:', currentLayout);
// Add a new page to the existing stack
// The page automatically appears at the end
const page3 = engine.block.create('page');
engine.block.setWidth(page3, 400);
engine.block.setHeight(page3, 300);
engine.block.appendChild(stack, page3);
// Add content to the new page
const block3 = engine.block.create('graphic');
const shape3 = engine.block.createShape('rect');
engine.block.setShape(block3, shape3);
engine.block.setWidth(block3, 350);
engine.block.setHeight(block3, 250);
engine.block.setPositionX(block3, 25);
engine.block.setPositionY(block3, 25);
const fill3 = engine.block.createFill('color');
engine.block.setColor(fill3, 'fill/color/value', {
r: 0.9,
g: 0.5,
b: 0.3,
a: 1.0
});
engine.block.setFill(block3, fill3);
engine.block.appendChild(page3, block3);
// Reorder pages using insertChild
// Move page3 to the first position
engine.block.insertChild(stack, page3, 0);
// Verify the new order
const pageOrder = engine.block.getChildren(stack);
console.log('Page order after reordering:', pageOrder);
// Update spacing between stacked pages
engine.block.setFloat(stack, 'stack/spacing', 40);
// Verify the spacing value
const updatedSpacing = engine.block.getFloat(stack, 'stack/spacing');
console.log('Updated spacing:', updatedSpacing);
// Zoom to show all pages in the stack
await engine.scene.zoomToBlock(stack, { padding: 50 });
}
}
export default Example;
```
This guide covers how to:
- Create vertical and horizontal stack layouts
- Add pages and blocks to stacks
- Configure spacing between stacked pages
- Reorder pages within a stack
- Switch between stack and free layouts
## Create a Vertical Stack Layout
Vertical stacks arrange pages from top to bottom. Create a scene with `VerticalStack` layout, then add pages to the stack container.
```typescript highlight=highlight-vertical-stack
// Create a scene with vertical stack layout
// Pages arrange top-to-bottom automatically
engine.scene.create('VerticalStack');
// Get the stack container created with the scene
const [stack] = engine.block.findByType('stack');
// Create two pages that will stack vertically
const page1 = engine.block.create('page');
engine.block.setWidth(page1, 400);
engine.block.setHeight(page1, 300);
engine.block.appendChild(stack, page1);
const page2 = engine.block.create('page');
engine.block.setWidth(page2, 400);
engine.block.setHeight(page2, 300);
engine.block.appendChild(stack, page2);
// Configure spacing between stacked pages
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
```
When you create a scene with `VerticalStack` layout, CE.SDK automatically creates a stack container. Pages added to this container are positioned vertically with the configured spacing. The `spacingInScreenspace` property ensures spacing remains consistent regardless of zoom level.
## Add Blocks to Pages
Each page can contain multiple blocks. Create blocks with shapes and fills, position them within the page, then append them to the page.
```typescript highlight=highlight-add-blocks
// Add image content to page 1
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const block1 = await engine.block.addImage(imageUri, {
size: { width: 350, height: 250 }
});
engine.block.setPositionX(block1, 25);
engine.block.setPositionY(block1, 25);
engine.block.appendChild(page1, block1);
// Add a colored rectangle to page 2
const block2 = engine.block.create('graphic');
const shape2 = engine.block.createShape('rect');
engine.block.setShape(block2, shape2);
engine.block.setWidth(block2, 350);
engine.block.setHeight(block2, 250);
engine.block.setPositionX(block2, 25);
engine.block.setPositionY(block2, 25);
const fill2 = engine.block.createFill('color');
engine.block.setColor(fill2, 'fill/color/value', {
r: 0.3,
g: 0.6,
b: 0.9,
a: 1.0
});
engine.block.setFill(block2, fill2);
engine.block.appendChild(page2, block2);
```
Blocks require a shape and fill to be visible. Use `addImage()` for image content, or create graphic blocks with custom shapes and color fills. Position blocks within their parent page using `setPositionX()` and `setPositionY()`.
## Switch to Horizontal Layout
Change the layout direction at any time using `setLayout()`. Horizontal stacks arrange pages left-to-right instead of top-to-bottom.
```typescript highlight=highlight-horizontal-stack
// Switch to horizontal stack layout
// Pages now arrange left-to-right
engine.scene.setLayout('HorizontalStack');
// Verify the layout type
const currentLayout = engine.scene.getLayout();
console.log('Current layout:', currentLayout);
```
Horizontal layouts work well for carousels, timelines, and horizontal galleries. Existing pages automatically reposition when you change the layout type.
## Add Pages to Existing Stacks
Add new pages to an existing stack at any time. Pages automatically appear at the end of the stack with proper spacing.
```typescript highlight=highlight-add-page
// Add a new page to the existing stack
// The page automatically appears at the end
const page3 = engine.block.create('page');
engine.block.setWidth(page3, 400);
engine.block.setHeight(page3, 300);
engine.block.appendChild(stack, page3);
// Add content to the new page
const block3 = engine.block.create('graphic');
const shape3 = engine.block.createShape('rect');
engine.block.setShape(block3, shape3);
engine.block.setWidth(block3, 350);
engine.block.setHeight(block3, 250);
engine.block.setPositionX(block3, 25);
engine.block.setPositionY(block3, 25);
const fill3 = engine.block.createFill('color');
engine.block.setColor(fill3, 'fill/color/value', {
r: 0.9,
g: 0.5,
b: 0.3,
a: 1.0
});
engine.block.setFill(block3, fill3);
engine.block.appendChild(page3, block3);
```
The stack container manages positioning automatically. You can add content to the new page before or after appending it to the stack.
## Reorder Pages
Change page order using `insertChild()` to place a page at a specific index within the stack.
```typescript highlight=highlight-reorder
// Reorder pages using insertChild
// Move page3 to the first position
engine.block.insertChild(stack, page3, 0);
// Verify the new order
const pageOrder = engine.block.getChildren(stack);
console.log('Page order after reordering:', pageOrder);
```
Removing a page from its current position and reinserting it at index 0 moves it to the first position. All other pages shift to accommodate the change.
## Change Stack Spacing
Adjust spacing between pages using the `stack/spacing` property on the stack block.
```typescript highlight=highlight-spacing
// Update spacing between stacked pages
engine.block.setFloat(stack, 'stack/spacing', 40);
// Verify the spacing value
const updatedSpacing = engine.block.getFloat(stack, 'stack/spacing');
console.log('Updated spacing:', updatedSpacing);
```
Spacing updates immediately and pages reposition automatically. Use `getFloat()` to verify the current spacing value.
## Switch to Free Layout
For manual positioning, switch to `Free` layout. Pages keep their positions but stop auto-arranging.
```typescript
// Check current layout type
const layout = engine.scene.getLayout();
// Convert to free layout for manual positioning
engine.scene.setLayout('Free');
// Now position pages manually
const [page] = engine.block.findByType('page');
engine.block.setPositionX(page, 100);
engine.block.setPositionY(page, 200);
```
Free layout gives full control over page positions. Use this when you need precise positioning that stack layouts cannot provide.
## Troubleshooting
**Pages not arranging automatically** — Verify the scene layout type is `VerticalStack` or `HorizontalStack` using `getLayout()`.
**Spacing not applying** — Check that you're setting spacing on the stack block, not the scene. Use `findByType('stack')` to get the stack container.
**Pages overlapping** — Ensure pages are direct children of the stack container. Nested pages won't auto-arrange properly.
**Can't position manually** — Stack layouts override manual positions. Switch to `Free` layout for manual control.
**Wrong stacking order** — Child order determines position. Use `insertChild()` to move pages to specific positions.
## API Reference
| Method | Description |
|--------|-------------|
| `engine.scene.create(layout)` | Create a scene with specified layout (`'Free'`, `'VerticalStack'`, `'HorizontalStack'`) |
| `engine.scene.setLayout(layout)` | Change the layout type of the current scene |
| `engine.scene.getLayout()` | Get the current layout type |
| `engine.block.findByType('stack')` | Find the stack container block |
| `engine.block.setFloat(id, 'stack/spacing', value)` | Set spacing between stacked pages |
| `engine.block.getFloat(id, 'stack/spacing')` | Get current spacing value |
| `engine.block.appendChild(parent, child)` | Add a page to the stack |
| `engine.block.insertChild(parent, child, index)` | Insert a page at a specific position |
| `engine.block.getChildren(id)` | Get child blocks in order |
## Next Steps
- [Auto-resize](https://img.ly/docs/cesdk/sveltekit/automation/auto-resize-4c2d58/) — Make blocks fit parent containers
- [Manual Positioning](https://img.ly/docs/cesdk/sveltekit/edit-image/transform/move-818dd9/) — Position blocks in free layouts
- [Layer Hierarchies](https://img.ly/docs/cesdk/sveltekit/create-composition/layer-management-18f07a/) — Organize blocks in hierarchical structures
- [Create a Collage](https://img.ly/docs/cesdk/sveltekit/create-composition/collage-f7d28d/) — Build photo collages with templates
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Lock Design"
description: "Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. Control which properties users can edit at both global and block levels."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/lock-design-0a81de/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Lock Design](https://img.ly/docs/cesdk/sveltekit/create-composition/lock-design-0a81de/)
---
Protect design elements from unwanted modifications using CE.SDK's scope-based permission system.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-lock-design-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-lock-design-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-lock-design-browser/)
CE.SDK uses a two-layer scope system to control editing permissions. Global scopes set defaults for the entire scene, while block-level scopes override when the global setting is `Defer`. This enables flexible permission models from fully locked to selectively editable designs.
```typescript file=@cesdk_web_examples/guides-create-composition-lock-design-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext, Scope } from '@cesdk/cesdk-js';
import packageJson from './package.json';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
class LockDesign implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0]!;
// Create sample content to demonstrate different locking techniques
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Column 1: Fully Locked
const caption1 = engine.block.create('text');
engine.block.setString(caption1, 'text/text', 'Fully Locked');
engine.block.setFloat(caption1, 'text/fontSize', 32);
engine.block.setWidth(caption1, 220);
engine.block.setHeight(caption1, 50);
engine.block.setPositionX(caption1, 30);
engine.block.setPositionY(caption1, 30);
engine.block.appendChild(page, caption1);
const imageBlock = await engine.block.addImage(imageUri, {
x: 30,
y: 100,
size: { width: 220, height: 165 }
});
engine.block.appendChild(page, imageBlock);
// Column 2: Text Editing Only
const caption2 = engine.block.create('text');
engine.block.setString(caption2, 'text/text', 'Text Editing');
engine.block.setFloat(caption2, 'text/fontSize', 32);
engine.block.setWidth(caption2, 220);
engine.block.setHeight(caption2, 50);
engine.block.setPositionX(caption2, 290);
engine.block.setPositionY(caption2, 30);
engine.block.appendChild(page, caption2);
const textBlock = engine.block.create('text');
engine.block.setString(textBlock, 'text/text', 'Edit Me');
engine.block.setFloat(textBlock, 'text/fontSize', 72);
engine.block.setWidth(textBlock, 220);
engine.block.setHeight(textBlock, 165);
engine.block.setPositionX(textBlock, 290);
engine.block.setPositionY(textBlock, 100);
engine.block.appendChild(page, textBlock);
// Column 3: Image Replace Only
const caption3 = engine.block.create('text');
engine.block.setString(caption3, 'text/text', 'Image Replace');
engine.block.setFloat(caption3, 'text/fontSize', 32);
engine.block.setWidth(caption3, 220);
engine.block.setHeight(caption3, 50);
engine.block.setPositionX(caption3, 550);
engine.block.setPositionY(caption3, 30);
engine.block.appendChild(page, caption3);
const placeholderBlock = await engine.block.addImage(imageUri, {
x: 550,
y: 100,
size: { width: 220, height: 165 }
});
engine.block.appendChild(page, placeholderBlock);
// Lock the entire design by setting all scopes to Deny
const scopes = engine.editor.findAllScopes();
for (const scope of scopes) {
engine.editor.setGlobalScope(scope, 'Deny');
}
// Enable selection for specific blocks
engine.editor.setGlobalScope('editor/select', 'Defer');
engine.block.setScopeEnabled(textBlock, 'editor/select', true);
engine.block.setScopeEnabled(placeholderBlock, 'editor/select', true);
// Enable text editing on the text block
engine.editor.setGlobalScope('text/edit', 'Defer');
engine.editor.setGlobalScope('text/character', 'Defer');
engine.block.setScopeEnabled(textBlock, 'text/edit', true);
engine.block.setScopeEnabled(textBlock, 'text/character', true);
// Enable image replacement on the placeholder block
engine.editor.setGlobalScope('fill/change', 'Defer');
engine.block.setScopeEnabled(placeholderBlock, 'fill/change', true);
// Check if operations are permitted on blocks
const canEditText = engine.block.isAllowedByScope(textBlock, 'text/edit');
const canMoveImage = engine.block.isAllowedByScope(
imageBlock,
'layer/move'
);
const canReplacePlaceholder = engine.block.isAllowedByScope(
placeholderBlock,
'fill/change'
);
console.log('Permission status:');
console.log('- Can edit text:', canEditText); // true
console.log('- Can move locked image:', canMoveImage); // false
console.log('- Can replace placeholder:', canReplacePlaceholder); // true
// Discover all available scopes
const allScopes: Scope[] = engine.editor.findAllScopes();
console.log('Available scopes:', allScopes);
// Check global scope settings
const textEditGlobal = engine.editor.getGlobalScope('text/edit');
const layerMoveGlobal = engine.editor.getGlobalScope('layer/move');
console.log('Global text/edit:', textEditGlobal); // 'Defer'
console.log('Global layer/move:', layerMoveGlobal); // 'Deny'
// Check block-level scope settings
const textEditEnabled = engine.block.isScopeEnabled(textBlock, 'text/edit');
console.log('Text block text/edit enabled:', textEditEnabled); // true
// Select the text block to demonstrate editability
engine.block.select(textBlock);
}
}
export default LockDesign;
```
This guide covers how to lock entire designs, selectively enable specific editing capabilities, and check permissions programmatically.
## Understanding the Scope Permission Model
Scopes control what operations users can perform on design elements. CE.SDK combines global scope settings with block-level settings to determine the final permission.
| Global Scope | Block Scope | Result |
| ------------ | ----------- | --------- |
| `Allow` | any | Permitted |
| `Deny` | any | Blocked |
| `Defer` | enabled | Permitted |
| `Defer` | disabled | Blocked |
Global scopes have three possible values:
- **`Allow`**: The operation is always permitted, regardless of block-level settings
- **`Deny`**: The operation is always blocked, regardless of block-level settings
- **`Defer`**: The permission depends on the block-level scope setting
Block-level scopes are binary: enabled or disabled. They only take effect when the global scope is set to `Defer`.
## Locking an Entire Design
To lock all editing operations, iterate through all available scopes and set each to `Deny`. We use `engine.editor.findAllScopes()` to discover all scope names dynamically.
```typescript highlight=highlight-lock-entire-design
// Lock the entire design by setting all scopes to Deny
const scopes = engine.editor.findAllScopes();
for (const scope of scopes) {
engine.editor.setGlobalScope(scope, 'Deny');
}
```
When all scopes are set to `Deny`, users cannot modify any aspect of the design. This includes selecting, moving, editing text, or changing any visual properties.
## Enabling Selection for Interactive Blocks
Before users can interact with any block, you must enable the `editor/select` scope. Without selection, users cannot click on or access any blocks, even if other editing capabilities are enabled.
```typescript highlight=highlight-enable-selection
// Enable selection for specific blocks
engine.editor.setGlobalScope('editor/select', 'Defer');
engine.block.setScopeEnabled(textBlock, 'editor/select', true);
engine.block.setScopeEnabled(placeholderBlock, 'editor/select', true);
```
Setting the global `editor/select` scope to `Defer` delegates the decision to each block. We then enable selection only on the specific blocks users should be able to interact with.
## Selective Locking Patterns
Lock everything first, then selectively enable specific capabilities on chosen blocks. This pattern provides fine-grained control over what users can modify.
### Text-Only Editing
To allow users to edit text content while protecting everything else, enable the `text/edit` scope. For text styling changes like font, size, and color, also enable `text/character`.
```typescript highlight=highlight-text-editing
// Enable text editing on the text block
engine.editor.setGlobalScope('text/edit', 'Defer');
engine.editor.setGlobalScope('text/character', 'Defer');
engine.block.setScopeEnabled(textBlock, 'text/edit', true);
engine.block.setScopeEnabled(textBlock, 'text/character', true);
```
Users can now type new text content in the designated text block but cannot move, resize, or delete it.
### Image Replacement
To allow users to swap images while protecting layout and position, enable the `fill/change` scope on placeholder blocks.
```typescript highlight=highlight-image-replacement
// Enable image replacement on the placeholder block
engine.editor.setGlobalScope('fill/change', 'Defer');
engine.block.setScopeEnabled(placeholderBlock, 'fill/change', true);
```
Users can replace the image content but the block's position, dimensions, and other properties remain locked.
## Checking Permissions
Verify whether operations are permitted using `engine.block.isAllowedByScope()`. This method evaluates both global and block-level settings to return the effective permission state.
```typescript highlight=highlight-check-permissions
// Check if operations are permitted on blocks
const canEditText = engine.block.isAllowedByScope(textBlock, 'text/edit');
const canMoveImage = engine.block.isAllowedByScope(
imageBlock,
'layer/move'
);
const canReplacePlaceholder = engine.block.isAllowedByScope(
placeholderBlock,
'fill/change'
);
console.log('Permission status:');
console.log('- Can edit text:', canEditText); // true
console.log('- Can move locked image:', canMoveImage); // false
console.log('- Can replace placeholder:', canReplacePlaceholder); // true
```
The distinction between checking methods is:
- `isAllowedByScope()` returns the **effective permission** after evaluating all scope levels
- `isScopeEnabled()` returns only the **block-level setting**
- `getGlobalScope()` returns only the **global setting**
## Discovering Available Scopes
To work with scopes programmatically, you can discover all available scope names and check their current settings.
```typescript highlight=highlight-get-scopes
// Discover all available scopes
const allScopes: Scope[] = engine.editor.findAllScopes();
console.log('Available scopes:', allScopes);
// Check global scope settings
const textEditGlobal = engine.editor.getGlobalScope('text/edit');
const layerMoveGlobal = engine.editor.getGlobalScope('layer/move');
console.log('Global text/edit:', textEditGlobal); // 'Defer'
console.log('Global layer/move:', layerMoveGlobal); // 'Deny'
// Check block-level scope settings
const textEditEnabled = engine.block.isScopeEnabled(textBlock, 'text/edit');
console.log('Text block text/edit enabled:', textEditEnabled); // true
```
## Available Scopes Reference
| Scope | Description |
| ------------------------ | --------------------------------------- |
| `layer/move` | Move block position |
| `layer/resize` | Resize block dimensions |
| `layer/rotate` | Rotate block |
| `layer/flip` | Flip block horizontally or vertically |
| `layer/crop` | Crop block content |
| `layer/opacity` | Change block opacity |
| `layer/blendMode` | Change blend mode |
| `layer/visibility` | Toggle block visibility |
| `layer/clipping` | Change clipping behavior |
| `fill/change` | Change fill content |
| `fill/changeType` | Change fill type |
| `stroke/change` | Change stroke properties |
| `shape/change` | Change shape type |
| `text/edit` | Edit text content |
| `text/character` | Change text styling (font, size, color) |
| `appearance/adjustments` | Change color adjustments |
| `appearance/filter` | Apply or change filters |
| `appearance/effect` | Apply or change effects |
| `appearance/blur` | Apply or change blur |
| `appearance/shadow` | Apply or change shadows |
| `appearance/animation` | Apply or change animations |
| `lifecycle/destroy` | Delete the block |
| `lifecycle/duplicate` | Duplicate the block |
| `editor/add` | Add new blocks |
| `editor/select` | Select blocks |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Multi-Page Layouts"
description: "Create and manage multi-page designs in CE.SDK for documents like brochures, presentations, and catalogs with multiple pages in a single scene."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/multi-page-4d2b50/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Multi-Page Layouts](https://img.ly/docs/cesdk/sveltekit/create-composition/multi-page-4d2b50/)
---
Create multi-page designs in CE.SDK for brochures, presentations, catalogs, and other documents requiring multiple pages within a single scene.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-multi-page-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-multi-page-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-multi-page-browser/)
Multi-page layouts allow you to create documents with multiple artboards within a single scene. Each page operates as an independent canvas that can contain different content while sharing the same scene context. CE.SDK provides scene layout modes that automatically arrange pages vertically, horizontally, or in a free-form canvas.
```typescript file=@cesdk_web_examples/guides-create-composition-multi-page-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Multi-Page Layouts Guide
*
* This example demonstrates:
* - Creating scenes with multiple pages
* - Adding and configuring pages
* - Scene layout types (HorizontalStack)
* - Stack spacing between pages
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
// Create a scene with HorizontalStack layout
engine.scene.create('HorizontalStack');
// Get the stack container
const [stack] = engine.block.findByType('stack');
// Add spacing between pages (20 pixels in screen space)
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
// Create the first page
const firstPage = engine.block.create('page');
engine.block.setWidth(firstPage, 800);
engine.block.setHeight(firstPage, 600);
engine.block.appendChild(stack, firstPage);
// Add content to the first page
const imageBlock1 = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_1.jpg',
{ size: { width: 300, height: 200 } }
);
engine.block.setPositionX(imageBlock1, 250);
engine.block.setPositionY(imageBlock1, 200);
engine.block.appendChild(firstPage, imageBlock1);
// Create a second page with different content
const secondPage = engine.block.create('page');
engine.block.setWidth(secondPage, 800);
engine.block.setHeight(secondPage, 600);
engine.block.appendChild(stack, secondPage);
// Add a different image to the second page
const imageBlock2 = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_2.jpg',
{ size: { width: 300, height: 200 } }
);
engine.block.setPositionX(imageBlock2, 250);
engine.block.setPositionY(imageBlock2, 200);
engine.block.appendChild(secondPage, imageBlock2);
engine.block.select(firstPage);
engine.scene.enableZoomAutoFit(firstPage, 'Both');
}
}
export default Example;
```
This guide covers how to create multi-page scenes, add pages, and configure spacing between pages.
## Using the Built-in Page Management UI
The CE.SDK editor provides built-in UI controls for managing pages. Users can interact with the page panel to add new pages, duplicate existing ones, reorder them with drag-and-drop, delete pages, and navigate between pages by clicking.
The page panel displays thumbnails of all pages in the scene, making it easy to understand the document structure at a glance. When you click a page thumbnail, the viewport automatically zooms to that page.
## Creating Multi-Page Scenes Programmatically
We can create scenes with multiple pages using the engine API. The scene acts as a container for pages, and each page can hold any number of content blocks.
### Creating a Scene with Pages
We create a new scene using `engine.scene.create()` and specify the layout type. The layout type determines how pages are arranged in the viewport. After creating the scene, we get the stack container and create pages within it.
```typescript highlight=highlight-create-scene
// Create a scene with HorizontalStack layout
engine.scene.create('HorizontalStack');
// Get the stack container
const [stack] = engine.block.findByType('stack');
// Add spacing between pages (20 pixels in screen space)
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
// Create the first page
const firstPage = engine.block.create('page');
engine.block.setWidth(firstPage, 800);
engine.block.setHeight(firstPage, 600);
engine.block.appendChild(stack, firstPage);
```
The scene is created with a `HorizontalStack` layout, meaning pages are arranged side by side from left to right. We then create a page, set its dimensions, and append it to the stack container.
### Configuring Page Spacing
We can add spacing between pages in a stack layout using the `stack/spacing` property. This creates visual separation between pages.
```typescript highlight=highlight-stack-spacing
// Add spacing between pages (20 pixels in screen space)
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
```
Setting `stack/spacingInScreenspace` to `true` means the spacing value is interpreted as screen pixels, maintaining consistent visual spacing regardless of zoom level.
### Adding More Pages
To add additional pages, we create new page blocks, set their dimensions, and append them to the stack container.
```typescript highlight=highlight-add-page
// Create a second page with different content
const secondPage = engine.block.create('page');
engine.block.setWidth(secondPage, 800);
engine.block.setHeight(secondPage, 600);
engine.block.appendChild(stack, secondPage);
// Add a different image to the second page
const imageBlock2 = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_2.jpg',
{ size: { width: 300, height: 200 } }
);
engine.block.setPositionX(imageBlock2, 250);
engine.block.setPositionY(imageBlock2, 200);
engine.block.appendChild(secondPage, imageBlock2);
```
Each page can contain different content. Here we add different images to each page to demonstrate independent page content.
## Scene Layout Types
CE.SDK supports different layout modes that control how pages are arranged on the canvas. You specify the layout type when creating the scene with `engine.scene.create()`.
**Free Layout** is the default where pages can be positioned anywhere on the canvas. This provides complete control over page placement.
**VerticalStack Layout** arranges pages automatically in a vertical stack from top to bottom. This is useful for scroll-based document previews.
**HorizontalStack Layout** arranges pages side by side from left to right. This is useful for carousel-style presentations or side-by-side comparisons.
## Setting the Zoom Level
We can control the viewport zoom level using `engine.scene.setZoomLevel()`. A value of 1.0 represents 100% zoom.
```typescript highlight=highlight-zoom
engine.block.select(firstPage);
engine.scene.enableZoomAutoFit(firstPage, 'Both');
```
## Troubleshooting
**Page not visible after creation**: Ensure the page is attached to the stack with `appendChild()` and has valid dimensions set with `setWidth()` and `setHeight()`.
**Cannot add content to page**: Verify you're appending blocks to the page block, not the scene directly. Content blocks should be children of pages.
**Pages overlapping**: When using stack layouts, make sure pages are appended to the stack container (found via `findByType('stack')`), not directly to the scene.
**Spacing not visible**: Check that `stack/spacing` is set to a positive value and that you're using a stack layout (HorizontalStack or VerticalStack).
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Combine and arrange multiple elements to create complex, multi-page, or layered design compositions."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/overview-5b19c5/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Overview](https://img.ly/docs/cesdk/sveltekit/create-composition/overview-5b19c5/)
---
## Exporting Compositions
CE.SDK compositions can be exported in several formats:
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Programmatic Creation"
description: "Documentation for Programmatic Creation"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-composition/programmatic-a688bf/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/sveltekit/create-composition-db709c/) > [Programmatic Creation](https://img.ly/docs/cesdk/sveltekit/create-composition/programmatic-a688bf/)
---
Build compositions entirely through code using CE.SDK's APIs for automation, batch processing, and custom interfaces.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-programmatic-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-programmatic-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-programmatic-browser/)
CE.SDK provides a complete API for building designs through code. Instead of relying on user interactions through the built-in UI, you can create scenes, add blocks like text, images, and shapes, and position them programmatically. This approach enables automation workflows, batch processing, server-side rendering, and integration with custom interfaces.
```typescript file=@cesdk_web_examples/guides-create-composition-programmatic-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Programmatic Creation Guide
*
* Demonstrates building compositions entirely through code:
* - Creating scenes and pages with social media dimensions
* - Setting page background colors
* - Adding text blocks with mixed styling (bold, italic, colors)
* - Adding line shapes as dividers
* - Adding images
* - Positioning and sizing blocks
*/
// Roboto typeface with all variants for mixed styling
const ROBOTO_TYPEFACE = {
name: 'Roboto',
fonts: [
{
uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf',
subFamily: 'Regular'
},
{
uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf',
subFamily: 'Bold',
weight: 'bold' as const
},
{
uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf',
subFamily: 'Italic',
style: 'italic' as const
},
{
uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf',
subFamily: 'Bold Italic',
weight: 'bold' as const,
style: 'italic' as const
}
]
};
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 1080, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const scene = engine.scene.get()!;
engine.block.setFloat(scene, 'scene/dpi', 300);
const page = engine.block.findByType('page')[0];
// Set page background to light lavender color
const backgroundFill = engine.block.createFill('color');
engine.block.setColor(backgroundFill, 'fill/color/value', {
r: 0.94,
g: 0.93,
b: 0.98,
a: 1.0
});
engine.block.setFill(page, backgroundFill);
// Add main headline text with bold Roboto font
const headline = engine.block.create('text');
engine.block.replaceText(
headline,
'Integrate\nCreative Editing\ninto your App'
);
engine.block.setFont(
headline,
ROBOTO_TYPEFACE.fonts[0].uri,
ROBOTO_TYPEFACE
);
engine.block.setFloat(headline, 'text/lineHeight', 0.78);
// Make headline bold
if (engine.block.canToggleBoldFont(headline)) {
engine.block.toggleBoldFont(headline);
}
engine.block.setTextColor(headline, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 });
// Set fixed container size and enable automatic font sizing
engine.block.setWidthMode(headline, 'Absolute');
engine.block.setHeightMode(headline, 'Absolute');
engine.block.setWidth(headline, 960);
engine.block.setHeight(headline, 300);
engine.block.setBool(headline, 'text/automaticFontSizeEnabled', true);
engine.block.setPositionX(headline, 60);
engine.block.setPositionY(headline, 80);
engine.block.appendChild(page, headline);
// Add tagline with mixed styling using range-based APIs
// "in hours," (purple italic) + "not months." (black bold)
const tagline = engine.block.create('text');
const taglineText = 'in hours,\nnot months.';
engine.block.replaceText(tagline, taglineText);
// Set up Roboto typeface with all variants for mixed styling
engine.block.setFont(
tagline,
ROBOTO_TYPEFACE.fonts[0].uri,
ROBOTO_TYPEFACE
);
engine.block.setFloat(tagline, 'text/lineHeight', 0.78);
// Style "in hours," - purple and italic (characters 0-9)
engine.block.setTextColor(
tagline,
{ r: 0.2, g: 0.2, b: 0.8, a: 1.0 },
0,
9
);
if (engine.block.canToggleItalicFont(tagline, 0, 9)) {
engine.block.toggleItalicFont(tagline, 0, 9);
}
// Style "not months." - black and bold (characters 10-21)
engine.block.setTextColor(
tagline,
{ r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
10,
21
);
if (engine.block.canToggleBoldFont(tagline, 10, 21)) {
engine.block.toggleBoldFont(tagline, 10, 21);
}
// Set fixed container size and enable automatic font sizing
engine.block.setWidthMode(tagline, 'Absolute');
engine.block.setHeightMode(tagline, 'Absolute');
engine.block.setWidth(tagline, 960);
engine.block.setHeight(tagline, 220);
engine.block.setBool(tagline, 'text/automaticFontSizeEnabled', true);
engine.block.setPositionX(tagline, 60);
engine.block.setPositionY(tagline, 551);
engine.block.appendChild(page, tagline);
// Add CTA text "Start a Free Trial" with bold font
const ctaTitle = engine.block.create('text');
engine.block.replaceText(ctaTitle, 'Start a Free Trial');
engine.block.setFont(
ctaTitle,
ROBOTO_TYPEFACE.fonts[0].uri,
ROBOTO_TYPEFACE
);
engine.block.setFloat(ctaTitle, 'text/fontSize', 80);
engine.block.setFloat(ctaTitle, 'text/lineHeight', 1.0);
if (engine.block.canToggleBoldFont(ctaTitle)) {
engine.block.toggleBoldFont(ctaTitle);
}
engine.block.setTextColor(ctaTitle, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 });
engine.block.setWidthMode(ctaTitle, 'Absolute');
engine.block.setHeightMode(ctaTitle, 'Auto');
engine.block.setWidth(ctaTitle, 664.6);
engine.block.setPositionX(ctaTitle, 64);
engine.block.setPositionY(ctaTitle, 952);
engine.block.appendChild(page, ctaTitle);
// Add website URL with regular font
const ctaUrl = engine.block.create('text');
engine.block.replaceText(ctaUrl, 'www.img.ly');
engine.block.setFont(ctaUrl, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE);
engine.block.setFloat(ctaUrl, 'text/fontSize', 80);
engine.block.setFloat(ctaUrl, 'text/lineHeight', 1.0);
engine.block.setTextColor(ctaUrl, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 });
engine.block.setWidthMode(ctaUrl, 'Absolute');
engine.block.setHeightMode(ctaUrl, 'Auto');
engine.block.setWidth(ctaUrl, 664.6);
engine.block.setPositionX(ctaUrl, 64);
engine.block.setPositionY(ctaUrl, 1006);
engine.block.appendChild(page, ctaUrl);
// Add horizontal divider line
const dividerLine = engine.block.create('graphic');
const lineShape = engine.block.createShape('line');
engine.block.setShape(dividerLine, lineShape);
const lineFill = engine.block.createFill('color');
engine.block.setColor(lineFill, 'fill/color/value', {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0
});
engine.block.setFill(dividerLine, lineFill);
engine.block.setWidth(dividerLine, 418);
engine.block.setHeight(dividerLine, 11.3);
engine.block.setPositionX(dividerLine, 64);
engine.block.setPositionY(dividerLine, 460);
engine.block.appendChild(page, dividerLine);
// Add IMG.LY logo image
const logo = engine.block.create('graphic');
const logoShape = engine.block.createShape('rect');
engine.block.setShape(logo, logoShape);
const logoFill = engine.block.createFill('image');
engine.block.setString(
logoFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/imgly_logo.jpg'
);
engine.block.setFill(logo, logoFill);
engine.block.setContentFillMode(logo, 'Contain');
engine.block.setWidth(logo, 200);
engine.block.setHeight(logo, 65);
engine.block.setPositionX(logo, 820);
engine.block.setPositionY(logo, 960);
engine.block.appendChild(page, logo);
// Export the composition to PNG
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1080,
targetHeight: 1080
});
// In browser, create a download link
const url = URL.createObjectURL(blob);
console.log('Export complete. Download URL:', url);
// Zoom to show the page
await cesdk.actions.run('zoom.toPage', { autoFit: true });
}
}
export default Example;
```
This guide covers how to create a scene structure with social media dimensions, set background colors, add text with mixed styling, line shapes, images, and export the finished composition.
## Initialize CE.SDK
We start by initializing CE.SDK and loading the asset sources. The asset source plugins (imported from `@cesdk/cesdk-js/plugins`) provide access to fonts, images, and other assets.
```typescript highlight=highlight-setup
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
```
## Create Scene Structure
We create the foundation of our composition with social media dimensions (1080x1080 pixels for Instagram). A scene contains one or more pages, and pages contain the design blocks.
```typescript highlight=highlight-create-scene
await cesdk.actions.run('scene.create', {
page: { width: 1080, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
const scene = engine.scene.get()!;
```
The `cesdk.actions.run('scene.create')` method creates a new design scene with a page. We set the page dimensions using `setWidth()` and `setHeight()`.
## Set Page Background
We set the page background using a color fill. This demonstrates how to create and assign fills to blocks.
```typescript highlight=highlight-add-background
// Set page background to light lavender color
const backgroundFill = engine.block.createFill('color');
engine.block.setColor(backgroundFill, 'fill/color/value', {
r: 0.94,
g: 0.93,
b: 0.98,
a: 1.0
});
engine.block.setFill(page, backgroundFill);
```
We create a color fill using `createFill('color')`, set the color via `setColor()` with the `fill/color/value` property, then assign the fill to the page.
## Add Text Blocks
Text blocks allow you to add and style text content. We demonstrate three different approaches to text sizing and styling.
### Create Text and Set Content
Create a text block and set its content with `replaceText()`:
```typescript highlight=highlight-text-create
// Add main headline text with bold Roboto font
const headline = engine.block.create('text');
engine.block.replaceText(
headline,
'Integrate\nCreative Editing\ninto your App'
);
engine.block.setFont(
headline,
ROBOTO_TYPEFACE.fonts[0].uri,
ROBOTO_TYPEFACE
);
engine.block.setFloat(headline, 'text/lineHeight', 0.78);
```
### Style Entire Text Block
Apply styling to the entire text block using `toggleBoldFont()` and `setTextColor()`:
```typescript highlight=highlight-text-style-block
// Make headline bold
if (engine.block.canToggleBoldFont(headline)) {
engine.block.toggleBoldFont(headline);
}
engine.block.setTextColor(headline, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 });
```
### Enable Automatic Font Sizing
Configure the text block to automatically scale its font size to fit within fixed dimensions:
```typescript highlight=highlight-text-auto-size
// Set fixed container size and enable automatic font sizing
engine.block.setWidthMode(headline, 'Absolute');
engine.block.setHeightMode(headline, 'Absolute');
engine.block.setWidth(headline, 960);
engine.block.setHeight(headline, 300);
engine.block.setBool(headline, 'text/automaticFontSizeEnabled', true);
```
### Range-based Text Styling
Apply different styles to specific character ranges within a single text block:
```typescript highlight=highlight-text-range-style
// Style "in hours," - purple and italic (characters 0-9)
engine.block.setTextColor(
tagline,
{ r: 0.2, g: 0.2, b: 0.8, a: 1.0 },
0,
9
);
if (engine.block.canToggleItalicFont(tagline, 0, 9)) {
engine.block.toggleItalicFont(tagline, 0, 9);
}
// Style "not months." - black and bold (characters 10-21)
engine.block.setTextColor(
tagline,
{ r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
10,
21
);
if (engine.block.canToggleBoldFont(tagline, 10, 21)) {
engine.block.toggleBoldFont(tagline, 10, 21);
}
```
The range-based APIs accept start and end character indices:
- `setTextColor(id, color, from, to)` - Apply color to a specific character range
- `toggleBoldFont(id, from, to)` - Toggle bold styling for a range
- `toggleItalicFont(id, from, to)` - Toggle italic styling for a range
### Fixed Font Size
Set an explicit font size instead of using auto-sizing:
```typescript highlight=highlight-text-fixed-size
// Add CTA text "Start a Free Trial" with bold font
const ctaTitle = engine.block.create('text');
engine.block.replaceText(ctaTitle, 'Start a Free Trial');
engine.block.setFont(
ctaTitle,
ROBOTO_TYPEFACE.fonts[0].uri,
ROBOTO_TYPEFACE
);
engine.block.setFloat(ctaTitle, 'text/fontSize', 80);
engine.block.setFloat(ctaTitle, 'text/lineHeight', 1.0);
```
## Add Shapes
We create shapes using graphic blocks. CE.SDK supports `rect`, `line`, `ellipse`, `polygon`, `star`, and `vector_path` shapes.
### Create a Shape Block
Create a graphic block and assign a shape to it:
```typescript highlight=highlight-shape-create
// Add horizontal divider line
const dividerLine = engine.block.create('graphic');
const lineShape = engine.block.createShape('line');
engine.block.setShape(dividerLine, lineShape);
```
### Apply Fill to Shape
Create a color fill and apply it to the shape:
```typescript highlight=highlight-shape-fill
const lineFill = engine.block.createFill('color');
engine.block.setColor(lineFill, 'fill/color/value', {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0
});
engine.block.setFill(dividerLine, lineFill);
```
## Add Images
We add images using graphic blocks with image fills.
### Create an Image Block
Create a graphic block with a rect shape and an image fill:
```typescript highlight=highlight-image-create
// Add IMG.LY logo image
const logo = engine.block.create('graphic');
const logoShape = engine.block.createShape('rect');
engine.block.setShape(logo, logoShape);
const logoFill = engine.block.createFill('image');
engine.block.setString(
logoFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/imgly_logo.jpg'
);
engine.block.setFill(logo, logoFill);
```
We set the image URL via `setString()` with the `fill/image/imageFileURI` property.
## Position and Size Blocks
All blocks use the same positioning and sizing APIs:
```typescript highlight=highlight-block-position
engine.block.setContentFillMode(logo, 'Contain');
engine.block.setWidth(logo, 200);
engine.block.setHeight(logo, 65);
engine.block.setPositionX(logo, 820);
engine.block.setPositionY(logo, 960);
engine.block.appendChild(page, logo);
```
- `setWidth()` / `setHeight()` - Set block dimensions
- `setPositionX()` / `setPositionY()` - Set block position
- `setContentFillMode()` - Control how content fills the block (`Contain`, `Cover`, `Crop`)
- `appendChild()` - Add the block to the page hierarchy
## Export the Composition
CE.SDK provides two approaches for exporting compositions in browser environments.
### Export Using the Engine API
The `engine.block.export()` method exports a block as a blob that you can use programmatically:
```typescript highlight=highlight-export-api
// Export the composition to PNG
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1080,
targetHeight: 1080
});
// In browser, create a download link
const url = URL.createObjectURL(blob);
console.log('Export complete. Download URL:', url);
```
In browser environments, you can create a download URL from the blob using `URL.createObjectURL()`.
### Export Using Built-in Actions
Alternatively, use the built-in export panel or actions for a complete export dialog:
```typescript
await cesdk.actions.run('exportDesign', {
mimeType: 'image/png'
});
```
The export panel lets users choose format and settings interactively, while `cesdk.actions.run('export.page', options)` triggers export directly with specified options.
## Troubleshooting
- **Blocks not appearing**: Verify that `appendChild()` attaches blocks to the page. Blocks must be part of the scene hierarchy to render.
- **Text styling not applied**: Verify character indices are correct for range-based APIs. The indices are UTF-16 based.
- **Image stretched**: Use `setContentFillMode(block, 'Contain')` to maintain the image's aspect ratio.
- **Export fails**: Verify that page dimensions are set before export. The export requires valid dimensions.
## Next Steps
- [Layer Management](https://img.ly/docs/cesdk/sveltekit/create-composition/layer-management-18f07a/) - Control block stacking and organization
- [Positioning and Alignment](https://img.ly/docs/cesdk/sveltekit/insert-media/position-and-align-cc6b6a/) - Precise block placement
- [Group and Ungroup](https://img.ly/docs/cesdk/sveltekit/create-composition/group-and-ungroup-62565a/) - Group blocks for unified transforms
- [Blend Modes](https://img.ly/docs/cesdk/sveltekit/create-composition/blend-modes-ad3519/) - Control how blocks interact visually
- [Export](https://img.ly/docs/cesdk/sveltekit/export-save-publish/export-82f968/) - Export options and formats
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Templates"
description: "Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/sveltekit/create-templates/overview-4ebe30/) - Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK.
- [Create From Scratch](https://img.ly/docs/cesdk/sveltekit/create-templates/from-scratch-663cda/) - Build reusable design templates programmatically using CE.SDK's APIs. Create scenes, add text and graphic blocks, configure placeholders and variables, apply editing constraints, and save templates for reuse.
- [Import Templates](https://img.ly/docs/cesdk/sveltekit/create-templates/import-e50084/) - Load and import design templates into CE.SDK from URLs, archives, and serialized strings.
- [Dynamic Content](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content-53fad7/) - Use variables and placeholders to inject dynamic data into templates at design or runtime.
- [Lock the Template](https://img.ly/docs/cesdk/sveltekit/create-templates/lock-131489/) - Restrict editing access to specific elements or properties in a template to enforce design rules.
- [Edit or Remove Templates](https://img.ly/docs/cesdk/sveltekit/create-templates/edit-or-remove-38a8be/) - Modify existing templates and manage template lifecycle by loading, editing, saving, and removing templates from asset sources.
- [Add to Template Library](https://img.ly/docs/cesdk/sveltekit/create-templates/add-to-template-library-8bfbc7/) - Save and organize templates in an asset source for users to browse and apply from the template library.
- [Overview](https://img.ly/docs/cesdk/sveltekit/use-templates/overview-ae74e1/) - Learn how to browse, apply, and dynamically populate templates in CE.SDK to streamline design workflows.
- [Template Library](https://img.ly/docs/cesdk/sveltekit/use-templates/library-b3c704/) - Learn how to provide a set of predefined templates in the CreativeEditor SDK.
- [Apply a Template](https://img.ly/docs/cesdk/sveltekit/use-templates/apply-template-35c73e/) - Learn how to apply template scenes via API in the CreativeEditor SDK.
- [Generate From Template](https://img.ly/docs/cesdk/sveltekit/use-templates/generate-334e15/) - Learn how to generate finished designs from templates by loading, populating variables, and exporting to images, PDFs, or videos.
- [Replace Content](https://img.ly/docs/cesdk/sveltekit/use-templates/replace-content-4c482b/) - Learn how to dynamically replace content within templates using CE.SDK's placeholder and variable systems.
- [Use Templates Programmatically](https://img.ly/docs/cesdk/sveltekit/use-templates/programmatic-9349f3/) - Work with templates programmatically through CE.SDK's engine APIs to load existing templates, build new templates from scratch, modify template structures, and populate templates with dynamic data for batch processing and automation.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Dynamic Content"
description: "Use variables and placeholders to inject dynamic data into templates at design or runtime."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content-53fad7/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content-53fad7/)
---
Dynamic content transforms static designs into flexible, data-driven templates. CE.SDK provides three complementary capabilities—text variables, placeholders, and editing constraints—that work together to enable personalization while maintaining design integrity.

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-add-dynamic-content-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-add-dynamic-content-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-add-dynamic-content-browser/)
```typescript file=@cesdk_web_examples/guides-create-templates-add-dynamic-content-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Dynamic Content Overview
*
* Demonstrates the dynamic content capabilities in CE.SDK templates:
* - Text Variables: Insert {{tokens}} that resolve to dynamic values
* - Placeholders: Create drop zones for swappable images/videos
* - Editing Constraints: Lock properties while allowing controlled changes
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Set editor role to Adopter for template usage
engine.editor.setRole('Adopter');
const page = engine.block.findByType('page')[0];
// Content area: 480px wide, centered (left margin = 160px)
const contentX = 160;
const contentWidth = 480;
// TEXT VARIABLES: Define variables for personalization
engine.variable.setString('firstName', 'Jane');
engine.variable.setString('lastName', 'Doe');
engine.variable.setString('companyName', 'IMG.LY');
// Create heading with company variable
const headingText = engine.block.create('text');
engine.block.replaceText(
headingText,
'Welcome to {{companyName}}, {{firstName}} {{lastName}}.'
);
engine.block.setWidth(headingText, contentWidth);
engine.block.setHeightMode(headingText, 'Auto');
engine.block.setFloat(headingText, 'text/fontSize', 64);
engine.block.setEnum(headingText, 'text/horizontalAlignment', 'Left');
engine.block.appendChild(page, headingText);
engine.block.setPositionX(headingText, contentX);
engine.block.setPositionY(headingText, 200);
// Create description with bullet points
const descriptionText = engine.block.create('text');
engine.block.replaceText(
descriptionText,
'This example demonstrates dynamic templates.\n\n' +
'• Text Variables — Personalize content with {{tokens}}\n' +
'• Placeholders — Swappable images and media\n' +
'• Editing Constraints — Protected brand elements'
);
engine.block.setWidth(descriptionText, contentWidth);
engine.block.setHeightMode(descriptionText, 'Auto');
engine.block.setFloat(descriptionText, 'text/fontSize', 44);
engine.block.setEnum(descriptionText, 'text/horizontalAlignment', 'Left');
engine.block.appendChild(page, descriptionText);
engine.block.setPositionX(descriptionText, contentX);
engine.block.setPositionY(descriptionText, 300);
// Discover all variables in the scene
const allVariables = engine.variable.findAll();
console.log('Variables in scene:', allVariables);
// PLACEHOLDERS: Create hero image as a swappable drop zone
const heroImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_1.jpg',
{ size: { width: contentWidth, height: 140 } }
);
engine.block.appendChild(page, heroImage);
engine.block.setPositionX(heroImage, contentX);
engine.block.setPositionY(heroImage, 40);
// Enable placeholder behavior for the hero image
if (engine.block.supportsPlaceholderBehavior(heroImage)) {
engine.block.setPlaceholderBehaviorEnabled(heroImage, true);
engine.block.setPlaceholderEnabled(heroImage, true);
if (engine.block.supportsPlaceholderControls(heroImage)) {
engine.block.setPlaceholderControlsOverlayEnabled(heroImage, true);
engine.block.setPlaceholderControlsButtonEnabled(heroImage, true);
}
}
// Find all placeholders in the scene
const placeholders = engine.block.findAllPlaceholders();
console.log('Placeholders in scene:', placeholders.length);
// EDITING CONSTRAINTS: Add logo that cannot be moved or selected
const logo = await engine.block.addImage(
'https://img.ly/static/ubq_samples/imgly_logo.jpg',
{ size: { width: 100, height: 25 } }
);
engine.block.appendChild(page, logo);
engine.block.setPositionX(logo, 350);
engine.block.setPositionY(logo, 540);
// Lock the logo: prevent moving, resizing, and selection
engine.block.setScopeEnabled(logo, 'layer/move', false);
engine.block.setScopeEnabled(logo, 'layer/resize', false);
engine.block.setScopeEnabled(logo, 'editor/select', false);
// Verify constraints are applied
const canSelect = engine.block.isScopeEnabled(logo, 'editor/select');
const canMove = engine.block.isScopeEnabled(logo, 'layer/move');
console.log('Logo - canSelect:', canSelect, 'canMove:', canMove);
// Zoom to fit the page with autoFit enabled
await cesdk.actions.run('zoom.toBlock', page, {
padding: 40,
animate: false,
autoFit: true
});
console.log('Dynamic Content demo initialized.');
cesdk.engine.block.setSelected(page, false);
}
}
export default Example;
```
This guide covers how to use dynamic content capabilities in CE.SDK templates. The example creates a social media card with personalized name and company variables, a replaceable hero image, and a protected logo.
## Dynamic Content Capabilities
CE.SDK offers three ways to make templates dynamic:
- **Text Variables** — Insert `{{tokens}}` in text that resolve to dynamic values at runtime
- **Placeholders** — Mark blocks as drop zones where users can swap images or videos
- **Editing Constraints** — Lock specific properties to protect brand elements while allowing controlled changes
## Text Variables
Text variables enable data-driven text personalization. Define variables using `engine.variable.setString()`, then reference them in text blocks with `{{variableName}}` tokens.
```typescript highlight-text-variables
engine.variable.setString('firstName', 'Jane');
engine.variable.setString('lastName', 'Doe');
engine.variable.setString('companyName', 'IMG.LY');
// Create heading with company variable
const headingText = engine.block.create('text');
engine.block.replaceText(
headingText,
'Welcome to {{companyName}}, {{firstName}} {{lastName}}.'
);
```
Variables are defined globally and can be referenced in any text block. The `findAll()` method returns all variable keys in the scene, useful for building dynamic editing interfaces.
> **Note:** Variable keys are case-sensitive. `{{Name}}` and `{{name}}` are different variables.
## Placeholders
Placeholders turn design blocks into drop zones for swappable media. Mark an image block as a placeholder, and users can replace its content while the surrounding design remains fixed.
```typescript highlight-placeholders
// Enable placeholder behavior for the hero image
if (engine.block.supportsPlaceholderBehavior(heroImage)) {
engine.block.setPlaceholderBehaviorEnabled(heroImage, true);
engine.block.setPlaceholderEnabled(heroImage, true);
if (engine.block.supportsPlaceholderControls(heroImage)) {
engine.block.setPlaceholderControlsOverlayEnabled(heroImage, true);
engine.block.setPlaceholderControlsButtonEnabled(heroImage, true);
}
}
```
Enable placeholder behavior with `setPlaceholderBehaviorEnabled()`, then enable user interaction with `setPlaceholderEnabled()`. The visual overlay and replace button are controlled separately via `setPlaceholderControlsOverlayEnabled()` and `setPlaceholderControlsButtonEnabled()`.
## Editing Constraints
Editing constraints protect design integrity by limiting what users can modify. Use scope-based APIs to lock specific properties while keeping others editable.
```typescript highlight-editing-constraints
// Lock the logo: prevent moving, resizing, and selection
engine.block.setScopeEnabled(logo, 'layer/move', false);
engine.block.setScopeEnabled(logo, 'layer/resize', false);
engine.block.setScopeEnabled(logo, 'editor/select', false);
// Verify constraints are applied
const canSelect = engine.block.isScopeEnabled(logo, 'editor/select');
const canMove = engine.block.isScopeEnabled(logo, 'layer/move');
console.log('Logo - canSelect:', canSelect, 'canMove:', canMove);
```
The `setScopeEnabled()` method controls individual properties. Setting `'editor/select'` to `false` prevents users from selecting the block entirely, making it completely non-interactive. Combined with `'layer/move'` and `'layer/resize'`, this creates a fully protected element.
## Choosing the Right Capability
| Need | Capability |
| --- | --- |
| Dynamic text content | Text Variables |
| Swappable images/videos | Placeholders |
| Lock specific properties | Editing Constraints |
## API Reference
| Method | Description |
| --- | --- |
| `engine.editor.setRole()` | Set user role (Creator, Adopter, Viewer) |
| `engine.variable.findAll()` | Get all variable keys in the scene |
| `engine.variable.setString()` | Create or update a text variable |
| `engine.variable.getString()` | Read a variable's current value |
| `engine.block.supportsPlaceholderBehavior()` | Check placeholder support |
| `engine.block.setPlaceholderBehaviorEnabled()` | Enable placeholder behavior |
| `engine.block.setPlaceholderEnabled()` | Enable user interaction |
| `engine.block.findAllPlaceholders()` | Find all placeholder blocks |
| `engine.block.setScopeEnabled()` | Enable or disable editing scope |
| `engine.block.isScopeEnabled()` | Query scope state |
---
## Related Pages
- [Text Variables](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/) - Define dynamic text elements that can be populated with custom values during design generation.
- [Placeholders](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/placeholders-d9ba8a/) - Use placeholders to mark editable image, video, or text areas within a locked template layout.
- [Set Editing Constraints](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) - Learn how to control editing capabilities in CE.SDK templates using the Scope system to lock positions, prevent transformations, and create guided editing experiences
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Placeholders"
description: "Use placeholders to mark editable image, video, or text areas within a locked template layout."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/placeholders-d9ba8a/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content-53fad7/) > [Placeholders](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/placeholders-d9ba8a/)
---
Placeholders turn design blocks into drop-zones that users can swap content into while maintaining full control over layout and styling.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-dynamic-content-placeholders-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-dynamic-content-placeholders-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-dynamic-content-placeholders-browser/)
```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-placeholders-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Dynamic Content Placeholders
*
* This example demonstrates three different placeholder configurations:
* 1. All placeholder controls enabled (all scopes + behavior + controls)
* 2. Fill properties only (fill scopes + behavior + controls)
* 3. No placeholder features (default state)
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 1200, height: 800, unit: 'Pixel' }
});
const engine = cesdk.engine;
engine.editor.setRole('Adopter');
// Get the page
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Set page dimensions for horizontal layout
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Set page background to light gray
const pageFill = engine.block.getFill(page);
engine.block.setColor(pageFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
// Layout configuration for 3 blocks horizontally
const blockWidth = 300;
const blockHeight = 300;
const spacing = 50;
const startX = (pageWidth - blockWidth * 3 - spacing * 2) / 2;
const blockY = (pageHeight - blockHeight) / 2 + 40; // Offset for labels
const labelY = blockY - 50;
// Sample images
const imageUri1 = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const imageUri2 = 'https://img.ly/static/ubq_samples/sample_2.jpg';
const imageUri3 = 'https://img.ly/static/ubq_samples/sample_3.jpg';
// Define ALL available scopes for reference
const allScopes: Array<
| 'text/edit'
| 'text/character'
| 'fill/change'
| 'fill/changeType'
| 'stroke/change'
| 'shape/change'
| 'layer/move'
| 'layer/resize'
| 'layer/rotate'
| 'layer/flip'
| 'layer/crop'
| 'layer/opacity'
| 'layer/blendMode'
| 'layer/visibility'
| 'layer/clipping'
| 'appearance/adjustments'
| 'appearance/filter'
| 'appearance/effect'
| 'appearance/blur'
| 'appearance/shadow'
| 'appearance/animation'
| 'lifecycle/destroy'
| 'lifecycle/duplicate'
| 'editor/add'
| 'editor/select'
> = [
'text/edit',
'text/character',
'fill/change',
'fill/changeType',
'stroke/change',
'shape/change',
'layer/move',
'layer/resize',
'layer/rotate',
'layer/flip',
'layer/crop',
'layer/opacity',
'layer/blendMode',
'layer/visibility',
'layer/clipping',
'appearance/adjustments',
'appearance/filter',
'appearance/effect',
'appearance/blur',
'appearance/shadow',
'appearance/animation',
'lifecycle/destroy',
'lifecycle/duplicate',
'editor/add',
'editor/select'
];
// Block 1: All Placeholder Controls Enabled
const block1 = await engine.block.addImage(imageUri1, {
size: {
width: blockWidth,
height: blockHeight
}
});
engine.block.appendChild(page, block1);
engine.block.setPositionX(block1, startX);
engine.block.setPositionY(block1, blockY);
// Step 1: Explicitly disable ALL scopes first
allScopes.forEach((scope) => {
engine.block.setScopeEnabled(block1, scope, false);
});
// Step 2: Enable specific scopes for full placeholder functionality
// General/Layer options
engine.block.setScopeEnabled(block1, 'layer/opacity', true);
engine.block.setScopeEnabled(block1, 'layer/blendMode', true);
engine.block.setScopeEnabled(block1, 'lifecycle/duplicate', true);
engine.block.setScopeEnabled(block1, 'lifecycle/destroy', true);
// Arrange scopes
engine.block.setScopeEnabled(block1, 'layer/move', true);
engine.block.setScopeEnabled(block1, 'layer/resize', true);
engine.block.setScopeEnabled(block1, 'layer/rotate', true);
engine.block.setScopeEnabled(block1, 'layer/flip', true);
// Fill scopes (for image replacement and cropping)
engine.block.setScopeEnabled(block1, 'fill/change', true);
engine.block.setScopeEnabled(block1, 'fill/changeType', true);
engine.block.setScopeEnabled(block1, 'layer/crop', true);
// Appearance scopes
engine.block.setScopeEnabled(block1, 'appearance/adjustments', true);
engine.block.setScopeEnabled(block1, 'appearance/filter', true);
engine.block.setScopeEnabled(block1, 'appearance/effect', true);
engine.block.setScopeEnabled(block1, 'appearance/blur', true);
engine.block.setScopeEnabled(block1, 'appearance/shadow', true);
engine.block.setScopeEnabled(block1, 'appearance/animation', true);
engine.block.setScopeEnabled(block1, 'editor/select', true);
// Step 3: Enable placeholder behavior ("Act as a placeholder")
// This makes the block interactive in Adopter mode
engine.block.setPlaceholderEnabled(block1, true);
// Step 4: Check if block/fill supports placeholder features
const fill1 = engine.block.getFill(block1);
const supportsBehavior = engine.block.supportsPlaceholderBehavior(fill1);
const supportsControls = engine.block.supportsPlaceholderControls(block1);
// Enable placeholder behavior on the fill (for graphic blocks)
if (supportsBehavior) {
engine.block.setPlaceholderBehaviorEnabled(fill1, true);
}
// Enable placeholder overlay pattern
if (supportsControls) {
engine.block.setPlaceholderControlsOverlayEnabled(block1, true);
}
// Enable placeholder button
if (supportsControls) {
engine.block.setPlaceholderControlsButtonEnabled(block1, true);
}
// Complete "Act as Placeholder" setup
const fillForConfig = engine.block.getFill(block1);
if (engine.block.supportsPlaceholderBehavior(fillForConfig)) {
engine.block.setPlaceholderBehaviorEnabled(fillForConfig, true);
}
if (supportsControls) {
engine.block.setPlaceholderControlsOverlayEnabled(block1, true);
engine.block.setPlaceholderControlsButtonEnabled(block1, true);
}
// Block 2: Fill Properties Only
const block2 = await engine.block.addImage(imageUri2, {
size: {
width: blockWidth,
height: blockHeight
}
});
engine.block.appendChild(page, block2);
engine.block.setPositionX(block2, startX + blockWidth + spacing);
engine.block.setPositionY(block2, blockY);
// Batch operation: Apply settings to multiple blocks
const graphicBlocks = [block1, block2];
graphicBlocks.forEach((block) => {
// Enable placeholder for each block
engine.block.setPlaceholderEnabled(block, true);
const fill = engine.block.getFill(block);
if (engine.block.supportsPlaceholderBehavior(fill)) {
engine.block.setPlaceholderBehaviorEnabled(fill, true);
}
});
// Step 1: Explicitly disable ALL scopes first
allScopes.forEach((scope) => {
engine.block.setScopeEnabled(block2, scope, false);
});
// Step 2: Enable ONLY fill-related scopes
engine.block.setScopeEnabled(block2, 'fill/change', true);
engine.block.setScopeEnabled(block2, 'fill/changeType', true);
engine.block.setScopeEnabled(block2, 'layer/crop', true);
engine.block.setScopeEnabled(block2, 'editor/select', true);
// Step 3: Enable placeholder behavior ("Act as a placeholder")
engine.block.setPlaceholderEnabled(block2, true);
// Step 4: Enable fill-based placeholder behavior and visual controls
const fill2 = engine.block.getFill(block2);
if (engine.block.supportsPlaceholderBehavior(fill2)) {
engine.block.setPlaceholderBehaviorEnabled(fill2, true);
}
if (engine.block.supportsPlaceholderControls(block2)) {
engine.block.setPlaceholderControlsOverlayEnabled(block2, true);
engine.block.setPlaceholderControlsButtonEnabled(block2, true);
}
// Block 3: No Placeholder Features (Default State)
const block3 = await engine.block.addImage(imageUri3, {
size: {
width: blockWidth,
height: blockHeight
}
});
engine.block.appendChild(page, block3);
engine.block.setPositionX(block3, startX + (blockWidth + spacing) * 2);
engine.block.setPositionY(block3, blockY);
// Explicitly disable ALL scopes to ensure default state
allScopes.forEach((scope) => {
engine.block.setScopeEnabled(block3, scope, false);
});
// No placeholder behavior enabled - this block remains non-interactive
// Add descriptive labels above each block
const labelConfig = {
height: 40,
fontSize: 34,
fontUri:
'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Bold.ttf',
fontFamily: 'Roboto'
};
// Label for Block 1
const label1 = engine.block.create('text');
engine.block.appendChild(page, label1);
engine.block.setPositionX(label1, startX);
engine.block.setPositionY(label1, labelY);
engine.block.setWidth(label1, blockWidth);
engine.block.setHeight(label1, labelConfig.height);
engine.block.replaceText(label1, 'All Controls');
engine.block.setTextColor(label1, {
r: 0.2,
g: 0.2,
b: 0.2,
a: 1.0
});
engine.block.setFont(label1, labelConfig.fontUri, {
name: labelConfig.fontFamily,
fonts: [
{
uri: labelConfig.fontUri,
subFamily: 'Bold'
}
]
});
engine.block.setFloat(label1, 'text/fontSize', labelConfig.fontSize);
engine.block.setEnum(label1, 'text/horizontalAlignment', 'Center');
// Label for Block 2
const label2 = engine.block.create('text');
engine.block.appendChild(page, label2);
engine.block.setPositionX(label2, startX + blockWidth + spacing);
engine.block.setPositionY(label2, labelY);
engine.block.setWidth(label2, blockWidth);
engine.block.setHeight(label2, labelConfig.height);
engine.block.replaceText(label2, 'Fill Only');
engine.block.setTextColor(label2, {
r: 0.2,
g: 0.2,
b: 0.2,
a: 1.0
});
engine.block.setFont(label2, labelConfig.fontUri, {
name: labelConfig.fontFamily,
fonts: [
{
uri: labelConfig.fontUri,
subFamily: 'Bold'
}
]
});
engine.block.setFloat(label2, 'text/fontSize', labelConfig.fontSize);
engine.block.setEnum(label2, 'text/horizontalAlignment', 'Center');
// Label for Block 3
const label3 = engine.block.create('text');
engine.block.appendChild(page, label3);
engine.block.setPositionX(label3, startX + (blockWidth + spacing) * 2);
engine.block.setPositionY(label3, labelY);
engine.block.setWidth(label3, blockWidth);
engine.block.setHeight(label3, labelConfig.height);
engine.block.replaceText(label3, 'Disabled');
engine.block.setTextColor(label3, {
r: 0.2,
g: 0.2,
b: 0.2,
a: 1.0
});
engine.block.setFont(label3, labelConfig.fontUri, {
name: labelConfig.fontFamily,
fonts: [
{
uri: labelConfig.fontUri,
subFamily: 'Bold'
}
]
});
engine.block.setFloat(label3, 'text/fontSize', labelConfig.fontSize);
engine.block.setEnum(label3, 'text/horizontalAlignment', 'Center');
// Verify configurations
// eslint-disable-next-line no-console
console.log('Block 1 - All Controls:');
// eslint-disable-next-line no-console
console.log(
' Placeholder enabled:',
engine.block.isPlaceholderEnabled(block1)
);
// eslint-disable-next-line no-console
console.log(' Scopes enabled:');
// eslint-disable-next-line no-console
console.log(
' - layer/move:',
engine.block.isScopeEnabled(block1, 'layer/move')
);
// eslint-disable-next-line no-console
console.log(
' - layer/resize:',
engine.block.isScopeEnabled(block1, 'layer/resize')
);
// eslint-disable-next-line no-console
console.log(
' - fill/change:',
engine.block.isScopeEnabled(block1, 'fill/change')
);
// eslint-disable-next-line no-console
console.log(
' - layer/crop:',
engine.block.isScopeEnabled(block1, 'layer/crop')
);
// eslint-disable-next-line no-console
console.log(
' - appearance/adjustments:',
engine.block.isScopeEnabled(block1, 'appearance/adjustments')
);
// eslint-disable-next-line no-console
console.log('\nBlock 2 - Fill Only:');
// eslint-disable-next-line no-console
console.log(
' Placeholder enabled:',
engine.block.isPlaceholderEnabled(block2)
);
// eslint-disable-next-line no-console
console.log(' Scopes enabled:');
// eslint-disable-next-line no-console
console.log(
' - layer/move:',
engine.block.isScopeEnabled(block2, 'layer/move')
);
// eslint-disable-next-line no-console
console.log(
' - fill/change:',
engine.block.isScopeEnabled(block2, 'fill/change')
);
// eslint-disable-next-line no-console
console.log(
' - fill/changeType:',
engine.block.isScopeEnabled(block2, 'fill/changeType')
);
// eslint-disable-next-line no-console
console.log(
' - layer/crop:',
engine.block.isScopeEnabled(block2, 'layer/crop')
);
// eslint-disable-next-line no-console
console.log('\nBlock 3 - Disabled:');
// eslint-disable-next-line no-console
console.log(
' Placeholder enabled:',
engine.block.isPlaceholderEnabled(block3)
);
// eslint-disable-next-line no-console
console.log(' Scopes enabled:');
// eslint-disable-next-line no-console
console.log(
' - layer/move:',
engine.block.isScopeEnabled(block3, 'layer/move')
);
// eslint-disable-next-line no-console
console.log(
' - fill/change:',
engine.block.isScopeEnabled(block3, 'fill/change')
);
// eslint-disable-next-line no-console
console.log('\nPlaceholder configurations initialized successfully');
}
}
export default Example;
```
This guide covers placeholder fundamentals, checking support, enabling behavior, and configuring visual controls for graphic and text blocks.
## Placeholder Fundamentals
Placeholders convert design blocks into interactive drop-zones where users can swap content while maintaining layout and styling control.
### Two Distinct Features
**Placeholder behavior** enables drag-and-drop replacement and exposes scope checks for content validation.
**Placeholder controls** provide visual affordances: an overlay pattern and a "Replace" button for guided interaction.
### Block-Level vs Fill-Level Behavior
The key distinction in placeholder implementation depends on block type:
**For graphic blocks** (images/videos): Placeholder behavior is enabled on the **fill**, while controls are enabled on the **block**. This pattern reflects how graphic blocks contain fills that can be replaced.
**For text blocks**: Placeholder behavior and controls are both enabled directly on the **block**.
We can check support with `supportsPlaceholderBehavior()` for both blocks and fills, and `supportsPlaceholderControls()` for blocks.
## Checking Placeholder Support
Before enabling placeholder features, check if a block supports them:
```typescript highlight-check-support
// Step 4: Check if block/fill supports placeholder features
const fill1 = engine.block.getFill(block1);
const supportsBehavior = engine.block.supportsPlaceholderBehavior(fill1);
const supportsControls = engine.block.supportsPlaceholderControls(block1);
```
The `supportsPlaceholderBehavior()` method indicates whether a block can become a drop-zone. The `supportsPlaceholderControls()` method shows if visual controls (overlay and button) are available.
## Enabling Placeholder Behavior
To convert a block into a placeholder drop-zone, enable placeholder behavior. The approach differs based on block type.
### For Graphic Blocks (Images/Videos)
For graphic blocks, placeholder behavior must be enabled on the **fill**, not the block itself:
```typescript highlight-enable-behavior
// Enable placeholder behavior on the fill (for graphic blocks)
if (supportsBehavior) {
engine.block.setPlaceholderBehaviorEnabled(fill1, true);
}
```
This pattern is critical: `setPlaceholderBehaviorEnabled()` is called on the fill obtained from `block.getFill()`. This reflects the underlying architecture where graphic blocks contain replaceable fills.
### For Text Blocks
For text blocks, placeholder behavior is enabled directly on the block, as text blocks don't use fills in the same way.
We can verify placeholder behavior state with `isPlaceholderBehaviorEnabled()` on the appropriate target (fill for graphics, block for text).
## Enabling Adopter Mode Interaction
Placeholder behavior alone isn't enough - blocks must also be enabled for interaction in Adopter mode:
```typescript highlight-enable-adopter-mode
// Step 3: Enable placeholder behavior ("Act as a placeholder")
// This makes the block interactive in Adopter mode
engine.block.setPlaceholderEnabled(block1, true);
```
The `setPlaceholderEnabled()` method controls whether the placeholder is interactive for users in Adopter role. CE.SDK distinguishes Creator (full access) and Adopter (replace-only) roles.
### Automatic Management
In practice, `setPlaceholderEnabled()` is typically managed automatically by the editor: when you enable relevant scopes (like `fill/change` for graphics or `text/edit` for text), the placeholder interaction is automatically enabled. When all scopes are disabled, placeholder interaction is automatically disabled. This automatic behavior simplifies template creation workflows.
## Configuring Visual Feedback
Placeholders can display visual indicators to guide users through the replacement workflow.
### Combined Setup: The "Act as Placeholder" Pattern
In the CE.SDK UI, the "Act as Placeholder" checkbox enables placeholder behavior and both visual controls simultaneously. This combined pattern is the recommended approach:
```typescript highlight-full-configuration
// Complete "Act as Placeholder" setup
const fillForConfig = engine.block.getFill(block1);
if (engine.block.supportsPlaceholderBehavior(fillForConfig)) {
engine.block.setPlaceholderBehaviorEnabled(fillForConfig, true);
}
if (supportsControls) {
engine.block.setPlaceholderControlsOverlayEnabled(block1, true);
engine.block.setPlaceholderControlsButtonEnabled(block1, true);
}
```
This pattern ensures placeholder behavior is enabled on the appropriate target (fill for graphics, block for text) along with both visual controls on the block.
### Individual Control Options
Visual controls can also be managed independently if needed:
**Overlay Pattern** - The overlay shows a dotted surface indicating a drop-zone:
```typescript highlight-enable-overlay
// Enable placeholder overlay pattern
if (supportsControls) {
engine.block.setPlaceholderControlsOverlayEnabled(block1, true);
}
```
**Replace Button** - The button provides a single-tap entry point for touch devices:
```typescript highlight-enable-button
// Enable placeholder button
if (supportsControls) {
engine.block.setPlaceholderControlsButtonEnabled(block1, true);
}
```
Individual control is useful when you want specific visual feedback without the full placeholder workflow.
## Scope Requirements and Dependencies
Placeholders depend on specific scopes being enabled to function correctly. Understanding these dependencies prevents common configuration errors.
For graphic blocks (images/videos), the `fill/change` scope must be enabled before placeholder behavior will work. When you disable `fill/change`, the editor automatically disables placeholder behavior and controls to maintain consistency.
For text blocks, the `text/edit` scope must be enabled before placeholder behavior can function.
**Optional related scopes** that enhance placeholder functionality:
- `fill/changeType` - Allows changing between image, video, and solid color fills
- `layer/crop` - Enables cropping replacement images
- `text/character` - Allows font and character formatting for text placeholders
## Working with Multiple Placeholders
When creating templates with multiple placeholders, apply settings systematically:
```typescript highlight-batch-operation
// Batch operation: Apply settings to multiple blocks
const graphicBlocks = [block1, block2];
graphicBlocks.forEach((block) => {
// Enable placeholder for each block
engine.block.setPlaceholderEnabled(block, true);
const fill = engine.block.getFill(block);
if (engine.block.supportsPlaceholderBehavior(fill)) {
engine.block.setPlaceholderBehaviorEnabled(fill, true);
}
});
```
This pattern works well for collage templates, product showcases, or any layout requiring multiple content slots.
## API Reference
| Method | Description |
|--------|-------------|
| `engine.block.supportsPlaceholderBehavior()` | Checks whether the block supports placeholder behavior |
| `engine.block.setPlaceholderBehaviorEnabled()` | Enables or disables placeholder behavior for a block |
| `engine.block.isPlaceholderBehaviorEnabled()` | Queries whether placeholder behavior is enabled |
| `engine.block.setPlaceholderEnabled()` | Enables or disables placeholder interaction in Adopter mode |
| `engine.block.isPlaceholderEnabled()` | Queries whether placeholder interaction is enabled |
| `engine.block.supportsPlaceholderControls()` | Checks whether the block supports placeholder controls |
| `engine.block.setPlaceholderControlsOverlayEnabled()` | Enables or disables the placeholder overlay pattern |
| `engine.block.isPlaceholderControlsOverlayEnabled()` | Queries whether the overlay pattern is shown |
| `engine.block.setPlaceholderControlsButtonEnabled()` | Enables or disables the placeholder button |
| `engine.block.isPlaceholderControlsButtonEnabled()` | Queries whether the placeholder button is shown |
## Next Steps
- [Lock the Template](https://img.ly/docs/cesdk/sveltekit/create-templates/lock-131489/) - Restrict editing access to specific elements or properties to enforce design rules
- [Text Variables](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/) - Define dynamic text elements that can be populated with custom values
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Set Editing Constraints"
description: "Learn how to control editing capabilities in CE.SDK templates using the Scope system to lock positions, prevent transformations, and create guided editing experiences"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/set-editing-constraints-c892c0/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content-53fad7/) > [Set Editing Constraints](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/set-editing-constraints-c892c0/)
---
Control what users can edit in templates by setting fine-grained permissions on individual blocks or globally across your scene using CE.SDK's Scope system.

> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-dynamic-content-set-editing-constraints-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-dynamic-content-set-editing-constraints-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-dynamic-content-set-editing-constraints-browser/)
Editing constraints in CE.SDK allow you to lock specific properties of design elements while keeping others editable. The Scope system provides granular control over 20+ editing capabilities including movement, resizing, rotation, fill changes, text editing, and lifecycle operations.
```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-set-editing-constraints-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Set Editing Constraints Guide
*
* This example demonstrates:
* - Setting global scopes to respect block-level settings
* - Disabling move scope to lock position
* - Disabling lifecycle scopes to prevent deletion
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
// Create a design scene
await cesdk.actions.run('scene.create', {
page: { width: 1200, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the page
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Set page background color
const pageFill = engine.block.getFill(page);
engine.block.setColor(pageFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
// ===== Configure Global Scopes =====
// Set global scopes to 'Defer' to respect block-level scope settings
// Without this, global 'Allow' settings might override block-level restrictions
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('layer/resize', 'Defer');
engine.editor.setGlobalScope('lifecycle/destroy', 'Defer');
engine.editor.setGlobalScope('lifecycle/duplicate', 'Defer');
// Global scope modes:
// - 'Allow': Always allow (overrides block-level settings)
// - 'Deny': Always deny (overrides block-level settings)
// - 'Defer': Use block-level settings (respects setScopeEnabled)
// Calculate layout for 4 examples (2x2 grid)
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const margin = 40;
const spacing = 20;
const blockWidth = (pageWidth - margin * 2 - spacing) / 2;
const blockHeight = (pageHeight - margin * 2 - spacing) / 2;
const getPosition = (index: number) => {
const col = index % 2;
const row = Math.floor(index / 2);
return {
x: margin + col * (blockWidth + spacing),
y: margin + row * (blockHeight + spacing)
};
};
// Helper function to create a labeled example block
const createExampleBlock = (
labelText: string,
backgroundColor: { r: number; g: number; b: number },
applyScopesCallback?: (blockId: number) => void
): number => {
// Create container block
const block = engine.block.create('graphic');
const shape = engine.block.createShape('rect');
engine.block.setShape(block, shape);
engine.block.setWidth(block, blockWidth);
engine.block.setHeight(block, blockHeight);
// Set background color
const fill = engine.block.createFill('color');
engine.block.setFill(block, fill);
engine.block.setColor(fill, 'fill/color/value', {
...backgroundColor,
a: 1.0
});
// Add label text
const textBlock = engine.block.create('text');
engine.block.setWidth(textBlock, blockWidth * 0.85);
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setString(textBlock, 'text/text', labelText);
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
engine.block.setFloat(textBlock, 'text/fontSize', 36);
// Append text to get dimensions
engine.block.appendChild(block, textBlock);
// Center text in container
const textWidth = engine.block.getWidth(textBlock);
const textHeight = engine.block.getHeight(textBlock);
engine.block.setPositionX(textBlock, (blockWidth - textWidth) / 2);
engine.block.setPositionY(textBlock, (blockHeight - textHeight) / 2);
// Set text color to white
const textFill = engine.block.createFill('color');
engine.block.setFill(textBlock, textFill);
engine.block.setColor(textFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Apply scope configuration to both blocks
if (applyScopesCallback) {
applyScopesCallback(block);
applyScopesCallback(textBlock);
}
// Append container to page
engine.block.appendChild(page, block);
return block;
};
// ===== Example 1: Lock Position (Disable Move Scope) =====
const disableMoveScope = (block: number) => {
// Disable move scope
engine.block.setScopeEnabled(block, 'layer/move', false);
// Explicitly enable other transform scopes
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
// Explicitly enable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', true);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true);
};
const moveLockedBlock = createExampleBlock(
'Locked\nposition',
{
r: 0.5,
g: 0.75,
b: 0.9
},
disableMoveScope
);
// Block position is locked - users cannot move or reposition it
// Other scopes are explicitly enabled: resizing, rotation, flipping, deletion, duplication
// ===== Example 2: Prevent Deletion (Disable Lifecycle Scopes) =====
const disableLifecycleScopes = (block: number) => {
// Disable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false);
// Explicitly enable transform scopes
engine.block.setScopeEnabled(block, 'layer/move', true);
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
};
const lifecycleLockedBlock = createExampleBlock(
'Cannot\ndelete',
{
r: 0.75,
g: 0.75,
b: 0.75
},
disableLifecycleScopes
);
// Block cannot be deleted or duplicated
// Other scopes are explicitly enabled: moving, resizing, rotation, flipping
// ===== Example 3: All Scopes Enabled =====
const enableAllScopes = (block: number) => {
// Explicitly enable all transform scopes
engine.block.setScopeEnabled(block, 'layer/move', true);
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
// Explicitly enable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', true);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true);
// Explicitly enable fill scopes
engine.block.setScopeEnabled(block, 'fill/change', true);
engine.block.setScopeEnabled(block, 'fill/changeType', true);
// Explicitly enable text scopes
engine.block.setScopeEnabled(block, 'text/edit', true);
engine.block.setScopeEnabled(block, 'text/character', true);
};
const fullyEditableBlock = createExampleBlock(
'Fully\neditable',
{
r: 0.5,
g: 0.85,
b: 0.5
},
enableAllScopes
);
// All scopes are explicitly enabled - users have full editing capabilities
// This is the default behavior, but explicitly enabling shows clear intent
// ===== Example 4: All Scopes Disabled =====
const disableAllScopes = (block: number) => {
// Disable all transform scopes
engine.block.setScopeEnabled(block, 'layer/move', false);
engine.block.setScopeEnabled(block, 'layer/resize', false);
engine.block.setScopeEnabled(block, 'layer/rotate', false);
engine.block.setScopeEnabled(block, 'layer/flip', false);
engine.block.setScopeEnabled(block, 'layer/crop', false);
// Disable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false);
// Disable fill scopes
engine.block.setScopeEnabled(block, 'fill/change', false);
engine.block.setScopeEnabled(block, 'fill/changeType', false);
engine.block.setScopeEnabled(block, 'stroke/change', false);
// Disable text scopes
engine.block.setScopeEnabled(block, 'text/edit', false);
engine.block.setScopeEnabled(block, 'text/character', false);
// Disable shape scopes
engine.block.setScopeEnabled(block, 'shape/change', false);
// Disable editor scopes
engine.block.setScopeEnabled(block, 'editor/select', false);
// Disable appearance scopes
engine.block.setScopeEnabled(block, 'layer/opacity', false);
engine.block.setScopeEnabled(block, 'layer/blendMode', false);
engine.block.setScopeEnabled(block, 'layer/visibility', false);
};
const fullyLockedBlock = createExampleBlock(
'Fully\nlocked',
{
r: 0.9,
g: 0.5,
b: 0.5
},
disableAllScopes
);
// All scopes are disabled - block is completely locked and cannot be edited
// Useful for watermarks, logos, or legal disclaimers
// ===== Block-Level Scope Setting Example =====
// Check if a scope is enabled for a specific block
const canMove = engine.block.isScopeEnabled(moveLockedBlock, 'layer/move');
const canDelete = engine.block.isScopeEnabled(
lifecycleLockedBlock,
'lifecycle/destroy'
);
const canEditFully = engine.block.isScopeEnabled(
fullyEditableBlock,
'layer/move'
);
const canEditLocked = engine.block.isScopeEnabled(
fullyLockedBlock,
'layer/move'
);
// eslint-disable-next-line no-console
console.log('Move-locked block - layer/move enabled:', canMove); // false
// eslint-disable-next-line no-console
console.log(
'Lifecycle-locked block - lifecycle/destroy enabled:',
canDelete
); // false
// eslint-disable-next-line no-console
console.log('Fully editable block - layer/move enabled:', canEditFully); // true
// eslint-disable-next-line no-console
console.log('Fully locked block - layer/move enabled:', canEditLocked); // false
// Position blocks in 2x2 grid
const blocks = [
fullyEditableBlock,
moveLockedBlock,
lifecycleLockedBlock,
fullyLockedBlock
];
blocks.forEach((block, index) => {
const pos = getPosition(index);
engine.block.setPositionX(block, pos.x);
engine.block.setPositionY(block, pos.y);
});
// Deselect all blocks
engine.block.findAllSelected().forEach((block) => {
engine.block.setSelected(block, false);
});
// Zoom to fit content
await engine.scene.zoomToBlock(page, {
padding: 50,
animate: false
});
// Log instructions
// eslint-disable-next-line no-console
console.log(`
=== Editing Constraints Demo ===
Try interacting with the 4 examples (arranged in 2x2 grid):
Top row:
1. "Fully editable" (green): All scopes enabled - complete editing freedom
2. "Locked position" (light blue): Cannot move, but can resize/edit/delete
Bottom row:
3. "Cannot delete" (light grey): Cannot delete/duplicate, but can move/resize/edit
4. "Fully locked" (red): All scopes disabled - completely locked
Note: Global scopes are set to 'Defer' to respect block-level settings.
`);
}
}
export default Example;
```
This guide demonstrates how to apply editing constraints to create brand templates, guided editing experiences, and form-based workflows.
## Understanding Scopes
### What are Scopes?
A scope is a permission key that controls a specific editing capability in CE.SDK. Each scope represents a distinct action users can perform, such as moving blocks (`'layer/move'`), changing fills (`'fill/change'`), or editing text content (`'text/edit'`). By enabling or disabling scopes, you control exactly what users can and cannot do with each design element.
Scopes exist at two levels:
- **Block-level scopes**: Per-block permissions set using `setScopeEnabled()`
- **Global scopes**: Default behavior for all blocks set using `setGlobalScope()`
### Available Scope Categories
CE.SDK provides scopes organized into logical categories:
| Category | Purpose | Example Scopes |
| --- | --- | --- |
| **Text Editing** | Control text content and formatting | `text/edit`, `text/character` |
| **Fill & Stroke** | Manage colors and gradients | `fill/change`, `fill/changeType`, `stroke/change` |
| **Shape** | Modify shape properties | `shape/change` |
| **Layer Transform** | Control position and dimensions | `layer/move`, `layer/resize`, `layer/rotate`, `layer/flip`, `layer/crop` |
| **Layer Appearance** | Manage visual properties | `layer/opacity`, `layer/blendMode`, `layer/visibility` |
| **Effects & Filters** | Apply visual effects | `appearance/adjustments`, `appearance/filter`, `appearance/effect`, `appearance/blur`, `appearance/shadow` |
| **Lifecycle** | Control creation and deletion | `lifecycle/destroy`, `lifecycle/duplicate` |
| **Editor** | Manage scene-level actions | `editor/add`, `editor/select` |
## Scope Configuration
### Global Scope Modes
Global scopes set the default behavior for all blocks in the scene. They have three modes:
- **Allow**: Always allow the action, overriding block-level settings
- **Deny**: Always deny the action, overriding block-level settings
- **Defer**: Use block-level settings (default mode)
To ensure block-level scope settings are respected, set relevant global scopes to 'Defer':
```typescript highlight=highlight-global-scopes
// Set global scopes to 'Defer' to respect block-level scope settings
// Without this, global 'Allow' settings might override block-level restrictions
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('layer/resize', 'Defer');
engine.editor.setGlobalScope('lifecycle/destroy', 'Defer');
engine.editor.setGlobalScope('lifecycle/duplicate', 'Defer');
// Global scope modes:
// - 'Allow': Always allow (overrides block-level settings)
// - 'Deny': Always deny (overrides block-level settings)
// - 'Defer': Use block-level settings (respects setScopeEnabled)
```
Without setting global scopes to 'Defer', default 'Allow' settings might override your block-level restrictions. This is essential when applying fine-grained constraints.
### Scope Resolution Priority
When both global and block-level scopes are set, they resolve in this order:
1. **Global Deny** takes highest priority (action always denied)
2. **Global Allow** takes second priority (action always allowed)
3. **Global Defer** defers to block-level settings (default behavior)
## Setting Block-Level Constraints
### Locking Position
Prevent users from moving or repositioning a block while allowing other edits:
```typescript highlight=highlight-disable-move-scope
const disableMoveScope = (block: number) => {
// Disable move scope
engine.block.setScopeEnabled(block, 'layer/move', false);
// Explicitly enable other transform scopes
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
// Explicitly enable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', true);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true);
};
const moveLockedBlock = createExampleBlock(
'Locked\nposition',
{
r: 0.5,
g: 0.75,
b: 0.9
},
disableMoveScope
);
// Block position is locked - users cannot move or reposition it
// Other scopes are explicitly enabled: resizing, rotation, flipping, deletion, duplication
```
The block position is locked—users cannot move or reposition it. Other scopes remain enabled, allowing resizing, editing, and deletion. This pattern maintains layout integrity while allowing content updates.
### Preventing Deletion
Protect blocks from being deleted or duplicated:
```typescript highlight=highlight-disable-lifecycle-scopes
const disableLifecycleScopes = (block: number) => {
// Disable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false);
// Explicitly enable transform scopes
engine.block.setScopeEnabled(block, 'layer/move', true);
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
};
const lifecycleLockedBlock = createExampleBlock(
'Cannot\ndelete',
{
r: 0.75,
g: 0.75,
b: 0.75
},
disableLifecycleScopes
);
// Block cannot be deleted or duplicated
// Other scopes are explicitly enabled: moving, resizing, rotation, flipping
```
Users cannot delete or duplicate the block but can still move, resize, and edit it. Use this for essential template elements that must remain present.
### Checking Scope State
Query the current state of any scope for a block:
```typescript highlight=highlight-block-level-scope-check
// Check if a scope is enabled for a specific block
const canMove = engine.block.isScopeEnabled(moveLockedBlock, 'layer/move');
const canDelete = engine.block.isScopeEnabled(
lifecycleLockedBlock,
'lifecycle/destroy'
);
const canEditFully = engine.block.isScopeEnabled(
fullyEditableBlock,
'layer/move'
);
const canEditLocked = engine.block.isScopeEnabled(
fullyLockedBlock,
'layer/move'
);
// eslint-disable-next-line no-console
console.log('Move-locked block - layer/move enabled:', canMove); // false
// eslint-disable-next-line no-console
console.log(
'Lifecycle-locked block - lifecycle/destroy enabled:',
canDelete
); // false
// eslint-disable-next-line no-console
console.log('Fully editable block - layer/move enabled:', canEditFully); // true
// eslint-disable-next-line no-console
console.log('Fully locked block - layer/move enabled:', canEditLocked); // false
```
Use `isScopeEnabled()` to check the block-level setting. This returns whether the scope is enabled at the block level, but doesn't consider global scope settings.
### Checking Effective Permissions
Check the effective permission considering both block and global settings:
```typescript
// Check if scope is allowed (considers global + block settings)
const moveAllowed = engine.block.isAllowedByScope(block, 'layer/move');
```
`isAllowedByScope()` returns the final permission after resolving block-level and global scope settings. Use this when you need to know if an action is actually permitted.
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.setScopeEnabled()` | Enable or disable a scope for a specific block |
| `engine.block.isScopeEnabled()` | Check if a scope is enabled at the block level |
| `engine.block.isAllowedByScope()` | Check if a scope is allowed considering both block and global settings |
| `engine.editor.setGlobalScope()` | Set global scope policy (`'Allow'`, `'Deny'`, or `'Defer'`) |
| `engine.editor.findAllScopes()` | List all available scope keys |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Text Variables"
description: "Define dynamic text elements that can be populated with custom values during design generation."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content-53fad7/) > [Text Variables](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/)
---
Text variables enable data-driven template personalization in CE.SDK. Insert
placeholder tokens like `{{ firstName }}` into text blocks, then populate them
with actual values programmatically. This separates design from content,
enabling automated document generation, batch processing, and mass
personalization workflows.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-dynamic-content-text-variables-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-dynamic-content-text-variables-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-dynamic-content-text-variables-browser/)
```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-text-variables-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Text Variables Guide
*
* Demonstrates text variable management in CE.SDK with a single comprehensive example:
* - Discovering variables with findAll()
* - Creating and updating variables with setString()
* - Reading variable values with getString()
* - Binding variables to text blocks with {{variable}} tokens
* - Detecting variable references with referencesAnyVariables()
* - Removing variables with remove()
* - Localizing variable labels
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Localize variable labels that appear in the Variables panel UI
cesdk.i18n.setTranslations({
en: {
'variables.firstName.label': 'First Name',
'variables.lastName.label': 'Last Name',
'variables.email.label': 'Email Address',
'variables.company.label': 'Company Name',
'variables.title.label': 'Job Title'
}
});
// Pattern 1: Discover all existing variables in the scene
// This is useful when loading templates to see what variables need values
const existingVariables = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log('Existing variables:', existingVariables); // []
// Pattern 2: Create and update text variables
// If a variable doesn't exist, setString() creates it
// If it already exists, setString() updates its value
engine.variable.setString('firstName', 'Alex');
engine.variable.setString('lastName', 'Smith');
engine.variable.setString('email', 'alex.smith@example.com');
engine.variable.setString('company', 'IMG.LY');
engine.variable.setString('title', 'Creative Developer');
// Pattern 3: Read variable values at runtime
const firstName = engine.variable.getString('firstName');
// eslint-disable-next-line no-console
console.log('First name variable:', firstName); // 'Alex'
// Create a single comprehensive text block demonstrating all variable patterns
const textBlock = engine.block.create('text');
// Multi-line text combining:
// - Single variable ({{firstName}})
// - Multiple variables ({{firstName}} {{lastName}})
// - Variables in context (Email: {{email}})
const textContent = `Hello, {{firstName}}!
Full Name: {{firstName}} {{lastName}}
Email: {{email}}
Position: {{title}}
Company: {{company}}`;
engine.block.replaceText(textBlock, textContent);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setFloat(textBlock, 'text/fontSize', 52);
engine.block.appendChild(page, textBlock);
// Center the text block on the page (after font size is set)
// Get the actual frame dimensions of the block (including its bounds)
const frameX = engine.block.getFrameX(textBlock);
const frameY = engine.block.getFrameY(textBlock);
const frameWidth = engine.block.getFrameWidth(textBlock);
const frameHeight = engine.block.getFrameHeight(textBlock);
// Calculate centered position accounting for frame offset
engine.block.setPositionX(textBlock, (pageWidth - frameWidth) / 2 - frameX);
engine.block.setPositionY(
textBlock,
(pageHeight - frameHeight) / 2 - frameY
);
// Check if the block contains variable references
const hasVariables = engine.block.referencesAnyVariables(textBlock);
// eslint-disable-next-line no-console
console.log('Text block has variables:', hasVariables); // true
// Create and then remove a temporary variable to demonstrate removal
engine.variable.setString('tempVariable', 'Temporary Value');
// eslint-disable-next-line no-console
console.log('Variables before removal:', engine.variable.findAll());
// Remove the temporary variable
engine.variable.remove('tempVariable');
// eslint-disable-next-line no-console
console.log('Variables after removal:', engine.variable.findAll());
// Select the text block to show the Variables panel
engine.block.setSelected(textBlock, true);
// Final check: List all variables in the scene
const finalVariables = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log('Final variables in scene:', finalVariables);
// Expected: ['firstName', 'lastName', 'email', 'company', 'title']
// Build a custom Variables Manager panel
// CE.SDK doesn't include a built-in UI for creating/managing variables,
// so you can build one using the Panel Builder API
cesdk.ui.registerPanel(
'ly.img.variablesManager',
({ builder, engine: panelEngine, state }) => {
const { Section, TextInput, Button } = builder;
// State for creating new variables
const newVariableName = state('newVariableName', '');
const newVariableValue = state('newVariableValue', '');
// Section: Create New Variable
Section('create-variable', {
title: 'Create New Variable',
children: () => {
TextInput('new-name', {
inputLabel: 'Variable Name',
...newVariableName
});
TextInput('new-value', {
inputLabel: 'Default Value',
...newVariableValue
});
Button('create-btn', {
label: 'Create Variable',
color: 'accent',
isDisabled: !newVariableName.value.trim(),
onClick: () => {
const name = newVariableName.value.trim();
if (name) {
panelEngine.variable.setString(name, newVariableValue.value);
newVariableName.setValue('');
newVariableValue.setValue('');
}
}
});
}
});
// Section: Existing Variables
const variables = panelEngine.variable.findAll();
Section('existing-variables', {
title: `Manage Variables (${variables.length})`,
children: () => {
if (variables.length === 0) {
builder.Text('no-vars', { content: 'No variables defined yet.' });
return;
}
variables.forEach(varName => {
TextInput(`var-${varName}`, {
inputLabel: varName,
value: panelEngine.variable.getString(varName),
setValue: value => {
panelEngine.variable.setString(varName, value);
},
suffix: {
icon: '@imgly/TrashBin',
tooltip: 'Delete variable',
onClick: () => {
panelEngine.variable.remove(varName);
}
}
});
});
}
});
}
);
// Set the panel title
cesdk.i18n.setTranslations({
en: {
'panel.ly.img.variablesManager': 'Custom Variables Panel'
}
});
// Add a dock button to open the panel
cesdk.ui.registerComponent('variablesManager.dock', ({ builder: b }) => {
const isPanelOpen = cesdk.ui.isPanelOpen('ly.img.variablesManager');
b.Button('variables-dock-btn', {
label: 'Variables',
icon: '@imgly/Text',
onClick: () => {
if (isPanelOpen) {
cesdk.ui.closePanel('ly.img.variablesManager');
} else {
cesdk.ui.openPanel('ly.img.variablesManager');
}
},
isActive: isPanelOpen
});
});
// Add button to dock
cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [
...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }),
'ly.img.spacer',
'variablesManager.dock'
]);
}
}
export default Example;
```
This guide covers how to discover, create, update, and manage text variables both through the UI and programmatically using the Variables API.
## Introduction
Text variables allow you to design templates once and personalize them with different content for each use. At render time, CE.SDK replaces variable tokens with actual values provided through the Variables API. This approach is ideal for:
- **Automated document generation** - Certificates, invoices, reports
- **Mass personalization** - Marketing materials with recipient data
- **Data-driven design** - Templates populated from JSON, CSV, or APIs
- **Form-based editing** - Expose variables through custom interfaces
## Using the Built-in Insert Variable UI
CE.SDK includes an *Insert Variable* dropdown in the text editing canvas menu that allows template authors to insert variable tokens into text blocks.
> **Caution:** The Insert Variable dropdown **only appears when at least one variable is
> already defined** in the scene. If no variables exist, the dropdown will not
> be visible. You must first create variables programmatically using
> `engine.variable.setString()` before the UI becomes available.
To use the Insert Variable UI:
1. **Define variables** using `engine.variable.setString()` — the dropdown won't appear without this step
2. **Enter text edit mode** by double-clicking a text block
3. **Click the Insert Variable dropdown** in the canvas menu (or press `Ctrl+Shift+L` / `Cmd+Shift+L`)
The dropdown allows you to:
- **Insert tokens** into text blocks using `{{variableName}}` syntax
- **Select from all defined variables** in the current scene
Variables appear with localized labels when you configure translations through the i18n API.
> **Note:** CE.SDK does not include a built-in UI for end users to *create* or *manage*
> variables — the Insert Variable dropdown only lets users insert existing
> variables into text. If you need a UI for creating and editing variables, see
> [Building a Variables Manager Panel](#building-a-variables-manager-panel)
> below.
## Discovering Variables
When working with templates that already contain variables, discover what variables exist before populating them with values.
```typescript highlight-discover-variables
// Pattern 1: Discover all existing variables in the scene
// This is useful when loading templates to see what variables need values
const existingVariables = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log('Existing variables:', existingVariables); // []
```
The `findAll()` method returns an array of all variable keys defined in the scene. This is essential when loading templates to understand what data needs to be provided.
## Creating and Updating Variables
Create or update variables using `setString()`. If the variable doesn't exist, it will be created. If it already exists, its value will be updated.
```typescript highlight-create-update-variables
// Pattern 2: Create and update text variables
// If a variable doesn't exist, setString() creates it
// If it already exists, setString() updates its value
engine.variable.setString('firstName', 'Alex');
engine.variable.setString('lastName', 'Smith');
engine.variable.setString('email', 'alex.smith@example.com');
engine.variable.setString('company', 'IMG.LY');
engine.variable.setString('title', 'Creative Developer');
```
> **Note:** Variable keys are case-sensitive. `{{ Name }}` and `{{ name }}` are different
> variables.
## Reading Variable Values
Retrieve the current value of a variable at runtime using `getString()`. This is useful for validation or displaying current values in custom UI.
```typescript highlight-read-variable-value
// Pattern 3: Read variable values at runtime
const firstName = engine.variable.getString('firstName');
// eslint-disable-next-line no-console
console.log('First name variable:', firstName); // 'Alex'
```
## Binding Variables to Text Blocks
Insert variable tokens directly into text block content using the `{{variableName}}` syntax. CE.SDK automatically detects and resolves these tokens at render time.
### Single Variable
```typescript highlight-single-variable-binding
// Create a single comprehensive text block demonstrating all variable patterns
const textBlock = engine.block.create('text');
// Multi-line text combining:
// - Single variable ({{firstName}})
// - Multiple variables ({{firstName}} {{lastName}})
// - Variables in context (Email: {{email}})
const textContent = `Hello, {{firstName}}!
Full Name: {{firstName}} {{lastName}}
Email: {{email}}
Position: {{title}}
Company: {{company}}`;
engine.block.replaceText(textBlock, textContent);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setFloat(textBlock, 'text/fontSize', 52);
engine.block.appendChild(page, textBlock);
```
### Multiple Variables
Combine multiple variables in a single text block:
```typescript highlight-multiple-variable-binding
// Create a single comprehensive text block demonstrating all variable patterns
const textBlock = engine.block.create('text');
// Multi-line text combining:
// - Single variable ({{firstName}})
// - Multiple variables ({{firstName}} {{lastName}})
// - Variables in context (Email: {{email}})
const textContent = `Hello, {{firstName}}!
Full Name: {{firstName}} {{lastName}}
Email: {{email}}
Position: {{title}}
Company: {{company}}`;
engine.block.replaceText(textBlock, textContent);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setFloat(textBlock, 'text/fontSize', 52);
engine.block.appendChild(page, textBlock);
```
The variables resolve in place, maintaining the surrounding text and formatting.
## Detecting Variable References
Check if a block contains variable references using `referencesAnyVariables()`. This returns `true` if the block's text contains any `{{variable}}` tokens.
```typescript highlight-detect-variable-references
// Check if the block contains variable references
const hasVariables = engine.block.referencesAnyVariables(textBlock);
// eslint-disable-next-line no-console
console.log('Text block has variables:', hasVariables); // true
```
This is useful for identifying which blocks need variable values before export or for implementing validation logic.
## Removing Variables
Remove unused variables from the scene with `remove()`. This cleans up the variable store when certain variables are no longer needed.
```typescript highlight-remove-variable
// Create and then remove a temporary variable to demonstrate removal
engine.variable.setString('tempVariable', 'Temporary Value');
// eslint-disable-next-line no-console
console.log('Variables before removal:', engine.variable.findAll());
// Remove the temporary variable
engine.variable.remove('tempVariable');
// eslint-disable-next-line no-console
console.log('Variables after removal:', engine.variable.findAll());
```
After removal, the variable no longer exists in the scene. Text blocks that reference removed variables will display the token literally (e.g., `{{removedVar}}`).
## Localizing Variable Labels
In CE.SDK (with UI), display friendly labels for variables in the inspector panel using i18n translations. Map variable keys to human-readable names that appear in the UI.
```typescript highlight-localize-variables
// Localize variable labels that appear in the Variables panel UI
cesdk.i18n.setTranslations({
en: {
'variables.firstName.label': 'First Name',
'variables.lastName.label': 'Last Name',
'variables.email.label': 'Email Address',
'variables.company.label': 'Company Name',
'variables.title.label': 'Job Title'
}
});
```
Without localization, the Insert Variable dropdown shows the technical variable key (e.g., `firstName`). With localization, it shows the friendly label (e.g., "First Name").
## Combining with Other Features
Text variables work seamlessly with other CE.SDK template features:
### With Placeholders
Use **placeholders** for dynamic images and videos while **variables** personalize text content. This combination enables fully dynamic templates where both visuals and copy change per use case.
### With Editing Constraints
Lock layout elements while allowing only variable token editing. This ensures brand consistency while enabling content personalization.
### With Role-Based Editing
Show the Insert Variable dropdown only to template authors (Creator role) and hide it from end users (Adopter role). This guides the editing experience based on user permissions.
## Building a Variables Manager Panel
CE.SDK's built-in Insert Variable dropdown only allows users to insert existing variables — it doesn't provide a UI for creating or managing variables. If you want end users to create, edit, and delete variables through the editor interface, you can build a custom panel using the Panel Builder API.
The following example creates a Variables Manager panel with:
- A form to create new variables with a name and default value
- A list of existing variables with editable values and delete buttons
- A dock button to toggle the panel
```typescript highlight-variables-manager-panel
// Build a custom Variables Manager panel
// CE.SDK doesn't include a built-in UI for creating/managing variables,
// so you can build one using the Panel Builder API
cesdk.ui.registerPanel(
'ly.img.variablesManager',
({ builder, engine: panelEngine, state }) => {
const { Section, TextInput, Button } = builder;
// State for creating new variables
const newVariableName = state('newVariableName', '');
const newVariableValue = state('newVariableValue', '');
// Section: Create New Variable
Section('create-variable', {
title: 'Create New Variable',
children: () => {
TextInput('new-name', {
inputLabel: 'Variable Name',
...newVariableName
});
TextInput('new-value', {
inputLabel: 'Default Value',
...newVariableValue
});
Button('create-btn', {
label: 'Create Variable',
color: 'accent',
isDisabled: !newVariableName.value.trim(),
onClick: () => {
const name = newVariableName.value.trim();
if (name) {
panelEngine.variable.setString(name, newVariableValue.value);
newVariableName.setValue('');
newVariableValue.setValue('');
}
}
});
}
});
// Section: Existing Variables
const variables = panelEngine.variable.findAll();
Section('existing-variables', {
title: `Manage Variables (${variables.length})`,
children: () => {
if (variables.length === 0) {
builder.Text('no-vars', { content: 'No variables defined yet.' });
return;
}
variables.forEach(varName => {
TextInput(`var-${varName}`, {
inputLabel: varName,
value: panelEngine.variable.getString(varName),
setValue: value => {
panelEngine.variable.setString(varName, value);
},
suffix: {
icon: '@imgly/TrashBin',
tooltip: 'Delete variable',
onClick: () => {
panelEngine.variable.remove(varName);
}
}
});
});
}
});
}
);
// Set the panel title
cesdk.i18n.setTranslations({
en: {
'panel.ly.img.variablesManager': 'Custom Variables Panel'
}
});
// Add a dock button to open the panel
cesdk.ui.registerComponent('variablesManager.dock', ({ builder: b }) => {
const isPanelOpen = cesdk.ui.isPanelOpen('ly.img.variablesManager');
b.Button('variables-dock-btn', {
label: 'Variables',
icon: '@imgly/Text',
onClick: () => {
if (isPanelOpen) {
cesdk.ui.closePanel('ly.img.variablesManager');
} else {
cesdk.ui.openPanel('ly.img.variablesManager');
}
},
isActive: isPanelOpen
});
});
// Add button to dock
cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [
...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }),
'ly.img.spacer',
'variablesManager.dock'
]);
```
This panel integrates with the dock and provides a complete variable management experience. Users can create new variables, edit existing values, and remove variables they no longer need.
> **Note:** For more information on building custom panels, see the
> [Create a Custom Panel](https://img.ly/docs/cesdk/sveltekit/user-interface/ui-extensions/create-custom-panel-d87b83/) guide.
## API Reference
| Method | Description |
| --------------------------------------- | ------------------------------------------- |
| `engine.variable.findAll()` | Get array of all variable keys in the scene |
| `engine.variable.setString()` | Create or update a text variable |
| `engine.variable.getString()` | Read the current value of a variable |
| `engine.variable.remove()` | Delete a variable from the scene |
| `engine.block.referencesAnyVariables()` | Check if a block contains variable tokens |
| `engine.block.replaceText()` | Set text content (supports variable tokens) |
| `cesdk.i18n.setTranslations()` | Set UI labels for variable names |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add to Template Library"
description: "Save and organize templates in an asset source for users to browse and apply from the template library."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/add-to-template-library-8bfbc7/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Add to Template Library](https://img.ly/docs/cesdk/sveltekit/create-templates/add-to-template-library-8bfbc7/)
---
Create a template library where users can browse, preview, and apply templates from a custom asset source.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-add-to-template-library-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-add-to-template-library-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-add-to-template-library-browser/)
Templates in CE.SDK are stored and accessed through the asset system. A template library is a local asset source configured to hold and serve template assets, allowing users to browse thumbnails and apply templates to their designs.
```typescript file=@cesdk_web_examples/guides-create-templates-add-to-template-library-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Add to Template Library
*
* This example demonstrates how to create a template library by:
* 1. Creating a local asset source for templates
* 2. Adding templates with metadata (label, thumbnail, URI)
* 3. Configuring the UI to display the template library
* 4. Saving scenes as templates
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
// Create a local asset source for templates
engine.asset.addLocalSource('my-templates', undefined, async (asset) => {
// Apply the selected template to the current scene
await engine.scene.applyTemplateFromURL(asset.meta!.uri as string);
// Set zoom to auto-fit after applying template
await cesdk.actions.run('zoom.toPage', { autoFit: true });
return undefined;
});
// Add a template to the source with metadata
engine.asset.addAssetToSource('my-templates', {
id: 'template-postcard',
label: { en: 'Postcard' },
meta: {
uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene',
thumbUri:
'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_postcard_1.jpg'
}
});
// Add more templates
engine.asset.addAssetToSource('my-templates', {
id: 'template-business-card',
label: { en: 'Business Card' },
meta: {
uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_business_card_1.scene',
thumbUri:
'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_business_card_1.jpg'
}
});
engine.asset.addAssetToSource('my-templates', {
id: 'template-social-media',
label: { en: 'Social Media Post' },
meta: {
uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_post_1.scene',
thumbUri:
'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_instagram_post_1.jpg'
}
});
// Add translation for the library entry
cesdk.i18n.setTranslations({
en: { 'libraries.my-templates-entry.label': 'My Templates' }
});
// Add the template source to the asset library
cesdk.ui.addAssetLibraryEntry({
id: 'my-templates-entry',
sourceIds: ['my-templates'],
previewLength: 3,
previewBackgroundType: 'cover',
gridBackgroundType: 'cover',
gridColumns: 2,
cardLabelPosition: () => 'below'
});
// Add template library to the dock
cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [
...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }),
'ly.img.spacer',
{
id: 'ly.img.assetLibrary.dock',
key: 'my-templates-dock',
label: 'My Templates',
icon: '@imgly/Template',
entries: ['my-templates-entry']
}
]);
// Load the first template
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
// Set zoom to auto-fit
await cesdk.actions.run('zoom.toPage', { autoFit: true });
// Open the template library panel by default
cesdk.ui.openPanel('//ly.img.panel/assetLibrary', {
payload: { entries: ['my-templates-entry'] }
});
// Save as string format (lightweight, references remote assets)
const templateString = await engine.scene.saveToString();
console.log('Template saved as string. Length:', templateString.length);
// Save as archive format (self-contained with bundled assets)
const templateBlob = await engine.scene.saveToArchive();
console.log('Template saved as archive. Size:', templateBlob.size, 'bytes');
// List all registered asset sources
const sources = engine.asset.findAllSources();
console.log('Registered sources:', sources);
// Notify UI when source contents change
engine.asset.assetSourceContentsChanged('my-templates');
// Query templates from the source
const queryResult = await engine.asset.findAssets('my-templates', {
page: 0,
perPage: 10
});
console.log('Templates in library:', queryResult.total);
// Remove a template from the source
engine.asset.removeAssetFromSource('my-templates', 'template-social-media');
console.log('Removed template-social-media from library');
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, {
id: 'ly.img.actions.navigationBar',
children: [
'ly.img.saveScene.navigationBar',
'ly.img.exportArchive.navigationBar'
]
});
}
}
export default Example;
```
This guide covers how to save scenes as templates, create a template asset source, and add templates with metadata.
## Saving Templates
Scenes can be exported in two formats for use as templates.
### String Format
Use `engine.scene.saveToString()` to serialize the scene to a base64 string. This lightweight format references remote assets by URL and is ideal for templates where assets are hosted on a CDN.
```typescript highlight=highlight-save-string
// Save as string format (lightweight, references remote assets)
const templateString = await engine.scene.saveToString();
console.log('Template saved as string. Length:', templateString.length);
```
### Archive Format
For self-contained templates that bundle all assets, use `engine.scene.saveToArchive()`. This returns a Blob containing all assets bundled together, making templates portable without external dependencies.
```typescript highlight=highlight-save-archive
// Save as archive format (self-contained with bundled assets)
const templateBlob = await engine.scene.saveToArchive();
console.log('Template saved as archive. Size:', templateBlob.size, 'bytes');
```
## Creating a Template Asset Source
Register a local asset source using `engine.asset.addLocalSource()` with an ID and `applyAsset` callback.
```typescript highlight=highlight-create-source
// Create a local asset source for templates
engine.asset.addLocalSource('my-templates', undefined, async (asset) => {
// Apply the selected template to the current scene
await engine.scene.applyTemplateFromURL(asset.meta!.uri as string);
// Set zoom to auto-fit after applying template
await cesdk.actions.run('zoom.toPage', { autoFit: true });
return undefined;
});
```
The `applyAsset` callback receives the selected asset and determines how to apply it. We use `engine.scene.applyTemplateFromURL()` to load the template from the asset's `meta.uri` property. The template is applied to the current scene, adjusting content to fit the existing page dimensions.
## Adding Templates to the Source
Register templates using `engine.asset.addAssetToSource()` with an asset definition that includes metadata for display and loading.
```typescript highlight=highlight-add-templates
// Add a template to the source with metadata
engine.asset.addAssetToSource('my-templates', {
id: 'template-postcard',
label: { en: 'Postcard' },
meta: {
uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene',
thumbUri:
'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_postcard_1.jpg'
}
});
```
Each template asset requires:
- `id` - Unique identifier for the template
- `label` - Localized display name shown in the template library
- `meta.uri` - URL to the `.scene` file that will be loaded when the template is selected
- `meta.thumbUri` - URL to a preview image displayed in the template library grid
## Managing Templates
After the initial setup, you can manage templates programmatically.
```typescript highlight=highlight-manage-templates
// List all registered asset sources
const sources = engine.asset.findAllSources();
console.log('Registered sources:', sources);
// Notify UI when source contents change
engine.asset.assetSourceContentsChanged('my-templates');
// Query templates from the source
const queryResult = await engine.asset.findAssets('my-templates', {
page: 0,
perPage: 10
});
console.log('Templates in library:', queryResult.total);
// Remove a template from the source
engine.asset.removeAssetFromSource('my-templates', 'template-social-media');
console.log('Removed template-social-media from library');
```
Use `engine.asset.findAllSources()` to list registered sources. When you add or remove templates from a source, call `engine.asset.assetSourceContentsChanged()` to refresh the UI. To remove a template, use `engine.asset.removeAssetFromSource()`.
## Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| Templates not appearing in UI | Asset source not added to library entry | Ensure `sourceIds` includes the template source ID |
| Template fails to load | Incorrect URI in asset meta | Verify the `uri` points to a valid `.scene` file |
| Thumbnails not displaying | Missing or incorrect `thumbUri` | Check the thumbnail URL is accessible |
| Apply callback not triggered | `applyAsset` not defined in `addLocalSource` | Provide the callback when creating the source |
## API Reference
| Method | Description |
| --- | --- |
| `engine.asset.addLocalSource()` | Register a local asset source with an apply callback |
| `engine.asset.addAssetToSource()` | Add an asset to a registered source |
| `engine.asset.removeAssetFromSource()` | Remove an asset from a source by ID |
| `engine.asset.assetSourceContentsChanged()` | Notify UI that source contents changed |
| `engine.scene.saveToString()` | Serialize scene to base64 string format |
| `engine.scene.saveToArchive()` | Save scene as self-contained archive blob |
| `engine.scene.applyTemplateFromURL()` | Apply a template to the current scene |
| `cesdk.ui.addAssetLibraryEntry()` | Add a library entry to the asset library |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Edit or Remove Templates"
description: "Modify existing templates and manage template lifecycle by loading, editing, saving, and removing templates from asset sources."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/edit-or-remove-38a8be/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Edit or Remove Templates](https://img.ly/docs/cesdk/sveltekit/create-templates/edit-or-remove-38a8be/)
---
Modify existing templates and manage template lifecycle in your asset library using CE.SDK.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-edit-or-remove-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-edit-or-remove-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-edit-or-remove-browser/)
Templates evolve as designs change. You might need to update branding, fix content errors, or remove outdated templates from your library. CE.SDK provides APIs for adding, editing, and removing templates from asset sources.
```typescript file=@cesdk_web_examples/guides-create-templates-edit-or-remove-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Edit or Remove Templates Guide
*
* Demonstrates template management workflows:
* - Adding templates to local asset sources with thumbnails
* - Editing template content and updating in asset sources
* - Removing templates from asset sources
* - Saving updated templates with new content
*/
// Helper function to generate SVG thumbnail with text label
function generateThumbnail(label: string): string {
const svg = ``;
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
}
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 600, unit: 'Pixel' }
});
const engine = cesdk.engine;
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Create a local asset source for managing templates
engine.asset.addLocalSource('my-templates', undefined, async (asset) => {
const uri = asset.meta?.uri;
if (!uri) return undefined;
const base64Content = uri.split(',')[1];
if (!base64Content) return undefined;
await engine.scene.loadFromString(base64Content);
return engine.scene.get() ?? undefined;
});
// Add the template source to the dock as an asset library entry
cesdk.ui.addAssetLibraryEntry({
id: 'my-templates-entry',
sourceIds: ['my-templates'],
title: 'My Templates',
icon: '@imgly/Template',
gridColumns: 2,
gridItemHeight: 'square'
});
// Add a spacer to push "My Templates" to the bottom of the dock
cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [
...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }),
'ly.img.spacer',
{
id: 'ly.img.assetLibrary.dock',
key: 'my-templates',
icon: '@imgly/Template',
label: 'My Templates',
entries: ['my-templates-entry']
}
]);
// Create the template with text blocks
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Original Template');
engine.block.setFloat(titleBlock, 'text/fontSize', 64);
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.appendChild(page, titleBlock);
const subtitleBlock = engine.block.create('text');
engine.block.replaceText(
subtitleBlock,
'Browse "My Templates" at the bottom of the dock'
);
engine.block.setFloat(subtitleBlock, 'text/fontSize', 42);
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
// Position text blocks centered on the page
const titleWidth = engine.block.getFrameWidth(titleBlock);
const titleHeight = engine.block.getFrameHeight(titleBlock);
engine.block.setPositionX(titleBlock, (pageWidth - titleWidth) / 2);
engine.block.setPositionY(titleBlock, pageHeight / 2 - titleHeight - 20);
const subtitleWidth = engine.block.getFrameWidth(subtitleBlock);
engine.block.setPositionX(subtitleBlock, (pageWidth - subtitleWidth) / 2);
engine.block.setPositionY(subtitleBlock, pageHeight / 2 + 20);
// Save template content and add to asset source
const originalContent = await engine.scene.saveToString();
engine.asset.addAssetToSource('my-templates', {
id: 'template-original',
label: { en: 'Original Template' },
meta: {
uri: `data:application/octet-stream;base64,${originalContent}`,
thumbUri: generateThumbnail('Original Template')
}
});
// eslint-disable-next-line no-console
console.log('Original template added to asset source');
// Edit the template content and save as a new version
engine.block.replaceText(titleBlock, 'Updated Template');
engine.block.replaceText(
subtitleBlock,
'This template was edited and saved'
);
const updatedContent = await engine.scene.saveToString();
engine.asset.addAssetToSource('my-templates', {
id: 'template-updated',
label: { en: 'Updated Template' },
meta: {
uri: `data:application/octet-stream;base64,${updatedContent}`,
thumbUri: generateThumbnail('Updated Template')
}
});
// Re-center after modification
const newTitleWidth = engine.block.getFrameWidth(titleBlock);
const newTitleHeight = engine.block.getFrameHeight(titleBlock);
engine.block.setPositionX(titleBlock, (pageWidth - newTitleWidth) / 2);
engine.block.setPositionY(titleBlock, pageHeight / 2 - newTitleHeight - 20);
const newSubtitleWidth = engine.block.getFrameWidth(subtitleBlock);
engine.block.setPositionX(
subtitleBlock,
(pageWidth - newSubtitleWidth) / 2
);
// eslint-disable-next-line no-console
console.log('Updated template added to asset source');
// Add a temporary template to demonstrate removal
engine.asset.addAssetToSource('my-templates', {
id: 'template-temporary',
label: { en: 'Temporary Template' },
meta: {
uri: `data:application/octet-stream;base64,${originalContent}`,
thumbUri: generateThumbnail('Temporary Template')
}
});
// Remove the temporary template from the asset source
engine.asset.removeAssetFromSource('my-templates', 'template-temporary');
// eslint-disable-next-line no-console
console.log('Temporary template removed from asset source');
// Update an existing template by removing and re-adding with same ID
engine.block.replaceText(subtitleBlock, 'Updated again with new content');
const reUpdatedContent = await engine.scene.saveToString();
engine.asset.removeAssetFromSource('my-templates', 'template-updated');
engine.asset.addAssetToSource('my-templates', {
id: 'template-updated',
label: { en: 'Updated Template' },
meta: {
uri: `data:application/octet-stream;base64,${reUpdatedContent}`,
thumbUri: generateThumbnail('Updated Template')
}
});
// Notify that the asset source contents have changed
engine.asset.assetSourceContentsChanged('my-templates');
// Re-center subtitle after final update
const reUpdatedSubtitleWidth = engine.block.getFrameWidth(subtitleBlock);
engine.block.setPositionX(
subtitleBlock,
(pageWidth - reUpdatedSubtitleWidth) / 2
);
// eslint-disable-next-line no-console
console.log('Template updated in asset source');
// Apply the original template to show the starting point
await engine.scene.loadFromString(originalContent);
// eslint-disable-next-line no-console
console.log(
'Original template applied - browse "My Templates" in the dock'
);
}
}
export default Example;
```
This guide covers how to add templates to asset sources, edit template content, remove templates, and save updated versions.
## Adding Templates
First, create a local asset source to store your templates:
```typescript highlight-create-source
// Create a local asset source for managing templates
engine.asset.addLocalSource('my-templates', undefined, async (asset) => {
const uri = asset.meta?.uri;
if (!uri) return undefined;
const base64Content = uri.split(',')[1];
if (!base64Content) return undefined;
await engine.scene.loadFromString(base64Content);
return engine.scene.get() ?? undefined;
});
```
Next, create your template content using block APIs:
```typescript highlight-create-template
// Create the template with text blocks
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Original Template');
engine.block.setFloat(titleBlock, 'text/fontSize', 64);
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.appendChild(page, titleBlock);
const subtitleBlock = engine.block.create('text');
engine.block.replaceText(
subtitleBlock,
'Browse "My Templates" at the bottom of the dock'
);
engine.block.setFloat(subtitleBlock, 'text/fontSize', 42);
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
```
Then save the template and add it to the asset source using `addAssetToSource()`. Each template needs a unique ID, a label, and metadata containing the template URI and thumbnail:
```typescript highlight-add-to-source
// Save template content and add to asset source
const originalContent = await engine.scene.saveToString();
engine.asset.addAssetToSource('my-templates', {
id: 'template-original',
label: { en: 'Original Template' },
meta: {
uri: `data:application/octet-stream;base64,${originalContent}`,
thumbUri: generateThumbnail('Original Template')
}
});
```
The `meta.uri` field contains the template content as a data URI. The `meta.thumbUri` provides a thumbnail image for display in the asset library.
## Editing Templates
Modify template content using block APIs. You can update text, change images, adjust positions, and reconfigure any block properties.
```typescript highlight-modify-template
// Edit the template content and save as a new version
engine.block.replaceText(titleBlock, 'Updated Template');
engine.block.replaceText(
subtitleBlock,
'This template was edited and saved'
);
const updatedContent = await engine.scene.saveToString();
engine.asset.addAssetToSource('my-templates', {
id: 'template-updated',
label: { en: 'Updated Template' },
meta: {
uri: `data:application/octet-stream;base64,${updatedContent}`,
thumbUri: generateThumbnail('Updated Template')
}
});
```
After editing, save the modified template as a new asset or update an existing one.
## Removing Templates
Remove templates from asset sources using `removeAssetFromSource()`. This permanently deletes the template entry from the source.
```typescript highlight-remove-template
// Add a temporary template to demonstrate removal
engine.asset.addAssetToSource('my-templates', {
id: 'template-temporary',
label: { en: 'Temporary Template' },
meta: {
uri: `data:application/octet-stream;base64,${originalContent}`,
thumbUri: generateThumbnail('Temporary Template')
}
});
// Remove the temporary template from the asset source
engine.asset.removeAssetFromSource('my-templates', 'template-temporary');
```
> **Warning:** Removal is permanent. The template is no longer accessible from the asset source after removal. If you need to restore templates, maintain backups or implement a soft-delete mechanism.
## Saving Updated Templates
To update an existing template, first remove it using `removeAssetFromSource()`, then add the updated version with `addAssetToSource()` using the same asset ID.
```typescript highlight-update-in-source
// Update an existing template by removing and re-adding with same ID
engine.block.replaceText(subtitleBlock, 'Updated again with new content');
const reUpdatedContent = await engine.scene.saveToString();
engine.asset.removeAssetFromSource('my-templates', 'template-updated');
engine.asset.addAssetToSource('my-templates', {
id: 'template-updated',
label: { en: 'Updated Template' },
meta: {
uri: `data:application/octet-stream;base64,${reUpdatedContent}`,
thumbUri: generateThumbnail('Updated Template')
}
});
// Notify that the asset source contents have changed
engine.asset.assetSourceContentsChanged('my-templates');
```
After updating templates, call `assetSourceContentsChanged()` to notify the UI that the asset source contents have changed.
## Best Practices
### Versioning Strategies
When managing template updates, consider these approaches:
- **Replace in place**: Use the same asset ID to update templates without changing references. Existing designs using the template won't break.
- **Version suffixes**: Create new entries with version identifiers (e.g., `template-v2`). This preserves old versions while introducing new ones.
- **Archive old versions**: Move deprecated templates to a separate source before removal. This maintains a history without cluttering the main library.
### Batch Operations
When adding, updating, or removing multiple templates, call `assetSourceContentsChanged()` once after all operations complete rather than after each individual change. This reduces UI refreshes and improves performance.
### Template IDs
Use descriptive, unique IDs that reflect the template's purpose (e.g., `marketing-banner-2024`, `social-post-square`). Consistent naming conventions make templates easier to find and manage programmatically.
### Thumbnails
Generate meaningful thumbnails that accurately represent template content. Good thumbnails improve discoverability in the asset library and help users quickly identify the right template.
### Memory Considerations
Templates stored as base64 data URIs remain in memory. For production applications with many templates, consider storing template content externally and using URLs in the `meta.uri` field instead of inline data URIs.
## API Reference
| Method | Description |
| --- | --- |
| `engine.asset.addLocalSource()` | Create a local asset source |
| `engine.asset.addAssetToSource()` | Add template to asset source |
| `engine.asset.removeAssetFromSource()` | Remove template from asset source |
| `engine.asset.assetSourceContentsChanged()` | Notify UI of asset source changes |
| `engine.scene.saveToString()` | Save scene as base64 string |
| `engine.scene.loadFromString()` | Load scene from base64 string |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create From Scratch"
description: "Build reusable design templates programmatically using CE.SDK's APIs. Create scenes, add text and graphic blocks, configure placeholders and variables, apply editing constraints, and save templates for reuse."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/from-scratch-663cda/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Create From Scratch](https://img.ly/docs/cesdk/sveltekit/create-templates/from-scratch-663cda/)
---
Build reusable design templates entirely through code using CE.SDK's programmatic APIs for automation, batch generation, and custom template creation tools.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-from-scratch-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-from-scratch-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-from-scratch-browser/)
CE.SDK provides a complete API for building design templates through code. Instead of starting from an existing template, you can create a blank scene, define page dimensions, add text and graphic blocks, configure placeholders for swappable media, add text variables for dynamic content, apply editing constraints to protect layout integrity, and save the template for reuse. This approach enables automation workflows, batch template generation, and integration with custom template creation tools.
```typescript file=@cesdk_web_examples/guides-create-templates-from-scratch-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Create Templates From Scratch Guide
*
* Demonstrates building a reusable promotional card template entirely in code:
* - Creating a blank scene with print-ready dimensions (1200x1600)
* - Adding text blocks with variable tokens and proper font styling
* - Adding graphic blocks as image placeholders using addImage()
* - Configuring placeholder behavior for swappable media
* - Applying editing constraints (scopes) to protect layout integrity
* - Saving the template in multiple formats
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
const engine = cesdk.engine;
// Template layout constants for a promotional card
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 1000;
const PADDING = 40;
const CONTENT_WIDTH = CANVAS_WIDTH - PADDING * 2;
// Create a blank scene with custom dimensions
engine.scene.create('Free', {
page: { size: { width: CANVAS_WIDTH, height: CANVAS_HEIGHT } }
});
// Set design unit to Pixel for precise coordinate mapping
engine.scene.setDesignUnit('Pixel');
// Get the page that was automatically created
const page = engine.block.findByType('page')[0];
// Set a gradient background for the template
const backgroundFill = engine.block.createFill('gradient/linear');
engine.block.setGradientColorStops(backgroundFill, 'fill/gradient/colors', [
{ color: { r: 0.4, g: 0.2, b: 0.6, a: 1.0 }, stop: 0 }, // Purple
{ color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } // Blue
]);
engine.block.setFill(page, backgroundFill);
// Font URIs for consistent typography
const FONT_BOLD =
'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Bold.ttf';
const FONT_REGULAR =
'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Regular.ttf';
// Create headline text block with {{title}} variable
const headline = engine.block.create('text');
engine.block.replaceText(headline, '{{title}}');
// Set font with proper typeface for consistent rendering
engine.block.setFont(headline, FONT_BOLD, {
name: 'Roboto',
fonts: [{ uri: FONT_BOLD, subFamily: 'Bold', weight: 'bold' }]
});
engine.block.setFloat(headline, 'text/fontSize', 28);
engine.block.setTextColor(headline, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
// Position and size the headline
engine.block.setWidthMode(headline, 'Absolute');
engine.block.setHeightMode(headline, 'Auto');
engine.block.setWidth(headline, CONTENT_WIDTH);
engine.block.setPositionX(headline, PADDING);
engine.block.setPositionY(headline, 50);
engine.block.setEnum(headline, 'text/horizontalAlignment', 'Center');
engine.block.appendChild(page, headline);
// Set default value for the title variable
engine.variable.setString('title', 'Summer Sale');
// Create subheadline text block with {{subtitle}} variable
const subheadline = engine.block.create('text');
engine.block.replaceText(subheadline, '{{subtitle}}');
engine.block.setFont(subheadline, FONT_REGULAR, {
name: 'Roboto',
fonts: [{ uri: FONT_REGULAR, subFamily: 'Regular', weight: 'normal' }]
});
engine.block.setFloat(subheadline, 'text/fontSize', 14);
engine.block.setTextColor(subheadline, { r: 0.9, g: 0.9, b: 0.95, a: 1.0 });
engine.block.setWidthMode(subheadline, 'Absolute');
engine.block.setHeightMode(subheadline, 'Auto');
engine.block.setWidth(subheadline, CONTENT_WIDTH);
engine.block.setPositionX(subheadline, PADDING);
engine.block.setPositionY(subheadline, 175);
engine.block.setEnum(subheadline, 'text/horizontalAlignment', 'Center');
engine.block.appendChild(page, subheadline);
engine.variable.setString('subtitle', 'Up to 50% off all items');
// Create image placeholder in the center of the card
const imageBlock = engine.block.create('graphic');
const imageShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, imageShape);
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setWidth(imageBlock, CONTENT_WIDTH);
engine.block.setHeight(imageBlock, 420);
engine.block.setPositionX(imageBlock, PADDING);
engine.block.setPositionY(imageBlock, 295);
engine.block.appendChild(page, imageBlock);
// Enable placeholder behavior on the image fill
const fill = engine.block.getFill(imageBlock);
if (fill !== null && engine.block.supportsPlaceholderBehavior(fill)) {
engine.block.setPlaceholderBehaviorEnabled(fill, true);
}
engine.block.setPlaceholderEnabled(imageBlock, true);
// Enable visual controls for the placeholder
engine.block.setPlaceholderControlsOverlayEnabled(imageBlock, true);
engine.block.setPlaceholderControlsButtonEnabled(imageBlock, true);
// Create CTA (call-to-action) text block with {{cta}} variable
const cta = engine.block.create('text');
engine.block.replaceText(cta, '{{cta}}');
engine.block.setFont(cta, FONT_BOLD, {
name: 'Roboto',
fonts: [{ uri: FONT_BOLD, subFamily: 'Bold', weight: 'bold' }]
});
engine.block.setFloat(cta, 'text/fontSize', 8.4);
engine.block.setTextColor(cta, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
engine.block.setWidthMode(cta, 'Absolute');
engine.block.setHeightMode(cta, 'Auto');
engine.block.setWidth(cta, CONTENT_WIDTH);
engine.block.setPositionX(cta, PADDING);
engine.block.setPositionY(cta, 765);
engine.block.setEnum(cta, 'text/horizontalAlignment', 'Center');
engine.block.appendChild(page, cta);
engine.variable.setString('cta', 'Learn More');
// Set global scope to 'Defer' for per-block control
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('layer/resize', 'Defer');
// Lock all text block positions but allow text editing
const textBlocks = [headline, subheadline, cta];
textBlocks.forEach((block) => {
engine.block.setScopeEnabled(block, 'layer/move', false);
engine.block.setScopeEnabled(block, 'layer/resize', false);
});
// Lock image position but allow fill replacement
engine.block.setScopeEnabled(imageBlock, 'layer/move', false);
engine.block.setScopeEnabled(imageBlock, 'layer/resize', false);
engine.block.setScopeEnabled(imageBlock, 'fill/change', true);
// Register role toggle component for switching between Creator and Adopter
cesdk.ui.registerComponent('role.toggle', ({ builder }) => {
const role = engine.editor.getRole();
builder.ButtonGroup('role-toggle', {
children: () => {
builder.Button('creator-btn', {
label: 'Creator',
isActive: role === 'Creator',
onClick: () => engine.editor.setRole('Creator')
});
builder.Button('adopter-btn', {
label: 'Adopter',
isActive: role === 'Adopter',
onClick: () => engine.editor.setRole('Adopter')
});
}
});
});
// Register button component for saving template as string
cesdk.ui.registerComponent('save.string', ({ builder }) => {
builder.Button('save-string-btn', {
label: 'Save String',
icon: '@imgly/Download',
variant: 'regular',
onClick: async () => {
const templateString = await engine.scene.saveToString();
console.log(
'Template saved as string:',
templateString.substring(0, 100) + '...'
);
// Download the string as a file
const blob = new Blob([templateString], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'template.scene';
link.click();
URL.revokeObjectURL(url);
}
});
});
// Register button component for saving template as archive
cesdk.ui.registerComponent('save.archive', ({ builder }) => {
builder.Button('save-archive-btn', {
label: 'Save Archive',
icon: '@imgly/Download',
variant: 'regular',
onClick: async () => {
const templateArchive = await engine.scene.saveToArchive();
console.log(
'Template saved as archive:',
templateArchive.size,
'bytes'
);
// Download the archive as a file
const url = URL.createObjectURL(templateArchive);
const link = document.createElement('a');
link.href = url;
link.download = 'template.zip';
link.click();
URL.revokeObjectURL(url);
}
});
});
// Add role toggle and save buttons to the navigation bar
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'role.toggle');
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'save.string');
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'save.archive');
// Enable auto-fit zoom to continuously fit the page with padding
engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40);
}
}
export default Example;
```
This guide covers how to create a blank scene, add text blocks with variables, add image placeholders, apply editing constraints, and save the template.
## Initialize CE.SDK
We start by initializing CE.SDK and loading the asset sources. The asset source plugins (imported from `@cesdk/cesdk-js/plugins`) provide access to fonts, images, and other assets.
```typescript highlight=highlight-setup
const engine = cesdk.engine;
```
## Create a Blank Scene
We create the foundation of our template with custom page dimensions. The `engine.scene.create()` method accepts page options to set width, height, and background color.
```typescript highlight=highlight-create-scene
// Template layout constants for a promotional card
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 1000;
const PADDING = 40;
const CONTENT_WIDTH = CANVAS_WIDTH - PADDING * 2;
// Create a blank scene with custom dimensions
engine.scene.create('Free', {
page: { size: { width: CANVAS_WIDTH, height: CANVAS_HEIGHT } }
});
// Set design unit to Pixel for precise coordinate mapping
engine.scene.setDesignUnit('Pixel');
```
The scene creation method accepts a layout mode and optional page configuration. When options are provided, the scene automatically includes a page with the specified dimensions.
## Set Page Background
We set a light background color to give the template a consistent base appearance.
```typescript highlight=highlight-add-background
// Set a gradient background for the template
const backgroundFill = engine.block.createFill('gradient/linear');
engine.block.setGradientColorStops(backgroundFill, 'fill/gradient/colors', [
{ color: { r: 0.4, g: 0.2, b: 0.6, a: 1.0 }, stop: 0 }, // Purple
{ color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } // Blue
]);
engine.block.setFill(page, backgroundFill);
```
We create a color fill using `engine.block.createFill('color')`, set the color via `engine.block.setColor()` with the `fill/color/value` property, then assign the fill to the page using `engine.block.setFill()`.
## Add Text Blocks
Text blocks allow you to add styled text content. We create a headline that includes a variable token for dynamic content.
```typescript highlight=highlight-add-text
// Create headline text block with {{title}} variable
const headline = engine.block.create('text');
engine.block.replaceText(headline, '{{title}}');
// Set font with proper typeface for consistent rendering
engine.block.setFont(headline, FONT_BOLD, {
name: 'Roboto',
fonts: [{ uri: FONT_BOLD, subFamily: 'Bold', weight: 'bold' }]
});
engine.block.setFloat(headline, 'text/fontSize', 28);
engine.block.setTextColor(headline, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
// Position and size the headline
engine.block.setWidthMode(headline, 'Absolute');
engine.block.setHeightMode(headline, 'Auto');
engine.block.setWidth(headline, CONTENT_WIDTH);
engine.block.setPositionX(headline, PADDING);
engine.block.setPositionY(headline, 50);
engine.block.setEnum(headline, 'text/horizontalAlignment', 'Center');
engine.block.appendChild(page, headline);
```
We create a text block using `engine.block.create('text')`, set its content with `engine.block.replaceText()`, configure dimensions and position, and append it to the page using `engine.block.appendChild()`.
## Add Text Variables
Text variables enable data-driven personalization. By using `{{variableName}}` tokens in text blocks, you can populate content programmatically.
```typescript highlight=highlight-add-variable
// Set default value for the title variable
engine.variable.setString('title', 'Summer Sale');
```
The `engine.variable.setString()` method sets the default value for the variable. When the template is used, this value can be changed to personalize the content.
## Add Graphic Blocks
Graphic blocks serve as containers for images. We create an image block that will become a placeholder for swappable media.
```typescript highlight=highlight-add-graphic
// Create image placeholder in the center of the card
const imageBlock = engine.block.create('graphic');
const imageShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, imageShape);
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setWidth(imageBlock, CONTENT_WIDTH);
engine.block.setHeight(imageBlock, 420);
engine.block.setPositionX(imageBlock, PADDING);
engine.block.setPositionY(imageBlock, 295);
engine.block.appendChild(page, imageBlock);
```
We create a graphic block with `engine.block.create('graphic')`, assign a rectangle shape using `engine.block.createShape('rect')` and `engine.block.setShape()`, create an image fill with `engine.block.createFill('image')`, set the image URI via `engine.block.setString()`, and position it on the page.
## Configure Placeholders
Placeholders turn design blocks into drop-zones where users can swap content while maintaining layout integrity. We enable placeholder behavior on the image fill and configure visual controls.
```typescript highlight=highlight-configure-placeholder
// Enable placeholder behavior on the image fill
const fill = engine.block.getFill(imageBlock);
if (fill !== null && engine.block.supportsPlaceholderBehavior(fill)) {
engine.block.setPlaceholderBehaviorEnabled(fill, true);
}
engine.block.setPlaceholderEnabled(imageBlock, true);
// Enable visual controls for the placeholder
engine.block.setPlaceholderControlsOverlayEnabled(imageBlock, true);
engine.block.setPlaceholderControlsButtonEnabled(imageBlock, true);
```
Placeholder behavior is enabled on the fill (not the block) for graphic blocks. We also enable the overlay pattern and replace button for visual guidance.
## Apply Editing Constraints
Editing constraints protect template elements by restricting what users can modify. We use scopes to lock position and size while allowing content changes.
```typescript highlight=highlight-apply-constraints
// Set global scope to 'Defer' for per-block control
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('layer/resize', 'Defer');
// Lock all text block positions but allow text editing
const textBlocks = [headline, subheadline, cta];
textBlocks.forEach((block) => {
engine.block.setScopeEnabled(block, 'layer/move', false);
engine.block.setScopeEnabled(block, 'layer/resize', false);
});
// Lock image position but allow fill replacement
engine.block.setScopeEnabled(imageBlock, 'layer/move', false);
engine.block.setScopeEnabled(imageBlock, 'layer/resize', false);
engine.block.setScopeEnabled(imageBlock, 'fill/change', true);
```
Setting global scope to `'Defer'` enables per-block control. We then disable movement and resizing for both blocks while enabling fill changes for the image placeholder.
## Save the Template
We persist the template in two formats: a lightweight string for CDN-hosted assets and a self-contained archive with embedded assets.
```typescript highlight=highlight-save-template
// Register button component for saving template as string
cesdk.ui.registerComponent('save.string', ({ builder }) => {
builder.Button('save-string-btn', {
label: 'Save String',
icon: '@imgly/Download',
variant: 'regular',
onClick: async () => {
const templateString = await engine.scene.saveToString();
console.log(
'Template saved as string:',
templateString.substring(0, 100) + '...'
);
// Download the string as a file
const blob = new Blob([templateString], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'template.scene';
link.click();
URL.revokeObjectURL(url);
}
});
});
// Register button component for saving template as archive
cesdk.ui.registerComponent('save.archive', ({ builder }) => {
builder.Button('save-archive-btn', {
label: 'Save Archive',
icon: '@imgly/Download',
variant: 'regular',
onClick: async () => {
const templateArchive = await engine.scene.saveToArchive();
console.log(
'Template saved as archive:',
templateArchive.size,
'bytes'
);
// Download the archive as a file
const url = URL.createObjectURL(templateArchive);
const link = document.createElement('a');
link.href = url;
link.download = 'template.zip';
link.click();
URL.revokeObjectURL(url);
}
});
});
// Add role toggle and save buttons to the navigation bar
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'role.toggle');
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'save.string');
cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'save.archive');
```
The `engine.scene.saveToString()` method creates a compact string format suitable for storage when assets are hosted externally. The `engine.scene.saveToArchive()` method creates a ZIP bundle containing all assets, ideal for offline use or distribution.
## Troubleshooting
- **Blocks not appearing**: Verify that `engine.block.appendChild()` attaches blocks to the page. Blocks must be part of the scene hierarchy to render.
- **Variables not resolving**: Verify the variable name in the text matches exactly, including curly braces syntax `{{variableName}}`.
- **Placeholder not interactive**: Ensure `engine.block.setPlaceholderEnabled()` is called on the block and the appropriate scope (`fill/change`) is enabled.
- **Constraints not enforced**: Verify `engine.editor.setGlobalScope()` is set to `'Defer'` before setting per-block scopes.
## API Reference
| Method | Description |
| --- | --- |
| `engine.scene.create()` | Create a new design scene with optional page size |
| `engine.scene.setDesignUnit()` | Set the design unit (Pixel, Millimeter, Inch) |
| `engine.scene.saveToString()` | Save scene to string format |
| `engine.scene.saveToArchive()` | Save scene to ZIP archive |
| `engine.block.create()` | Create a design block (page, text, graphic) |
| `engine.block.appendChild()` | Append a child block to a parent |
| `engine.block.findByType()` | Find blocks by their type |
| `engine.block.createFill()` | Create a fill (color, image, etc.) |
| `engine.block.setFill()` | Assign a fill to a block |
| `engine.block.getFill()` | Get the fill of a block |
| `engine.block.createShape()` | Create a shape (rect, ellipse, etc.) |
| `engine.block.setShape()` | Assign a shape to a graphic block |
| `engine.block.setString()` | Set a string property on a block |
| `engine.block.setColor()` | Set a color property |
| `engine.block.replaceText()` | Set text content |
| `engine.block.setFont()` | Set font with typeface |
| `engine.block.setPlaceholderBehaviorEnabled()` | Enable placeholder behavior on fill |
| `engine.block.setPlaceholderEnabled()` | Enable placeholder interaction on block |
| `engine.block.setPlaceholderControlsOverlayEnabled()` | Enable overlay visual control |
| `engine.block.setPlaceholderControlsButtonEnabled()` | Enable button visual control |
| `engine.variable.setString()` | Set a text variable value |
| `engine.editor.setGlobalScope()` | Set global scope permission |
| `engine.block.setScopeEnabled()` | Enable/disable scope on a block |
## Next Steps
- [Placeholders](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/placeholders-d9ba8a/) - Configure placeholder behavior and visual controls in depth
- [Text Variables](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/text-variables-7ecb50/) - Implement dynamic text personalization with variables
- [Set Editing Constraints](https://img.ly/docs/cesdk/sveltekit/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) - Lock layout properties to protect design integrity
- [Add to Template Library](https://img.ly/docs/cesdk/sveltekit/create-templates/add-to-template-library-8bfbc7/) - Register templates in the asset library for users to discover
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Import Templates"
description: "Load and import design templates into CE.SDK from URLs, archives, and serialized strings."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/import-e50084/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Import Templates](https://img.ly/docs/cesdk/sveltekit/create-templates/import-e50084/)
---
Load design templates into CE.SDK from archive URLs, scene URLs, and serialized strings.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-import-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-import-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-import-browser/)
Templates are pre-designed scenes that provide starting points for user projects. CE.SDK supports loading templates from archive URLs with bundled assets, remote scene URLs, or serialized strings stored in databases.
```typescript file=@cesdk_web_examples/guides-create-templates-import-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
// Import scene file as string for loadFromString demonstration
import businessCardSceneString from './assets/business-card.scene?raw';
// Template sources
const fashionAdArchiveUrl =
'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip';
const postcardSceneUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene';
/**
* CE.SDK Plugin: Import Templates
*
* Demonstrates loading templates from different sources:
* - Archive URLs (.zip files with bundled assets)
* - Scene URLs (.scene files)
* - Serialized strings (imported scene content)
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (cesdk == null) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
// Load template from a scene file URL
await engine.scene.loadFromURL(postcardSceneUrl);
// Zoom viewport to fit the loaded scene
const scene = engine.scene.get();
if (scene != null) {
await engine.scene.zoomToBlock(scene, { padding: 40 });
}
// Verify the loaded scene
const loadedScene = engine.scene.get();
if (loadedScene != null) {
const pages = engine.scene.getPages();
// eslint-disable-next-line no-console
console.log(`Template loaded with ${pages.length} page(s)`);
}
// Configure navigation bar with template loading buttons
cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [
'ly.img.undoRedo.navigationBar',
'ly.img.spacer',
{
id: 'ly.img.action.navigationBar',
key: 'load-archive',
label: 'Import Archive',
icon: '@imgly/Download',
variant: 'regular',
onClick: async () => {
// Load template from archive URL (bundled assets)
await engine.scene.loadFromArchiveURL(fashionAdArchiveUrl);
const s = engine.scene.get();
if (s != null) {
await engine.scene.zoomToBlock(s, { padding: 40 });
}
}
},
{
id: 'ly.img.action.navigationBar',
key: 'load-url',
label: 'Import URL',
icon: '@imgly/Download',
variant: 'regular',
onClick: async () => {
// Load template from scene URL
await engine.scene.loadFromURL(postcardSceneUrl);
const s = engine.scene.get();
if (s != null) {
await engine.scene.zoomToBlock(s, { padding: 40 });
}
}
},
{
id: 'ly.img.action.navigationBar',
key: 'load-string',
label: 'Import String',
icon: '@imgly/Download',
variant: 'regular',
onClick: async () => {
// Load template from serialized string
await engine.scene.loadFromString(businessCardSceneString);
const s = engine.scene.get();
if (s != null) {
await engine.scene.zoomToBlock(s, { padding: 40 });
}
}
}
]);
}
}
export default Example;
```
This guide covers how to load templates from archives, URLs, and strings, and work with the loaded content.
## Load from Archive
Load a template from an archive URL using `loadFromArchiveURL()`. Archives are `.zip` files that bundle the scene with all its assets, making them portable and self-contained.
```typescript highlight=highlight-load-from-archive
// Load template from archive URL (bundled assets)
await engine.scene.loadFromArchiveURL(fashionAdArchiveUrl);
```
## Load from URL
Load a template from a remote `.scene` file URL using `loadFromURL()`. The scene file is a JSON-based format that references assets via URLs.
```typescript highlight=highlight-load-from-url
// Load template from a scene file URL
await engine.scene.loadFromURL(postcardSceneUrl);
```
## Load from String
For templates stored in databases or received from APIs, load from a serialized string using `loadFromString()`. This method works with content previously saved using `engine.scene.saveToString()`.
```typescript highlight=highlight-load-from-string
// Load template from serialized string
await engine.scene.loadFromString(businessCardSceneString);
```
## Working with the Loaded Scene
After loading a template, you can verify its contents and adjust the viewport.
### Verify the Scene
Use `engine.scene.get()` to retrieve the scene block and `engine.scene.getPages()` to inspect its pages.
```typescript highlight=highlight-get-scene
// Verify the loaded scene
const loadedScene = engine.scene.get();
if (loadedScene != null) {
const pages = engine.scene.getPages();
// eslint-disable-next-line no-console
console.log(`Template loaded with ${pages.length} page(s)`);
}
```
### Zoom to Content
Fit the loaded template in the viewport using `zoomToBlock()` with optional padding.
```typescript highlight=highlight-zoom-to-scene
// Zoom viewport to fit the loaded scene
const scene = engine.scene.get();
if (scene != null) {
await engine.scene.zoomToBlock(scene, { padding: 40 });
}
```
---
## Related Pages
- [Import Templates from Scene Files](https://img.ly/docs/cesdk/sveltekit/create-templates/import/from-scene-file-52a01e/) - Load and import templates from scene files in CE.SDK for web applications
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Import Templates from Scene Files"
description: "Load and import templates from scene files in CE.SDK for web applications"
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/import/from-scene-file-52a01e/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Import Templates](https://img.ly/docs/cesdk/sveltekit/create-templates/import-e50084/) > [From Scene File](https://img.ly/docs/cesdk/sveltekit/create-templates/import/from-scene-file-52a01e/)
---
CE.SDK lets you load complete design templates from scene files to start projects from pre-designed templates, implement template galleries, and build template management systems.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-import-from-scene-file-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-import-from-scene-file-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-import-from-scene-file-browser/)
Scene files are portable design templates that preserve the entire design structure including blocks, assets, styles, and layout.
```typescript file=@cesdk_web_examples/guides-create-templates-import-from-scene-file-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Import Templates from Scene Files
*
* This example demonstrates:
* - Loading scenes from .scene file URLs
* - Loading scenes from .archive (ZIP) URLs
* - Applying templates while preserving page dimensions
* - Understanding the difference between loading and applying templates
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
// ===== Example: Load Scene from Archive URL =====
// This is the recommended approach for loading complete templates
// with all their assets embedded in a ZIP file
// Load a complete template from an archive (ZIP) file
// This loads both the scene structure and all embedded assets
await engine.scene.loadFromArchiveURL(
'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip'
);
// Alternative: Load scene from URL (.scene file)
// This loads only the scene structure - assets must be accessible via URLs
// Uncomment to try:
// await engine.scene.loadFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
// );
// Alternative: Apply template while preserving current page dimensions
// This is useful when you want to load template content into an existing scene
// with specific dimensions
// Uncomment to try:
// // First create a scene with specific dimensions
// await cesdk.actions.run('scene.create', { page: { width: 1920, height: 1080, unit: 'Pixel' } });
// const page = engine.block.findByType('page')[0];
//
// // Now apply template - content will be adjusted to fit
// await engine.scene.applyTemplateFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene'
// );
// Get the loaded scene
const scene = engine.scene.get();
if (scene) {
// eslint-disable-next-line no-console
console.log('Scene loaded successfully:', scene);
// Get information about the loaded scene
const pages = engine.scene.getPages();
// eslint-disable-next-line no-console
console.log(`Scene has ${pages.length} page(s)`);
// Get design unit
const designUnit = engine.scene.getDesignUnit();
// eslint-disable-next-line no-console
console.log('Design unit:', designUnit);
}
// Zoom to fit the loaded content
if (scene) {
await engine.scene.zoomToBlock(scene, {
padding: 40
});
}
}
}
export default Example;
```
This guide covers loading scenes from archives, loading from URLs, applying templates while preserving dimensions, and understanding scene file formats.
## Scene File Formats
CE.SDK supports two scene file formats for importing templates:
### Scene Format (.scene)
Scene files are JSON-based representations of design structures. They reference external assets via URLs, making them lightweight and suitable for database storage. However, the referenced assets must remain accessible at their URLs.
**When to use:**
- Templates stored in databases
- Templates with hosted assets
- Lightweight transmission
### Archive Format (.archive or .zip)
Archive files are self-contained packages that bundle the scene structure with all referenced assets in a ZIP file. This makes them portable and suitable for offline use.
**When to use:**
- Template distribution
- Offline-capable templates
- Complete portability
- **Recommended for most use cases**
## Load Scene from Archive
The most common way to load templates is from archive URLs. This method loads both the scene structure and all embedded assets:
```typescript file=@cesdk_web_examples/guides-create-templates-import-from-scene-file-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Import Templates from Scene Files
*
* This example demonstrates:
* - Loading scenes from .scene file URLs
* - Loading scenes from .archive (ZIP) URLs
* - Applying templates while preserving page dimensions
* - Understanding the difference between loading and applying templates
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
const engine = cesdk.engine;
// ===== Example: Load Scene from Archive URL =====
// This is the recommended approach for loading complete templates
// with all their assets embedded in a ZIP file
// Load a complete template from an archive (ZIP) file
// This loads both the scene structure and all embedded assets
await engine.scene.loadFromArchiveURL(
'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip'
);
// Alternative: Load scene from URL (.scene file)
// This loads only the scene structure - assets must be accessible via URLs
// Uncomment to try:
// await engine.scene.loadFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
// );
// Alternative: Apply template while preserving current page dimensions
// This is useful when you want to load template content into an existing scene
// with specific dimensions
// Uncomment to try:
// // First create a scene with specific dimensions
// await cesdk.actions.run('scene.create', { page: { width: 1920, height: 1080, unit: 'Pixel' } });
// const page = engine.block.findByType('page')[0];
//
// // Now apply template - content will be adjusted to fit
// await engine.scene.applyTemplateFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene'
// );
// Get the loaded scene
const scene = engine.scene.get();
if (scene) {
// eslint-disable-next-line no-console
console.log('Scene loaded successfully:', scene);
// Get information about the loaded scene
const pages = engine.scene.getPages();
// eslint-disable-next-line no-console
console.log(`Scene has ${pages.length} page(s)`);
// Get design unit
const designUnit = engine.scene.getDesignUnit();
// eslint-disable-next-line no-console
console.log('Design unit:', designUnit);
}
// Zoom to fit the loaded content
if (scene) {
await engine.scene.zoomToBlock(scene, {
padding: 40
});
}
}
}
export default Example;
```
```typescript highlight-load-from-archive
// Load a complete template from an archive (ZIP) file
// This loads both the scene structure and all embedded assets
await engine.scene.loadFromArchiveURL(
'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip'
);
```
When you load from an archive:
- The ZIP file is fetched and extracted
- All assets are registered with CE.SDK
- The scene structure is loaded
- Asset paths are automatically resolved
## Load Scene from URL
You can also load scenes directly from .scene file URLs. This approach requires that all referenced assets remain accessible at their original URLs:
```typescript highlight-load-from-url
// await engine.scene.loadFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
// );
```
**Important:** With this method, if asset URLs become unavailable (404 errors, CORS issues, etc.), those assets won't load and your template may appear incomplete.
## Apply Template vs Load Scene
CE.SDK provides two approaches for working with templates, each serving different purposes:
### Load Scene
When you use `loadFromURL()` or `loadFromArchiveURL()`, CE.SDK:
- Replaces the entire current scene
- Adopts the template's page dimensions
- Loads all content as-is
This is appropriate when starting a new project from a template.
### Apply Template
When you use `applyTemplateFromURL()` or `applyTemplateFromString()`, CE.SDK:
- Keeps your current page dimensions
- Adjusts template content to fit
- Preserves your scene structure
This is useful when you want to load template content into an existing scene with specific dimensions:
```typescript highlight-apply-template
// // First create a scene with specific dimensions
// await cesdk.actions.run('scene.create', { page: { width: 1920, height: 1080, unit: 'Pixel' } });
// const page = engine.block.findByType('page')[0];
//
// // Now apply template - content will be adjusted to fit
// await engine.scene.applyTemplateFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene'
// );
```
## Error Handling
When loading templates, several issues can occur:
### Network Errors
Template URLs might be unreachable:
```typescript
try {
await engine.scene.loadFromArchiveURL(templateUrl);
} catch (error) {
console.error('Failed to load template:', error);
// Show error message to user
// Fall back to default template or empty scene
}
```
### Invalid Scene Format
The file might not be a valid scene:
```typescript
try {
await engine.scene.loadFromURL(sceneUrl);
} catch (error) {
if (error.message.includes('parse')) {
console.error('Invalid scene file format');
}
}
```
### Missing Assets
For .scene files, referenced assets might be unavailable. The scene loads but assets appear missing. Consider using archives to avoid this issue.
## Performance Considerations
### Loading Time
Archive size directly impacts loading time:
- Small archives (\< 1MB): Nearly instant
- Medium archives (1-5MB): 1-2 seconds
- Large archives (> 5MB): Several seconds
Show loading indicators for better user experience.
## CORS Considerations
When loading templates from external URLs, ensure proper CORS headers are set on the server hosting the files. Archives must be accessible with appropriate CORS policies.
## API Reference
| Method | Description |
| ---------------------------------------- | --------------------------------------------------------- |
| `engine.scene.loadFromArchiveURL()` | Loads a complete scene from an archive (ZIP) file |
| `engine.scene.loadFromURL()` | Loads a scene from a .scene file URL |
| `engine.scene.applyTemplateFromURL()` | Applies a template while preserving page dimensions |
| `engine.scene.get()` | Returns the current scene block ID |
| `engine.scene.getPages()` | Returns all page IDs in the scene |
| `engine.scene.getDesignUnit()` | Returns the measurement unit |
| `engine.scene.zoomToBlock()` | Zooms the viewport to fit a specific block |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Lock the Template"
description: "Restrict editing access to specific elements or properties in a template to enforce design rules."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/lock-131489/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Lock the Template](https://img.ly/docs/cesdk/sveltekit/create-templates/lock-131489/)
---
Set up a two-surface integration where template creators have full editing access while template adopters can only modify designated areas.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-lock-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-lock-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-lock-browser/)
Many integrations need two different editing experiences: one for designers who build templates, and one for end users who customize them. The Creator and Adopter roles make this possible—same CE.SDK, different permissions based on who's using it. For detailed scope configuration patterns, see [Lock Content](https://img.ly/docs/cesdk/sveltekit/rules/lock-content-9fa727/).
In the live example, the headline text is pre-selected and the Placeholder panel is open, showing the scope settings that control what Adopters can edit. Toggle the role to Adopter and try selecting the logo to see the restrictions in action.
```typescript file=@cesdk_web_examples/guides-create-templates-lock-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { DesignEditorConfig } from './design-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Lock the Template
*
* This example demonstrates the two-surface pattern for template workflows:
* - Creator role: Full editing access for designers building templates
* - Adopter role: Restricted access for users customizing templates
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new DesignEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] }));
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.blank.*',
'ly.img.templates.presentation.*',
'ly.img.templates.print.*',
'ly.img.templates.social.*',
'ly.img.image.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(new PagePresetsAssetSource());
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
page: { width: 800, height: 500, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the page and set dimensions
const page = engine.block.findByType('page')[0];
// Create a brand template with a logo and headline
const logoBlock = await engine.block.addImage(
'https://img.ly/static/ubq_samples/imgly_logo.jpg',
{ size: { width: 120, height: 30 } }
);
engine.block.appendChild(page, logoBlock);
engine.block.setPositionX(logoBlock, 40);
engine.block.setPositionY(logoBlock, 40);
engine.block.setName(logoBlock, 'Logo');
const headlineBlock = engine.block.create('text');
engine.block.replaceText(headlineBlock, 'Edit this headline');
engine.block.setWidth(headlineBlock, 720);
engine.block.setHeightMode(headlineBlock, 'Auto');
engine.block.setFloat(headlineBlock, 'text/fontSize', 48);
engine.block.setEnum(headlineBlock, 'text/horizontalAlignment', 'Center');
engine.block.appendChild(page, headlineBlock);
engine.block.setPositionX(headlineBlock, 40);
engine.block.setPositionY(headlineBlock, 200);
engine.block.setName(headlineBlock, 'Headline');
// Configure which elements Adopters can edit
// Enable selection and text editing on the headline
engine.block.setScopeEnabled(headlineBlock, 'editor/select', true);
engine.block.setScopeEnabled(headlineBlock, 'text/edit', true);
// Leave all scopes disabled on the logo (default state)
// This prevents Adopters from selecting or modifying the logo
// The Creator role ignores all scope restrictions
engine.editor.setRole('Creator');
// Add a role toggle to the navigation bar (engine calls are reactive)
cesdk.ui.registerComponent(
'ly.img.roleToggle.navigationBar',
({ builder }) => {
const role = engine.editor.getRole();
builder.ButtonGroup('role-toggle', {
children: () => {
builder.Button('creator', {
label: 'Creator',
isActive: role === 'Creator',
onClick: () => engine.editor.setRole('Creator')
});
builder.Button('adopter', {
label: 'Adopter',
isActive: role === 'Adopter',
onClick: () => {
// Close the placeholder panel since Adopters can't configure scopes
cesdk.ui.closePanel(
'//ly.img.panel/inspector/placeholderSettings'
);
engine.editor.setRole('Adopter');
}
});
}
});
}
);
cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [
'ly.img.undoRedo.navigationBar',
'ly.img.spacer',
'ly.img.roleToggle.navigationBar',
'ly.img.spacer'
]);
await engine.scene.zoomToBlock(page, { padding: 40 });
// Select the headline and open the placeholder panel so users see the scope settings
engine.block.select(headlineBlock);
setTimeout(() => {
cesdk.ui.openPanel('//ly.img.panel/inspector/placeholderSettings');
}, 300);
}
}
export default Example;
```
This guide covers how to understand the two-surface pattern, configure roles for different user groups, and set up scope restrictions that control what Adopters can edit.
## Understanding the Two-Surface Pattern
Template-based workflows typically involve two distinct user groups with different needs:
| Surface | Users | Role | What they can do |
|---------|-------|------|------------------|
| Creator Surface | Designers, admins | `Creator` | Full editing—build templates, set locks |
| Adopter Surface | End users, marketers | `Adopter` | Restricted editing—only modify unlocked areas |
This separation protects design intent while enabling customization. The Creator role ignores all locks, giving full access. The Adopter role respects locks, restricting users to what's explicitly allowed.
## Setting Up the Creator Surface
The Creator surface is where templates are built. We use `engine.editor.setRole('Creator')` to give designers unrestricted access.
```typescript highlight=highlight-creator-surface
// The Creator role ignores all scope restrictions
engine.editor.setRole('Creator');
```
In Creator mode, all operations are permitted regardless of scope settings. This is where designers build the template layout, configure which elements should be editable, set scope restrictions using `engine.block.setScopeEnabled()`, and save the template for distribution.
## Setting Up the Adopter Surface
The Adopter surface is where templates are used. Call `engine.editor.setRole('Adopter')` to enforce the restrictions configured by creators. In Adopter mode, users can only interact with blocks that have the appropriate scopes enabled. The Adopter role respects all lock configurations, ensuring brand consistency and design intent are maintained.
## When to Use This Pattern
This two-surface approach works well for:
- **Brand template systems**: Marketing teams customize approved templates
- **Design approval workflows**: Creators build, reviewers can't accidentally modify
- **Self-service customization**: End users personalize within guardrails
- **White-label products**: Customers can only edit designated areas
For simpler use cases where all users have the same permissions, you may not need separate surfaces.
## Configuring What Users Can Edit
The scope system controls what Adopters can modify. In Creator mode, we enable specific scopes on blocks that should be editable.
```typescript highlight=highlight-configure-scopes
// Configure which elements Adopters can edit
// Enable selection and text editing on the headline
engine.block.setScopeEnabled(headlineBlock, 'editor/select', true);
engine.block.setScopeEnabled(headlineBlock, 'text/edit', true);
// Leave all scopes disabled on the logo (default state)
// This prevents Adopters from selecting or modifying the logo
```
When Adopters load this template, they can edit the headline text but nothing else. The `editor/select` scope must be enabled for users to interact with a block at all. For comprehensive scope configuration patterns, see [Lock Content](https://img.ly/docs/cesdk/sveltekit/rules/lock-content-9fa727/).
## Configuring Scopes in the Editor UI
Designers can also configure scopes visually without writing code. In Creator mode, select any block and open the Placeholder panel in the inspector. This panel provides toggles for each scope:
- **Allow selecting** (`editor/select`): Users can click to select the block
- **Allow editing text** (`text/edit`): Users can modify text content
- **Allow changing fill** (`fill/change`): Users can swap images or change colors
- **Allow moving** (`layer/move`): Users can reposition the block
- **Allow deleting** (`lifecycle/destroy`): Users can remove the block
Changes made in the Placeholder panel are equivalent to calling `engine.block.setScopeEnabled()` programmatically. When the template is saved, these settings persist and apply when Adopters load the template.
## Adding a Role Toggle
Add a segmented control to the navigation bar that switches between Creator and Adopter modes. Engine calls inside the builder are automatically reactive—the component re-renders when the role changes.
```typescript highlight=highlight-toggle-role
// Add a role toggle to the navigation bar (engine calls are reactive)
cesdk.ui.registerComponent(
'ly.img.roleToggle.navigationBar',
({ builder }) => {
const role = engine.editor.getRole();
builder.ButtonGroup('role-toggle', {
children: () => {
builder.Button('creator', {
label: 'Creator',
isActive: role === 'Creator',
onClick: () => engine.editor.setRole('Creator')
});
builder.Button('adopter', {
label: 'Adopter',
isActive: role === 'Adopter',
onClick: () => {
// Close the placeholder panel since Adopters can't configure scopes
cesdk.ui.closePanel(
'//ly.img.panel/inspector/placeholderSettings'
);
engine.editor.setRole('Adopter');
}
});
}
});
}
);
cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [
'ly.img.undoRedo.navigationBar',
'ly.img.spacer',
'ly.img.roleToggle.navigationBar',
'ly.img.spacer'
]);
```
## Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| Adopter can edit everything | Wrong role or scopes not configured | Verify role is `Adopter` and scopes are set in Creator mode |
| Adopter can't edit anything | `editor/select` scope not enabled | Enable `editor/select` on blocks users should interact with |
| Creator can't set locks | Wrong role | Switch to Creator role before configuring scopes |
| Changes not persisting | Template not saved after scope changes | Save template after configuring scopes in Creator mode |
## API Reference
| Method | Description |
|--------|-------------|
| `engine.editor.setRole(role)` | Set the editing role (`'Creator'`, `'Adopter'`, or `'Viewer'`) |
| `engine.editor.getRole()` | Get the current editing role |
| `engine.block.setScopeEnabled(block, scope, enabled)` | Enable or disable a scope on a block |
| `engine.block.isScopeEnabled(block, scope)` | Check if a scope is enabled on a block |
| `cesdk.ui.registerComponent(id, renderFn)` | Register a custom UI component |
| `builder.ButtonGroup(id, { children })` | Create a segmented control |
| `builder.Button(id, { label, isActive, onClick })` | Create a button |
### Common Scopes
| Scope | Description |
|-------|-------------|
| `'editor/select'` | Allow selecting the block (required for any interaction) |
| `'fill/change'` | Allow changing the block's fill (images, colors) |
| `'text/edit'` | Allow editing text content |
| `'text/character'` | Allow changing text formatting (font, size, color) |
| `'layer/move'` | Allow moving the block |
| `'layer/resize'` | Allow resizing the block |
| `'layer/rotate'` | Allow rotating the block |
| `'layer/crop'` | Allow cropping the block |
| `'lifecycle/destroy'` | Allow deleting the block |
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-templates/overview-4ebe30/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/sveltekit/create-templates-3aef79/) > [Overview](https://img.ly/docs/cesdk/sveltekit/create-templates/overview-4ebe30/)
---
These imported designs can then be adapted into editable, structured templates inside CE.SDK.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Videos"
description: "Learn how to create and customize videos in CE.SDK using scenes, assets, and time-based editing."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-video-c41a08/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/sveltekit/create-video-c41a08/)
---
---
## Related Pages
- [Create Videos Overview](https://img.ly/docs/cesdk/sveltekit/create-video/overview-b06512/) - Learn how to create and customize videos in CE.SDK using scenes, assets, and time-based editing.
- [Video and Audio Timeline Web Editor](https://img.ly/docs/cesdk/sveltekit/create-video/timeline-editor-912252/) - Use the timeline editor to arrange and edit video clips, audio, and animations frame by frame.
- [Control Audio and Video](https://img.ly/docs/cesdk/sveltekit/create-video/control-daba54/) - Learn to play, pause, seek, and preview audio and video content in CE.SDK using playback controls and solo mode.
- [Trim Video Clips](https://img.ly/docs/cesdk/sveltekit/edit-video/trim-4f688b/) - Learn how to trim video clips in CE.SDK to control which portion of media plays back.
- [Split Video and Audio](https://img.ly/docs/cesdk/sveltekit/edit-video/split-464167/) - Learn how to split video and audio clips at specific time points in CE.SDK, creating two independent segments from a single clip.
- [Join and Arrange Video Clips](https://img.ly/docs/cesdk/sveltekit/edit-video/join-and-arrange-3bbc30/) - Combine multiple video clips into sequences and organize them on the timeline using tracks and time offsets in CE.SDK.
- [Transform](https://img.ly/docs/cesdk/sveltekit/edit-video/transform-369f28/) - Documentation for Transform
- [Add Captions](https://img.ly/docs/cesdk/sveltekit/edit-video/add-captions-f67565/) - Documentation for adding captions to videos
- [Update Caption Presets](https://img.ly/docs/cesdk/sveltekit/create-video/update-caption-presets-e9c385/) - Extend video captions with custom caption styles using simple content.json updates
- [Add Watermark](https://img.ly/docs/cesdk/sveltekit/edit-video/add-watermark-762ce6/) - Add text and image watermarks to videos using CE.SDK for copyright protection, branding, and content attribution with timeline management and visibility controls.
- [Redact Sensitive Content in Videos](https://img.ly/docs/cesdk/sveltekit/edit-video/redaction-cf6d03/) - Redact sensitive video content using blur, pixelization, or solid overlays. Essential for privacy protection when obscuring faces, license plates, or personal information.
- [Video Editor SDK](https://img.ly/docs/cesdk/sveltekit/overview-7d12d5/) - Explore video editing features in CE.SDK including trimming, splitting, captions, and programmatic editing.
- [Video Limitations](https://img.ly/docs/cesdk/sveltekit/create-video/limitations-6a740d/) - Understand resolution limits, duration constraints, codec support, and browser-specific restrictions when working with video in CE.SDK.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Adjust Audio Playback Speed"
description: "Learn how to adjust audio playback speed in CE.SDK to create slow-motion, time-stretched, and fast-forward audio effects."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-video/audio/adjust-speed-908d57/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/sveltekit/create-audio/audio-2f700b/) > [Adjust Speed](https://img.ly/docs/cesdk/sveltekit/create-video/audio/adjust-speed-908d57/)
---
Control audio playback speed by adjusting the speed multiplier using CE.SDK's
timeline UI and programmatic speed adjustment API.

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-adjust-speed-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-adjust-speed-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-audio-adjust-speed-browser/)
Playback speed adjustment changes how fast or slow audio plays without changing its pitch (though pitch shifting may occur depending on the audio processing implementation). A speed multiplier of 1.0 represents normal speed, values below 1.0 slow down playback, and values above 1.0 speed it up. This technique is commonly used for podcast speed controls, time-compressed narration, slow-motion audio effects, and accessibility features.
```typescript file=@cesdk_web_examples/guides-create-audio-audio-adjust-speed-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Adjust Audio Speed Guide
*
* Demonstrates audio playback speed adjustment in CE.SDK:
* - Loading audio files
* - Adjusting playback speed with setPlaybackSpeed
* - Three speed presets: slow-motion (0.5x), normal (1.0x), and maximum (3.0x)
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.instagram.story'
}
});
const engine = cesdk.engine;
const scene = engine.scene.get();
const pages = engine.block.findByType('page');
const page = pages.length > 0 ? pages[0] : scene;
// Use a sample audio file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a';
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Wait for audio resource to load
await engine.block.forceLoadAVResource(audioBlock);
// Slow Motion Audio (0.5x - half speed)
const slowAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, slowAudioBlock);
engine.block.setPositionX(slowAudioBlock, 100);
engine.block.setPositionY(slowAudioBlock, 200);
engine.block.setPlaybackSpeed(slowAudioBlock, 0.5);
// Normal Speed Audio (1.0x)
const normalAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, normalAudioBlock);
engine.block.setPositionX(normalAudioBlock, 100);
engine.block.setPositionY(normalAudioBlock, 400);
engine.block.setPlaybackSpeed(normalAudioBlock, 1.0);
// Maximum Speed Audio (3.0x - triple speed)
const maxSpeedAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, maxSpeedAudioBlock);
engine.block.setPositionX(maxSpeedAudioBlock, 100);
engine.block.setPositionY(maxSpeedAudioBlock, 600);
engine.block.setPlaybackSpeed(maxSpeedAudioBlock, 3.0);
// Log duration changes for demonstration
const slowDuration = engine.block.getDuration(slowAudioBlock);
const normalDuration = engine.block.getDuration(normalAudioBlock);
const maxDuration = engine.block.getDuration(maxSpeedAudioBlock);
// eslint-disable-next-line no-console
console.log(`Slow motion (0.5x) duration: ${slowDuration.toFixed(2)}s`);
// eslint-disable-next-line no-console
console.log(`Normal speed (1.0x) duration: ${normalDuration.toFixed(2)}s`);
// eslint-disable-next-line no-console
console.log(`Maximum speed (3.0x) duration: ${maxDuration.toFixed(2)}s`);
// Remove the original audio block (we only need the duplicates)
engine.block.destroy(audioBlock);
// Zoom to fit all audio blocks and labels
engine.scene.zoomToBlock(page, 40, 40, 40, 40);
}
}
export default Example;
```
This guide covers how to adjust audio playback speed programmatically using the Engine API, understand speed constraints, and manage how speed changes affect block duration.
## Understanding Speed Concepts
CE.SDK supports playback speeds from **0.25x** (quarter speed) to **3.0x** (triple speed), with **1.0x** as the default normal speed. Values below 1.0 slow down playback, values above 1.0 speed it up.
**Speed and Duration**: Adjusting speed automatically changes the block's duration following an inverse relationship: `perceived_duration = original_duration / speed_multiplier`. A 10-second clip at 2.0x speed plays in 5 seconds; at 0.5x speed it takes 20 seconds. This automatic adjustment maintains synchronization when coordinating audio with other elements.
**Common use cases**: Podcast playback controls (1.5x-2.0x), accessibility features (0.75x for easier comprehension), time-compressed narration, dramatic slow-motion effects (0.25x-0.5x), transcription work, and music tempo adjustments.
## Setting Up Audio for Speed Adjustment
### Loading Audio Files
We create an audio block and load an audio file by setting its `fileURI` property.
```typescript highlight-create-audio
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Wait for audio resource to load
await engine.block.forceLoadAVResource(audioBlock);
```
Unlike video or image blocks which use fills, audio blocks store the file URI directly on the block itself using the `audio/fileURI` property. The `forceLoadAVResource` call ensures CE.SDK has downloaded the audio file and loaded its metadata, which is essential for accurate duration information and playback speed control.
## Adjusting Playback Speed
### Setting Normal Speed
By default, audio plays at normal speed (1.0x). We can explicitly set this to ensure consistent baseline behavior.
```typescript highlight-set-normal-speed
// Normal Speed Audio (1.0x)
const normalAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, normalAudioBlock);
engine.block.setPositionX(normalAudioBlock, 100);
engine.block.setPositionY(normalAudioBlock, 400);
engine.block.setPlaybackSpeed(normalAudioBlock, 1.0);
```
Setting speed to 1.0 ensures the audio plays at its original recorded rate. This is useful after experimenting with different speeds and wanting to return to normal, or when initializing audio blocks programmatically to ensure consistent starting states.
### Querying Current Speed
We can check the current playback speed at any time using `getPlaybackSpeed`.
```typescript highlight-set-normal-speed
// Normal Speed Audio (1.0x)
const normalAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, normalAudioBlock);
engine.block.setPositionX(normalAudioBlock, 100);
engine.block.setPositionY(normalAudioBlock, 400);
engine.block.setPlaybackSpeed(normalAudioBlock, 1.0);
```
This returns the current speed multiplier as a number. Use this to populate UI controls, validate that speed changes were applied, or make relative adjustments based on existing speeds.
## Common Speed Presets
### Slow Motion Audio (0.5x)
Slowing audio to half speed creates a slow-motion effect that's useful for careful listening or transcription.
```typescript highlight-set-slow-motion
// Slow Motion Audio (0.5x - half speed)
const slowAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, slowAudioBlock);
engine.block.setPositionX(slowAudioBlock, 100);
engine.block.setPositionY(slowAudioBlock, 200);
engine.block.setPlaybackSpeed(slowAudioBlock, 0.5);
```
At 0.5x speed, a 10-second audio clip will take 20 seconds to play. This slower pace makes it easier to catch details, transcribe speech accurately, or create dramatic slow-motion audio effects in creative projects.
### Maximum Speed (3.0x)
The maximum supported speed is 3.0x, three times normal playback rate.
```typescript highlight-set-maximum-speed
// Maximum Speed Audio (3.0x - triple speed)
const maxSpeedAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, maxSpeedAudioBlock);
engine.block.setPositionX(maxSpeedAudioBlock, 100);
engine.block.setPositionY(maxSpeedAudioBlock, 600);
engine.block.setPlaybackSpeed(maxSpeedAudioBlock, 3.0);
```
At maximum speed, audio plays very quickly—a 10-second clip finishes in just 3.33 seconds. This extreme speed is useful for rapidly skimming through content to find specific moments, though comprehension becomes challenging at this rate.
## Speed and Block Duration
### Understanding Duration Changes
When we change playback speed, CE.SDK automatically adjusts the block's duration to reflect the new playback time.
```typescript highlight-speed-and-duration
// Log duration changes for demonstration
const slowDuration = engine.block.getDuration(slowAudioBlock);
const normalDuration = engine.block.getDuration(normalAudioBlock);
const maxDuration = engine.block.getDuration(maxSpeedAudioBlock);
// eslint-disable-next-line no-console
console.log(`Slow motion (0.5x) duration: ${slowDuration.toFixed(2)}s`);
// eslint-disable-next-line no-console
console.log(`Normal speed (1.0x) duration: ${normalDuration.toFixed(2)}s`);
// eslint-disable-next-line no-console
console.log(`Maximum speed (3.0x) duration: ${maxDuration.toFixed(2)}s`);
```
The original duration represents how long the audio takes to play at normal speed. When we double the speed to 2.0x, the duration is automatically halved. The audio content is the same, but it plays through in half the time, so the block duration shrinks accordingly.
This automatic adjustment keeps your composition synchronized. If you have multiple audio tracks or need to coordinate audio with video, the block durations will accurately reflect the new playback duration after speed changes.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Adjust Audio Volume"
description: "Learn how to adjust audio volume in CE.SDK to control playback levels, mute audio, and balance multiple audio sources in video projects."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-video/audio/adjust-volume-7ecc4a/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/sveltekit/create-audio/audio-2f700b/) > [Adjust Volume](https://img.ly/docs/cesdk/sveltekit/create-video/audio/adjust-volume-7ecc4a/)
---
Control audio playback volume using CE.SDK's timeline UI and the programmatic
volume control API, from silent (0.0) to full volume (1.0).

> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-adjust-volume-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-adjust-volume-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-audio-adjust-volume-browser/)
Volume control adjusts how loud or quiet audio plays during playback. CE.SDK uses a normalized 0.0-1.0 range where 0.0 is completely silent and 1.0 is full volume. This applies to both audio blocks and video fills with embedded audio. Volume settings are commonly used for balancing multiple audio sources, creating fade effects, and allowing users to adjust playback levels.
```typescript file=@cesdk_web_examples/guides-create-audio-audio-adjust-volume-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
/**
* CE.SDK Plugin: Adjust Audio Volume Guide
*
* Demonstrates audio volume control in CE.SDK:
* - Setting volume levels with setVolume
* - Muting and unmuting with setMuted
* - Querying volume and mute states
* - Volume levels for multiple audio sources
*/
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: {
sourceId: 'ly.img.page.presets',
assetId: 'ly.img.page.presets.instagram.story'
}
});
const engine = cesdk.engine;
const scene = engine.scene.get();
const pages = engine.block.findByType('page');
const page = pages.length > 0 ? pages[0] : scene;
// Use a sample audio file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a';
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Wait for audio resource to load
await engine.block.forceLoadAVResource(audioBlock);
// Set volume to 80% (0.8 on a 0.0-1.0 scale)
const fullVolumeAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, fullVolumeAudio);
engine.block.setTimeOffset(fullVolumeAudio, 0);
engine.block.setVolume(fullVolumeAudio, 0.8);
// Set volume to 30% for background music
const lowVolumeAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, lowVolumeAudio);
engine.block.setTimeOffset(lowVolumeAudio, 5);
engine.block.setVolume(lowVolumeAudio, 0.3);
// Mute an audio block (preserves volume setting)
const mutedAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, mutedAudio);
engine.block.setTimeOffset(mutedAudio, 10);
engine.block.setVolume(mutedAudio, 1.0);
engine.block.setMuted(mutedAudio, true);
// Query current volume and mute states
const currentVolume = engine.block.getVolume(fullVolumeAudio);
const isMuted = engine.block.isMuted(mutedAudio);
const isForceMuted = engine.block.isForceMuted(mutedAudio);
// eslint-disable-next-line no-console
console.log(`Full volume audio: ${(currentVolume * 100).toFixed(0)}%`);
// eslint-disable-next-line no-console
console.log(
`Low volume audio: ${(
engine.block.getVolume(lowVolumeAudio) * 100
).toFixed(0)}%`
);
// eslint-disable-next-line no-console
console.log(
`Muted audio - isMuted: ${isMuted}, isForceMuted: ${isForceMuted}`
);
// Remove the original audio block (we only need the duplicates)
engine.block.destroy(audioBlock);
// Zoom to fit all audio blocks
engine.scene.zoomToBlock(page, 40, 40, 40, 40);
}
}
export default Example;
```
This guide covers how to adjust audio volume programmatically using the Engine API, mute and unmute audio, and query volume and mute states.
## Understanding Volume Concepts
CE.SDK supports volume levels from **0.0** (silent) to **1.0** (full volume), with **1.0** as the default for new audio blocks. Values in between represent proportional volume levels—0.5 is half volume, 0.25 is quarter volume.
**Volume vs Muting**: Setting volume to 0.0 makes audio silent, but muting with `setMuted()` is preferred when you want to temporarily silence audio without losing the volume setting. Unmuting restores the previous volume level.
**Common use cases**: Background music mixing (0.3-0.5 under voiceover), user volume controls, audio balancing for multi-track projects, fade effects (gradually adjusting volume over time), and accessibility features.
## Setting Up Audio for Volume Control
### Loading Audio Files
We create an audio block and load an audio file by setting its `fileURI` property.
```typescript highlight-create-audio
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Wait for audio resource to load
await engine.block.forceLoadAVResource(audioBlock);
```
Unlike video or image blocks which use fills, audio blocks store the file URI directly on the block itself using the `audio/fileURI` property. The `forceLoadAVResource` call ensures CE.SDK has downloaded the audio file and loaded its metadata before we manipulate it.
## Adjusting Volume
### Setting Volume
We can set volume using `setVolume()` with a value between 0.0 and 1.0.
```typescript highlight-set-volume
// Set volume to 80% (0.8 on a 0.0-1.0 scale)
const fullVolumeAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, fullVolumeAudio);
engine.block.setTimeOffset(fullVolumeAudio, 0);
engine.block.setVolume(fullVolumeAudio, 0.8);
```
Setting volume to 0.8 (80%) is useful when you want prominent audio that isn't at maximum level, leaving headroom for other audio sources or preventing distortion.
### Setting Low Volume for Background Audio
For background music that should be audible but not prominent, use lower volume levels.
```typescript highlight-set-low-volume
// Set volume to 30% for background music
const lowVolumeAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, lowVolumeAudio);
engine.block.setTimeOffset(lowVolumeAudio, 5);
engine.block.setVolume(lowVolumeAudio, 0.3);
```
At 0.3 (30%) volume, the audio is clearly audible but stays in the background. This is a common level for background music under voiceover or dialogue.
## Muting Audio
### Mute and Unmute
Use `setMuted()` to mute audio without changing its volume setting. This is useful for toggle controls.
```typescript highlight-mute-audio
// Mute an audio block (preserves volume setting)
const mutedAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, mutedAudio);
engine.block.setTimeOffset(mutedAudio, 10);
engine.block.setVolume(mutedAudio, 1.0);
engine.block.setMuted(mutedAudio, true);
```
When you mute an audio block, the volume setting (1.0 in this case) is preserved. Unmuting later with `setMuted(block, false)` restores playback at the same volume level.
### Querying Volume and Mute States
You can query the current volume and mute states at any time.
```typescript highlight-query-volume
// Query current volume and mute states
const currentVolume = engine.block.getVolume(fullVolumeAudio);
const isMuted = engine.block.isMuted(mutedAudio);
const isForceMuted = engine.block.isForceMuted(mutedAudio);
// eslint-disable-next-line no-console
console.log(`Full volume audio: ${(currentVolume * 100).toFixed(0)}%`);
// eslint-disable-next-line no-console
console.log(
`Low volume audio: ${(
engine.block.getVolume(lowVolumeAudio) * 100
).toFixed(0)}%`
);
// eslint-disable-next-line no-console
console.log(
`Muted audio - isMuted: ${isMuted}, isForceMuted: ${isForceMuted}`
);
```
Use `getVolume()` to read the current volume level, `isMuted()` to check if the block is muted by the user, and `isForceMuted()` to check if the engine has automatically muted the block due to playback rules.
## Mixing Multiple Audio Sources
### Balancing Tracks
When working with multiple audio sources, use different volume levels to create a balanced mix. A common approach is to keep voiceover or dialogue at higher levels (0.8-1.0) and background music at lower levels (0.3-0.5).
### Common Mixing Patterns
**Voiceover prominent**: Set background music to 0.3 and voiceover to 1.0 for clear narration with musical accompaniment.
**Balanced dialogue and music**: Set both to 0.6-0.7 when both elements are equally important.
**Sound effects as accents**: Set sound effects to 0.5-0.8 depending on how prominent they should be in the mix.
## Building Volume Controls
### Volume Slider
When building a volume slider UI, map the slider value directly to the 0.0-1.0 range. Display percentages (0-100%) for user-friendly labels.
```typescript
// Example: Update volume from slider (0-100)
const sliderValue = 75; // User drags slider to 75%
const volume = sliderValue / 100; // Convert to 0.0-1.0
engine.block.setVolume(audioBlock, volume);
```
### Mute Toggle
Implement mute buttons using `setMuted()` and indicate the current state using `isMuted()`. Show a different icon when `isForceMuted()` returns true to indicate the engine has automatically muted the audio.
```typescript
// Example: Toggle mute state
const currentlyMuted = engine.block.isMuted(audioBlock);
engine.block.setMuted(audioBlock, !currentlyMuted);
// Check if engine force-muted (e.g., high playback speed)
if (engine.block.isForceMuted(audioBlock)) {
// Show "force muted" indicator
}
```
## Troubleshooting
### Volume Changes Not Audible
Check if the block is muted with `isMuted()` or force muted with `isForceMuted()`. Also verify the audio resource has loaded successfully.
### Force Muted State
Video fills at playback speeds above 3.0x are automatically force muted by the engine. Reduce the playback speed to restore audio output.
### Volume Not Persisting
Ensure you're setting volume on the correct block ID. Volume settings are block-specific and don't propagate to duplicates or other instances.
---
## More Resources
- **[SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md)** - Browse all SvelteKit documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/sveltekit/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/sveltekit/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Control Audio and Video"
description: "Learn to play, pause, seek, and preview audio and video content in CE.SDK using playback controls and solo mode."
platform: sveltekit
url: "https://img.ly/docs/cesdk/sveltekit/create-video/control-daba54/"
---
> This is one page of the CE.SDK SvelteKit documentation. For a complete overview, see the [SvelteKit Documentation Index](https://img.ly/docs/cesdk/sveltekit.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/sveltekit/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/sveltekit/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/sveltekit/create-video-c41a08/) > [Control Audio and Video](https://img.ly/docs/cesdk/sveltekit/create-video/control-daba54/)
---
Play, pause, seek, and preview audio and video content programmatically using CE.SDK's playback control APIs.

> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-control-browser)
>
> - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-control-browser)
>
> - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-control-browser/)
CE.SDK provides playback control for audio and video through the Block API. Playback state, seeking, and solo preview are controlled programmatically. Resources must be loaded before accessing metadata like duration and dimensions.
```typescript file=@cesdk_web_examples/guides-create-video-control-browser/browser.ts reference-only
import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';
import {
BlurAssetSource,
CaptionPresetsAssetSource,
ColorPaletteAssetSource,
CropPresetsAssetSource,
DemoAssetSources,
EffectsAssetSource,
FiltersAssetSource,
PagePresetsAssetSource,
StickerAssetSource,
TextAssetSource,
TextComponentAssetSource,
TypefaceAssetSource,
UploadAssetSources,
VectorShapeAssetSource
} from '@cesdk/cesdk-js/plugins';
import { VideoEditorConfig } from './video-editor/plugin';
import packageJson from './package.json';
class Example implements EditorPlugin {
name = packageJson.name;
version = packageJson.version;
async initialize({ cesdk }: EditorPluginContext): Promise {
if (!cesdk) {
throw new Error('CE.SDK instance is required for this plugin');
}
await cesdk.addPlugin(new VideoEditorConfig());
// Add asset source plugins
await cesdk.addPlugin(new BlurAssetSource());
await cesdk.addPlugin(new CaptionPresetsAssetSource());
await cesdk.addPlugin(new ColorPaletteAssetSource());
await cesdk.addPlugin(new CropPresetsAssetSource());
await cesdk.addPlugin(
new UploadAssetSources({
include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload']
})
);
await cesdk.addPlugin(
new DemoAssetSources({
include: [
'ly.img.templates.video.*',
'ly.img.image.*',
'ly.img.audio.*',
'ly.img.video.*'
]
})
);
await cesdk.addPlugin(new EffectsAssetSource());
await cesdk.addPlugin(new FiltersAssetSource());
await cesdk.addPlugin(
new PagePresetsAssetSource({
include: [
'ly.img.page.presets.instagram.*',
'ly.img.page.presets.facebook.*',
'ly.img.page.presets.x.*',
'ly.img.page.presets.linkedin.*',
'ly.img.page.presets.pinterest.*',
'ly.img.page.presets.tiktok.*',
'ly.img.page.presets.youtube.*',
'ly.img.page.presets.video.*'
]
})
);
await cesdk.addPlugin(new StickerAssetSource());
await cesdk.addPlugin(new TextAssetSource());
await cesdk.addPlugin(new TextComponentAssetSource());
await cesdk.addPlugin(new TypefaceAssetSource());
await cesdk.addPlugin(new VectorShapeAssetSource());
await cesdk.actions.run('scene.create', {
layout: 'DepthStack',
page: { width: 1920, height: 1080, unit: 'Pixel' }
});
const engine = cesdk.engine;
// Get the page and set to 16:9 landscape for video
const page = engine.block.findByType('page')[0]!;
// Create a track for video blocks
const track = engine.block.create('track');
engine.block.appendChild(page, track);
// Create a video block and add it to the track
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
const videoBlock = engine.block.create('graphic');
engine.block.setShape(videoBlock, engine.block.createShape('rect'));
engine.block.setWidth(videoBlock, 1920);
engine.block.setHeight(videoBlock, 1080);
// Create and configure video fill
const videoFill = engine.block.createFill('video');
engine.block.setString(videoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(videoBlock, videoFill);
// Add to track and set duration
engine.block.appendChild(track, videoBlock);
engine.block.setDuration(videoBlock, 10);
await engine.block.forceLoadAVResource(videoFill);
const videoWidth = engine.block.getVideoWidth(videoFill);
const videoHeight = engine.block.getVideoHeight(videoFill);
const totalDuration = engine.block.getAVResourceTotalDuration(videoFill);
console.log(`Video dimensions: ${videoWidth}x${videoHeight}`);
console.log(`Total duration: ${totalDuration}s`);
if (engine.block.supportsPlaybackControl(page)) {
console.log(`Is playing: ${engine.block.isPlaying(page)}`);
engine.block.setPlaying(page, true);
}
if (engine.block.supportsPlaybackTime(page)) {
engine.block.setPlaybackTime(page, 1.0);
console.log(`Playback time: ${engine.block.getPlaybackTime(page)}s`);
}
console.log(
`Visible at current time: ${engine.block.isVisibleAtCurrentPlaybackTime(
videoBlock
)}`
);
engine.block.setSoloPlaybackEnabled(videoFill, true);
console.log(
`Solo enabled: ${engine.block.isSoloPlaybackEnabled(videoFill)}`
);
engine.block.setSoloPlaybackEnabled(videoFill, false);
// Select the video block for inspection
engine.block.select(videoBlock);
}
}
export default Example;
```
This guide covers how to play and pause media, seek to specific positions, preview individual blocks with solo mode, check visibility at playback time, and access video resource metadata.
## Force Loading Resources
Media resource metadata is unavailable until the resource is loaded. Call `forceLoadAVResource` on the video fill to ensure dimensions and duration are accessible.
```typescript highlight=highlight-force-load
await engine.block.forceLoadAVResource(videoFill);
```
Without loading the resource first, accessing properties like duration or dimensions throws an error.
## Getting Video Metadata
Once the resource is loaded, query the video dimensions and total duration.
```typescript highlight=highlight-get-metadata
const videoWidth = engine.block.getVideoWidth(videoFill);
const videoHeight = engine.block.getVideoHeight(videoFill);
const totalDuration = engine.block.getAVResourceTotalDuration(videoFill);
```
The `getVideoWidth` and `getVideoHeight` methods return the original video dimensions in pixels. The `getAVResourceTotalDuration` method returns the full duration of the source media in seconds.
## Playing and Pausing
Check if the block supports playback control using `supportsPlaybackControl`, then start or stop playback with `setPlaying`.
```typescript highlight=highlight-playback-control
if (engine.block.supportsPlaybackControl(page)) {
console.log(`Is playing: ${engine.block.isPlaying(page)}`);
engine.block.setPlaying(page, true);
}
```
The `isPlaying` method returns the current playback state.
## Seeking
To jump to a specific playback position, use `setPlaybackTime`. First, check if the block supports playback time with `supportsPlaybackTime`.
```typescript highlight=highlight-seeking
if (engine.block.supportsPlaybackTime(page)) {
engine.block.setPlaybackTime(page, 1.0);
console.log(`Playback time: ${engine.block.getPlaybackTime(page)}s`);
}
```
Playback time is specified in seconds. The `getPlaybackTime` method returns the current position.
## Visibility at Current Time
Check if a block is visible at the current playback position using `isVisibleAtCurrentPlaybackTime`. This is useful when blocks have different time offsets or durations.
```typescript highlight=highlight-visibility
console.log(
`Visible at current time: ${engine.block.isVisibleAtCurrentPlaybackTime(
videoBlock
)}`
);
```
## Solo Playback
Solo playback allows you to preview an individual block while the rest of the scene stays frozen. Enable it on a video fill or audio block with `setSoloPlaybackEnabled`.
```typescript highlight=highlight-solo-playback
engine.block.setSoloPlaybackEnabled(videoFill, true);
console.log(
`Solo enabled: ${engine.block.isSoloPlaybackEnabled(videoFill)}`
);
engine.block.setSoloPlaybackEnabled(videoFill, false);
```
Enabling solo on one block automatically disables it on all others. This is useful for previewing a specific clip without affecting the overall scene playback.
## Troubleshooting
### Properties Unavailable Before Resource Load
**Symptom**: Accessing duration, dimensions, or trim values throws an error.
**Cause**: Media resource not yet loaded.
**Solution**: Always `await engine.block.forceLoadAVResource()` before accessing these properties.
### Block Not Playing
**Symptom**: Calling `setPlaying(true)` has no effect.
**Cause**: Block doesn't support playback control or scene not in playback mode.
**Solution**: Check `supportsPlaybackControl()` returns true; ensure scene playback is active.
### Solo Playback Not Working
**Symptom**: Enabling solo doesn't isolate the block.
**Cause**: Solo applied to wrong block type or block not visible.
**Solution**: Apply solo to video fills or audio blocks, ensure block is at current playback time.
## Next Steps
[Trim Video and Audio](https://img.ly/docs/cesdk/sveltekit/edit-video/trim-4f688b/) - Control which portion of source media plays
[Loop Audio](https://img.ly/docs/cesdk/sveltekit/create-audio/audio/loop-937be7/) - Enable repeating playback for audio blocks
[Adjust Volume](https://img.ly/docs/cesdk/sveltekit/create-video/audio/adjust-volume-7ecc4a/) - Control audio volume and muting
[Adjust Speed](https://img.ly/docs/cesdk/sveltekit/create-video/audio/adjust-speed-908d57/) - Change playback speed for audio
[Video Timeline Overview](https://img.ly/docs/cesdk/sveltekit/create-video/timeline-editor-912252/) - Timeline editing system