# Imgly Vue > [Source](https:/img.ly/docs/cesdk/vue/user-interface-5a089a) --- # Imgly Vue Documentation Source: https://img.ly/docs/cesdk/vue/llms-full.txt --- [Source](https:/img.ly/docs/cesdk/vue/user-interface-5a089a) --- # User Interface --- [Source](https:/img.ly/docs/cesdk/vue/what-is-cesdk-2e7acd) --- # Vue.js Creative Editor The CreativeEditor SDK delivers a powerful Vue.js library designed for crafting and editing rich visual designs directly within the browser. ### What is CE.SDK?[#](#what-is-cesdk) This CE.SDK configuration is fully customizable and extendable, offering a comprehensive range of design editing capabilities, including templating, layout management, asset integration, and more, as well as advanced features like background removal. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## Key Features of the Vue.js Creative Editor SDK[#](#key-features-of-the-vuejs-creative-editor-sdk) ![Transformations](/docs/cesdk/_astro/Transform.By5kJRew_2acCrV.webp) ### Transformations Execute operations like cropping, rotating, and resizing design components. ![Templating](/docs/cesdk/_astro/Templating.eMNm9_jD_ycnVt.webp) ### Templating Build and apply design templates with placeholders and text variables for dynamic content. ![Placeholders & Lockable Design](/docs/cesdk/_astro/Placeholders.DzG3E33B_bmQxQ.webp) ### Placeholders & Lockable Design Constrain templates to guide your users’ design and ensure brand consistency. ![Asset Handling](/docs/cesdk/_astro/AssetLibraries.Ce9MfYvX_HmsaC.webp) ### Asset Handling Import and manage images, shapes, and other assets for your design projects. ![Design Collages](/docs/cesdk/_astro/VideoCollage.23LDUE8e_1VDFAj.webp) ### Design Collages Arrange multiple elements on a single canvas to create intricate layouts. ![Text Editing](/docs/cesdk/_astro/TextEditing.B8Ra1KOE_2lGC8C.webp) ### Text Editing Incorporate and style text blocks using various fonts, colors, and effects. ![Client-Side Operations](/docs/cesdk/_astro/ClientSide.CECpQO_1_c6mBh.webp) ### Client-Side Operations All design editing tasks are performed directly in the browser, eliminating the need for server dependencies. ![Headless & Automation](/docs/cesdk/_astro/Headless.qEVopH3n_20CWbD.webp) ### Headless & Automation Programmatically edit designs within your React application using the engine API. ![Extendible](/docs/cesdk/_astro/Extendible.CRYmRihj_BmNTE.webp) ### Extendible Easily enhance functionality with plugins and custom scripts. ![Customizable UI](/docs/cesdk/_astro/CustomizableUI.DtHv9rY-_2fNrB2.webp) ### Customizable UI Develop and integrate custom UIs tailored to your application’s design requirements. ![Background Removal](/docs/cesdk/_astro/GreenScreen.CI2APgl0_Z8GtPY.webp) ### Background Removal This plugin simplifies the process of removing backgrounds from images entirely within the browser. ![Print Optimization](/docs/cesdk/_astro/CutoutLines.kN4c7WBK_lB3LB.webp) ### Print Optimization Ideal for web-to-print scenarios, supporting spot colors and cut-outs. ## Browser Support[#](#browser-support) The CE.SDK Design Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari. See the full list of [supported browsers here](vue/browser-support-28c1b0/) . ## Supported Formats[#](#supported-formats) CE.SDK supports a wide range of file types to ensure maximum flexibility for developers: ### Importing Media[#](#importing-media) | Category | Supported Formats | | --- | --- | | **Images** | `.png`, `.jpeg`, `.jpg`, `.gif`, `.webp`, `.svg`, `.bmp` | | **Video** | `.mp4` (H.264/AVC, H.265/HEVC), `.mov` (H.264/AVC, H.265/HEVC), `.webm` (VP8, VP9, AV1) | | **Audio** | `.mp3`, `.m4a`, `.mp4` (AAC or MP3), `.mov` (AAC or MP3) | | **Animation** | `.json` (Lottie) | Need to import a format not listed here? CE.SDK allows you to create custom importers for any file type by using our Scene and Block APIs programmatically. ### Exporting Media[#](#exporting-media) | Category | Supported Formats | | --- | --- | | **Images** | `.png` (with transparency), `.jpeg`, `.webp`, `.tga` | | **Video** | `.mp4` (H.264 or H.265 on supported platforms with limited transparency support) | | **Print** | `.pdf` (supports underlayer printing and spot colors) | | **Scene** | `.scene` (description of the scene without any assets) | | **Archive** | `.zip` (fully self-contained archive that bundles the `.scene` file with all assets) | Our custom cross-platform C++ based rendering and layout engine ensures consistent output quality across devices. ### Importing Templates[#](#importing-templates) | Format | Description | | --- | --- | | `.idml` | InDesign | | `.psd` | Photoshop | | `.scene` | CE.SDK Native | Need to import a format not listed here? CE.SDK allows you to create custom importers for any file type by using our Scene and Block APIs to generate scenes programmatically. For detailed information, see the [full file format support list](vue/file-format-support-3c4b2a/) . ## Understanding CE.SDK Architecture & API[#](#understanding-cesdk-architecture--api) The following sections provide an overview of the key components of the CE.SDK design editor UI and its API architecture. If you’re ready to start integrating CE.SDK into your Vue.js application, refer to our [Getting Started guide](vue/get-started/overview-e18f40/) or explore the Essential Guides. ### CreativeEditor Design UI[#](#creativeeditor-design-ui) The CE.SDK design UI is designed for intuitive design creation and editing. Key components and customizable elements within the UI include: ![](/docs/cesdk/_astro/CESDK-UI.BD2Iwmum_2gw9UM.webp) * **Canvas:** The main interaction area for design content. * **Dock:** An entry point for interactions not directly related to the selected design block, often used for accessing asset libraries. * **Canvas Menu:** Access block-specific settings like duplication or deletion. * **Inspector Bar:** Manage block-specific functionalities, such as adjusting properties of the selected block. * **Navigation Bar:** Handles global scene actions like undo/redo and zoom. * **Canvas Bar:** Provides tools for managing the overall canvas, such as adding pages or controlling zoom. * **Layer Panel:** Manage the stacking order and visibility of design elements. Learn more about interacting with and manipulating design controls in our design editor UI guide. ### CreativeEngine[#](#creativeengine) The CreativeEngine is the core of CE.SDK, responsible for rendering and manipulating design scenes. It can operate in headless mode or be integrated with the CreativeEditor UI. Key features and APIs provided by CreativeEngine include: * **Scene Management:** Create, load, save, and modify design scenes programmatically. * **Block Manipulation:** Create and manage design elements, such as shapes, text, and images. * **Asset Management:** Load assets like images and SVGs from URLs or local sources. * **Variable Management:** Define and manipulate variables within scenes for dynamic content. * **Event Handling:** Subscribe to events like block creation or updates for dynamic interaction. ## API Overview[#](#api-overview) CE.SDK’s APIs are categorized into several groups, reflecting different aspects of scene management and manipulation. [**Scene API:**](vue/concepts/scenes-e8596d/) \- **Creating and Loading Scenes**: ``` engine.scene.create();engine.scene.loadFromURL(url); ``` * **Zoom Control**: ``` engine.scene.setZoomLevel(1.0);engine.scene.zoomToBlock(blockId); ``` [**Block API:**](vue/concepts/blocks-90241e/) : - **Creating Blocks**: ``` const block = engine.block.create('shapes/rectangle'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 });engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color');const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](vue/create-templates/add-dynamic-content/text-variables-7ecb50/) Variables allow dynamic content within scenes, enabling programmatic creation of design variations. * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value');const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](vue/import-media/concepts-5e6197/) : - **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](vue/concepts/events-353f97/) : - **Subscribing to Events**: ``` // Subscribe to scene changesengine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get();}); ``` ## Customizing the Vue.js Creative Editor[#](#customizing-the-vuejs-creative-editor) CE.SDK provides extensive customization options to tailor the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements. ### Basic Customizations[#](#basic-customizations) * **Configuration Object:** When initializing the CreativeEditor, pass a configuration object that defines basic settings such as the base URL for assets, language, theme, and license key. ``` const config = { baseURL: `https://cdn.img.ly/packages/imgly/cesdk-js/${CreativeEditorSDK.version}/assets`, // license: 'YOUR_CESDK_LICENSE_KEY',}; ``` * **Localization:** Customize the language and labels used in the editor to support different locales. ``` const config = {}; CreativeEditorSDK.create('#cesdk_container', config).then(async cesdk => { // Set theme using the UI API cesdk.ui.setTheme('light'); // 'dark' | 'system' cesdk.i18n.setLocale('en'); cesdk.i18n.setTranslations({ en: { variables: { my_custom_variable: { label: 'Custom Label', }, }, }, });}); ``` * [Custom Asset Sources](vue/import-media/concepts-5e6197/) : Serve custom image or SVG assets from a remote URL. ### UI Customization Options[#](#ui-customization-options) * **Theme:** Select from predefined themes like ‘dark’, ‘light’, or ‘system’. ``` CreativeEditorSDK.create('#cesdk_container', config).then(async cesdk => { // Set theme using the UI API cesdk.ui.setTheme('dark'); // 'light' | 'system'}); ``` * **UI Components:** Enable or disable specific UI components as needed. ``` const config = { ui: { elements: { toolbar: true, inspector: false, }, },}; ``` ## Advanced Customizations[#](#advanced-customizations) Explore more ways to extend editor functionality and customize its UI to your specific needs by consulting our detailed [customization guide](vue/user-interface-5a089a/) . Below is an overview of the available APIs and components. ### Order APIs[#](#order-apis) The Order APIs manage the customization of the web editor’s components and their order within these locations, allowing the addition, removal, or reordering of elements. Each location has its own Order API, such as `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. ### Layout Components[#](#layout-components) CE.SDK offers special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components. ### Registration of New Components[#](#registration-of-new-components) Custom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, seamlessly integrating custom logic into the editor. ### Feature API[#](#feature-api) The Feature API allows for the conditional display and functionality of components based on the current context, enabling dynamic customization. For instance, certain buttons can be hidden for specific block types. ## Plugins[#](#plugins) Customizing the CE.SDK web editor during its initialization is possible through the APIs outlined above. For many use cases, this will be sufficient. However, there are situations where encapsulating functionality for reuse is necessary. Plugins come in handy here. Follow our [guide on building your own plugins](vue/user-interface-5a089a/) to learn more or explore some of the plugins we’ve created using this API: * **Background Removal**: Adds a button to the canvas menu to remove image backgrounds. * **Vectorizer**: Adds a button to the canvas menu to quickly vectorize a graphic. ## Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](/docs/cesdk/_astro/HP.BZ1qDNii_ZpK5Lk.webp) ![Shopify logo](/docs/cesdk/_astro/Shopify.Dmyk4png_ZRKWXF.webp) ![Reuters logo](/docs/cesdk/_astro/Reuters.B8BV2Fek_ZLrHFJ.webp) ![Hootsuite logo](/docs/cesdk/_astro/Hootsuite.C94d5fhs_Zsc4gx.webp) ![Semrush logo](/docs/cesdk/_astro/Semrush.B2YsPaIn_23cDNx.webp) ![Shutterfly logo](/docs/cesdk/_astro/Shutterfly.Cc7Sw48y_Z3TjCs.webp) ![Sprout Social logo](/docs/cesdk/_astro/Sprout-Social.VxlY6_Tc_E0Dzh.webp) ![One.com logo](/docs/cesdk/_astro/Onecom.BQ_oPnlz_Z1btrtu.webp) ![Constant Contact logo](/docs/cesdk/_astro/Constant-Contact.1rh975Q__Z2ob7wU.webp) --- [Source](https:/img.ly/docs/cesdk/vue/upgrade-4f8715) --- # Upgrade --- [Source](https:/img.ly/docs/cesdk/vue/to-v1-32-1b6ae8) --- # To v1.32 Version v1.32 introduced powerful new APIs for customizing the CE.SDK web editor. These new APIs render some of the existing configurations obsolete, requiring code migration to leverage the more flexible new options. This guide will help you navigate these changes and explain what you need to do to update your integration. ## Configuring the Dock[#](#configuring-the-dock) Until version 1.32, the dock was configured by two configuration options (although most users only used one of them and kept the others default): * `config.ui.elements.libraries.insert.entries` and * `config.ui.elements.dock` If your configuration adapted one of these two (mostly `config.ui.elements.libraries.insert.entries`), you are affected by this change. For now, it is only deprecated and we will try to do an internal migration for you, but this still might include a breaking change depending on how you used the configuration options before. ### Breaking Change[#](#breaking-change) `config.ui.elements.libraries.insert.entries` was called repeatedly with a context of currently selected blocks. Most users and configurations have not used this behavior and just returned the same static list of entries for every call. In this case, your configuration should work as before, but if you have relied on this fact, you have to migrate your configuration to the new API, listen to selection changes, and update the asset library entries accordingly. ### Migration to new APIs[#](#migration-to-new-apis) Defining the dock is now done by our new APIs in a consistent way to all other customizable locations of the editor. With the [Dock API](vue/user-interface/customization/dock-cb916c/) , you now have much greater control of how and what is displayed in the dock. This does not only include dock buttons to open asset libraries but also arbitrary buttons and other elements. Please take a look at the [Dock API](vue/user-interface/customization/dock-cb916c/) or learn about the general concept [here](vue/user-interface/overview-41101a/) . If you aren’t affected by the breaking change mentioned above, the easiest way to migrate is to first copy your current dock order after the editor has been inialized. This can be done by calling the new `cesdk.ui.getDockOrder()` method. Now you can take this order and set it during the initialization of the editor by using `cesdk.ui.setDockOrder(copiedDockOrder)`. The old configuration (`config.ui.elements.libraries.insert.entries` and `config.ui.elements.dock`) can be removed afterwards. Of course, you could also just remove the old configuration and use the new API to define the dock order from scratch. Please note, that changes to the asset library entries are now done by the [Asset Library Entry API](vue/import-media/asset-panel/basics-f29078/) and the dock order is just referring to these. So if you, for instance, want to add an asset source id to be shown in a library, you have to add this asset source id to the asset library entry and not to the dock order. ``` // Before// ======const config: Configuration = { ui: { elements: { libraries: { insert: { entries: (defaultEntries) => { return [ // Changing some of the default entries ...defaultEntries.map((entry) => { if (entry.id === 'ly.img.image') { entry.sourceIds.push('my-own-image-source'); } return entry; }), // Adding a new entry { id: 'my-own-entry', sourceIds: ['my-own-source'] } ]; } } } } }}; // After// ======cesdk.ui.setDockOrder([ ...cesdk.ui.getDockOrder(), // Add a new button referencing your new entry { id: 'ly.img.assetLibrary.dock', label: 'My Own Entry', icon: [...], entries: ['my-own-entry'] }]); // Adding your custom entrycesdk.ui.addAssetLibraryEntry({ id: 'my-own-entry', sourceIds: ['my-own-source']}); // Updating an existing default entrycesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: ({ currentIds }) => [...currentIds, 'my-own-image-source']}); ``` ## Configuring the Asset Replacement Panel[#](#configuring-the-asset-replacement-panel) Similar to the definition of the dock, we deprecate the configuration `config.ui.elements.libraries.replace.entries` of the asset library entries for the replacement panel. This method is deprecated but we will try to migrate your configuration internally until it is removed. We recommend you to migrate to the new API as soon as possible. The new API is similar with subtle differences. With `cesdk.ui.setReplaceAssetLibraryEntries` you register a function that is called with the current context (of selected blocks) but only returns ids of entries. ### Breaking Change[#](#breaking-change-1) With the `config.ui.elements.libraries.replace.entries` it was possible to take the default entries and modify them. In theory, you could change the entries and have different “default” entries for insert or replace. Now a change to a default entry provided by the editor via the [Asset Library Entry API](vue/import-media/asset-panel/basics-f29078/) will be reflected in both the insert and replace entries. To solve this you can just copy one entry for replacement, modify it, and return its id instead. ### Migration to new APIs[#](#migration-to-new-apis-1) Take the function from `config.ui.elements.libraries.replace.entries` and use it in `cesdk.ui.setReplaceAssetLibraryEntries` by replacing the entries with their ids. If you have made changes to the default entries or added new custom ones you need to add or change them via the [Asset Library Entry API](vue/import-media/asset-panel/basics-f29078/) on initialization of the editor. --- [Source](https:/img.ly/docs/cesdk/vue/to-v1-19-55bcad) --- # To v1.19 Version v1.19 of CreativeEngineSDK and CreativeEditorSDK introduces structural changes to many of the current design blocks, making them more composable and more powerful. Along with this update, there are mandatory license changes that require attention. This comes with a number of breaking changes. This document will explain the changes and describe the steps you need to take to adapt them to your setup. ## **License Changes**[#](#license-changes) The `license` parameter is now required for CreativeEngineSDK and CreativeEditorSDK. This means that you will need to update your license parameter in the `CreativeEngine.init` and `CreativeEditorSDK.create` configuration object properties. There is also a new `userId`, an optional unique ID tied to your application’s user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they’re counted once. Providing this aids in better data accuracy. ## **Graphic Design Block**[#](#graphic-design-block) A new generic `Graphic` design block with the type id `//ly.img.ubq/graphic` has been introduced, which forms the basis of the new unified block structure. ## **Shapes**[#](#shapes) Similar to how the fill of a block is a separate object which can be attached to and replaced on a design block, we have now introduced a similar concept for the shape of a block. You use the new `createShape`, `getShape` and `setShape` APIs in order to define the shape of a design block. Only the new `//ly.img.ubq/graphic` block allows for its shape to be changed with these APIs. The new available shape types are: * `//ly.img.ubq/shape/rect` * `//ly.img.ubq/shape/line` * `//ly.img.ubq/shape/ellipse` * `//ly.img.ubq/shape/polygon` * `//ly.img.ubq/shape/star` * `//ly.img.ubq/shape/vector_path` The following block types are now removed in favor of using a `Graphic` block with one of the above mentioned shape instances: * `//ly.img.ubq/shapes/rect` * `//ly.img.ubq/shapes/line` * `//ly.img.ubq/shapes/ellipse` * `//ly.img.ubq/shapes/polygon` * `//ly.img.ubq/shapes/star` * `//ly.img.ubq/vector_path` (The removed type ids use the plural “shapes” and the new shape types use the singular “shape”) This structural change means that the shape-specific properties (e.g. the number of sides of a polygon) are not available on the design block any more but on the shape instances instead. You will have to add calls to `getShape` to get the instance id of the shape instance and then pass that to the property getter and setter APIs. Also remember to change property key strings in the getter and setter calls from the plural `shapes/…` to the singular `shape/…` to match the new type identifiers. ## **Image and Sticker**[#](#image-and-sticker) Previously, `//ly.img.ubq/image` and `//ly.img.ubq/sticker` were their own high-level design block types. They do not support the fill APIs nor the effects APIs. Both of these blocks are now removed in favor of using a `Graphic` block with an image fill (`//ly.img.ubq/fill/image`) and using the effects APIs instead of the legacy image block’s numerous effects properties. At its core, the sticker block has always just been an image block that is heavily limited in its capabilities. You can not crop it, nor apply any effects to it. In order to replicate this difference as closely as possible in the new unified structure, more fine-grained scopes have been added. You can now limit the adopter’s ability to crop a block and to edit its appearance. Note that since these scopes only apply to a user of the editor with the “Adopter” role, a “Creator” user will now have all of the same editing options for both images and for blocks that used to be stickers. ## **Scopes**[#](#scopes) The following is the list of changes to the design block scopes: * (Breaking) The permission to crop a block was split from `content/replace` and `design/style` into a separate scope: `layer/crop`. * Deprecated the `design/arrange` scope and renamed `design/arrange/move` → `layer/move` `design/arrange/resize` → `layer/resize` `design/arrange/rotate` → `layer/rotate` `design/arrange/flip` → `layer/flip` * Deprecated the `content/replace` scope. For `//ly.img.ubq/text` blocks, it is replaced with the new `text/edit` scope. For other blocks it is replaced with `fill/change`. * Deprecated the `design/style` scope and replaced it with the following fine-grained scopes: `text/character`, `stroke/change`, `layer/opacity`, `layer/blendMode`, `layer/visibility`, `layer/clipping`, `appearance/adjustments`, `appearance/filter`, `appearance/effect`, `appearance/blur`, `appearance/shadow` * Introduced `fill/change`, `stroke/change`, and `shape/change` scopes that control whether the fill, stroke or shape of a block may be edited by a user with an “Adopter” role. * The deprecated scopes are automatically mapped to their new corresponding scopes by the scope APIs for now until they will be removed completely in a future update. ## **Kind**[#](#kind) While the new unified block structure both simplifies a lot of code and makes design blocks more powerful, it also means that many of the design blocks that used to have unique type ids now all have the same generic `//ly.img.ubq/graphic` type, which means that calls to the `findByType` cannot be used to filter blocks based on their legacy type ids any more. Simultaneously, there are many instances in which different blocks in the scene which might have the same type and underlying technical structure have different semantic roles in the document and should therefore be treated differently by the user interface. To solve both of these problems, we have introduced the concept of a block “kind”. This is a mutable string that can be used to tag different blocks with a semantic label. You can get the kind of a block using the `getKind` API and you can query blocks with a specific kind using the `findByKind` API. CreativeEngine provides the following default kind values: * `image` * `video` * `sticker` * `scene` * `camera` * `stack` * `page` * `audio` * `text` * `shape` * `group` Unlike the immutable design block type id, you can change the kind of a block with the new `setKind` API. It is important to remember that the underlying structure and properties of a design block are not strictly defined by its kind, since the kind, shape, fill and effects of a block can be changed independent of each other. Therefore, a user-interface should not make assumptions about available properties of a block purely based on its kind. **Note** Due to legacy reasons, blocks with the kind “sticker” will continue to not allow their contents to be cropped. This special behavior will be addressed and replaced with a more general-purpose implementation in a future update. ​ ## **Asset Definitions**[#](#asset-definitions) The asset definitions have been updated to reflect the deprecation of legacy block type ids and the introduction of the “kind” property. In addition to the “blockType” meta property, you can now also define the `“shapeType”` ,`“fillType”` and `“kind”` of the block that should be created by the default implementation of the applyAsset function. * `“blockType”` defaults to `“//ly.img.ubq/graphic”` if left unspecified. * `“shapeType”` defaults to `“//ly.img.ubq/shape/rect”` if left unspecified * `“fillType”` defaults to `“//ly.img.ubq/fill/color”` if left unspecified Video block asset definitions used to specify the `“blockType”` as `“//ly.img.ubq/fill/video“`. The `“fillType”` meta asset property should now be used instead for such fill type ids. ## **Automatic Migration**[#](#automatic-migration) CreativeEngine will always continue to support scene files that contain the now removed legacy block types. Those design blocks will be automatically replaced by the equivalent new unified block structure when the scene is loaded, which means that the types of all legacy blocks will change to `“//ly.img.ubq/graphic”`. Note that this can mean that a block gains new capabilities that it did not have before. For example, the line shape block did not have any stroke properties, so the `hasStroke` API used to return `false`. However, after the automatic migration its `Graphic` design block replacement supports both strokes and fills, so the `hasStroke` API now returns `true` . Similarly, the image block did not support fills or effects, but the `Graphic` block does. ## List of all Removed Block Type IDs[#](#list-of-all-removed-block-type-ids) * `//ly.img.ubq/image` * `//ly.img.ubq/sticker` * `//ly.img.ubq/shapes/rect` * `//ly.img.ubq/shapes/line` * `//ly.img.ubq/shapes/ellipse` * `//ly.img.ubq/shapes/polygon` * `//ly.img.ubq/shapes/star` * `//ly.img.ubq/vector_path` ## **UI Configuration**[#](#ui-configuration) The configuration options for the legacy blocks have also been removed under `config.ui.elements.blocks` and a new configuration option for the `ly.img.ubq/graphic` block type have been introduced which will then define which UI controls to enable for graphic blocks (crop, filters, adjustments, effects, blur). This new configuration option follows the same structure as before. Here is a list of the deprecated block configuration options: * `//ly.img.ubq/image` * `//ly.img.ubq/fill/video` * `//ly.img.ubq/shapes/rect` * `//ly.img.ubq/shapes/line` * `//ly.img.ubq/shapes/star` * `//ly.img.ubq/shapes/polygon` * `//ly.img.ubq/shapes/ellipse` * `//ly.img.ubq/vector_path` ## Translations[#](#translations) Some of the translation keys related to Scopes and Placeholder-Settings have been also updated: * Removed the following keys: * `scope.content.replace` * `scope.design.arrange` * `scope.design.style` * Renamed the following keys: * `scope.design.arrange.flip` is now `scope.layer.flip` * `scope.design.arrange.move` is now `scope.layer.move` * `scope.design.arrange.resize` is now `scope.layer.resize` * `scope.design.arrange.rotate` is now `scope.layer.rotate` * Added the following keys: * `component.placeholder.appearance.description` * `component.placeholder.appearance` * `component.placeholder.arrange.description` * `component.placeholder.arrange` * `component.placeholder.disableAll` * `component.placeholder.enableAll` * `component.placeholder.fill.description` * `component.placeholder.fill` * `component.placeholder.general.description` * `component.placeholder.general` * `component.placeholder.shape.description` * `component.placeholder.shape` * `component.placeholder.stroke.description` * `component.placeholder.stroke` * `component.placeholder.text.description` * `component.placeholder.text` * `scope.appearance.adjustments` * `scope.appearance.blur` * `scope.appearance.effect` * `scope.appearance.filter` * `scope.appearance.shadow` * `scope.fill.change` * `scope.layer.blendMode` * `scope.layer.opacity` * `scope.shape.change` * `scope.stroke.change` * `scope.text.character` * `scope.text.edit` ## **Types and API Signatures**[#](#types-and-api-signatures) To improve the type safety of our APIs, we have moved away from using the `DesignBlockType` enum and replaced with a set of types. Those changes have affected the following APIs: * `CESDK.engine.block.create()` * `CESDK.engine.block.createFill()` * `CESDK.engine.block.createEffect()` * `CESDK.engine.block.createBlur()` * `CESDK.engine.block.findByType()` * `CESDK.engine.block.getType()` **Note** The `create`, `findByType`, and `getType` APIs will no longer accept the IDs of the deprecated legacy blocks and will throw an error when those are passed ## **Code Examples**[#](#code-examples) This section will show some code examples of the breaking changes and how it would look like after migrating. ``` /** Block Creation */ // Creating an Image before migrationconst image = cesdk.engine.block.create('image');cesdk.engine.block.setString( image, 'image/imageFileURI', 'https://domain.com/link-to-image.jpg',); // Creating an Image after migrationconst block = cesdk.engine.block.create('graphic');const rectShape = cesdk.engine.block.createShape('rect');const imageFill = cesdk.engine.block.createFill('image');cesdk.engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://domain.com/link-to-image.jpg',);cesdk.engine.block.setShape(block, rectShape);cesdk.engine.block.setFill(block, imageFill);cesdk.engine.block.setKind(block, 'image'); // Creating a star shape before migrationconst star = cesdk.engine.block.create('shapes/star');cesdk.engine.block.setInt(star, 'shapes/star/points', 8); // Creating a star shape after migrationconst block = cesdk.engine.block.create('graphic');const starShape = cesdk.engine.block.createShape('star');const colorFill = cesdk.engine.block.createFill('color');cesdk.engine.block.setInt(starShape, 'shape/star/points', 8);cesdk.engine.block.setShape(block, starShape);cesdk.engine.block.setFill(block, colorFill);cesdk.engine.block.setKind(block, 'shape'); // Creating a sticker before migrationconst sticker = cesdk.engine.block.create('sticker');cesdk.engine.setString( sticker, 'sticker/imageFileURI', 'https://domain.com/link-to-sticker.png',); // Creating a sticker after migrationconst block = cesdk.engine.block.create('graphic');const rectShape = cesdk.engine.block.createShape('rect');const imageFill = cesdk.engine.block.createFill('image');cesdk.engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://domain.com/link-to-sticker.png',);cesdk.engine.block.setShape(block, rectShape);cesdk.engine.block.setFill(block, imageFill);cesdk.engine.block.setKind(block, 'sticker'); /** Block Creation */ ``` ``` /** Block Exploration */ // Query all images in the scene before migrationconst images = cesdk.engine.block.findByType('image'); // Query all images in the scene after migrationconst images = cesdk.engine.block.findByType('graphic').filter(block => { const fill = cesdk.engine.block.getFill(block); return ( cesdk.engine.block.isValid(fill) && cesdk.engine.block.getType(fill) === '//ly.img.ubq/fill/image' );}); // Query all stickers in the scene before migrationconst stickers = cesdk.engine.block.findByType('sticker'); // Query all stickers in the scene after migrationconst stickers = cesdk.engine.block.findByKind('sticker'); // Query all Polygon shapes in the scene before migrationconst polygons = cesdk.engine.block.findByType('shapes/polygon'); // Query all Polygon shapes in the scene after migrationconst polygons = cesdk.engine.block.findByType('graphic').filter(block => { const shape = cesdk.engine.block.getShape(block); return ( cesdk.engine.block.isValid(shape) && cesdk.engine.block.getType(shape) === '//ly.img.ubq/shape/polygon' );}); /** Block Exploration */ ``` --- [Source](https:/img.ly/docs/cesdk/vue/to-v1-13-d1ac5d) --- # To v1.13 In version v1.13, the way the CreativeEngine and CreativeEditor SDK are configured has changed. Several configuration options that were previously passed to `CreativeEngine.init()` or `CreativeEditor SDK.init()` have been removed or replaced. This document will explain the changes and describe the steps you can take to adapt them to your setup. #### CreativeEditorSDK initialization[#](#creativeeditorsdk-initialization) We are also introducing a new way of instantiating the CreativeEditorSDK, that provides more precise control over the initialization. Using `CreativeEditorSDK.create()`, you have the chance to configure the SDK before loading or creating a scene. This was not possible before. When using `CreativeEditorSDK.init()`, the SDK would create the initial scene before giving you the option to perform additional configuration via the API. The `create()` method will not create an initial scene for you. You need to do that yourself, using either the Scene API at `cesdk.editor.scene`, or one of the methods on the CreativeEditorSDK instance itself (`createDesignScene`, `createVideoScene`, `loadFromUrl`, `loadFromString`). ### Rationale[#](#rationale) Over time the number options you could pass into the call to `CreativeEngine.init({...config})` has grown quite a bit. Initially this was the only place where you could configure the behavior and settings of the CreativeEngine, but over the past year we introduced several new APIs. One of those APIs is the EditorAPI, which lets you [adjust](vue/settings-970c98/) many [settings](vue/settings-970c98/) at runtime, not just at the launch of the app. To improve consistency of our APIs, we decided to scale back the options available in the configuration object in favor of changing settings via the EditorAPI. The only options that remain are those that are strictly necessary for the initialization of the CreativeEngine, such as the `baseUrl` and `license`. These changes were performed with the Creative Engine in mind, but since the CreativeEditor SDK shares a lot of the same code, the changes described in this document also apply to the configuration for the CE.SDK. ### Changed configuration options[#](#changed-configuration-options) The following is a list of all configuration options that have been changed or removed, along with instructions on how to migrate the use of these options in your codebase: * `scene` options (`dpi`, `pixelScaleFactor`) have been removed. `scene/dpi` and `scene/pixelScaleFactor` can now be found as [properties on the scene in the BlockAPI](vue/concepts/blocks-90241e/) . * `page` options have been removed. * `page.title.show` has been replaced with `cesdk.engine.editor.setSettingBool('page/title/show', enabled)` * `page.title.fontFileUri` has been replaced with `cesdk.engine.editor.setSettingString('page/title/fontFileUri', uri)` * `page.dimOutOfPageAreas` has been replaced with `cesdk.engine.editor.setSettingBool('page/dimOutOfPageAreas', dimEnabled)` * `assetSources` have been removed. To add asset sources, use the AssetAPI at `cesdk.engine.asset`. * `preset.colors` has been removed as it was never used, previously. * `presets.colorPalettes` has been removed from CreativeEngine as it was not used. It has been moved to `ui.colorPalette` in the CESDK. * `presets.images` has been removed. To add assets and asset sources, use the AssetAPI at `cesdk.engine.asset`. * `presets.pageFormats` has been removed from the CreativeEngine as it was not used. Is has been moved to `ui.pageFormats` in the CESDK. Previously it was possible to mark a page format as default by setting `meta: {default: true}` in it. In `ui.pageFormats`, this has been simplified, to just `default: true`. * `variables` has been removed. Use the [VariableAPI](vue/create-templates/add-dynamic-content/text-variables-7ecb50/) instead. * `callbacks.log` has been moved to `logger`. Previously the logger callback would take a `Loglevel` enum as a second parameter. This enum has been removed. Instead you can define the loglevel with plain strings `'Warning' | 'Error' | 'Info'` ### Change initialization code[#](#change-initialization-code) To ensure your users perceive a consistent UI experience, settings that have been moved to api calls should be made immediately after initializing the CreativeEngine. For the Creative Editor SDK, use the `create()` method, instead of `init()` #### CreativeEngine[#](#creativeengine) ``` const engine = await CreativeEngine.init(config);// 1. Configure Engineengine.editor.setSettingEnum('doubleClickSelectionMode', 'Direct');// ... other settings // 2. Create/load sceneconst sceneId = await engine.scene.create(); // 3. Append Engine canvas element to the DOMdocument.getElementById('my-engine-container').append(engine.element); // ... your application code ``` #### CreativeEngine SDK[#](#creativeengine-sdk) ``` const cesdk = await CreativeEditorSDK.create('my-engine-container', config);// 1. Configure SDKcesdk.engine.asset.addSource(myAssetSource);// ... other settings // 2. Create/load sceneconst sceneId = await cesdk.createDesignScene(myPageFormats[pageFormatId]); // ... your application code ``` ### Fallback and warnings[#](#fallback-and-warnings) The CreativeEngine and CreativeEditor SDK still interpret the config object with its previous settings. If removed configuration options are detected during intialization, a warning is printed to the console, with individual instructions on how to migrate them. It is recommended to adjust your configuration as described above for the best compatibility with future developments and to get rid of these warnings. The fallback mechanism is not enabled for the new `CreativeEditorSDK.create()` method! Passing removed configuration options to `create()` will cause that option to be ignored and an error will be printed to the console. ### CreativeEngine Typescript definitions[#](#creativeengine-typescript-definitions) The more complex parts of our configuration (such as the page format definitions) were previously exporting type definitions under the `ConfigTypes` namespace in the CreativeEngine package. This namespace and all the types in it have been removed or moved elsewhere. For now we still export `ConfigTypes`, but have marked it and its members as `@deprecated`. Most of them are not used at all anymore, the rest have been moved elsewhere: * `ConfigTypes.Color` can now be imported directly as `PaletteColor` * `ConfigTypes.TypefaceDefinition` can be imported directly, as `TypefaceDefinition`. * `ConfigTypes.PageFormatDefinition` can be imported directly as `PageFormatDefinition` (CE.SDK only). * `ConfigTypes.Logger` can be imported directly as `Logger` The `LogLevel` enum that was previously used by `Logger` has been replaced with a string union (`'Warning' | 'Error' | 'Info'`). ### CreativeEditorSDK Typescript definitions[#](#creativeeditorsdk-typescript-definitions) The CreativeEditor SDK package still _does_ export a `ConfigTypes` namespace. For use with the new `CreativeEditorSDK.create()`, we are offering a new type `CreateConfiguration`, which is lacking all of the removed keys instead of marking them as deprecated. ### Demo asset sources[#](#demo-asset-sources) When adding demo asset sources using `cesdk.addDemoAssetSources()`, or `engine.addDemoAssetSources()`, make sure to specify the correct scene mode. The installed demo asset sources vary between Design and Video modes. If you don’t specify a scene mode, `addDemoAssetSources()` will try to add the correct sources based on the current scene, and default to `'Design'`. If you call `addDemoAssetSources()` _without_ a scene mode, and _before_ loading or creating a video scene, the audio and video asset sources will not be added. --- [Source](https:/img.ly/docs/cesdk/vue/to-v1-10-1ff469) --- # To v1.10 Version v1.10 introduced major changes to how and where engine and the UI store assets. This guide helps you navigate those changes and explains what you need to do to bring your integration up to speed. ## 1\. Scene Uploads are no longer serialized[#](#1-scene-uploads-are-no-longer-serialized) Image uploads are no longer stored in the scene and will not reappear upon scene load. To offer specific assets in your editor, configure and [add an asset source](vue/serve-assets-b0827c/) containing the desired assets. ## 2\. Deprecating Extensions[#](#2-deprecating-extensions) Starting with `v1.10` we fully embrace [Asset Sources](vue/import-media/from-remote-source/unsplash-8f31f0/) as our standard interface for asset management. We’re deprecating extension packs, previously stored in `/assets/extensions` and indexed via `manifest.json` files. **Fonts are not affected by this deprecation yet, but will receive the same treatment in an upcoming version.** We’ll deprecate the `config.extensions` field for `CreativeEditorSDK`. As part of this deprecation, we’ll **no longer ship** the following packs in the `/assets/extensions` directory in our `npm` packages: * `ly.img.cesdk.images.samples` * `ly.img.cesdk.shapes.default` * `ly.img.cesdk.stickers.doodle` * `ly.img.cesdk.stickers.emoji` * `ly.img.cesdk.stickers.emoticons` * `ly.img.cesdk.stickers.hand` * `ly.img.cesdk.vectorpaths` * `ly.img.cesdk.vectorpaths.abstract` To keep offering the contained assets in your deployment, use our new [convenience functions](#making-use-of-default-and-demo-asset-sources) to instantiate asset sources holding these assets. If you have existing scenes where an asset from an extension pack might be included, you must make sure you’re still serving the corresponding files from your baseURL, so that `/extensions/…` paths still resolve properly. You can acquire a copy of the extension packs shipped in `v1.9.2` [from our CDN](https://cdn.img.ly/packages/imgly/cesdk-engine/1.9.2/assets/extensions.zip). Otherwise your scenes will **render missing asset alerts**. ### 2.1 Making use of Default and Demo Asset Sources[#](#21-making-use-of-default-and-demo-asset-sources) We still want to offer a package, that has all batteries included and quickly gets you up to speed. To do so, we introduced two new convenience functions, that can be used to add a set of predefined asset sources to your integration: #### `addDefaultAssetSources`[#](#adddefaultassetsources) Adds a set of asset sources containing our default assets. These assets may be used in production and [served from your own servers](vue/serve-assets-b0827c/) . The assets are parsed from the IMG.LY CDN at `{{base_url}}//content.json`, where `base_url` defaults to `https://cdn.img.ly/assets/v1`. Each source is created via `addLocalSource` and populated with the parsed assets. You can specify your own `base_url` or exclude certain source IDs. The following sources are added: | ID | Description | | --- | --- | | `'ly.img.sticker'` | Various stickers | | `'ly.img.vectorpath'` | Shapes and arrows | | `'ly.img.filter.lut'` | LUT effects of various kinds. | | `'ly.img.filter.duotone'` | Color effects of various kinds. | #### `addDemoAssetSources`[#](#adddemoassetsources) Registers a set of demo asset sources containing our example assets. These are not to meant to be used in your production code. The assets are parsed from the IMG.LY CDN at `https://cdn.img.ly/assets/demo/v1`. The `sceneMode` and `withUploadAssetSources` parameters control whether audio/video and upload sources are added. The following sources are added: | ID | Description | | --- | --- | | `'ly.img.image'` | Sample images | | `'ly.img.image.upload'` | Demo source to upload image assets | | `'ly.img.audio'` | Sample audios | | `'ly.img.audio.upload'` | Demo source to upload audio assets | | `'ly.img.video'` | Sample audios | | `'ly.img.video.upload'` | Demo source to upload video assets | #### Modifying Default & Demo Sources[#](#modifying-default--demo-sources) After registration you can freely modify the contained assets using the Asset APIs. You can add or remove entire asset sources or individual assets. #### Upload Asset Sources[#](#upload-asset-sources) The upload asset sources and library entries for video and audio were added to the default configuration from `addDefaultAssetSources`. If you have added these sources manually (like mentioned in our video docs) you can remove them now. ## 3\. AssetAPI Changes[#](#3-assetapi-changes) To further streamline interaction, the following breaking changes were made to the AssetAPI: * The `applyAsset` callbacks and `defaultApplyAsset` API now return an optional design block id in their callback if they created a new block. * `thumbUri` and `size` properties in `AssetDefinition` and `AssetResult` are now part of the `meta` property dictionary. * Values of the `blockType` asset meta property must now be design block type ids (e.g. `//ly.img.ubq/image`) ## 4\. A New Way to Add Images[#](#4-a-new-way-to-add-images) Instead of specifying additional images for the `CreativeEditorSDK` in `config.presets.images`, you should make use of `asset.addAsset` and add your images into the `ly.img.image` asset source. ## 5\. General API Changes[#](#5-general-api-changes) The `blockType` `meta` property for assets changed from `ly.img.` to fully qualified block types: E.g. `'ly.img.image'` now needs to be `'//ly.img.ubq/image'`. As we’re starting to apply the ‘fill’ concept to more parts of the interface, we deprecated various fill color related APIs: * Deprecated `hasFillColor`, use `hasFill` and query `block.getEnum(id, 'fill/type')` for `Solid` type instead. * Deprecated `get/setFillColorRGBA`, use `setFillSolidColor`instead.. * Deprecated `isFillColorEnabled`, use `isFillEnabled` instead. * Deprecated `setFillType` and `setFillGradientType`, use `createFill`, e.g., with type ‘color’ and then apply the fill block with `setFill` to the block instead. If the block has a fill, it should be removed with `getFill` and `destroy`. * Deprecated `getFillType` and `getFillGradientType`, query `block.getEnum(id, 'fill/type')` and `block.getEnum(id, 'fill/gradient/type')` instead instead * Deprecated `add/removeFillGradientColorStop` and `get/setFillGradientColorStops`. * Deprecated `get/setFillGradientControlPointX/Y`, use `block.getFloat(fill, keypath)` and `block.setFloat(fill, keypath, value)` with key paths ‘fill/gradient/linear/startPointX/Y’, ‘fill/gradient/radial/centerPointX/Y’, and ‘fill/gradient/conical/centerPointX/Y’ instead. * Deprecated `get/setFillGradientRadius`, use `block.getFloat(fill, 'fill/gradient/radial/radius')` and `block.setFloat(fill, 'fill/gradient/radial/radius', value)` instead.” `camera/clearColor` property was replaced it with a global `clearColor` setting to allow controlling the clear color before loading a scene. Properties affecting playback that existed on both `Audio` and `VideoFill` where moved to a common `playback/` namespace: * `'fill/video/looping'` and `'audio/looping'` are now `'playback/looping'` * `'fill/video/volume'` and `'audio/volume'` are now `'playback/volume'` * `'fill/video/muted'` and `'audio/muted'` are now `'playback/muted'` --- [Source](https:/img.ly/docs/cesdk/vue/text-8a993a) --- # Text --- [Source](https:/img.ly/docs/cesdk/vue/stickers-3d4e5f) --- # Create and Edit Stickers --- [Source](https:/img.ly/docs/cesdk/vue/shapes-9f1b2c) --- # Create and Edit Shapes --- [Source](https:/img.ly/docs/cesdk/vue/settings-970c98) --- # Settings Settings are configuration values that control CE.SDK editor behavior without modifying scene content. They are accessed via key paths (e.g., `page/title/show`) and support multiple types including Bool, Int, Float, String, Color, and Enum. Use settings to customize visual appearance, interaction behavior, resource paths, and feature toggles. ## When to Change Settings[#](#when-to-change-settings) Settings can be changed at any time after engine initialization, but they fall into two categories based on when they should be modified. ### Initialization-Only Settings[#](#initialization-only-settings) Some settings should only be set once during or immediately after engine initialization. Changing them later may have no effect or cause unexpected behavior: * `license` - The license key validates on startup; changing it later has no effect * `basePath` - The base URL for resolving assets; should be set before loading any resources * `defaultFontFileUri` / `defaultEmojiFontFileUri` - Default fonts used when no font is specified; should be set early * `maxImageSize` - Memory limit for images; changing mid-session won’t affect already-loaded images ### Runtime Settings[#](#runtime-settings) Most settings can be changed at any time and take effect immediately: * **Visual appearance**: `highlightColor`, `snappingGuideColor`, `cropOverlayColor`, `page/title/color` * **Interaction behavior**: `doubleClickToCropEnabled`, `doubleClickSelectionMode`, `touch/*`, `mouse/*` * **Control gizmos**: `controlGizmo/showResizeHandles`, `controlGizmo/showRotateHandles` * **Page display**: `page/title/show`, `page/dimOutOfPageAreas`, `page/title/separator` * **Feature toggles**: `blockAnimations/enabled`, `useSystemFontFallback`, `forceSystemEmojis` * **Snapping thresholds**: `positionSnappingThreshold`, `rotationSnappingThreshold` These runtime settings are commonly used to adapt the editor UI to different modes, user preferences, or workflow states. ## Using the Settings API[#](#using-the-settings-api) ### Discover Available Settings[#](#discover-available-settings) Use `findAllSettings()` to get a list of all available settings: ``` const allSettings = engine.editor.findAllSettings(); ``` ### Read and Write Settings[#](#read-and-write-settings) Use `getSetting()` and `setSetting()` to read and write settings. The API automatically handles the correct type based on the setting key: ``` // Boolean settingsengine.editor.setSetting('doubleClickToCropEnabled', true);const cropEnabled = engine.editor.getSetting('doubleClickToCropEnabled'); // Number settingsengine.editor.setSetting('maxImageSize', 4096);engine.editor.setSetting('positionSnappingThreshold', 2.0); // String settingsengine.editor.setSetting('page/title/separator', ' | '); // Color settingsengine.editor.setSetting('highlightColor', { r: 1, g: 0, b: 1, a: 1 }); // Enum settingsengine.editor.setSetting('doubleClickSelectionMode', 'Direct');const enumOptions = engine.editor.getSettingEnumOptions( 'doubleClickSelectionMode',); ``` ### Subscribe to Settings Changes[#](#subscribe-to-settings-changes) React to settings changes using `onSettingsChanged()`: ``` const unsubscribe = engine.editor.onSettingsChanged(() => { console.log('Editor settings have changed');}); // Later, to stop listening:unsubscribe(); ``` ### Role Management[#](#role-management) Roles apply predefined defaults for scopes and settings: ``` // Get current roleconst currentRole = engine.editor.getRole(); // Set role and apply role-dependent defaultsengine.editor.setRole('Adopter'); // React to role changesconst unsubscribe = engine.editor.onRoleChanged(role => { console.log('Role changed to:', role);}); ``` ## Available Settings[#](#available-settings) ## Settings Type[#](#settings-type) Editor Settings This section describes the all available editor settings. | Property | Type | Default | Description | | --- | --- | --- | --- | | `alwaysHighlightPlaceholders` | `Bool` | `false` | Whether placeholder elements should always be highlighted in the scene. | | `basePath` | `String` | `"some-base-path"` | The root directory to be used when resolving relative paths or when accessing `bundle://` URIs on platforms that don’t offer bundles. | | `blockAnimations/enabled` | `Bool` | `true` | Whether animations should be enabled or not. | | `borderOutlineColor` | `Color` | `{"r":0,"g":0,"b":0,"a":1}` | The border outline color. | | `camera/clamping/overshootMode` | `Enum` | `"Reverse"` | Controls what happens when the clamp area is smaller than the viewport. Center: the clamp area is centered in the viewport. Reverse: the clamp area can move inside the viewport until it hits the edges., Possible values: `"Center"`, `"Reverse"` | | `clampThumbnailTextureSizes` | `Bool` | `true` | Whether to clamp thumbnail texture sizes to reduce memory usage. | | `clearColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0}` | The color with which the render target is cleared before scenes get rendered. Only used while renderMode == RenderMode::Preview. | | `colorMaskingSettings/maskColor` | `Color` | `{"r":1,"g":1,"b":1,"a":1}` | The current mask color. Defaults to white, which disabled all masking. | | `colorMaskingSettings/secondPass` | `Bool` | `false` | Whether color masking should render the second pass. | | `controlGizmo/blockScaleDownLimit` | `Float` | `8` | Scale-down limit for blocks in screen pixels when scaling them with the gizmos or with touch gestures. The limit is ensured to be at least 0.1 to prevent scaling to size zero. | | `controlGizmo/showCropHandles` | `Bool` | `true` | Whether or not to show the handles to adjust the crop area during crop mode. | | `controlGizmo/showCropScaleHandles` | `Bool` | `true` | Whether or not to display the outer handles that scale the full image during crop. | | `controlGizmo/showMoveHandles` | `Bool` | `true` | Whether or not to show the move handles. | | `controlGizmo/showResizeHandles` | `Bool` | `true` | Whether or not to display the non-proportional resize handles (edge handles). | | `controlGizmo/showRotateHandles` | `Bool` | `true` | Whether or not to show the rotation handles. | | `controlGizmo/showScaleHandles` | `Bool` | `true` | Whether or not to display the proportional scale handles (corner handles). | | `cropOverlayColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0.39}` | Color of the dimming overlay that’s added in crop mode. | | `defaultEmojiFontFileUri` | `String` | `"https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf"` | URI of default font file for emojis. | | `defaultFontFileUri` | `String` | `"bundle://ly.img.cesdk/fonts/imgly_font_inter_semibold.otf"` | URI of default font file. This font file is the default everywhere unless overriden in specific settings. | | `doubleClickSelectionMode` | `Enum` | `"Hierarchical"` | The current mode of selection on double-click., Possible values: `"Direct"`, `"Hierarchical"` | | `doubleClickToCropEnabled` | `Bool` | `true` | Whether double clicking on an image element should switch into the crop editing mode. | | `errorStateColor` | `Color` | `{"r":1,"g":1,"b":1,"a":0.7}` | The error state color for design blocks. | | `fallbackFontUri` | `String` | `""` | The URI of the fallback font to use for text that is missing certain characters. | | `forceSystemEmojis` | `Bool` | `true` | Whether the system emojis should be used for text. | | `handleFillColor` | `Color` | `{"r":1,"g":1,"b":1,"a":1}` | The fill color for handles. | | `highlightColor` | `Color` | `{"r":0.2,"g":0.333,"b":1,"a":1}` | Color of the selection, hover, and group frames and for the handle outlines for non-placeholder elements. | | `license` | `String` | `""` | A valid license string in JWT format. | | `maxImageSize` | `Int` | `4096` | The maximum size at which images are loaded into the engine. Images that exceed this size are down-scaled prior to rendering. Reducing this size further reduces the memory footprint. | | `mouse/enableScroll` | `Bool` | `true` | Whether the engine processes mouse scroll events. | | `mouse/enableZoom` | `Bool` | `true` | Whether the engine processes mouse zoom events. | | `page/allowCropInteraction` | `Bool` | `true` | If crop interaction (by handles and gestures) should be possible when the enabled arrangements allow resizing. | | `page/allowMoveInteraction` | `Bool` | `false` | If move interaction (by handles and gestures) should be possible when the enabled arrangements allow moving and if the page layout is not controlled by the scene. | | `page/allowResizeInteraction` | `Bool` | `false` | If a resize interaction (by handles and gestures) should be possible when the enabled arrangements allow resizing. | | `page/allowRotateInteraction` | `Bool` | `false` | If rotation interaction (by handles and gestures) should be possible when the enabled arrangements allow rotation and if the page layout is not controlled by the scene. | | `page/dimOutOfPageAreas` | `Bool` | `true` | Whether the opacity of the region outside of all pages should be reduced. | | `page/innerBorderColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0}` | Color of the inner frame around the page. | | `page/marginFillColor` | `Color` | `{"r":0.79,"g":0.12,"b":0.4,"a":0.1}` | Color filled into the bleed margins of pages. | | `page/marginFrameColor` | `Color` | `{"r":0.79,"g":0.12,"b":0.4,"a":0.15}` | Color of frame around the bleed margin area of the pages. | | `page/moveChildrenWhenCroppingFill` | `Bool` | `false` | Whether the children of the page should be transformed to match their old position relative to the page fill when a page fill is cropped. | | `page/outerBorderColor` | `Color` | `{"r":1,"g":1,"b":1,"a":0}` | Color of the outer frame around the page. | | `page/restrictResizeInteractionToFixedAspectRatio` | `Bool` | `false` | If the resize interaction should be restricted to fixed aspect ratio resizing. | | `page/selectWhenNoBlocksSelected` | `Bool` | `false` | Whether the page should automatically be selected when no blocks are selected. | | `page/title/appendPageName` | `Bool` | `true` | Whether to append the page name to the title if a page name is set even if the name is not specified in the template or the template is not shown. | | `page/title/color` | `Color` | `{"r":1,"g":1,"b":1,"a":1}` | Color of page titles visible in preview mode, can change with different themes. | | `page/title/fontFileUri` | `String` | `"bundle://ly.img.cesdk/fonts/imgly_font_inter_semibold.otf"` | Font of page titles. | | `page/title/separator` | `String` | `"-"` | Title label separator between the page number and the page name. | | `page/title/show` | `Bool` | `true` | Whether to show titles above each page. | | `page/title/showOnSinglePage` | `Bool` | `true` | Whether to hide the the page title when only a single page is given. | | `page/title/showPageTitleTemplate` | `Bool` | `true` | Whether to include the default page title from `page.titleTemplate`. | | `pageHighlightColor` | `Color` | `{"r":0.5,"g":0.5,"b":0.5,"a":0.2}` | Color of the outline of each page. | | `placeholderControls/showButton` | `Bool` | `true` | Show the placeholder button. | | `placeholderControls/showOverlay` | `Bool` | `true` | Show the overlay pattern. | | `placeholderHighlightColor` | `Color` | `{"r":0.77,"g":0.06,"b":0.95,"a":1}` | Color of the selection, hover, and group frames and for the handle outlines for placeholder elements. | | `positionSnappingThreshold` | `Float` | `4` | Position snapping threshold in screen space. | | `progressColor` | `Color` | `{"r":1,"g":1,"b":1,"a":0.7}` | The progress indicator color. | | `renderTextCursorAndSelectionInEngine` | `Bool` | `true` | Whether the engine should render the text cursor and selection highlights during text editing. This can be set to false, if the platform wants to perform this rendering itself. | | `rotationSnappingGuideColor` | `Color` | `{"r":1,"g":0.004,"b":0.361,"a":1}` | Color of the rotation snapping guides. | | `rotationSnappingThreshold` | `Float` | `0.15` | Rotation snapping threshold in radians. | | `showBuildVersion` | `Bool` | `false` | Show the build version on the canvas. | | `snappingGuideColor` | `Color` | `{"r":1,"g":0.004,"b":0.361,"a":1}` | Color of the position snapping guides. | | `textVariableHighlightColor` | `Color` | `{"r":0.7,"g":0,"b":0.7,"a":1}` | Color of the text variable highlighting borders. | | `touch/dragStartCanSelect` | `Bool` | `true` | Whether dragging an element requires selecting it first. When not set, elements can be directly dragged. | | `touch/pinchAction` | `Enum` | `"Scale"` | The action to perform when a pinch gesture is performed., Possible values: `"None"`, `"Zoom"`, `"Scale"` | | `touch/rotateAction` | `Enum` | `"Rotate"` | Whether or not the two finger turn gesture can rotate selected elements., Possible values: `"None"`, `"Rotate"` | | `touch/singlePointPanning` | `Bool` | `true` | Whether or not dragging on the canvas should move the camera (scrolling). When not set, the scroll bars have to be used. | | `upload/supportedMimeTypes` | `String` | `""` | The MIME types supported for file uploads. | | `useSystemFontFallback` | `Bool` | `false` | Whether the IMG.LY hosted font fallback is used for fonts that are missing certain characters, covering most of the unicode range. | | `web/fetchCredentials` | `String` | `"same-origin"` | The credentials mode to use for fetch requests (same-origin, include, or omit). | ### GlobalScopes[#](#globalscopes) | Member | Type | Default | Description | | --- | --- | --- | --- | | text | `Scope` | `Allow` | Scope for text operations. | | fill | `Scope` | `Allow` | Scope for fill operations. | | stroke | `Scope` | `Allow` | Scope for stroke operations. | | shape | `Scope` | `Allow` | Scope for shape operations. | | layer | `Scope` | `Allow` | Scope for layer operations. | | appearance | `Scope` | `Allow` | Scope for appearance operations. | | lifecycle | `Scope` | `Allow` | Scope for lifecycle operations. | | editor | `Scope` | `Allow` | Scope for editor operations. | --- [Source](https:/img.ly/docs/cesdk/vue/serve-assets-b0827c) --- # Serve Assets Configure CE.SDK to load engine and content assets from your own servers instead of the IMG.LY CDN for production deployments. Self-hosting assets is required for production use. The IMG.LY CDN is intended for development and prototyping only—you’ll see a console warning when using the default CDN configuration. Hosting assets yourself gives you control over performance, availability, and compliance requirements. [Download Assets (v1.67.0)](https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/imgly-assets.zip) This guide covers the asset categories CE.SDK uses, how to configure `baseURL` and asset source paths, and how to automate asset updates during SDK upgrades. ## Quick Start[#](#quick-start) Download and extract the essential assets for your SDK version: Terminal window ``` --- # Download assets for current SDK versioncurl -O https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/imgly-assets.zip --- # Create versioned directory and extract assetsmkdir -p public/cesdk/1.67.0unzip imgly-assets.zip -d public/cesdk/1.67.0/rm imgly-assets.zip ``` Then configure CE.SDK to use your self-hosted assets: ``` import CreativeEditorSDK from '@cesdk/cesdk-js'; const config = { license: 'YOUR_CESDK_LICENSE_KEY', baseURL: `https://cdn.yourdomain.com/cesdk/${CreativeEditorSDK.version}/`,}; CreativeEditorSDK.create(container, config).then(cesdk => { cesdk.addDefaultAssetSources({ baseURL: `https://cdn.yourdomain.com/cesdk/${CreativeEditorSDK.version}/`, });}); ``` **Versioned paths recommended**: Using version-specific paths like `/cesdk/${CreativeEditorSDK.version}/` allows you to run multiple SDK versions in parallel, simplifies rollbacks, and ensures clean cache invalidation during upgrades. ## Understanding CE.SDK Assets[#](#understanding-cesdk-assets) CE.SDK assets are distributed in an `imgly-assets.zip` file available for download from the IMG.LY CDN. Understanding what’s in this archive helps you decide which assets to host and how to keep them updated. ### Asset Categories[#](#asset-categories) The ZIP file contains directories organized by function: | Directory | Contents | Version-Locked? | Required? | | --- | --- | --- | --- | | `core/` | WASM engine files (`.wasm`, `.data`, `worker-host.js`) | **Yes** | **Yes** | | `ui/` | UI resources (audio waveform, fonts, stylesheets) | **Yes** | **Yes** | | `emoji/` | Emoji assets | **Yes** | **Yes** | | `fonts/` | System fonts | **Yes** | **Yes** | | `i18n/` | Translations | **Yes** | No (bundled in SDK) | | `ly.img.sticker/` | Stickers | No | If using default assets | | `ly.img.sticker.misc/` | Additional stickers | No | If using default assets | | `ly.img.vectorpath/` | Shapes and arrows | No | If using default assets | | `ly.img.typeface/` | Font definitions | No | If using default assets | | `ly.img.filter.lut/` | LUT filter effects | No | If using default assets | | `ly.img.filter.duotone/` | Duotone effects | No | If using default assets | | `ly.img.effect/` | Visual effects | No | If using default assets | | `ly.img.blur/` | Blur presets | No | If using default assets | | `ly.img.colors.defaultPalette/` | Color palettes | No | If using default assets | | `ly.img.crop.presets/` | Crop aspect ratios | No | If using default assets | | `ly.img.page.presets/` | Page format presets | No | If using default assets | | `ly.img.page.presets.video/` | Video page presets | No | If using default assets | | `ly.img.captionPresets/` | Caption formatting presets | No | If using default assets | | `ly.img.animation/` | Animation presets | No | If using default assets | | `ly.img.animation.text/` | Text animation presets | No | If using default assets | | `ly.img.image/` | Sample images (demo content) | No | No | | `ly.img.video/` | Sample videos (demo content) | No | No | | `ly.img.audio/` | Sample audio (demo content) | No | No | | `ly.img.template/` | Design templates (demo content) | No | No | | `ly.img.video.template/` | Video templates (demo content) | No | No | | `ly.img.textComponents/` | Text components (demo content) | No | No | For most integrations, you need `core/`, `ui/`, `emoji/`, `fonts/`, and the `ly.img.*` asset sources you use. ### Version-Locked vs. Independent Assets[#](#version-locked-vs-independent-assets) **Version-locked assets** (`core/`, `ui/`, `i18n/`) must match your SDK version. Mismatched versions cause load errors—the engine will fail to initialize if core files don’t match. **Independent assets** (`ly.img.*` directories) can be updated separately. Configure these via `addDefaultAssetSources({ baseURL })` and `addDemoAssetSources({ baseURL })`. Check the changelog when upgrading to see if asset versions have changed. ### Default Asset Sources[#](#default-asset-sources) Calling `addDefaultAssetSources()` registers these asset sources: * `ly.img.sticker` - Stickers * `ly.img.sticker.misc` - Additional stickers * `ly.img.vectorpath` - Shapes and arrows * `ly.img.typeface` - Font definitions * `ly.img.colors.defaultPalette` - Color palettes * `ly.img.filter.lut` - LUT effects * `ly.img.filter.duotone` - Duotone effects * `ly.img.effect` - Visual effects * `ly.img.blur` - Blur presets * `ly.img.crop.presets` - Crop aspect ratios * `ly.img.page.presets` - Page format presets * `ly.img.page.presets.video` - Video page presets * `ly.img.captionPresets` - Caption formatting * `ly.img.animation` - Animation presets * `ly.img.animation.text` - Text animation presets ### Demo Asset Sources[#](#demo-asset-sources) Calling `addDemoAssetSources()` registers sample content sources for development: * `ly.img.image` - Sample images * `ly.img.video` - Sample videos * `ly.img.audio` - Sample audio * `ly.img.template` - Design templates * `ly.img.video.template` - Video templates * `ly.img.textComponents` - Text components * `ly.img.image.upload`, `ly.img.video.upload`, `ly.img.audio.upload` - Upload sources These are intended for development and prototyping—replace them with your own content in production. ## Configuration Options[#](#configuration-options) Browser configuration involves three settings: `baseURL`, `core.baseURL`, and the `baseURL` option for asset sources. ### Editor Configuration[#](#editor-configuration) Pass configuration to `CreativeEditorSDK.create()`: ``` import CreativeEditorSDK from '@cesdk/cesdk-js'; const config = { baseURL: `https://cdn.yourdomain.com/cesdk/${CreativeEditorSDK.version}/`, core: { baseURL: 'core/', },}; CreativeEditorSDK.create(container, config).then(cesdk => { // Editor initialized with self-hosted assets}); ``` **`baseURL`** — Base path for all engine assets. Can be an absolute URL or a relative path. Relative paths resolve against `window.location.href`. Defaults to the IMG.LY CDN. **`core.baseURL`** — Path to WASM files. Defaults to `core/` relative to `baseURL`. Usually you don’t need to change this unless hosting WASM files separately. ### Asset Sources Configuration[#](#asset-sources-configuration) Configure asset sources after initializing the editor: ``` CreativeEditorSDK.create(container, config).then(cesdk => { // Point default assets to your server cesdk.addDefaultAssetSources({ baseURL: `https://cdn.yourdomain.com/cesdk/${CreativeEditorSDK.version}/`, }); // Optional: Add demo assets for development cesdk.addDemoAssetSources({ baseURL: `https://cdn.yourdomain.com/cesdk/${CreativeEditorSDK.version}/`, });}); ``` The `baseURL` for asset sources must be an absolute URL. The engine looks up asset definitions at `{baseURL}/{sourceId}/content.json` and references files like `{baseURL}/{sourceId}/images/example.png`. ## Excluding Unused Asset Sources[#](#excluding-unused-asset-sources) If you only need a subset of default assets, use `excludeAssetSourceIds` to skip loading sources you don’t use: ``` cesdk.addDefaultAssetSources({ baseURL: `https://cdn.yourdomain.com/cesdk/${CreativeEditorSDK.version}/`, excludeAssetSourceIds: ['ly.img.sticker', 'ly.img.page.presets.video'],}); ``` This reduces initial load time by not fetching unused asset definitions. ## Troubleshooting[#](#troubleshooting) ### WASM Load Errors[#](#wasm-load-errors) If the engine fails to initialize with missing `.wasm` or `.data` errors, verify: 1. The assets ZIP version matches your SDK version 2. The `core.baseURL` points to the correct directory 3. Your server returns correct MIME types for `.wasm` files (`application/wasm`) ### 404 Errors for Assets[#](#404-errors-for-assets) If the console shows 404 errors for `content.json` files: 1. Verify the `baseURL` in `addDefaultAssetSources()` is correct 2. Check that asset directories exist at the expected paths 3. Configure CORS headers if serving assets from a different domain ### CDN Warning in Console[#](#cdn-warning-in-console) The warning “You’re using the IMG.LY CDN” appears when using default configuration. Set `baseURL` in your config to use self-hosted assets and remove the warning. ## API Reference[#](#api-reference) | Method/Config | Purpose | | --- | --- | | `CreativeEditorSDK.create(container, config)` | Initialize editor with configuration | | `config.baseURL` | Base path for all engine assets | | `config.core.baseURL` | Path to WASM/core files (relative to baseURL) | | `cesdk.addDefaultAssetSources(options)` | Register default asset sources | | `cesdk.addDemoAssetSources(options)` | Register demo asset sources | | `CreativeEditorSDK.version` | Get current SDK version string | --- [Source](https:/img.ly/docs/cesdk/vue/security-777bfd) --- # Security This document provides a comprehensive overview of CE.SDK’s security practices, focusing on data handling, privacy, and our commitment to maintaining the highest standards of security for our customers and their end users. ## Key Security Features[#](#key-security-features) * **Client-Side Processing**: All image and design processing occurs directly on the user’s device or your servers, not on our servers * **No Data Transmission**: Your content (e.g. images, designs, templates, videos, audio, etc.) is never uploaded to or processed on IMG.LY servers * **Minimal Data Collection**: We only collect device identifiers and count exports for licensing purposes * **GDPR Compliance**: Our data collection practices adhere to GDPR regulations * **Secure Licensing**: Enterprise licenses are secured with RSA SHA256 encryption ## Data Protection & Access Controls[#](#data-protection--access-controls) ### Data Collection[#](#data-collection) CE.SDK requires minimal data to provide its services. The only potentially personally identifiable information (PII) collected includes device-specific identifiers such as `identifierForVendor` on iOS and `ANDROID_ID` on Android. These identifiers are: * Used solely for tracking monthly active users for our usage-based pricing models * Reset when the user reinstalls the app or resets their device * Collected under GDPR’s legitimate interest provision (no explicit consent required as they are necessary for our licensing system) Additionally, we track export operations for billing purposes in usage-based pricing models. For enterprise customers who prefer more accurate tracking, integrators can provide their own userID. This allows for more precise measurement of usage without requiring additional device identifiers. ### Data Storage & Encryption[#](#data-storage--encryption) **We do not collect or store user data beyond the device identifiers and export counts mentioned above.** Since CE.SDK operates entirely client-side: * All content processing happens on the user’s device * No images, designs, or user content is transmitted to IMG.LY servers * No content data is stored on IMG.LY infrastructure We use standard HTTPS (SSL/TLS) encryption for all communications between CE.SDK instances and our licensing backend. ### Access Controls[#](#access-controls) We are using established industry standard practices to handle sensitive customer data. Therefore access control concerns are minimized. The limited data we do handle is protected as follows: * Billing information is stored in Stripe and accessed only by members of our finance team and C-level executives * API keys and credentials are stored securely in 1Password or GitHub with granular access levels * All employees sign Confidentiality Agreements to protect customer information This refers to data of our direct customers, not their users or customers. ## Licensing System[#](#licensing-system) CE.SDK uses a licensing system that works as follows: 1. During instantiation, an API key is provided to the CE.SDK instance 2. This API key is held in memory (never stored permanently on the device) 3. The SDK validates the key with our licensing backend 4. Upon successful validation, the backend returns a temporary local license 5. This license is periodically refreshed to maintain valid usage For browser implementations, we protect licenses against misuse by pinning them to specific domains. For mobile applications, licenses are pinned to the application identifiers to prevent unauthorized use. For enterprise customers, we offer an alternative model: * A license file is passed directly to the instance * No communication with our licensing service is required * Licenses are secured using RSA SHA256 encryption ### CE.SDK Renderer[#](#cesdk-renderer) CE.SDK Renderer is a specialized variant of CE.SDK that consists of a native Linux binary bundled in a Docker container. It uses GPU acceleration and native code to render scenes and archives to various export formats. Due to bundled third-party codecs (mainly H.264 & H.265) and their associated patent requirements, CE.SDK Renderer implements additional licensing communication beyond the standard licensing handshake: 1. **Initial License Validation**: The tool performs the standard license validation with our licensing backend 2. **Periodic Heartbeats**: After successful validation, it sends periodic heartbeats to our licensing backend to track the number of active instances 3. **Instance Limits**: We limit the maximum number of active instances per license based on the settings in your dashboard 4. **Activation Control**: If the instance limit is exceeded, further activations (launches) of the tool will fail with a descriptive error message This additional communication allows us to ensure compliance with codec licensing requirements while providing transparent usage tracking for your organization. As with all CE.SDK products, no user data (images, videos, designs, or other content) is transmitted to IMG.LY servers - only device identifiers and instance counts are collected for licensing purposes. ## Security Considerations for User Input[#](#security-considerations-for-user-input) As CE.SDK deals primarily with arbitrary user input, we’ve implemented specific security measures to handle data safely: * The CreativeEngine reads files from external resources to fetch images, fonts, structured data, and other sources for designs. These reads are safeguarded by platform-specific default measures. * The engine never loads executable code or attempts to execute any data acquired from dynamic content. It generally relies on provided mime types to decode image data or falls back to byte-level inspection to choose the appropriate decoder. * For data writing operations, we provide a callback that returns a pointer to the to-be-written data. The engine itself never unconditionally writes to an externally defined path. If it writes to files directly, these are part of internal directories and can’t be modified externally. * Generated PDFs may have original image files embedded if the image was not altered via effects or blurs and the `exportPdfWithHighCompatibility` option was **not** enabled. This means a malicious image file could theoretically be included in the exported PDF. * Inline text-editing allows arbitrary input of strings by users. The engine uses platform-specific default inputs and APIs and doesn’t apply additional sanitization. The acquired strings are stored and used exclusively for text rendering - they are neither executed nor used for file operations. ## Security Infrastructure[#](#security-infrastructure) ### Vulnerability Management[#](#vulnerability-management) We take a proactive approach to security vulnerability management: * We use GitHub to track dependency vulnerabilities * We regularly update affected dependencies * We don’t maintain a private network, eliminating network vulnerability concerns in that context * We don’t manually maintain servers or infrastructure, as we don’t have live systems beyond those required for licensing * For storage and licensing, we use virtual instances in Google Cloud which are managed by the cloud provider * All security-related fixes are published in our public changelog at [https://img.ly/docs/cesdk/changelog/](https://img.ly/docs/cesdk/changelog/) ### Security Development Practices[#](#security-development-practices) Our development practices emphasize security: * We rely on established libraries with proven security track records * We don’t directly process sensitive user data in our code * Secrets (auth tokens, passwords, API credentials, certificates) are stored in GitHub or 1Password with granular access levels * We use RSA SHA256 encryption for our enterprise licenses * We rely on platform-standard SSL implementations for HTTPS communications ### API Key Management[#](#api-key-management) API keys for CE.SDK are handled securely: * Keys are passed during instantiation and held in memory only * Keys are never stored permanently on client devices * For web implementation, keys are pinned to specific domains to prevent unauthorized use * Enterprise licenses use a file-based approach that doesn’t require API key validation ## Compliance[#](#compliance) IMG.LY complies with the General Data Protection Regulation (GDPR) in all our operations, including CE.SDK. Our Privacy Policy is publicly available at [https://img.ly/privacy-policy](https://img.ly/privacy-policy). Our client-side approach to content processing significantly reduces privacy and compliance concerns, as user content never leaves their device environment for processing. ## FAQ[#](#faq) ### Does CE.SDK upload my images or designs to IMG.LY servers?[#](#does-cesdk-upload-my-images-or-designs-to-imgly-servers) No. CE.SDK processes all content locally on the user’s device. Your images, designs, and other content are never transmitted to IMG.LY servers. ### What data does IMG.LY collect through CE.SDK?[#](#what-data-does-imgly-collect-through-cesdk) CE.SDK only collects device identifiers (such as identifierForVendor on iOS or ANDROID\_ID on Android) for licensing purposes and export counts. No user content or personal information is collected. ### How does IMG.LY protect API keys?[#](#how-does-imgly-protect-api-keys) API keys are never stored permanently; they are held in memory during SDK operation. For web implementations, keys are pinned to specific domains to prevent unauthorized use. ### Has IMG.LY experienced any security breaches?[#](#has-imgly-experienced-any-security-breaches) No, IMG.LY has not been involved in any cybersecurity breaches in the last 12 months. ### Does IMG.LY conduct security audits?[#](#does-imgly-conduct-security-audits) As we don’t store customer data directly, but rely on third parties to do so, we focus our security efforts on dependency tracking and vulnerability management through GitHub’s security features. We don’t conduct security audits. ## Additional Information[#](#additional-information) For more detailed information about our data collection practices, please refer to our Data Privacy and Retention information below. Should you have any additional questions regarding security practices or require more information, please contact our team at [support@img.ly](mailto:support@img.ly). ## Data Privacy and Retention[#](#data-privacy-and-retention) At IMG.LY, we prioritize your data privacy and ensure that apart from a minimal contractually stipulated set of interactions with our servers all other operations take place on your local device. Below is an overview of our data privacy and retention policies: ### **Data Processing**[#](#data-processing) All data processed by CE.SDK remains strictly on your device. We do not transfer your data to our servers for processing. This means that operations such as rendering, editing, and other in-app functionalities happen entirely locally, ensuring that sensitive project or personal data stays with you. ### **Data Retention**[#](#data-retention) We do not store any project-related data on our servers. Since all data operations occur locally, no information about your edits, images, or video content is retained by CE.SDK. The only data that interacts with our servers is related to license validation and telemetry related to usage tied to your pricing plan. ### **License Validation**[#](#license-validation) CE.SDK performs a license validation check with our servers once upon initialization to validate the software license being used. This interaction is minimal and does not involve the transfer of any personal, project, or media data. ### **Event Tracking**[#](#event-tracking) While CE.SDK does not track user actions other than the exceptions listed below through telemetry or analytics by default, there are specific events tracked to manage customer usage, particularly for API key usage tracking. We gather the following information during these events: * **When the engine loads:** App identifier, platform, engine version, user ID (provided by the client), device ID (mobile only), and session ID. * **When a photo or video is exported:** User ID, device ID, session ID, media type (photo/video), resolution (width and height), FPS (video only), and duration (video only). This tracking is solely for ensuring accurate usage calculation and managing monthly active user billing. Enterprise clients can opt out of this tracking under specific agreements. ### **Personal Identifiable Information (PII)**[#](#personal-identifiable-information-pii) The only PII that is potentially collected includes device-specific identifiers such as `identifierForVendor` on iOS and `ANDROID_ID` on Android. These IDs are used for tracking purposes and are reset when the user reinstalls the app or resets the device. No consent is required for these identifiers because they are crucial for our usage-based pricing models. This is covered by the GDPR as legitimate interest. ### **User Consent**[#](#user-consent) As mentioned above, user consent is not required when solely using the CE.SDK. However, this may change depending on the specific enterprise agreement or additional regulatory requirements. IMG.LY is committed to maintaining compliance with **GDPR** and other applicable data protection laws, ensuring your privacy is respected at all times. For details consult our [privacy policy](https://img.ly/privacy-policy). --- [Source](https:/img.ly/docs/cesdk/vue/rules-1427c0) --- # Rules --- [Source](https:/img.ly/docs/cesdk/vue/prebuilt-solutions-d0ed07) --- # Prebuilt Solutions --- [Source](https:/img.ly/docs/cesdk/vue/plugins-693c48) --- # Plugins Learn how to extend CE.SDK functionality with plugins to add custom features, effects, and integrations. --- [Source](https:/img.ly/docs/cesdk/vue/performance-3c12eb) --- # Improve Performance Optimize CE.SDK integration for faster load times, efficient memory usage, and smooth runtime performance in browser environments. CE.SDK has a large bundle size that can impact initial page load. Optimizing how you load, use, and dispose of the editor improves application performance and user experience. This guide covers code splitting techniques to defer loading, source sets for managing large assets, export optimization with workers and quality settings, and proper lifecycle management. ## Code Splitting[#](#code-splitting) Delay loading the engine module to improve initial page load time. Use dynamic imports to load the engine only when needed. ### Using a Bundler[#](#using-a-bundler) Use dynamic `import()` with bundlers like Webpack, Rollup, or Vite to create separate chunks that load on demand. The bundler automatically handles code splitting when you use dynamic imports. ``` async function loadCreativeEditorSDK(): Promise { const { default: CreativeEditorSDK } = await import('@cesdk/cesdk-js'); return CreativeEditorSDK;} ``` This approach keeps CE.SDK in a separate bundle that loads only when `loadCreativeEditorSDK()` is called. Users who never open the editor avoid downloading the SDK entirely. ### Using CDN[#](#using-cdn) Load CE.SDK directly from CDN using dynamic imports. This works in modern browsers without any build tooling. ``` async function loadCreativeEditorSDK(): Promise { const { default: CreativeEditorSDK } = await import( 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/index.js' ); return CreativeEditorSDK;} ``` CDN loading offloads bandwidth from your servers and leverages global CDN caching for faster delivery. ## Managing Large Assets[#](#managing-large-assets) High-resolution images and videos consume significant memory. Optimize asset loading to reduce memory pressure and improve performance. ### Use Source Sets[#](#use-source-sets) Source sets allow you to provide multiple resolution variants of the same image. The engine automatically selects the most appropriate resolution based on the current display size, loading smaller images for previews and higher resolution assets only when needed for exports. ``` const imageFill = engine.block.createFill('image'); engine.block.setSourceSet(imageFill, 'fill/image/sourceSet', [ { uri: 'https://img.ly/static/ubq_samples/sample_1_512x341.jpg', width: 512, height: 341 }, { uri: 'https://img.ly/static/ubq_samples/sample_1_1024x683.jpg', width: 1024, height: 683 }, { uri: 'https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg', width: 2048, height: 1366 }]); engine.block.setFill(block, imageFill); ``` This approach reduces initial load times and memory usage during editing while ensuring high-quality exports. See [Source Sets](vue/import-media/source-sets-5679c8/) for more details. ### Additional Optimization Tips[#](#additional-optimization-tips) * Remove unused assets from the scene when no longer needed * Dispose the engine when the editing session ends * Use optimized image formats (WebP, AVIF) for faster loading ## Export Optimization[#](#export-optimization) Optimize export performance through worker configuration and export settings. ### Use Export Workers[#](#use-export-workers) CE.SDK uses Web Workers to offload export rendering to background threads, keeping the main thread responsive during exports. Workers are enabled by default for both image and video exports. For video exports, the `exportWorker` configuration option controls worker usage: ``` const config = { license: 'YOUR_CESDK_LICENSE_KEY', callbacks: { exportWorker: true // Enabled by default }}; const cesdk = await CreativeEditorSDK.create('#cesdk_container', config); ``` ### Optimize Export Settings[#](#optimize-export-settings) Adjust export parameters to balance quality and performance: * **Lower resolution**: Reduce `targetWidth` and `targetHeight` for faster exports on slower devices * **Adjust quality**: Use lower JPEG quality (0.7-0.8) for smaller file sizes and faster encoding * **Choose efficient formats**: WebP typically offers better compression than PNG for images with transparency ``` // Export with optimized settings for performanceconst blob = await engine.block.export(page, 'image/jpeg', { targetWidth: 1920, targetHeight: 1080, jpegQuality: 0.8}); ``` ### Export Size Limits[#](#export-size-limits) Check device export capabilities before attempting large exports using `engine.editor.getMaxExportSize()`. ``` const maxExportSize = engine.editor.getMaxExportSize(); // Verify design fits within limitsconst pageWidth = engine.block.getWidth(page);const pageHeight = engine.block.getHeight(page); if (pageWidth > maxExportSize || pageHeight > maxExportSize) { console.warn('Design exceeds maximum export size - consider reducing dimensions');} ``` This returns the maximum dimension in pixels that the device can reliably export. ## Engine Lifecycle[#](#engine-lifecycle) Follow proper patterns for initializing and disposing the engine to prevent memory leaks and ensure consistent behavior. ### Initialization[#](#initialization) Initialize the engine once and reuse the instance. Pass essential configuration including license key and baseURL. ``` import CreativeEditorSDK from '@cesdk/cesdk-js'; const config = { license: 'YOUR_CESDK_LICENSE_KEY', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.67.0/assets'}; const cesdk = await CreativeEditorSDK.create('#cesdk_container', config);const engine = cesdk.engine; ``` When using the full CE.SDK (not just the engine), the editor creates a managed canvas element accessible via `cesdk.engine`. Avoid creating multiple engine instances simultaneously. ### Disposal[#](#disposal) Call `dispose()` when the editing session ends to free all resources including GPU memory, textures, and native allocations. ``` cesdk.dispose(); ``` After disposal, the engine instance becomes unusable. Create a new instance if the user returns to the editor. ## Asset Loading[#](#asset-loading) For production deployments, host engine assets on your own servers to improve reliability and reduce dependency on external CDNs. Configure `baseURL` to point to your asset location. See [Serve Assets From Your Server](vue/serve-assets-b0827c/) for detailed setup instructions. ## Troubleshooting[#](#troubleshooting) ### Large Initial Load Time[#](#large-initial-load-time) Implement code splitting to defer engine loading until needed. Use dynamic imports with bundlers or CDN loading to keep the main bundle small. ### Slow Export Performance[#](#slow-export-performance) Enable export workers (default) and reduce export resolution or quality settings. For complex scenes, consider exporting at a lower resolution and scaling up if needed. ### Memory Leaks[#](#memory-leaks) Ensure `dispose()` is called when the editor is closed. In SPAs, use cleanup functions in component lifecycle hooks to dispose the engine on unmount. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `CreativeEditorSDK.create()` | Initialize a new CE.SDK instance | | `cesdk.dispose()` | Clean up all resources | | `engine.editor.getMaxExportSize()` | Get maximum export dimension in pixels | | `engine.block.export()` | Export a block to image or video | ## Next Steps[#](#next-steps) * [Architecture](vue/concepts/architecture-6ea9b2/) \- Understand CE.SDK structure and components * [Headless Mode](vue/concepts/headless-mode/browser-24ab98/) \- Run the engine without UI for automation * [Export Overview](vue/export-save-publish/export/overview-9ed3a8/) \- Learn about export formats and options --- [Source](https:/img.ly/docs/cesdk/vue/overview-7d12d5) --- # Video Editor SDK Use CreativeEditor SDK (CE.SDK) to build robust video editing experiences directly in your app. CE.SDK supports both video and audio editing — including trimming, joining, adding text, annotating, and more — all performed client-side without requiring a server. Developers can integrate editing functionality using a built-in UI or programmatically via the SDK API. CE.SDK also supports music and sound effects alongside video editing. You can integrate custom or third-party AI models to streamline creative workflows, such as converting image to video or generating clips from text. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## Core Capabilities[#](#core-capabilities) CreativeEditor SDK includes a comprehensive set of video editing tools, accessible through both a UI and programmatic interface. Supported editing actions include: * **Trim, Split, Join, and Arrange**: Modify clips, reorder segments, and stitch together content. * **Transform**: Crop, rotate, resize, scale, and flip. * **Audio Editing**: Add, adjust, and synchronize audio including music and effects. * **Programmatic Editing**: Control all editing features via API. CE.SDK is well-suited for scenarios like short-form content, reels, promotional videos, and other linear video workflows. ## Timeline Editor[#](#timeline-editor) The built-in timeline editor provides a familiar video editing experience for users. It supports: * Layered tracks for video and audio * Drag-and-drop sequencing with snapping * Trim handles, in/out points, and time offsets * Real-time preview updates The timeline is the main control for video editing: ![The editor timeline control.](/docs/cesdk/_astro/video_mode_timeline.BkrXFlTn_2e2pv5.webp) ## AI-Powered Editing[#](#ai-powered-editing) CE.SDK allows you to easily integrate AI tools directly into your video editing workflow. Users can generate images, videos, and audio from simple prompts — all from within the editor’s task bar, without switching tools or uploading external assets. [ Launch AI Editor Demo ](https://img.ly/showcases/cesdk/ai-editor/web) You can bring your own models or third-party APIs with minimal setup. AI tools can be added as standalone plugins, contextual buttons, or task bar actions. ## Supported Input Formats and Codecs[#](#supported-input-formats-and-codecs) CE.SDK supports a wide range of video input formats and encodings, including: | Category | Supported Formats | | --- | --- | | **Images** | `.png`, `.jpeg`, `.jpg`, `.gif`, `.webp`, `.svg`, `.bmp` | | **Video** | `.mp4` (H.264/AVC, H.265/HEVC), `.mov` (H.264/AVC, H.265/HEVC), `.webm` (VP8, VP9, AV1) | | **Audio** | `.mp3`, `.m4a`, `.mp4` (AAC or MP3), `.mov` (AAC or MP3) | | **Animation** | `.json` (Lottie) | Need to import a format not listed here? CE.SDK allows you to create custom importers for any file type by using our Scene and Block APIs programmatically. CE.SDK supports the most widely adopted video and audio codecs to ensure compatibility across platforms: ### **Video Codecs**[#](#video-codecs) * **H.264 / AVC** (in `.mp4`) * **H.265 / HEVC** (in `.mp4`, may require platform-specific support) ### **Audio Codecs**[#](#audio-codecs) * **MP3** (in `.mp3` or within `.mp4`) * **AAC** (in `.m4a` or within `.mp4` or `.mov`) ## Output and Export Options[#](#output-and-export-options) You can export edited videos in several formats, with control over resolution, encoding, and file size: | Category | Supported Formats | | --- | --- | | **Images** | `.png` (with transparency), `.jpeg`, `.webp`, `.tga` | | **Video** | `.mp4` (H.264 or H.265 on supported platforms with limited transparency support) | | **Print** | `.pdf` (supports underlayer printing and spot colors) | | **Scene** | `.scene` (description of the scene without any assets) | | **Archive** | `.zip` (fully self-contained archive that bundles the `.scene` file with all assets) | Our custom cross-platform C++ based rendering and layout engine ensures consistent output quality across devices. ## UI-Based vs. Programmatic Editing[#](#ui-based-vs-programmatic-editing) CE.SDK offers a fully interactive editor with intuitive UI tools for creators. At the same time, developers can build workflows entirely programmatically using the SDK API. * Use the UI to let users trim, arrange, and caption videos manually * Use the API to automate the assembly or editing of videos at scale ## Customization[#](#customization) You can tailor the editor to match your product’s design and user needs: * Show or hide tools * Reorder UI elements and dock items * Apply custom themes, colors, or typography * Add additional plugin components ## Performance and File Size Considerations[#](#performance-and-file-size-considerations) All editing operations are performed client-side. While this ensures user privacy and responsiveness, it introduces some limits: | Constraint | Recommendation / Limit | | --- | --- | | **Resolution** | Up to **4K UHD** is supported for **playback** and **export**, depending on the user’s hardware and available GPU resources. For **import**, CE.SDK does not impose artificial limits, but maximum video size is bounded by the **32-bit address space of WebAssembly (wasm32)** and the **browser tab’s memory cap (~2 GB)**. | | **Frame Rate** | 30 FPS at 1080p is broadly supported; 60 FPS and high-res exports benefit from hardware acceleration | | **Duration** | Stories and reels of up to **2 minutes** are fully supported. Longer videos are also supported, but we generally found a maximum duration of **10 minutes** to be a good balance for a smooth editing experience and a pleasant export duration of around one minute on modern hardware. | Performance scales with client hardware. For best results with high-resolution or high-frame-rate video, modern CPUs/GPUs with hardware acceleration are recommended. --- [Source](https:/img.ly/docs/cesdk/vue/overview-491658) --- # Overview In CE.SDK, _inserting media into a scene_ means placing visual or audio elements directly onto the canvas—images, videos, audio clips, shapes, or stickers—so they become part of the design. This differs from _importing assets_, which simply makes media available in the asset library. This guide helps you understand how insertion works, how inserted media behave within scenes, and how to control them via UI or code. By the end, you’ll know how media are represented, modified, saved, and exported. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## Inserting Media vs. Importing Assets[#](#inserting-media-vs-importing-assets) Before you can insert media into a scene, it must first be available to CE.SDK—this is where _importing_ comes in. Imported assets are added to the **Asset Library** (from local uploads, remote sources, etc.), where they become available for use. _Inserting_ means placing those assets into the actual scene—either as the fill of a design block (like an image inside a rectangle), or as a standalone visual/audio layer. This process creates scene elements that users can see, move, style, and manipulate. ## How Media Is Handled in Scenes[#](#how-media-is-handled-in-scenes) Internally, inserted media are structured as part of the scene graph. Most are represented as fills or as design blocks: * **Images and Videos** are typically inserted as _fills_ for graphic blocks or as independent blocks for visual layering. * **Audio** is inserted as a timeline-based media block, often invisible but timeline-active. * **Shapes and Stickers** are treated as standalone graphic blocks, with shape or vector fills. Each inserted item is assigned an ID and properties such as position, size, and rotation, and can be queried or modified programmatically. ## Inserting Media[#](#inserting-media) ### Insert via the UI[#](#insert-via-the-ui) You can insert media using the built-in CE.SDK interface. The most common methods are: * Drag-and-drop from the **Asset Library** into the canvas. * Clicking an asset in the panel to place it at the center of the scene. * Using context menus or toolbar buttons (e.g., “Add Image” or “Insert Audio”). You can also configure the UI to show or hide certain media categories, allowing for tailored user experiences. See the **Customize Asset Library** guide for more on controlling visible media types. ### Insert Programmatically[#](#insert-programmatically) Developers can insert media directly via the SDK. Whether you’re building a dynamic editor or triggering insertions via user input, CE.SDK exposes APIs for: * Creating a new design block and applying a media fill (e.g., image, video). * Controlling properties like position, size, rotation, opacity, and z-index. * Embedding logic to sync insertions with UI actions or backend data. ## Referencing Existing Assets[#](#referencing-existing-assets) Once an asset is imported, you can reference it multiple times without re-importing. Reuse is based on: * **URI** (useful for remote assets) When reusing an asset, you can apply different visual properties—each inserted instance can have its own size, position, rotation, or visual effects. ## Media Lifecycle Within a Scene[#](#media-lifecycle-within-a-scene) Inserted media are part of the live scene graph and follow CE.SDK’s scene lifecycle: * **Saving a Scene**: Inserted media references (or embedded content) are included in the saved `.scene` or `.archive`. * **Reloading a Scene**: CE.SDK reconstructs the scene graph and fetches any required media URIs or binary data. * **Exporting**: Media may be embedded directly (e.g., for self-contained exports) or referenced externally (e.g., smaller file sizes, shared assets). Keep in mind that media integrity on reload/export depends on how the asset was inserted—linked URIs must remain available, whereas embedded assets are bundled. ## Embedding vs. Linking Media[#](#embedding-vs-linking-media) CE.SDK supports two strategies for handling inserted media: | Mode | Description | Use Case | | --- | --- | --- | | **Referenced** (Scene Files) | Scene references the media via URI. | Smaller file sizes, shared asset use. | | **Embedded** (Archives) | Media is stored directly in the saved archive. | Offline editing, portable scenes. | **Embedded** media increase file size but ensure portability. **Referenced** media reduce storage needs but require external hosting. You can control this behavior when saving or exporting a scene. --- [Source](https:/img.ly/docs/cesdk/vue/outlines-b7820c) --- # Outlines --- [Source](https:/img.ly/docs/cesdk/vue/open-the-editor-23a1db) --- # Open the Editor --- [Source](https:/img.ly/docs/cesdk/vue/llms-txt-eb9cc5) --- # LLMs.txt You can also connect your AI assistant directly to our documentation using our [MCP Server](vue/get-started/mcp-server-fde71c/) . This enables real-time search and retrieval without downloading large files. Our documentation is now available in LLMs.txt format, optimized for AI reasoning engines. To better support platform-specific development, we’ve created separate documentation files for each platform. For Vue developers, this means you can now access documentation tailored to your specific platform, whether it’s iOS, Android, Web, or any other supported platform. This approach allows for a more focused and efficient use of AI tools in your development workflow. [ Download /vue/llms-full.txt ](https://img.ly/docs/cesdk/vue/llms-full.txt) These documentation files are substantial in size, with token counts exceeding the context windows of many AI models. This guide explains how to download and effectively use these platform-specific documentation files with AI tools to accelerate your development process. ## What are LLMs.txt files?[#](#what-are-llmstxt-files) LLMs.txt is an emerging standard for making documentation AI-friendly. Unlike traditional documentation formats, LLMs.txt: * Presents content in a clean, markdown-based format * Eliminates extraneous HTML, CSS, and JavaScript * Optimizes content for AI context windows * Provides a comprehensive view of documentation in a single file By using our platform-specific LLMs.txt files, you’ll ensure that AI tools have the most relevant and complete context for helping with your development tasks. ## Handling Large Documentation Files[#](#handling-large-documentation-files) Due to the size of our documentation files (upward of 500 000 tokens) most AI tools will face context window limitations. Standard models typically have context windows ranging from 8,000 to 200,000 tokens, making it challenging to process our complete documentation in a single session. ### Recommended AI Model for Full Documentation[#](#recommended-ai-model-for-full-documentation) For working with our complete documentation files, we recommend: * **Gemini 2.5 Flash**: Available via Google AI Studio with a context window of 1-2 million tokens, capable of handling even our largest documentation file --- [Source](https:/img.ly/docs/cesdk/vue/licensing-8aa063) --- # Licensing Thanks for your interest in CreativeEditor SDK (CE.SDK). We offer flexible commercial licensing options to support teams and projects of all sizes. Whether you’re building a new product or scaling an existing one, our goal is to provide the best creative editing experience—backed by a licensing model that aligns with your needs. Get in touch with us through our [contact sales form](https://img.ly/forms/contact-sales). ## Commercial Licensing[#](#commercial-licensing) CE.SDK is offered through a subscription-based commercial model. This allows us to: * Deliver ongoing updates and performance improvements * Ensure compatibility with new browsers and devices * Provide dedicated technical support * Build long-term partnerships with our customers ## How Licensing Works[#](#how-licensing-works) CE.SDK licenses are tied to a single commercial product instance, verified by the hostname for web apps and bundle/app ID for mobile apps. Licensing typically uses remote validation and includes lightweight event tracking. It’s possible to disable tracking or use offline-compatible options. To explore these options, [contact our sales team](https://img.ly/forms/contact-sales). ## Trial License Key[#](#trial-license-key) Trial licenses are available for evaluation and testing and are valid for **30 days**. They provide full access to CE.SDK’s features so you can explore its capabilities in your environment. If you need more time to evaluate, [contact our sales team](https://img.ly/forms/contact-sales). ## Testing and Production[#](#testing-and-production) Paid license keys can be used across development, staging, and production environments. Multiple domains or app identifiers can be added to support this setup. --- [Source](https:/img.ly/docs/cesdk/vue/key-concepts-21a270) --- # Key Concepts CE.SDK is built on two distinct technical layers that work together seamlessly: * **User Interface** — Pre-built editors optimized for different use cases * **Engine Interface** — Core rendering and processing engine ![The different layers CE.SDK is made of, see description below.](/docs/cesdk/_astro/layers._NZlSdtG_Zyol2N.webp) This intentional separation gives you powerful advantages: 1. **Cross-platform consistency** – The engine is cross-compiled to native web, iOS, Android, and Node.js, ensuring identical output everywhere 2. **Custom UI** – Build your own UI for simpler tools and workflows 3. **Headless automation** – Run the engine independently for automations and batch processing, both client-side and server-side ## Creative Engine[#](#creative-engine) The Creative Engine powers all core editing operations. It handles rendering, processing, and manipulation across images, layouts, text, video, audio, and vectors. **What the Engine Does:** * Maintains the scene file (your structured content) * Renders the canvas in real-time * Handles block positioning and resizing * Applies filters and effects to images * Manages text editing and typography * Controls templates with role-based permissions * Displays smart guides and snap lines Every engine capability is exposed through a comprehensive API, letting you build custom UIs, workflows, and automations. ## Headless / Engine only[#](#headless--engine-only) Use the engine without any UI for powerful automation scenarios: **Client-side automation** Perfect for in-browser batch operations and dynamic content generation without server dependencies. **Server-side automation with Node.js** Use the [Node.JS SDK](node/what-is-cesdk-2e7acd/) for following scenarios: * **High-resolution processing** – Edit on the client with preview quality, then render server-side with full-resolution assets * **Bulk generation** – Create a large volume of design variations for variable data printing * **Non-blocking workflows** – Let users continue designing while exports process in the background **Server-side export with the CE.SDK Renderer** When exporting complex graphics and videos, the [CE.SDK Renderer](renderer/cesdk-renderer-overview-7f3e9a/) can make use of GPU acceleration and video codecs on Linux server environments. **Plugin development** When building CE.SDK plugins, you get direct API access to manipulate canvas elements programmatically. ## User Interface Components[#](#user-interface-components) CE.SDK includes pre-built UI configurations optimized for different use cases: * **Photo editing** — Advanced image editing tools and filters * **Video editing** — Timeline-based video editing and effects * **Design editing** — Layout and graphic design tools (similar to Canva) * **2D product design** — Apparel, postcards, and custom product templates More configurations are coming based on customer needs. ## UI Customization[#](#ui-customization) While UI configurations provide a solid foundation, you maintain control over the user experience: * Apply **custom color schemes** and branding to match your product * Add **custom asset libraries** with your own fonts, images, graphics, videos, and audio The plugin architecture lets you add custom buttons and panels throughout the interface, ensuring the editor feels native to your product. --- [Source](https:/img.ly/docs/cesdk/vue/key-capabilities-dbb5b1) --- # Key Capabilities This guide gives you a high-level look at what CreativeEditor SDK (CE.SDK) can do—and how deeply it can integrate into your workflows. Whether you’re building a design editor into your product, enabling automation, or scaling personalized content creation, CE.SDK provides a flexible and future-ready foundation. [Launch Web Demo](https://img.ly/showcases/cesdk) It’s designed for developers, product teams, and technical decision-makers evaluating how CE.SDK fits their use case. * 100% client-side processing * Custom-built rendering engine for consistent cross-platform performance * Flexible enough for both low-code and fully custom implementations ## Design Creation and Editing[#](#design-creation-and-editing) CE.SDK provides comprehensive tools for creating and editing images, videos, and multi-page layouts directly in the browser with full feature parity to desktop applications. ### Core Capabilities[#](#core-capabilities) * Create, edit, compose, and customize visual content * Dual control: Use the built-in editor UI or programmatic API * Rich editing tools: filters, text styling, stickers, layers, and layout controls ### Supported Workflows[#](#supported-workflows) * Social media content creation and user-generated content flows * Marketing tools for creative teams * Branded asset creation (slides, product visuals, templates) * Composition tools for multi-page layouts, collages, and background blending ## Programmatic and UI-based[#](#programmatic-and-ui-based) You get full control over how your users interact with CE.SDK. * Use the built-in UI for manual workflows * Call the same editing and rendering functionality programmatically * Combine both for hybrid use cases (e.g., users edit manually, backend creates variations) Whether you’re serving designers or developers—or both—CE.SDK adapts to your product’s needs. ## Templates and Reusable Layouts[#](#templates-and-reusable-layouts) Define reusable templates to simplify design creation. These templates support: * Role-based editing (lock/unlock elements based on user type) * Smart placeholders (predefined image/text drop zones) * Preset styles for consistent branding * Programmatic or user-driven updates Templates make it easy to scale consistent design output while keeping editing intuitive. ## Automation and Dynamic Content[#](#automation-and-dynamic-content) You can generate visuals automatically by combining templates with structured data. Common use cases include personalized ads, localizations, product catalogs, or A/B testing. The SDK works in headless mode and supports batch workflows, making it easy to automate at scale. ## Multi-modal[#](#multi-modal) CE.SDK supports a wide range of content types and formats: * Input types: images, video, audio, structured data, templates * Output formats: PNG, JPEG, WebP, MP4, PDF, raw data All operations—including export—run client-side, ensuring fast performance and data privacy. ## AI Integration[#](#ai-integration) Instantly bring best-in-class AI-powered image and video editing to your application with just a few lines of code. All tools run directly inside the existing editor interface—so users stay in the flow while benefiting from powerful automation. Examples of what you can enable: * **Text to Image** – Use prompts to generate original visual content directly in the editor * **Text to Graphics** – Create crisp, scalable illustrations from simple text input * **Style Transfer** – Change the mood or style of an image while preserving its structure * **Create Variants** – Generate endless visual variations of a single subject for campaigns or personalization * **Image to Video** – Transform static images into dynamic motion content with a click * **Text to Speech** – Turn written copy into natural-sounding voiceovers, with control over tone and speed * **Smart Text Editing** – Rewrite or refine text using intelligent editing suggestions * **Swap Background** – Remove or replace backgrounds in seconds * **Add / Remove Objects** – Modify images with generative precision—no external tools required These capabilities can be integrated connecting to any third-party AI model or API with minimal setup. ## Customizable UI[#](#customizable-ui) The built-in interface is designed to be fully customizable: * Rearrange or rename tools * Apply theming and branding * Show/hide features based on user role * Add translations for international users It works across desktop and mobile, and can be extended or replaced entirely if needed. ## Extensibility[#](#extensibility) Need to add a custom feature or integrate with your backend? CE.SDK supports extensibility at multiple levels: * Custom UI components (or build a completely custom UI) * Backend data integrations (e.g., asset management systems) * Custom logic or validation rules * Advanced export workflows The SDK’s plugin architecture ensures you can scale your functionality without rebuilding the core editor. ## Content Libraries[#](#content-libraries) CE.SDK ships with a robust system for managing reusable content: * Built-in libraries of stickers, icons, overlays, and fonts * Integration with third-party providers like Getty Images, Unsplash, or Airtable * Programmatic filtering and categorization * Access assets from both code and UI * Organize by brand, user, or use case This makes it easy to deliver a seamless editing experience—no matter how many assets you manage. --- [Source](https:/img.ly/docs/cesdk/vue/insert-media-a217f5) --- # Insert Media Into Scenes --- [Source](https:/img.ly/docs/cesdk/vue/import-media-4e3703) --- # Import Media --- [Source](https:/img.ly/docs/cesdk/vue/guides-8d8b00) --- # Guides --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects-6f88ac) --- # Filters and Effects --- [Source](https:/img.ly/docs/cesdk/vue/fills-402ddc) --- # Fills --- [Source](https:/img.ly/docs/cesdk/vue/file-format-support-3c4b2a) --- # File Format Support CreativeEditor SDK (CE.SDK) supports a wide range of modern file types for importing assets and exporting final content. Whether you’re working with images, videos, audio, documents, or fonts, CE.SDK provides a client-side editing environment with excellent media compatibility and performance—optimized for modern client-side hardware. This guide outlines supported formats, codecs, and known limitations across media types. ## Importing Media[#](#importing-media) | Category | Supported Formats | | --- | --- | | **Images** | `.png`, `.jpeg`, `.jpg`, `.gif`, `.webp`, `.svg`, `.bmp` | | **Video** | `.mp4` (H.264/AVC, H.265/HEVC), `.mov` (H.264/AVC, H.265/HEVC), `.webm` (VP8, VP9, AV1) | | **Audio** | `.mp3`, `.m4a`, `.mp4` (AAC or MP3), `.mov` (AAC or MP3) | | **Animation** | `.json` (Lottie) | Need to import a format not listed here? CE.SDK allows you to create custom importers for any file type by using our Scene and Block APIs programmatically. ### SVG Limitations[#](#svg-limitations) CE.SDK uses Skia for SVG parsing and rendering. While most SVG files render correctly, there are some important limitations to be aware of: ### Text Elements[#](#text-elements) * SVG text elements are not supported - any text in SVG files will not be rendered. * Convert text to paths in your vector editor before exporting if text is needed. ### Styling Limitations[#](#styling-limitations) * CSS styles included in SVGs are not supported - use presentation attributes instead. * RGBA color syntax is not supported - use `fill-opacity` and `stroke-opacity` attributes. * When exporting SVGs from design tools, choose the “presentation attributes” option. ### Unsupported SVG Elements[#](#unsupported-svg-elements) The following SVG elements are not supported: * Animation elements (``) * Foreign object (``) * Text-related elements (``, ``, ``) * Script elements (` ``` Once CreativeEngine is initialized, you can access and [edit a scene with full flexibility](vue/open-the-editor-23a1db/). In the preceding code: * We add a sample image to the canvas. * We include a button that reduces its opacity. * Each click reduces the image’s opacity by 20%. In particular, the `changeOpacity()` function: * Uses the [CE.SDK headless `block` API](vue/concepts/blocks-90241e/). * Fetches the current opacity of the image. * Updates it dynamically. Rendering a canvas on browser is completely optional. The engine also works for **automation only—without any UI**. Automation Flow Example For instance, you could: 1. Adjust image properties (like opacity in memory). 1. **Directly export** or process the result without rendering it on screen. ### Style the Component[#](#style-the-component) Customize the appearance of your editor, by pasting the following CSS at the end of your `CustomEditor.vue` file: CustomEditor.vue ``` // ... Component code above ``` Modify the styles to fit your application’s design. See the final `CustomEditor.vue` file CustomEditor.vue ``` ``` ## Step 3: Embed the Custom Editor Component[#](#step-3-embed-the-custom-editor-component) To embed your new custom Creative Editor component: 1. Choose where you want to render it in your app. 2. Go to the ` ``` ## **Step 4: Use the Component in App.vue**[#](#step-4-use-the-component-in-appvue) Replace your template/script in `App.vue`: ``` ``` ### **Editor Setup Disclaimer**[#](#editor-setup-disclaimer) > Note: For convenience, CE.SDK serves assets (e.g., images, stickers, fonts) from IMG.LY’s CDN by default. In production, you should serve assets from your own server ## **Step 6: Run the Project**[#](#step-6-run-the-project) Terminal window ``` npm run serve ``` Go to `http://localhost:8080/` to see CE.SDK running in your Vue app. ## **Troubleshooting & Common Errors**[#](#troubleshooting--common-errors) ❌ **CE.SDK not visible** Ensure the `#cesdk_container` element is full-screen and not obstructed by parent layout. ❌ **Invalid license key** Use a valid [trial or production license key](https://img.ly/forms/free-trial). ❌ **Runtime error: Cannot read private member `#e`** This is caused by Babel transpiling CE.SDK’s internal code, which uses modern features like `#privateFields`. **Fix this by excluding CE.SDK modules from Babel:** Create or update `vue.config.js` in your project root: ``` const path = require('path');module.exports = { chainWebpack: config => { config.resolve.alias .set( '@cesdk/engine', path.resolve(__dirname, 'node_modules/@cesdk/engine'), ) .set( '@cesdk/cesdk-js', path.resolve(__dirname, 'node_modules/@cesdk/cesdk-js'), ); config.module .rule('js') .exclude.add(path.resolve(__dirname, 'node_modules/@cesdk/engine')) .add(path.resolve(__dirname, 'node_modules/@cesdk/cesdk-js')) .end(); },}; ``` Then clean and reinstall: Terminal window ``` rm -rf node_modulesnpm install ``` Restart your dev server with: Terminal window ``` npm run serve ``` ## **Next Steps**[#](#next-steps) * [Customize the Editor Configuration](vue/user-interface/customization-72b2f8/) * [Adapt the UI and Themes](vue/user-interface/appearance/theming-4b0938/) * [Serve Your Own Assets](vue/serve-assets-b0827c/) --- [Source](https:/img.ly/docs/cesdk/vue/get-started/mcp-server-fde71c) --- # MCP Server The CE.SDK MCP server provides a standardized interface that allows any compatible AI assistant to search and access our documentation. This enables AI tools like Claude, Cursor, and VS Code Copilot to provide more accurate, context-aware help when working with CE.SDK. ## What is MCP?[#](#what-is-mcp) The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that enables AI assistants to securely connect to external data sources. By connecting your AI tools to our MCP server, you get: * **Accurate answers**: AI assistants can search and retrieve the latest CE.SDK documentation * **Context-aware help**: Get platform-specific guidance for your development environment * **Up-to-date information**: Always access current documentation without relying on training data ## Available Tools[#](#available-tools) The MCP server exposes two tools: | Tool | Description | | --- | --- | | `search` | Search documentation by query string | | `fetch` | Retrieve the full content of a document by ID | ## Server Endpoint[#](#server-endpoint) | URL | Transport | | --- | --- | | `https://mcp.img.ly/mcp` | Streamable HTTP | No authentication is required. ## Setup Instructions[#](#setup-instructions) ### Claude Code[#](#claude-code) Add the MCP server with a single command: Terminal window ``` claude mcp add --transport http imgly_docs https://mcp.img.ly/mcp ``` ### Claude Desktop[#](#claude-desktop) 1. Open Claude Desktop and go to **Settings** (click your profile icon) 2. Navigate to **Connectors** in the sidebar 3. Click **Add custom connector** 4. Enter the URL: `https://mcp.img.ly/mcp` 5. Click **Add** to connect ### Cursor[#](#cursor) Add the following to your Cursor MCP configuration. You can use either: * **Project-specific**: `.cursor/mcp.json` in your project root * **Global**: `~/.cursor/mcp.json` ``` { "mcpServers": { "imgly_docs": { "url": "https://mcp.img.ly/mcp" } }} ``` ### VS Code[#](#vs-code) Add to your workspace configuration at `.vscode/mcp.json`: ``` { "servers": { "imgly_docs": { "type": "http", "url": "https://mcp.img.ly/mcp" } }} ``` ### Windsurf[#](#windsurf) Add the following to your Windsurf MCP configuration at `~/.codeium/windsurf/mcp_config.json`: ``` { "mcpServers": { "imgly_docs": { "serverUrl": "https://mcp.img.ly/mcp" } }} ``` ### Other Clients[#](#other-clients) For other MCP-compatible clients, use the endpoint `https://mcp.img.ly/mcp` with HTTP transport. Refer to your client’s documentation for the specific configuration format. ## Usage[#](#usage) Once configured, your AI assistant will automatically have access to CE.SDK documentation. You can ask questions like: * “How do I add a text block in CE.SDK?” * “Show me how to export a design as PNG” * “What are the available blend modes?” The AI will search our documentation and provide answers based on the latest CE.SDK guides and API references. --- [Source](https:/img.ly/docs/cesdk/vue/get-started/existing-project-o8789u) --- # Existing Vue.js Project This guide walks you through integrating the **CreativeEditor SDK (CE.SDK)** into an existing Vue.js application. By the end, you’ll have a functional editor rendered within a Vue component — ready for editing, templating, or further customization. ## **Who is This Guide For?**[#](#who-is-this-guide-for) This guide is for developers who: * Are already working with a **Vue 2 or 3** project (Vue CLI or Vite). * Want to embed a **powerful creative editor** directly into their app. * Prefer a **component-based integration** without starting from scratch. ## **What You’ll Achieve**[#](#what-youll-achieve) * Add CE.SDK to your existing Vue project. * Render the editor inside a reusable component. * Configure basic asset sources and scene loading. ## **Prerequisites**[#](#prerequisites) Ensure you have: * An existing **Vue.js project**. * **Node.js v20+** * **npm** or **yarn** * A valid **CE.SDK license key** ([Get a free trial](https://img.ly/forms/free-trial)). ## **Step 1: Install CE.SDK**[#](#step-1-install-cesdk) In your project root, run: Terminal window ``` npm install --save @cesdk/cesdk-js ``` ## **Step 2: Create the Editor Component**[#](#step-2-create-the-editor-component) Create a file at `src/components/CreativeEditor.vue`: ``` ``` ## **Step 3: Use the Component in App.vue**[#](#step-3-use-the-component-in-appvue) Open `App.vue` (or any parent view), and replace or extend the template/script like so: ``` ``` ## **Editor Setup Disclaimer**[#](#editor-setup-disclaimer) > Note: CE.SDK serves all assets (images, fonts, templates) from IMG.LY’s CDN by default. In production, you should serve assets from your own servers. ## **Step 4: Run Your Project**[#](#step-4-run-your-project) If using Vue CLI: Terminal window ``` npm run serve ``` Then visit `http://localhost:8080/` to see the editor. ## **Troubleshooting & Common Errors**[#](#troubleshooting--common-errors) ❌ **CE.SDK not visible** Ensure the container `
` is full screen and styled correctly. ❌ **Invalid license key** Check that your license key is correct and not expired. You can get a free trial [here](https://img.ly/forms/free-trial). ❌ **Runtime error: Cannot read private member `#e`** This is caused by Babel transpiling CE.SDK code, which uses modern JS features like `#privateFields`. **Fix this by excluding CE.SDK from Babel transpilation:** Create or update `vue.config.js` in your project root: ``` const path = require('path'); module.exports = { chainWebpack: config => { config.resolve.alias .set( '@cesdk/engine', path.resolve(__dirname, 'node_modules/@cesdk/engine'), ) .set( '@cesdk/cesdk-js', path.resolve(__dirname, 'node_modules/@cesdk/cesdk-js'), ); config.module .rule('js') .exclude.add(path.resolve(__dirname, 'node_modules/@cesdk/engine')) .add(path.resolve(__dirname, 'node_modules/@cesdk/cesdk-js')) .end(); },}; ``` Then clean and reinstall: Terminal window ``` rm -rf node_modulesnpm install ``` And restart: Terminal window ``` npm run serve ``` ## **Next Steps**[#](#next-steps) * [Customize the Editor Configuration](vue/user-interface/customization-72b2f8/) * [Adapt the UI and Themes](vue/user-interface/appearance/theming-4b0938/) * [Serve Your Own Assets](vue/serve-assets-b0827c/) --- [Source](https:/img.ly/docs/cesdk/vue/get-started/clone-github-project-p9890v) --- # Clone GitHub Vue Project This guide will walk you through downloading and running a pre-built CreativeEditor SDK (CE.SDK) Vue.js integration project from GitHub. It’s the fastest way to get started with CE.SDK without the need to build anything from scratch. ## Who Is This Guide For?[#](#who-is-this-guide-for) This guide is designed for developers who: * Want to explore CE.SDK without the hassle of configuring a custom environment. * Prefer getting started with a ready-made Vue.js sample project. * Are familiar with Git and Node.js for setting up local development environments. ## What You’ll Achieve[#](#what-youll-achieve) By following this guide, you will: * Clone the CE.SDK Vue.js integration project from GitHub. * Install the required dependencies and run the project on your local machine. * Launch a fully functional image and video editor directly in your browser. ## Prerequisites[#](#prerequisites) Before you begin, make sure you have the following: * **Git**: Needed to clone the project from GitHub. [Download Git](https://git-scm.com/downloads). * **The latest LTS version of Node.js and npm**: Required for installing dependencies and running the local server. [Download Node.js](https://nodejs.org/). * A valid **CE.SDK license key** ([Get a free trial](https://img.ly/forms/free-trial)). ## Step 1: Clone the GitHub Repository[#](#step-1-clone-the-github-repository) Clone the CE.SDK examples repository from GitHub: Terminal window ``` git clone https://github.com/imgly/cesdk-web-examples.git ``` Then navigate to the Vue integration folder: Terminal window ``` cd cesdk-web-examples/integrate-with-vue ``` ## Step 2: Install the Dependencies[#](#step-2-install-the-dependencies) Once inside, install the necessary dependencies via npm by running: Terminal window ``` npm install ``` This will download and install all the packages listed in the project’s `package.json` file. ## Step 3: Set Your CE.SDK License Key[#](#step-3-set-your-cesdk-license-key) Open the `App.vue` component and replace the placeholder license key with your valid CE.SDK key: **Note:** The cloned repository includes an example-specific license key for demonstration purposes only. You must replace this with your own valid license key for production use. [Get a free trial license](https://img.ly/forms/free-trial). ``` export default { name: 'App', components: { CreativeEditor }, data() { return { editorConfig: { license: '', // replace this with your CE.SDK license key // ... }, }; },}; ``` This configuration will be passed down as a prop to the CE.SDK editor component (imported from `components/CreativeEditor.vue`). ## Step 4: Run the Project[#](#step-4-run-the-project) Launch the local [Vite](https://vite.dev/) development server by executing: Terminal window ``` npm run dev ``` By default, the Vue.js application will be available on your localhost at `http://localhost:5173/`. Open this URL in your browser to see the creative editor component in action. ## Troubleshooting & Common Issues[#](#troubleshooting--common-issues) ❌ **Issue**: `Module not found` or missing packages * Make sure you’ve run `npm install` and that it completed successfully without any errors. ❌ **Error**: `'vite' is not recognized as an internal or external command, operable program or batch file.` * Confirm that you ran `npm install` before running `npm run dev`. This step also installs Vite, which is necessary to run the project locally. ❌ **Error**: `Editor engine could not be loaded: The License Key (API Key) you are using to access CE.SDK is invalid` * Ensure your CE.SDK license key is valid, hasn’t expired, and has been properly passed to the `CreativeEditor` component’s props. ❌ **Issue**: The editor doesn’t load * Check the browser’s console for any specific error messages. ## Next Steps[#](#next-steps) Congratulations! You’ve successfully integrated CE.SDK with Vue.js. When you’re ready, explore the SDK and move on to the next steps: * [Perform Basic Configuration](vue/user-interface/overview-41101a/) * [Configure the Callbacks](vue/actions-6ch24x/) * [Serve assets from your own servers](vue/serve-assets-b0827c/) * [Add Localization](vue/user-interface/localization-508e20/) * [Adapt the User Interface](vue/user-interface/appearance/theming-4b0938/) --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/support-a666dd) --- # Supported Filters and Effects Discover all available filters and effects in CE.SDK and learn how to check if a block supports them. ![Supported Filters and Effects example showing a gradient background with glow and vignette effects](/docs/cesdk/_astro/browser.hero.CEAAFSIr_sxhM1.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-support-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-support-browser) CE.SDK provides 22 built-in effect types for visual transformations including color adjustments, blur effects, artistic filters, and distortion effects. This reference guide shows how to check effect support and add effects programmatically, followed by detailed property tables for each effect type. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Supported Filters and Effects Reference * * Demonstrates how to check effect support and add effects to blocks: * - Checking if a block supports effects * - Creating and appending effects * - Configuring effect 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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Enable effects and filters in the inspector panel cesdk.feature.enable('ly.img.effect'); cesdk.feature.enable('ly.img.filter'); // Create a beautiful gradient background const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.02, g: 0.02, b: 0.08, a: 1.0 }, stop: 0 }, // Near black { color: { r: 0.04, g: 0.06, b: 0.18, a: 1.0 }, stop: 0.4 }, // Dark navy { color: { r: 0.08, g: 0.12, b: 0.28, a: 1.0 }, stop: 0.7 }, // Deep blue { color: { r: 0.1, g: 0.15, b: 0.35, a: 1.0 }, stop: 1 } // Dark blue ]); 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); // Define font for text const fontUri = 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Bold.ttf'; const typeface = { name: 'Roboto', fonts: [ { uri: fontUri, subFamily: 'Bold', weight: 'bold' as const, style: 'normal' as const } ] }; // Create title text: "Supported Filters and Effects" at 80pt (centered) const titleText = engine.block.create('text'); engine.block.appendChild(page, titleText); engine.block.replaceText(titleText, 'Supported Filters and Effects'); engine.block.setFont(titleText, fontUri, typeface); engine.block.setTextFontSize(titleText, 80); engine.block.setTextColor(titleText, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(titleText, 780); engine.block.setWidthMode(titleText, 'Absolute'); engine.block.setHeightMode(titleText, 'Auto'); engine.block.setPositionX(titleText, 10); engine.block.setPositionY(titleText, 160); // Create subtext: "img.ly" at 64pt (closer to title) const subtitleText = engine.block.create('text'); engine.block.appendChild(page, subtitleText); engine.block.replaceText(subtitleText, 'img.ly'); engine.block.setFont(subtitleText, fontUri, typeface); engine.block.setTextFontSize(subtitleText, 64); engine.block.setTextColor(subtitleText, { r: 0.75, g: 0.82, b: 1.0, a: 0.85 }); engine.block.setEnum(subtitleText, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(subtitleText, 780); engine.block.setWidthMode(subtitleText, 'Absolute'); engine.block.setHeightMode(subtitleText, 'Auto'); engine.block.setPositionX(subtitleText, 10); engine.block.setPositionY(subtitleText, 210); // Check if a block supports effects before applying them // Not all block types support effects - verify first to avoid errors // Add an image to demonstrate effects (centered below text) const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri, { size: { width: 300, height: 210 } }); engine.block.appendChild(page, imageBlock); // Center the image below the subtext engine.block.setPositionX(imageBlock, (800 - 300) / 2); // 250 engine.block.setPositionY(imageBlock, 310); // Image blocks support effects const imageSupportsEffects = engine.block.supportsEffects(imageBlock); console.log('Image supports effects:', imageSupportsEffects); // true // Create an effect using the effect type identifier // CE.SDK provides 22 built-in effect types (see property tables below) const duotoneEffect = engine.block.createEffect('duotone_filter'); // Append the effect to the image's effect stack engine.block.appendEffect(imageBlock, duotoneEffect); // Configure effect properties using the property path format: // effect/{effect-type}/{property-name} // Set duotone colors to match the dark blue gradient background engine.block.setColor(duotoneEffect, 'effect/duotone_filter/darkColor', { r: 0.02, g: 0.04, b: 0.12, a: 1.0 }); // Near black blue engine.block.setColor(duotoneEffect, 'effect/duotone_filter/lightColor', { r: 0.5, g: 0.7, b: 1.0, a: 1.0 }); // Light blue engine.block.setFloat(duotoneEffect, 'effect/duotone_filter/intensity', 0.8); // Retrieve all effects applied to a block const appliedEffects = engine.block.getEffects(imageBlock); console.log('Number of applied effects:', appliedEffects.length); // Log each effect's type appliedEffects.forEach((effect, index) => { const effectType = engine.block.getType(effect); console.log(`Effect ${index}: ${effectType}`); }); // Select the image to show effects in inspector engine.block.select(imageBlock); console.log( 'Support guide initialized. Select the image to see effects in the inspector.' ); }} export default Example; ``` This guide covers checking effect support on blocks, adding effects programmatically, and the complete list of available effect types with their properties. For detailed tutorials on configuring and combining multiple effects, see the [Apply Filters and Effects](vue/filters-and-effects/apply-2764e4/) guide. ## Check Effect Support[#](#check-effect-support) Before applying effects to a block, verify whether it supports them using `supportsEffects()`. Not all block types can have effects applied. ``` // Check if a block supports effects before applying them// Not all block types support effects - verify first to avoid errors // Add an image to demonstrate effects (centered below text)const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';const imageBlock = await engine.block.addImage(imageUri, { size: { width: 300, height: 210 }});engine.block.appendChild(page, imageBlock); // Center the image below the subtextengine.block.setPositionX(imageBlock, (800 - 300) / 2); // 250engine.block.setPositionY(imageBlock, 310); // Image blocks support effectsconst imageSupportsEffects = engine.block.supportsEffects(imageBlock);console.log('Image supports effects:', imageSupportsEffects); // true ``` Effect support is available for: * **Graphic blocks** with image or video fills * **Shape blocks** with fills * **Text blocks** (with limited effect types) * **Page blocks** (particularly when they have background fills) ## Add an Effect[#](#add-an-effect) Create an effect using `createEffect()` with the effect type identifier, then attach it to a block with `appendEffect()`. ``` // Create an effect using the effect type identifier// CE.SDK provides 22 built-in effect types (see property tables below)const duotoneEffect = engine.block.createEffect('duotone_filter'); // Append the effect to the image's effect stackengine.block.appendEffect(imageBlock, duotoneEffect); ``` ## Configure Effect Properties[#](#configure-effect-properties) Configure effect parameters using setter methods. Property paths follow the format `effect/{effect-type}/{property-name}`. ``` // Configure effect properties using the property path format:// effect/{effect-type}/{property-name}// Set duotone colors to match the dark blue gradient backgroundengine.block.setColor(duotoneEffect, 'effect/duotone_filter/darkColor', { r: 0.02, g: 0.04, b: 0.12, a: 1.0}); // Near black blueengine.block.setColor(duotoneEffect, 'effect/duotone_filter/lightColor', { r: 0.5, g: 0.7, b: 1.0, a: 1.0}); // Light blueengine.block.setFloat(duotoneEffect, 'effect/duotone_filter/intensity', 0.8); ``` CE.SDK provides typed setter methods for different parameter types: * **`setFloat()`** - For intensity, amount, and decimal values * **`setInt()`** - For discrete values like pixel sizes * **`setString()`** - For file URIs (LUT files) * **`setBool()`** - For enabling or disabling features * **`setColor()`** - For color values (RGBA format) ## Retrieve Applied Effects[#](#retrieve-applied-effects) Use `getEffects()` to retrieve all effects applied to a block. ``` // Retrieve all effects applied to a blockconst appliedEffects = engine.block.getEffects(imageBlock);console.log('Number of applied effects:', appliedEffects.length); // Log each effect's typeappliedEffects.forEach((effect, index) => { const effectType = engine.block.getType(effect); console.log(`Effect ${index}: ${effectType}`);}); ``` ## Effects[#](#effects) The following tables document all available effect types and their configurable properties. ## Adjustments Type[#](#adjustments-type) An effect block for basic image adjustments. This section describes the properties available for the **Adjustments Type** (`//ly.img.ubq/effect/adjustments`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/adjustments/blacks` | `Float` | `0` | Adjustment of only the blacks. | | `effect/adjustments/brightness` | `Float` | `0` | Adjustment of the brightness. | | `effect/adjustments/clarity` | `Float` | `0` | Adjustment of the detail. | | `effect/adjustments/contrast` | `Float` | `0` | Adjustment of the contrast. | | `effect/adjustments/exposure` | `Float` | `0` | Adjustment of the exposure. | | `effect/adjustments/gamma` | `Float` | `0` | Gamma correction, non-linear. | | `effect/adjustments/highlights` | `Float` | `0` | Adjustment of only the highlights. | | `effect/adjustments/saturation` | `Float` | `0` | Adjustment of the saturation. | | `effect/adjustments/shadows` | `Float` | `0` | Adjustment of only the shadows. | | `effect/adjustments/sharpness` | `Float` | `0` | Adjustment of the sharpness. | | `effect/adjustments/temperature` | `Float` | `0` | Adjustment of the color temperature. | | `effect/adjustments/whites` | `Float` | `0` | Adjustment of only the whites. | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Cross Cut Type[#](#cross-cut-type) An effect that distorts the image with horizontal slices. This section describes the properties available for the **Cross Cut Type** (`//ly.img.ubq/effect/cross_cut`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/cross_cut/offset` | `Float` | `0.07` | Horizontal offset per slice. | | `effect/cross_cut/slices` | `Float` | `5` | Number of horizontal slices. | | `effect/cross_cut/speedV` | `Float` | `0.5` | Vertical slice position. | | `effect/cross_cut/time` | `Float` | `1` | Randomness input. | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Dot Pattern Type[#](#dot-pattern-type) An effect that displays the image using a dot matrix. This section describes the properties available for the **Dot Pattern Type** (`//ly.img.ubq/effect/dot_pattern`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/dot_pattern/blur` | `Float` | `0.3` | Global blur. | | `effect/dot_pattern/dots` | `Float` | `30` | Number of dots. | | `effect/dot_pattern/size` | `Float` | `0.5` | Size of an individual dot. | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Duotone Filter Type[#](#duotone-filter-type) An effect that applies a two-tone color mapping. This section describes the properties available for the **Duotone Filter Type** (`//ly.img.ubq/effect/duotone_filter`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/duotone_filter/darkColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0}` | The darker of the two colors. Negative filter intensities emphasize this color. | | `effect/duotone_filter/intensity` | `Float` | `0` | The mixing weight of the two colors in the range \[-1, 1\]. | | `effect/duotone_filter/lightColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0}` | The brighter of the two colors. Positive filter intensities emphasize this color. | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Extrude Blur Type[#](#extrude-blur-type) An effect that applies a radial extrude blur. This section describes the properties available for the **Extrude Blur Type** (`//ly.img.ubq/effect/extrude_blur`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/extrude_blur/amount` | `Float` | `0.2` | Blur intensity. | ## Glow Type[#](#glow-type) An effect that applies an artificial glow. This section describes the properties available for the **Glow Type** (`//ly.img.ubq/effect/glow`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/glow/amount` | `Float` | `0.5` | Glow brightness. | | `effect/glow/darkness` | `Float` | `0.3` | Glow darkness. | | `effect/glow/size` | `Float` | `4` | Intensity of the glow. | ## Green Screen Type[#](#green-screen-type) An effect that replaces a specific color with transparency. This section describes the properties available for the **Green Screen Type** (`//ly.img.ubq/effect/green_screen`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/green_screen/colorMatch` | `Float` | `0.4` | Threshold between the source color and the from color. | | `effect/green_screen/fromColor` | `Color` | `{"r":0,"g":1,"b":0,"a":1}` | The color to be replaced. | | `effect/green_screen/smoothness` | `Float` | `0.08` | Controls the rate at which the color transition increases when the similarity threshold is exceeded. | | `effect/green_screen/spill` | `Float` | `0` | Controls the desaturation of the source color to reduce color spill. | ## Half Tone Type[#](#half-tone-type) An effect that overlays a halftone pattern. This section describes the properties available for the **Half Tone Type** (`//ly.img.ubq/effect/half_tone`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/half_tone/angle` | `Float` | `0` | Angle of pattern. | | `effect/half_tone/scale` | `Float` | `0.5` | Scale of pattern. | ## Linocut Type[#](#linocut-type) An effect that overlays a linocut pattern. This section describes the properties available for the **Linocut Type** (`//ly.img.ubq/effect/linocut`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/linocut/scale` | `Float` | `0.5` | Scale of pattern. | ## Liquid Type[#](#liquid-type) An effect that applies a liquefy distortion. This section describes the properties available for the **Liquid Type** (`//ly.img.ubq/effect/liquid`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/liquid/amount` | `Float` | `0.06` | Severity of the applied effect. | | `effect/liquid/scale` | `Float` | `0.62` | Global scale. | | `effect/liquid/time` | `Float` | `0.5` | Continuous randomness input. | ## Lut Filter Type[#](#lut-filter-type) An effect that applies a color lookup table (LUT). This section describes the properties available for the **Lut Filter Type** (`//ly.img.ubq/effect/lut_filter`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/lut_filter/horizontalTileCount` | `Int` | `5` | The horizontal number of tiles contained in the LUT image. | | `effect/lut_filter/intensity` | `Float` | `1` | A value in the range of \[0, 1\]. Defaults to 1.0. | | `effect/lut_filter/lutFileURI` | `String` | `""` | The URI to a LUT PNG file. | | `effect/lut_filter/verticalTileCount` | `Int` | `5` | The vertical number of tiles contained in the LUT image. | ## Mirror Type[#](#mirror-type) An effect that mirrors the image along a central axis. This section describes the properties available for the **Mirror Type** (`//ly.img.ubq/effect/mirror`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/mirror/side` | `Int` | `1` | Axis to mirror along. | ## Outliner Type[#](#outliner-type) An effect that highlights the outlines in an image. This section describes the properties available for the **Outliner Type** (`//ly.img.ubq/effect/outliner`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/outliner/amount` | `Float` | `0.5` | Intensity of edge highlighting. | | `effect/outliner/passthrough` | `Float` | `0.5` | Visibility of input image in non-edge areas. | ## Pixelize Type[#](#pixelize-type) An effect that pixelizes the image. This section describes the properties available for the **Pixelize Type** (`//ly.img.ubq/effect/pixelize`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/pixelize/horizontalPixelSize` | `Int` | `20` | The number of pixels on the x-axis. | | `effect/pixelize/verticalPixelSize` | `Int` | `20` | The number of pixels on the y-axis. | ## Posterize Type[#](#posterize-type) An effect that reduces the number of colors in the image. This section describes the properties available for the **Posterize Type** (`//ly.img.ubq/effect/posterize`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/posterize/levels` | `Float` | `3` | Number of color levels. | ## Radial Pixel Type[#](#radial-pixel-type) An effect that reduces the image into radial pixel rows. This section describes the properties available for the **Radial Pixel Type** (`//ly.img.ubq/effect/radial_pixel`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/radial_pixel/radius` | `Float` | `0.1` | Radius of an individual row of pixels, relative to the image. | | `effect/radial_pixel/segments` | `Float` | `0.01` | Proportional size of a pixel in each row. | ## Recolor Type[#](#recolor-type) An effect that replaces one color with another. This section describes the properties available for the **Recolor Type** (`//ly.img.ubq/effect/recolor`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/recolor/brightnessMatch` | `Float` | `1` | Affects the weight of brightness when calculating color similarity. | | `effect/recolor/colorMatch` | `Float` | `0.4` | Threshold between the source color and the from color. | | `effect/recolor/fromColor` | `Color` | `{"r":1,"g":1,"b":1,"a":1}` | The color to be replaced. | | `effect/recolor/smoothness` | `Float` | `0.08` | Controls the rate at which the color transition increases when the similarity threshold is exceeded. | | `effect/recolor/toColor` | `Color` | `{"r":0,"g":0,"b":1,"a":1}` | The color to replace with. | ## Sharpie Type[#](#sharpie-type) Cartoon-like effect. This section describes the properties available for the **Sharpie Type** (`//ly.img.ubq/effect/sharpie`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Shifter Type[#](#shifter-type) An effect that shifts individual color channels. This section describes the properties available for the **Shifter Type** (`//ly.img.ubq/effect/shifter`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/shifter/amount` | `Float` | `0.05` | Intensity of the shift. | | `effect/shifter/angle` | `Float` | `0.3` | Shift direction. | ## Tilt Shift Type[#](#tilt-shift-type) An effect that applies a tilt-shift blur. This section describes the properties available for the **Tilt Shift Type** (`//ly.img.ubq/effect/tilt_shift`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/tilt_shift/amount` | `Float` | `0.016` | Blur intensity. | | `effect/tilt_shift/position` | `Float` | `0.4` | Horizontal position in image. | ## Tv Glitch Type[#](#tv-glitch-type) An effect that mimics TV banding and distortion. This section describes the properties available for the **Tv Glitch Type** (`//ly.img.ubq/effect/tv_glitch`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/tv_glitch/distortion` | `Float` | `3` | Rough horizontal distortion. | | `effect/tv_glitch/distortion2` | `Float` | `1` | Fine horizontal distortion. | | `effect/tv_glitch/rollSpeed` | `Float` | `1` | Vertical offset. | | `effect/tv_glitch/speed` | `Float` | `2` | Number of changes per time change. | ## Vignette Type[#](#vignette-type) An effect that adds a vignette (darkened corners). This section describes the properties available for the **Vignette Type** (`//ly.img.ubq/effect/vignette`) block type. | Property | Type | Default | Description | | --- | --- | --- | --- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/vignette/darkness` | `Float` | `1` | Brightness of vignette. | | `effect/vignette/offset` | `Float` | `1` | Radial offset. | ## Next Steps[#](#next-steps) * [Apply Filters and Effects](vue/filters-and-effects/apply-2764e4/) \- Learn how to configure, combine, and manage multiple effects --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/overview-299b15) --- # Filters & Effects Library In CreativeEditor SDK (CE.SDK), _filters_ and _effects_ refer to visual modifications that enhance or transform the appearance of design elements. Filters typically adjust an element’s overall color or tone, while effects add specific visual treatments like blur, sharpness, or distortion. You can apply both filters and effects through the user interface or programmatically using the CE.SDK API. They allow you to refine the look of images, videos, and graphic elements in your designs with precision and flexibility. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## Understanding Filters vs. Effects[#](#understanding-filters-vs-effects) * **Filters** are broad transformations that modify the entire appearance of an element. Common examples include: * Color adjustments (e.g., brightness, contrast) * Lookup Table (LUT) filters for advanced color grading * Duotone color schemes * **Effects** apply targeted visual modifications to an element. Typical examples include: * Blurring an area to soften details * Sharpening to enhance clarity * Distorting shapes for creative transformations **Key Differences:** | Aspect | Filters | Effects | | --- | --- | --- | | Scope | Alters the entire visual appearance | Applies a specific modification | | Examples | LUTs, duotone, adjustments | Blur, sharpen, distortion | | Use Case | Color grading, mood setting | Stylizing or emphasizing parts | Understanding the distinction helps you choose the right tool depending on whether you want broad tonal changes or focused visual alterations. ## Supported Filters and Effects[#](#supported-filters-and-effects) CE.SDK offers a range of built-in filters and effects ready to use out of the box. **Available Filters:** * **Adjustment Filters:** Modify brightness, contrast, saturation, and more. * **LUT (Lookup Table) Filters:** Apply professional-grade color transformations. * **Duotone Filters:** Map the shadows and highlights of an image to two custom colors. **Available Effects:** * **Blur:** Soften an area for background separation or artistic focus. * **Sharpen:** Enhance fine details for a crisper appearance. * **Distortion:** Warp the geometry of an element for creative effects. * **Chroma Key (Green Screen):** Remove a background color, often used for compositing images. **Supported Element Types:** * Images * Videos * Shapes * Graphic blocks Before applying a filter or effect programmatically, you can verify support by checking the element type. ## Custom Filters and LUTs[#](#custom-filters-and-luts) CE.SDK supports creating **custom filters** to meet specific visual requirements. One powerful method of creating a custom filter is by using a **LUT (Lookup Table)**: * A LUT is a preset map that transforms input colors to output colors, allowing for sophisticated color grading. * By importing a custom LUT, you can apply unique color styles across your designs consistently. * LUTs are especially useful for achieving cinematic tones, brand color consistency, or specific visual moods. You can create your own LUT files externally and integrate them into CE.SDK, giving you full control over the look and feel of your projects. --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/gradients-0ff079) --- # Gradient Fills Create smooth color transitions in shapes, text, and design blocks using CE.SDK’s gradient fill system with support for linear, radial, and conical gradients. ![Gradient Fills example showing linear, radial, and conical gradient transitions](/docs/cesdk/_astro/browser.hero.DqAtWRn9_Z2jwPTA.webp) 20 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-fills-gradient-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-fills-gradient-browser) Gradient fills are one of the fundamental fill types in CE.SDK, allowing you to paint design blocks with smooth color transitions. Unlike solid color fills that apply a uniform color or image fills that display photo content, gradient fills create dynamic visual effects with depth and visual interest. The gradient fill system supports three types: linear gradients that transition along a straight line, radial gradients that emanate from a center point, and conical gradients that rotate around a center point like a color wheel. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json';import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Gradient Fills Guide * * This example demonstrates: * - Creating linear, radial, and conical gradient fills * - Configuring gradient color stops * - Positioning gradients * - Using different color spaces in gradients * - Advanced gradient techniques */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'); } // Fill features are enabled by default in CE.SDK // You can check and control fill feature availability: const engine = cesdk.engine; const isFillEnabled = cesdk.feature.isEnabled('ly.img.fill', { engine }); console.log('Fill feature enabled:', isFillEnabled); // Create a design scene using CE.SDK cesdk method await cesdk.createDesignScene(); // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page dimensions engine.block.setWidth(page, 1200); engine.block.setHeight(page, 900); // 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 }); // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 15); const { blockWidth, blockHeight, getPosition } = layout; // Helper function to create a shape with a fill const createShapeWithFill = ( fillType: 'gradient/linear' | 'gradient/radial' | 'gradient/conical' ): { block: number; fill: number } => { const block = engine.block.create('graphic'); const shape = engine.block.createShape('rect'); engine.block.setShape(block, shape); // Set size engine.block.setWidth(block, blockWidth); engine.block.setHeight(block, blockHeight); // Append to page engine.block.appendChild(page, block); // Check if block supports fills const canHaveFill = engine.block.supportsFill(block); if (!canHaveFill) { throw new Error('Block does not support fills'); } // Create gradient fill const gradientFill = engine.block.createFill(fillType); // Apply the fill to the block engine.block.setFill(block, gradientFill); return { block, fill: gradientFill }; }; // ============================================================================= // Example 1: Linear Gradient (Vertical) // ============================================================================= const { block: linearVerticalBlock, fill: linearVertical } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(linearVertical, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.8, b: 0.2, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.4, b: 0.7, a: 1.0 }, stop: 1 } ]); // Set vertical gradient (top to bottom) engine.block.setFloat( linearVertical, 'fill/gradient/linear/startPointX', 0.5 ); engine.block.setFloat( linearVertical, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat( linearVertical, 'fill/gradient/linear/endPointX', 0.5 ); engine.block.setFloat(linearVertical, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 2: Linear Gradient (Horizontal) // ============================================================================= const { block: linearHorizontalBlock, fill: linearHorizontal } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( linearHorizontal, 'fill/gradient/colors', [ { color: { r: 0.8, g: 0.2, b: 0.4, a: 1.0 }, stop: 0 }, { color: { r: 0.2, g: 0.8, b: 0.6, a: 1.0 }, stop: 1 } ] ); // Set horizontal gradient (left to right) engine.block.setFloat( linearHorizontal, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( linearHorizontal, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat( linearHorizontal, 'fill/gradient/linear/endPointX', 1 ); engine.block.setFloat( linearHorizontal, 'fill/gradient/linear/endPointY', 0.5 ); // ============================================================================= // Example 3: Linear Gradient (Diagonal) // ============================================================================= const { block: linearDiagonalBlock, fill: linearDiagonal } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(linearDiagonal, 'fill/gradient/colors', [ { color: { r: 0.5, g: 0.2, b: 0.8, a: 1.0 }, stop: 0 }, { color: { r: 0.9, g: 0.6, b: 0.2, a: 1.0 }, stop: 1 } ]); // Set diagonal gradient (top-left to bottom-right) engine.block.setFloat( linearDiagonal, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( linearDiagonal, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat(linearDiagonal, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(linearDiagonal, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 4: Multi-Stop Linear Gradient (Aurora Effect) // ============================================================================= const { block: auroraGradientBlock, fill: auroraGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(auroraGradient, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.1, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.8, g: 0.2, b: 0.6, a: 1 }, stop: 0.3 }, { color: { r: 1.0, g: 0.5, b: 0.3, a: 1 }, stop: 0.6 }, { color: { r: 1.0, g: 0.8, b: 0.2, a: 1 }, stop: 1 } ]); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(auroraGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/endPointY', 0.5 ); // ============================================================================= // Example 5: Radial Gradient (Centered) // ============================================================================= const { block: radialCenteredBlock, fill: radialCentered } = createShapeWithFill('gradient/radial'); engine.block.setGradientColorStops(radialCentered, 'fill/gradient/colors', [ { color: { r: 1.0, g: 1.0, b: 1.0, a: 0.3 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } ]); // Set center point (middle of block) engine.block.setFloat( radialCentered, 'fill/gradient/radial/centerPointX', 0.5 ); engine.block.setFloat( radialCentered, 'fill/gradient/radial/centerPointY', 0.5 ); engine.block.setFloat(radialCentered, 'fill/gradient/radial/radius', 0.8); // ============================================================================= // Example 6: Radial Gradient (Top-Left Highlight) // ============================================================================= const { block: radialHighlightBlock, fill: radialHighlight } = createShapeWithFill('gradient/radial'); engine.block.setGradientColorStops( radialHighlight, 'fill/gradient/colors', [ { color: { r: 1.0, g: 1.0, b: 1.0, a: 0.3 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } ] ); // Set top-left highlight engine.block.setFloat( radialHighlight, 'fill/gradient/radial/centerPointX', 0 ); engine.block.setFloat( radialHighlight, 'fill/gradient/radial/centerPointY', 0 ); engine.block.setFloat(radialHighlight, 'fill/gradient/radial/radius', 1.0); // ============================================================================= // Example 7: Radial Gradient (Vignette Effect) // ============================================================================= const { block: radialVignetteBlock, fill: radialVignette } = createShapeWithFill('gradient/radial'); engine.block.setGradientColorStops(radialVignette, 'fill/gradient/colors', [ { color: { r: 0.9, g: 0.9, b: 0.9, a: 1.0 }, stop: 0 }, { color: { r: 0.1, g: 0.1, b: 0.1, a: 1.0 }, stop: 1 } ]); // Centered vignette engine.block.setFloat( radialVignette, 'fill/gradient/radial/centerPointX', 0.5 ); engine.block.setFloat( radialVignette, 'fill/gradient/radial/centerPointY', 0.5 ); engine.block.setFloat(radialVignette, 'fill/gradient/radial/radius', 0.6); // ============================================================================= // Example 8: Conical Gradient (Color Wheel) // ============================================================================= const { block: conicalColorWheelBlock, fill: conicalColorWheel } = createShapeWithFill('gradient/conical'); engine.block.setGradientColorStops( conicalColorWheel, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.0, b: 0.0, a: 1 }, stop: 0 }, { color: { r: 1.0, g: 1.0, b: 0.0, a: 1 }, stop: 0.25 }, { color: { r: 0.0, g: 1.0, b: 0.0, a: 1 }, stop: 0.5 }, { color: { r: 0.0, g: 0.0, b: 1.0, a: 1 }, stop: 0.75 }, { color: { r: 1.0, g: 0.0, b: 0.0, a: 1 }, stop: 1 } ] ); // Set center point (middle of block) engine.block.setFloat( conicalColorWheel, 'fill/gradient/conical/centerPointX', 0.5 ); engine.block.setFloat( conicalColorWheel, 'fill/gradient/conical/centerPointY', 0.5 ); // ============================================================================= // Example 9: Conical Gradient (Loading Spinner) // ============================================================================= const { block: conicalSpinnerBlock, fill: conicalSpinner } = createShapeWithFill('gradient/conical'); engine.block.setGradientColorStops(conicalSpinner, 'fill/gradient/colors', [ { color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 0 }, stop: 0.75 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }, stop: 1 } ]); engine.block.setFloat( conicalSpinner, 'fill/gradient/conical/centerPointX', 0.5 ); engine.block.setFloat( conicalSpinner, 'fill/gradient/conical/centerPointY', 0.5 ); // ============================================================================= // Example 10: Gradient with CMYK Colors // ============================================================================= const { block: cmykGradientBlock, fill: cmykGradient } = createShapeWithFill('gradient/linear'); // CMYK color stops for print engine.block.setGradientColorStops(cmykGradient, 'fill/gradient/colors', [ { color: { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 0 }, { color: { c: 1.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 } ]); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat( cmykGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/endPointY', 0.5); // ============================================================================= // Example 11: Gradient with Spot Colors // ============================================================================= // First define spot colors engine.editor.setSpotColorRGB('BrandPrimary', 0.2, 0.4, 0.8); engine.editor.setSpotColorRGB('BrandSecondary', 1.0, 0.6, 0.0); const { block: spotGradientBlock, fill: spotGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(spotGradient, 'fill/gradient/colors', [ { color: { name: 'BrandPrimary', tint: 1.0, externalReference: '' }, stop: 0 }, { color: { name: 'BrandSecondary', tint: 1.0, externalReference: '' }, stop: 1 } ]); engine.block.setFloat(spotGradient, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(spotGradient, 'fill/gradient/linear/startPointY', 0); engine.block.setFloat(spotGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(spotGradient, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 12: Transparency Overlay Gradient // ============================================================================= const { block: overlayGradientBlock, fill: overlayGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( overlayGradient, 'fill/gradient/colors', [ { color: { r: 0.0, g: 0.0, b: 0.0, a: 0 }, stop: 0 }, { color: { r: 0.0, g: 0.0, b: 0.0, a: 0.7 }, stop: 1 } ] ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/startPointX', 0.5 ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/endPointX', 0.5 ); engine.block.setFloat(overlayGradient, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 13: Duotone Gradient // ============================================================================= const { block: duotoneGradientBlock, fill: duotoneGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( duotoneGradient, 'fill/gradient/colors', [ { color: { r: 0.8, g: 0.2, b: 0.9, a: 1 }, stop: 0 }, { color: { r: 0.2, g: 0.9, b: 0.8, a: 1 }, stop: 1 } ] ); engine.block.setFloat( duotoneGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( duotoneGradient, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat(duotoneGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(duotoneGradient, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 14: Shared Gradient Fill // ============================================================================= const block1 = engine.block.create('graphic'); const shape1 = engine.block.createShape('rect'); engine.block.setShape(block1, shape1); engine.block.setWidth(block1, blockWidth); engine.block.setHeight(block1, blockHeight / 2 - 5); engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, blockWidth); engine.block.setHeight(block2, blockHeight / 2 - 5); engine.block.appendChild(page, block2); // Create one gradient fill const sharedGradient = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(sharedGradient, 'fill/gradient/colors', [ { color: { r: 1, g: 0, b: 0, a: 1 }, stop: 0 }, { color: { r: 0, g: 0, b: 1, a: 1 }, stop: 1 } ]); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(sharedGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/endPointY', 0.5 ); // Apply to both blocks engine.block.setFill(block1, sharedGradient); engine.block.setFill(block2, sharedGradient); // Change gradient after a delay to show it affects both setTimeout(() => { engine.block.setGradientColorStops( sharedGradient, 'fill/gradient/colors', [ { color: { r: 0, g: 1, b: 0, a: 1 }, stop: 0 }, { color: { r: 1, g: 1, b: 0, a: 1 }, stop: 1 } ] ); }, 2000); // ============================================================================= // Example 15: Get Gradient Properties // ============================================================================= const { block: inspectGradientBlock, fill: inspectGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( inspectGradient, 'fill/gradient/colors', [ { color: { r: 0.6, g: 0.3, b: 0.7, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.7, b: 0.6, a: 1.0 }, stop: 1 } ] ); // Get current fill from block const fillId = engine.block.getFill(block1); const fillType = engine.block.getType(fillId); console.log('Fill type:', fillType); // '//ly.img.ubq/fill/gradient/linear' // Get gradient color stops const colorStops = engine.block.getGradientColorStops( inspectGradient, 'fill/gradient/colors' ); console.log('Color stops:', colorStops); // Get linear gradient position const startX = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/startPointX' ); const startY = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/startPointY' ); const endX = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/endPointX' ); const endY = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/endPointY' ); console.log('Linear gradient position:', { startX, startY, endX, endY }); // ===== Position all blocks in grid layout ===== const blocks = [ linearVerticalBlock, // Position 0 linearHorizontalBlock, // Position 1 linearDiagonalBlock, // Position 2 auroraGradientBlock, // Position 3 radialCenteredBlock, // Position 4 radialHighlightBlock, // Position 5 radialVignetteBlock, // Position 6 conicalColorWheelBlock, // Position 7 conicalSpinnerBlock, // Position 8 cmykGradientBlock, // Position 9 spotGradientBlock, // Position 10 overlayGradientBlock, // Position 11 duotoneGradientBlock, // Position 12 block1, // Position 13 (top half) inspectGradientBlock // Position 14 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Position block2 below block1 in the same grid cell const block1Pos = getPosition(13); engine.block.setPositionX(block2, block1Pos.x); engine.block.setPositionY(block2, block1Pos.y + blockHeight / 2 + 5); // Zoom to fit all content await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); }} export default Example; ``` This guide demonstrates how to create, apply, and configure gradient fills programmatically, work with color stops, position gradients, and create modern visual effects like aurora gradients and button highlights. ## Understanding Gradient Fills[#](#understanding-gradient-fills) ### What is a Gradient Fill?[#](#what-is-a-gradient-fill) A gradient fill is a fill object that paints a design block with smooth color transitions. Gradient fills are part of the broader fill system in CE.SDK and come in three types, each identified by a unique type string: * **Linear**: `'//ly.img.ubq/fill/gradient/linear'` or `'gradient/linear'` * **Radial**: `'//ly.img.ubq/fill/gradient/radial'` or `'gradient/radial'` * **Conical**: `'//ly.img.ubq/fill/gradient/conical'` or `'gradient/conical'` Each gradient type contains color stops that define colors at specific positions and positioning properties that control the gradient’s direction and coverage. ### Gradient Types Comparison[#](#gradient-types-comparison) #### Linear Gradients[#](#linear-gradients) Linear gradients transition colors along a straight line defined by start and end points. They’re the most common gradient type and create clean, modern looks. Common use cases include hero sections, call-to-action buttons, headers, and banners. ``` const { block: linearVerticalBlock, fill: linearVertical } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(linearVertical, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.8, b: 0.2, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.4, b: 0.7, a: 1.0 }, stop: 1 }]); ``` #### Radial Gradients[#](#radial-gradients) Radial gradients emanate from a central point outward, creating circular or elliptical color transitions. They add depth and create focal points or spotlight effects. Common use cases include button highlights, card shadows, vignettes, and circular badges. ``` engine.block.setGradientColorStops(radialCentered, 'fill/gradient/colors', [ { color: { r: 1.0, g: 1.0, b: 1.0, a: 0.3 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 }]); ``` #### Conical Gradients[#](#conical-gradients) Conical gradients transition colors around a center point like a color wheel, starting at the top (12 o’clock) and rotating clockwise. Colors are specified by position rather than angle. Common use cases include pie charts, loading spinners, circular progress indicators, and color picker wheels. ``` engine.block.setGradientColorStops( conicalColorWheel, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.0, b: 0.0, a: 1 }, stop: 0 }, { color: { r: 1.0, g: 1.0, b: 0.0, a: 1 }, stop: 0.25 }, { color: { r: 0.0, g: 1.0, b: 0.0, a: 1 }, stop: 0.5 }, { color: { r: 0.0, g: 0.0, b: 1.0, a: 1 }, stop: 0.75 }, { color: { r: 1.0, g: 0.0, b: 0.0, a: 1 }, stop: 1 } ]); ``` ### Gradient vs Other Fill Types[#](#gradient-vs-other-fill-types) Understanding how gradients differ from other fill types helps you choose the right fill for your design: * **Gradient fills**: Smooth color transitions (linear, radial, conical) * **Color fills**: Solid, uniform color * **Image fills**: Photo or raster content * **Video fills**: Animated video content ### Color Stops Explained[#](#color-stops-explained) Color stops define the colors at specific positions in the gradient. Each stop consists of: * `color`: An RGB, CMYK, or Spot color value * `stop`: Position value between 0.0 and 1.0 (0% to 100%) A gradient requires a minimum of two color stops. You can add multiple stops to create complex color transitions. Color stops can use any color space supported by CE.SDK, including RGB for screen display, CMYK for print, and Spot Colors for brand consistency. ``` engine.block.setGradientColorStops(auroraGradient, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.1, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.8, g: 0.2, b: 0.6, a: 1 }, stop: 0.3 }, { color: { r: 1.0, g: 0.5, b: 0.3, a: 1 }, stop: 0.6 }, { color: { r: 1.0, g: 0.8, b: 0.2, a: 1 }, stop: 1 }]); ``` ## Using the Built-in Gradient UI[#](#using-the-built-in-gradient-ui) CE.SDK provides built-in UI controls for working with gradients through the **inspector bar** and **advanced inspector**. Users can switch between solid color and gradient fills, select gradient type (linear, radial, conical), add and remove color stops visually, adjust individual stop colors, and drag gradient control points for positioning. **Note**: Currently, only **linear gradients** are fully supported in the built-in UI. Radial and conical gradients are available programmatically. ### Enabling Fill Features[#](#enabling-fill-features) Gradient controls are part of the fill feature system. You can check if the fill feature is enabled and control it programmatically: ``` // Fill features are enabled by default in CE.SDK// You can check and control fill feature availability:const engine = cesdk.engine;const isFillEnabled = cesdk.feature.isEnabled('ly.img.fill', { engine });console.log('Fill feature enabled:', isFillEnabled); ``` ## Checking Gradient Fill Support[#](#checking-gradient-fill-support) ### Verifying Block Compatibility[#](#verifying-block-compatibility) Before applying gradient fills, verify that the block type supports fills. Not all blocks support fills—for example, scenes and pages typically don’t. ``` // Check if block supports fillsconst canHaveFill = engine.block.supportsFill(block);if (!canHaveFill) { throw new Error('Block does not support fills');} ``` Always check `supportsFill()` before accessing fill APIs. Graphic blocks, shapes, and text typically support fills. ## Creating Gradient Fills[#](#creating-gradient-fills) ### Creating a New Linear Gradient[#](#creating-a-new-linear-gradient) Create a new linear gradient fill using the `createFill()` method with the type `'gradient/linear'`: ``` const { block: linearVerticalBlock, fill: linearVertical } = createShapeWithFill('gradient/linear'); ``` ### Creating a Radial Gradient[#](#creating-a-radial-gradient) Create a radial gradient using the type `'gradient/radial'`: ``` const { block: radialCenteredBlock, fill: radialCentered } = createShapeWithFill('gradient/radial'); ``` ### Creating a Conical Gradient[#](#creating-a-conical-gradient) Create a conical gradient using the type `'gradient/conical'`: ``` const { block: conicalColorWheelBlock, fill: conicalColorWheel } = createShapeWithFill('gradient/conical'); ``` The `createFill()` method returns a numeric fill ID. The fill exists independently until you attach it to a block. If you create a fill but don’t attach it to a block, you must destroy it manually to prevent memory leaks. ## Applying Gradient Fills[#](#applying-gradient-fills) ### Setting a Gradient Fill on a Block[#](#setting-a-gradient-fill-on-a-block) Once you’ve created a gradient fill, attach it to a block using `setFill()`: ``` // Create gradient fillconst gradientFill = engine.block.createFill(fillType); // Apply the fill to the blockengine.block.setFill(block, gradientFill); ``` ### Getting the Current Fill[#](#getting-the-current-fill) Retrieve the current fill attached to a block and inspect its type: ``` const { block: inspectGradientBlock, fill: inspectGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( inspectGradient, 'fill/gradient/colors', [ { color: { r: 0.6, g: 0.3, b: 0.7, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.7, b: 0.6, a: 1.0 }, stop: 1 } ]); // Get current fill from blockconst fillId = engine.block.getFill(block1);const fillType = engine.block.getType(fillId);console.log('Fill type:', fillType); // '//ly.img.ubq/fill/gradient/linear' ``` ## Configuring Gradient Color Stops[#](#configuring-gradient-color-stops) ### Setting Color Stops[#](#setting-color-stops) Set color stops using the `setGradientColorStops()` method with an array of color and position pairs: ``` engine.block.setGradientColorStops(linearVertical, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.8, b: 0.2, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.4, b: 0.7, a: 1.0 }, stop: 1 }]); ``` RGB values are normalized floats from 0.0 to 1.0. Stop positions are normalized where 0.0 represents the start and 1.0 represents the end. The alpha channel controls opacity per color stop. ### Getting Color Stops[#](#getting-color-stops) Retrieve the current color stops from a gradient fill: ``` // Get gradient color stopsconst colorStops = engine.block.getGradientColorStops( inspectGradient, 'fill/gradient/colors');console.log('Color stops:', colorStops); ``` ### Using Different Color Spaces[#](#using-different-color-spaces) Gradient color stops support multiple color spaces: ``` const { block: cmykGradientBlock, fill: cmykGradient } = createShapeWithFill('gradient/linear'); // CMYK color stops for printengine.block.setGradientColorStops(cmykGradient, 'fill/gradient/colors', [ { color: { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 0 }, { color: { c: 1.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 }]); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/startPointX', 0);engine.block.setFloat( cmykGradient, 'fill/gradient/linear/startPointY', 0.5);engine.block.setFloat(cmykGradient, 'fill/gradient/linear/endPointX', 1);engine.block.setFloat(cmykGradient, 'fill/gradient/linear/endPointY', 0.5); ``` ## Positioning Linear Gradients[#](#positioning-linear-gradients) ### Setting Start and End Points[#](#setting-start-and-end-points) Linear gradients are positioned using start and end points with normalized coordinates (0.0 to 1.0) relative to block dimensions: ``` // Set vertical gradient (top to bottom)engine.block.setFloat( linearVertical, 'fill/gradient/linear/startPointX', 0.5);engine.block.setFloat( linearVertical, 'fill/gradient/linear/startPointY', 0);engine.block.setFloat( linearVertical, 'fill/gradient/linear/endPointX', 0.5);engine.block.setFloat(linearVertical, 'fill/gradient/linear/endPointY', 1); ``` Coordinates are normalized where (0, 0) represents the top-left corner and (1, 1) represents the bottom-right corner. ### Common Linear Gradient Directions[#](#common-linear-gradient-directions) **Horizontal (Left to Right):** ``` engine.block.setFloat(linearGradient, 'fill/gradient/linear/startPointX', 0);engine.block.setFloat(linearGradient, 'fill/gradient/linear/startPointY', 0.5);engine.block.setFloat(linearGradient, 'fill/gradient/linear/endPointX', 1);engine.block.setFloat(linearGradient, 'fill/gradient/linear/endPointY', 0.5); ``` **Diagonal (Top-Left to Bottom-Right):** ``` engine.block.setFloat(linearGradient, 'fill/gradient/linear/startPointX', 0);engine.block.setFloat(linearGradient, 'fill/gradient/linear/startPointY', 0);engine.block.setFloat(linearGradient, 'fill/gradient/linear/endPointX', 1);engine.block.setFloat(linearGradient, 'fill/gradient/linear/endPointY', 1); ``` ### Getting Current Position[#](#getting-current-position) Retrieve the current position values: ``` // Get linear gradient positionconst startX = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/startPointX');const startY = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/startPointY');const endX = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/endPointX');const endY = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/endPointY');console.log('Linear gradient position:', { startX, startY, endX, endY }); ``` ## Positioning Radial Gradients[#](#positioning-radial-gradients) ### Setting Center Point and Radius[#](#setting-center-point-and-radius) Radial gradients are positioned using a center point and radius: ``` // Set center point (middle of block)engine.block.setFloat( radialCentered, 'fill/gradient/radial/centerPointX', 0.5);engine.block.setFloat( radialCentered, 'fill/gradient/radial/centerPointY', 0.5);engine.block.setFloat(radialCentered, 'fill/gradient/radial/radius', 0.8); ``` The `centerPointX/Y` properties use normalized coordinates (0.0 to 1.0) relative to block dimensions. The `radius` property is relative to the smaller side of the block frame, where 1.0 equals full coverage. Default values are centerX = 0.0, centerY = 0.0, and radius = 1.0. ### Common Radial Patterns[#](#common-radial-patterns) **Centered Circle:** ``` engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointX', 0.5);engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointY', 0.5);engine.block.setFloat(radialGradient, 'fill/gradient/radial/radius', 0.7); ``` **Top-Left Highlight:** ``` engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointX', 0);engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointY', 0);engine.block.setFloat(radialGradient, 'fill/gradient/radial/radius', 1.0); ``` **Bottom-Right Vignette:** ``` engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointX', 1);engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointY', 1);engine.block.setFloat(radialGradient, 'fill/gradient/radial/radius', 1.5); ``` ## Positioning Conical Gradients[#](#positioning-conical-gradients) ### Setting Center Point[#](#setting-center-point) Conical gradients are positioned using a center point. The rotation starts at the top (12 o’clock) and proceeds clockwise: ``` // Set center point (middle of block)engine.block.setFloat( conicalColorWheel, 'fill/gradient/conical/centerPointX', 0.5);engine.block.setFloat( conicalColorWheel, 'fill/gradient/conical/centerPointY', 0.5); ``` The `centerPointX/Y` properties use normalized coordinates (0.0 to 1.0) relative to block dimensions. There is no separate rotation or angle property—the gradient always starts at the top. Default values are centerX = 0.0 and centerY = 0.0. ## Additional Techniques[#](#additional-techniques) ### Sharing Gradient Fills[#](#sharing-gradient-fills) You can share a single gradient fill between multiple blocks. Changes to the shared gradient affect all blocks using it: ``` const block1 = engine.block.create('graphic');const shape1 = engine.block.createShape('rect');engine.block.setShape(block1, shape1);engine.block.setWidth(block1, blockWidth);engine.block.setHeight(block1, blockHeight / 2 - 5);engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic');const shape2 = engine.block.createShape('rect');engine.block.setShape(block2, shape2);engine.block.setWidth(block2, blockWidth);engine.block.setHeight(block2, blockHeight / 2 - 5);engine.block.appendChild(page, block2); // Create one gradient fillconst sharedGradient = engine.block.createFill('gradient/linear');engine.block.setGradientColorStops(sharedGradient, 'fill/gradient/colors', [ { color: { r: 1, g: 0, b: 0, a: 1 }, stop: 0 }, { color: { r: 0, g: 0, b: 1, a: 1 }, stop: 1 }]); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/startPointX', 0);engine.block.setFloat( sharedGradient, 'fill/gradient/linear/startPointY', 0.5);engine.block.setFloat(sharedGradient, 'fill/gradient/linear/endPointX', 1);engine.block.setFloat( sharedGradient, 'fill/gradient/linear/endPointY', 0.5); // Apply to both blocksengine.block.setFill(block1, sharedGradient);engine.block.setFill(block2, sharedGradient); // Change gradient after a delay to show it affects bothsetTimeout(() => { engine.block.setGradientColorStops( sharedGradient, 'fill/gradient/colors', [ { color: { r: 0, g: 1, b: 0, a: 1 }, stop: 0 }, { color: { r: 1, g: 1, b: 0, a: 1 }, stop: 1 } ] );}, 2000); ``` ### Duplicating Gradient Fills[#](#duplicating-gradient-fills) When you duplicate a block, its gradient fill is automatically duplicated, creating an independent copy. Each duplicate has its own fill instance that can be modified independently without affecting the original. ## Common Use Cases[#](#common-use-cases) ### Modern Hero Background (Aurora Effect)[#](#modern-hero-background-aurora-effect) Create dreamy multi-color gradient backgrounds for hero sections: ``` const { block: auroraGradientBlock, fill: auroraGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(auroraGradient, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.1, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.8, g: 0.2, b: 0.6, a: 1 }, stop: 0.3 }, { color: { r: 1.0, g: 0.5, b: 0.3, a: 1 }, stop: 0.6 }, { color: { r: 1.0, g: 0.8, b: 0.2, a: 1 }, stop: 1 }]); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/startPointX', 0);engine.block.setFloat( auroraGradient, 'fill/gradient/linear/startPointY', 0.5);engine.block.setFloat(auroraGradient, 'fill/gradient/linear/endPointX', 1);engine.block.setFloat( auroraGradient, 'fill/gradient/linear/endPointY', 0.5); ``` ### Button Highlight Effect[#](#button-highlight-effect) Use radial gradients to add depth and highlight effects to buttons: ``` const { block: radialHighlightBlock, fill: radialHighlight } = createShapeWithFill('gradient/radial'); engine.block.setGradientColorStops( radialHighlight, 'fill/gradient/colors', [ { color: { r: 1.0, g: 1.0, b: 1.0, a: 0.3 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } ]); // Set top-left highlightengine.block.setFloat( radialHighlight, 'fill/gradient/radial/centerPointX', 0);engine.block.setFloat( radialHighlight, 'fill/gradient/radial/centerPointY', 0);engine.block.setFloat(radialHighlight, 'fill/gradient/radial/radius', 1.0); ``` ### Loading Spinner (Conical)[#](#loading-spinner-conical) Create circular progress indicators and loading animations with conical gradients: ``` const { block: conicalSpinnerBlock, fill: conicalSpinner } = createShapeWithFill('gradient/conical'); engine.block.setGradientColorStops(conicalSpinner, 'fill/gradient/colors', [ { color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 0 }, stop: 0.75 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }, stop: 1 }]); engine.block.setFloat( conicalSpinner, 'fill/gradient/conical/centerPointX', 0.5);engine.block.setFloat( conicalSpinner, 'fill/gradient/conical/centerPointY', 0.5); ``` ### Transparency Overlay[#](#transparency-overlay) Create smooth transparency effects with alpha channel transitions: ``` const { block: overlayGradientBlock, fill: overlayGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( overlayGradient, 'fill/gradient/colors', [ { color: { r: 0.0, g: 0.0, b: 0.0, a: 0 }, stop: 0 }, { color: { r: 0.0, g: 0.0, b: 0.0, a: 0.7 }, stop: 1 } ]); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/startPointX', 0.5);engine.block.setFloat( overlayGradient, 'fill/gradient/linear/startPointY', 0);engine.block.setFloat( overlayGradient, 'fill/gradient/linear/endPointX', 0.5);engine.block.setFloat(overlayGradient, 'fill/gradient/linear/endPointY', 1); ``` ### Duotone Effect[#](#duotone-effect) Create modern two-color gradient overlays: ``` const { block: duotoneGradientBlock, fill: duotoneGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( duotoneGradient, 'fill/gradient/colors', [ { color: { r: 0.8, g: 0.2, b: 0.9, a: 1 }, stop: 0 }, { color: { r: 0.2, g: 0.9, b: 0.8, a: 1 }, stop: 1 } ]); engine.block.setFloat( duotoneGradient, 'fill/gradient/linear/startPointX', 0);engine.block.setFloat( duotoneGradient, 'fill/gradient/linear/startPointY', 0);engine.block.setFloat(duotoneGradient, 'fill/gradient/linear/endPointX', 1);engine.block.setFloat(duotoneGradient, 'fill/gradient/linear/endPointY', 1); ``` ## Troubleshooting[#](#troubleshooting) ### Gradient Not Visible[#](#gradient-not-visible) If your gradient doesn’t appear: * Check if fill is enabled: `engine.block.isFillEnabled(block)` * Verify color stops have visible colors (check alpha channels) * Ensure block has valid dimensions (width and height > 0) * Confirm block is in the scene hierarchy * Check if color stops are properly ordered by stop position ### Gradient Looks Different Than Expected[#](#gradient-looks-different-than-expected) If the gradient doesn’t look right: * Verify color stop positions are between 0.0 and 1.0 * Check gradient direction and positioning properties * Ensure correct gradient type is used (linear vs radial vs conical) * Review color space (RGB vs CMYK) for output medium * Confirm alpha values for transparency effects ### Gradient Direction Wrong[#](#gradient-direction-wrong) If the gradient direction is incorrect: * For linear gradients, check `startPointX/Y` and `endPointX/Y` values * Remember coordinates are normalized (0.0 to 1.0), not pixels * Verify the block’s coordinate system and transformations * Test with simple horizontal or vertical gradients first ### Memory Leaks[#](#memory-leaks) To prevent memory leaks: * Always destroy replaced gradients: `engine.block.destroy(oldFill)` * Don’t create gradient fills without attaching them to blocks * Clean up shared gradients when no longer needed ### Cannot Apply Gradient to Block[#](#cannot-apply-gradient-to-block) If you can’t apply a gradient fill: * Verify block supports fills: `engine.block.supportsFill(block)` * Check if block has a shape: Some blocks require shapes * Ensure gradient fill object is valid and not already destroyed ### Color Stops Not Updating[#](#color-stops-not-updating) If color stops don’t update: * Verify you’re calling `setGradientColorStops()` not `setColor()` * Ensure property name is exactly `'fill/gradient/colors'` * Check that color stop array is properly formatted * Confirm fill ID is correct and still valid ## API Reference[#](#api-reference) ### Core Methods[#](#core-methods) | Method | Description | | --- | --- | | `createFill('gradient/linear')` | Create a new linear gradient fill | | `createFill('gradient/radial')` | Create a new radial gradient fill | | `createFill('gradient/conical')` | Create a new conical gradient fill | | `setFill(block, fill)` | Assign gradient fill to a block | | `getFill(block)` | Get the fill ID from a block | | `setGradientColorStops(fill, property, stops)` | Set gradient color stops array | | `getGradientColorStops(fill, property)` | Get current gradient color stops | | `setFloat(fill, property, value)` | Set gradient position/radius properties | | `getFloat(fill, property)` | Get gradient position/radius values | | `setFillEnabled(block, enabled)` | Enable or disable fill rendering | | `isFillEnabled(block)` | Check if fill is enabled | | `supportsFill(block)` | Check if block supports fills | ### Linear Gradient Properties[#](#linear-gradient-properties) | Property | Type | Default | Description | | --- | --- | --- | --- | | `fill/gradient/colors` | GradientColorStop\[\] | \- | Array of color stops | | `fill/gradient/linear/startPointX` | Float (0.0-1.0) | 0.5 | Horizontal start position | | `fill/gradient/linear/startPointY` | Float (0.0-1.0) | 0.0 | Vertical start position | | `fill/gradient/linear/endPointX` | Float (0.0-1.0) | 0.5 | Horizontal end position | | `fill/gradient/linear/endPointY` | Float (0.0-1.0) | 1.0 | Vertical end position | ### Radial Gradient Properties[#](#radial-gradient-properties) | Property | Type | Default | Description | | --- | --- | --- | --- | | `fill/gradient/colors` | GradientColorStop\[\] | \- | Array of color stops | | `fill/gradient/radial/centerPointX` | Float (0.0-1.0) | 0.0 | Horizontal center position | | `fill/gradient/radial/centerPointY` | Float (0.0-1.0) | 0.0 | Vertical center position | | `fill/gradient/radial/radius` | Float | 1.0 | Radius relative to smaller side | ### Conical Gradient Properties[#](#conical-gradient-properties) | Property | Type | Default | Description | | --- | --- | --- | --- | | `fill/gradient/colors` | GradientColorStop\[\] | \- | Array of color stops | | `fill/gradient/conical/centerPointX` | Float (0.0-1.0) | 0.0 | Horizontal center position | | `fill/gradient/conical/centerPointY` | Float (0.0-1.0) | 0.0 | Vertical center position | **Note**: Conical gradients rotate clockwise starting from the top (12 o’clock). There is no rotation or angle property. ### GradientColorStop Interface[#](#gradientcolorstop-interface) ``` interface GradientColorStop { color: Color; // RGB, CMYK, or Spot color stop: number; // Position (0.0 to 1.0)} // Color formats supported:type Color = | { r: number; g: number; b: number; a: number } // RGB | { c: number; m: number; y: number; k: number; tint: number } // CMYK | { name: string; tint: number; externalReference: string }; // Spot ``` ## Next Steps[#](#next-steps) Now that you understand gradient fills, explore other fill types and color management features: * Learn about Color Fills for solid colors * Explore Image Fills for photo content * Understand Fill Overview for the comprehensive fill system * Review Apply Colors for color management across properties * Study Blocks Concept for understanding the block system --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/distortion-5b5a66) --- # Distortion Effects Apply distortion effects to warp, shift, and transform images and videos for dynamic artistic visuals. ![Distortion Effects](/docs/cesdk/_astro/browser.hero.B1UIwFMF_Zpb9RM.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-distortion-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-distortion-browser) Distortion effects differ from color filters in that they modify the geometry and spatial arrangement of pixels rather than their color values. CE.SDK provides several distortion effect types: liquid warping, mirror reflections, color channel shifting, radial pixelation, and TV glitch. Each effect offers configurable parameters to control the intensity and style of the distortion. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json';import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Distortion Effects Guide * * Demonstrates applying various distortion effects to image blocks: * - Checking effect support * - Applying liquid distortion * - Applying mirror effect * - Applying shifter (chromatic aberration) * - Applying radial pixel effect * - Applying TV glitch effect * - Combining multiple distortion effects * - Managing effects (enable/disable/remove) */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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); 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 based on page dimensions const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Use a sample image URL const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const blockSize = { width: blockWidth, height: blockHeight }; // Create a sample block to demonstrate effect support checking const sampleBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, sampleBlock); // Check if a block supports effects before applying them const supportsEffects = engine.block.supportsEffects(sampleBlock); console.log('Block supports effects:', supportsEffects); // Create an image block for liquid distortion demonstration const liquidBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, liquidBlock); // Create and apply liquid effect - creates flowing, organic warping const liquidEffect = engine.block.createEffect('liquid'); engine.block.setFloat(liquidEffect, 'effect/liquid/amount', 0.5); engine.block.setFloat(liquidEffect, 'effect/liquid/scale', 1.0); engine.block.setFloat(liquidEffect, 'effect/liquid/time', 0.0); engine.block.appendEffect(liquidBlock, liquidEffect); // Create an image block for mirror effect demonstration const mirrorBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, mirrorBlock); // Create and apply mirror effect - reflects image along a side const mirrorEffect = engine.block.createEffect('mirror'); // Side values: 0 = Left, 1 = Right, 2 = Top, 3 = Bottom engine.block.setInt(mirrorEffect, 'effect/mirror/side', 0); engine.block.appendEffect(mirrorBlock, mirrorEffect); // Create an image block for shifter effect demonstration const shifterBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, shifterBlock); // Create and apply shifter effect - displaces color channels const shifterEffect = engine.block.createEffect('shifter'); engine.block.setFloat(shifterEffect, 'effect/shifter/amount', 0.3); engine.block.setFloat(shifterEffect, 'effect/shifter/angle', 0.785); engine.block.appendEffect(shifterBlock, shifterEffect); // Create an image block for radial pixel effect demonstration const radialPixelBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, radialPixelBlock); // Create and apply radial pixel effect - pixelates in circular pattern const radialPixelEffect = engine.block.createEffect('radial_pixel'); engine.block.setFloat(radialPixelEffect, 'effect/radial_pixel/radius', 0.5); engine.block.setFloat( radialPixelEffect, 'effect/radial_pixel/segments', 0.5 ); engine.block.appendEffect(radialPixelBlock, radialPixelEffect); // Create an image block for TV glitch effect demonstration const tvGlitchBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, tvGlitchBlock); // Create and apply TV glitch effect - simulates analog TV interference const tvGlitchEffect = engine.block.createEffect('tv_glitch'); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/distortion', 0.4); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/distortion2', 0.2); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/speed', 0.5); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/rollSpeed', 0.1); engine.block.appendEffect(tvGlitchBlock, tvGlitchEffect); // Get all effects applied to a block const effects = engine.block.getEffects(tvGlitchBlock); console.log('Applied effects:', effects); // Get the type of each effect effects.forEach((effect, index) => { const effectType = engine.block.getType(effect); console.log(`Effect ${index}: ${effectType}`); }); // Check if an effect is enabled const isEnabled = engine.block.isEffectEnabled(liquidEffect); console.log('Liquid effect enabled:', isEnabled); // Disable an effect without removing it engine.block.setEffectEnabled(liquidEffect, false); console.log( 'Liquid effect now disabled:', !engine.block.isEffectEnabled(liquidEffect) ); // Re-enable the effect engine.block.setEffectEnabled(liquidEffect, true); // To remove an effect, get its index and use removeEffect const shifterEffects = engine.block.getEffects(shifterBlock); const effectIndex = shifterEffects.indexOf(shifterEffect); if (effectIndex !== -1) { // Remove effect at the specified index engine.block.removeEffect(shifterBlock, effectIndex); // Destroy the removed effect to free memory engine.block.destroy(shifterEffect); } // Re-add the effect for display purposes const newShifterEffect = engine.block.createEffect('shifter'); engine.block.setFloat(newShifterEffect, 'effect/shifter/amount', 0.3); engine.block.setFloat(newShifterEffect, 'effect/shifter/angle', 0.785); engine.block.appendEffect(shifterBlock, newShifterEffect); // Find all available properties for an effect const tvGlitchProperties = engine.block.findAllProperties(tvGlitchEffect); console.log('TV glitch properties:', tvGlitchProperties); // Position all blocks in grid layout const blocks = [ sampleBlock, liquidBlock, mirrorBlock, shifterBlock, radialPixelBlock, tvGlitchBlock ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Select the liquid effect block (second block) and open the effects panel engine.block.select(liquidBlock); cesdk.ui.openPanel('//ly.img.panel/inspector/effects'); console.log('Distortion effects guide initialized.'); }} export default Example; ``` This guide covers how to enable distortion effects in the built-in UI and how to apply and configure them programmatically using the block API. ## Using the Built-in Distortion UI[#](#using-the-built-in-distortion-ui) To enable distortion effects in the inspector panel, use the Feature API: ``` // Enable effects in the inspector panel using the Feature APIcesdk.feature.enable('ly.img.effect'); ``` Once enabled, users can access distortion effects from the inspector when selecting an image or video block. The effects panel displays available distortions with real-time preview as parameters are adjusted. ## Check Effect Support[#](#check-effect-support) Before applying distortion effects, verify the block supports them. Graphic blocks with image or video fills support effects, while scene blocks do not. ``` // Create a sample block to demonstrate effect support checkingconst sampleBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, sampleBlock); // Check if a block supports effects before applying themconst supportsEffects = engine.block.supportsEffects(sampleBlock);console.log('Block supports effects:', supportsEffects); ``` ## Apply Liquid Effect[#](#apply-liquid-effect) The liquid effect creates organic, flowing distortions that warp the image as if viewed through water. We can configure the intensity and scale of the warping. ``` // Create an image block for liquid distortion demonstrationconst liquidBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, liquidBlock); // Create and apply liquid effect - creates flowing, organic warpingconst liquidEffect = engine.block.createEffect('liquid');engine.block.setFloat(liquidEffect, 'effect/liquid/amount', 0.5);engine.block.setFloat(liquidEffect, 'effect/liquid/scale', 1.0);engine.block.setFloat(liquidEffect, 'effect/liquid/time', 0.0);engine.block.appendEffect(liquidBlock, liquidEffect); ``` The liquid effect parameters: * **amount** (0.0 to 1.0) - Controls the intensity of the warping * **scale** - Adjusts the size of the liquid pattern * **time** - Animation time offset for animated liquid distortions ## Apply Mirror Effect[#](#apply-mirror-effect) The mirror effect reflects the image along a configurable side, creating symmetrical compositions. ``` // Create an image block for mirror effect demonstrationconst mirrorBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, mirrorBlock); // Create and apply mirror effect - reflects image along a sideconst mirrorEffect = engine.block.createEffect('mirror');// Side values: 0 = Left, 1 = Right, 2 = Top, 3 = Bottomengine.block.setInt(mirrorEffect, 'effect/mirror/side', 0);engine.block.appendEffect(mirrorBlock, mirrorEffect); ``` The `side` parameter uses integer values: `0` (Left), `1` (Right), `2` (Top), or `3` (Bottom) to specify the reflection axis. ## Apply Shifter Effect[#](#apply-shifter-effect) The shifter effect displaces color channels at an angle, creating chromatic aberration commonly seen in glitch art and retro visuals. ``` // Create an image block for shifter effect demonstrationconst shifterBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, shifterBlock); // Create and apply shifter effect - displaces color channelsconst shifterEffect = engine.block.createEffect('shifter');engine.block.setFloat(shifterEffect, 'effect/shifter/amount', 0.3);engine.block.setFloat(shifterEffect, 'effect/shifter/angle', 0.785);engine.block.appendEffect(shifterBlock, shifterEffect); ``` The shifter effect parameters: * **amount** (0.0 to 1.0) - Controls the displacement distance * **angle** - Sets the direction of the shift in radians ## Apply Radial Pixel Effect[#](#apply-radial-pixel-effect) The radial pixel effect pixelates the image in a circular pattern emanating from the center, useful for focus effects or stylized treatments. ``` // Create an image block for radial pixel effect demonstrationconst radialPixelBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, radialPixelBlock); // Create and apply radial pixel effect - pixelates in circular patternconst radialPixelEffect = engine.block.createEffect('radial_pixel');engine.block.setFloat(radialPixelEffect, 'effect/radial_pixel/radius', 0.5);engine.block.setFloat( radialPixelEffect, 'effect/radial_pixel/segments', 0.5);engine.block.appendEffect(radialPixelBlock, radialPixelEffect); ``` The radial pixel effect parameters: * **radius** (0.0 to 1.0) - Controls the size of the pixelation effect * **segments** (0.0 to 1.0) - Controls the angular segmentation intensity ## Apply TV Glitch Effect[#](#apply-tv-glitch-effect) The TV glitch effect simulates analog television interference with horizontal distortion and rolling effects, popular for retro and digital aesthetics. ``` // Create an image block for TV glitch effect demonstrationconst tvGlitchBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, tvGlitchBlock); // Create and apply TV glitch effect - simulates analog TV interferenceconst tvGlitchEffect = engine.block.createEffect('tv_glitch');engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/distortion', 0.4);engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/distortion2', 0.2);engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/speed', 0.5);engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/rollSpeed', 0.1);engine.block.appendEffect(tvGlitchBlock, tvGlitchEffect); ``` The TV glitch effect parameters: * **distortion** - Primary horizontal distortion intensity * **distortion2** - Secondary distortion layer * **speed** - Animation speed for the glitch effect * **rollSpeed** - Vertical roll speed simulating signal sync issues ## List Applied Effects[#](#list-applied-effects) Retrieve all effects applied to a block to inspect or iterate over them. ``` // Get all effects applied to a blockconst effects = engine.block.getEffects(tvGlitchBlock);console.log('Applied effects:', effects); // Get the type of each effecteffects.forEach((effect, index) => { const effectType = engine.block.getType(effect); console.log(`Effect ${index}: ${effectType}`);}); ``` This returns an array of effect IDs in the order they were applied. ## Enable and Disable Effects[#](#enable-and-disable-effects) Toggle effects on and off without removing them from the block. This preserves all effect parameters while controlling visibility. ``` // Check if an effect is enabledconst isEnabled = engine.block.isEffectEnabled(liquidEffect);console.log('Liquid effect enabled:', isEnabled); // Disable an effect without removing itengine.block.setEffectEnabled(liquidEffect, false);console.log( 'Liquid effect now disabled:', !engine.block.isEffectEnabled(liquidEffect)); // Re-enable the effectengine.block.setEffectEnabled(liquidEffect, true); ``` Disabled effects remain attached to the block but won’t be rendered until re-enabled. This is useful for before/after comparisons or performance optimization. ## Remove Effects[#](#remove-effects) Remove effects from a block when they’re no longer needed. Always destroy removed effects to free memory. ``` // To remove an effect, get its index and use removeEffectconst shifterEffects = engine.block.getEffects(shifterBlock);const effectIndex = shifterEffects.indexOf(shifterEffect);if (effectIndex !== -1) { // Remove effect at the specified index engine.block.removeEffect(shifterBlock, effectIndex); // Destroy the removed effect to free memory engine.block.destroy(shifterEffect);} // Re-add the effect for display purposesconst newShifterEffect = engine.block.createEffect('shifter');engine.block.setFloat(newShifterEffect, 'effect/shifter/amount', 0.3);engine.block.setFloat(newShifterEffect, 'effect/shifter/angle', 0.785);engine.block.appendEffect(shifterBlock, newShifterEffect); ``` ## Discover Effect Properties[#](#discover-effect-properties) Use `findAllProperties()` to discover all available properties for any effect type. ``` // Find all available properties for an effectconst tvGlitchProperties = engine.block.findAllProperties(tvGlitchEffect);console.log('TV glitch properties:', tvGlitchProperties); ``` This returns an array of property paths that can be used with `setFloat()`, `setInt()`, or `setEnum()`. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `engine.block.supportsEffects(id)` | Check if a block supports effects | | `engine.block.createEffect(type)` | Create a new effect instance | | `engine.block.appendEffect(id, effectId)` | Add an effect to a block | | `engine.block.getEffects(id)` | Get all effects applied to a block | | `engine.block.setEffectEnabled(effectId, enabled)` | Enable or disable an effect | | `engine.block.isEffectEnabled(effectId)` | Check if an effect is enabled | | `engine.block.removeEffect(id, index)` | Remove an effect at a specific index | | `engine.block.findAllProperties(id)` | Discover all properties of an effect | | `engine.block.setFloat(id, property, value)` | Set a float property value | | `engine.block.setInt(id, property, value)` | Set an integer property value | | `engine.block.destroy(id)` | Destroy a block to free memory | | `engine.block.getType(id)` | Get the type of a block | ## Available Distortion Effects[#](#available-distortion-effects) | Effect Type | Description | Key Properties | | --- | --- | --- | | `liquid` | Flowing, organic warping | `amount`, `scale`, `time` | | `mirror` | Reflection along a side | `side` (0=Left, 1=Right, 2=Top, 3=Bottom) | | `shifter` | Chromatic aberration | `amount`, `angle` | | `radial_pixel` | Circular pixelation | `radius`, `segments` | | `tv_glitch` | Analog TV interference | `distortion`, `distortion2`, `speed`, `rollSpeed` | ## Next Steps[#](#next-steps) * [Apply Filters and Effects](vue/filters-and-effects/apply-2764e4/) \- Learn the foundational effect APIs * [Blur Effects](vue/filters-and-effects/blur-71d642/) \- Apply blur techniques for depth and focus effects --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/duotone-831fc5) --- # Duotone Apply duotone effects to images, mapping tones to two colors for stylized visuals and brand-specific treatments. ![CE.SDK Duotone](/docs/cesdk/_astro/browser.hero.CZsZgIk3_19wnCo.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-duotone-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-duotone-browser) Duotone is a color effect that maps image brightness to two colors: a dark color for shadows and a light color for highlights. The result is a striking two-tone image where all original colors are replaced by gradations between your chosen pair. This technique creates bold, cohesive visuals that are particularly effective for brand consistency, vintage aesthetics, and artistic treatments. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json';import { hexToRgba } from './utils'; /** * CE.SDK Plugin: Duotone Guide * * Demonstrates applying duotone effects to image blocks: * - Querying duotone presets from the asset library * - Applying duotone with preset colors * - Creating custom duotone color combinations * - Managing and removing 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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Enable duotone filters in the inspector panel cesdk.feature.enable('ly.img.filter'); // Use a sample image URL const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Create image block for preset demonstration const presetImageBlock = await engine.block.addImage(imageUri, { size: { width: 350, height: 250 } }); engine.block.appendChild(page, presetImageBlock); engine.block.setPositionX(presetImageBlock, 25); engine.block.setPositionY(presetImageBlock, 25); // Verify a block supports effects before applying them const canApplyEffects = engine.block.supportsEffects(presetImageBlock); if (!canApplyEffects) { console.warn('Block does not support effects'); return; } // Query duotone presets from the asset library const duotoneResults = await engine.asset.findAssets( 'ly.img.filter.duotone', { page: 0, perPage: 10 } ); const duotonePresets = duotoneResults.assets; // Apply a preset to the first image if (duotonePresets.length > 0) { const preset = duotonePresets[0]; // Create a new duotone effect block const duotoneEffect = engine.block.createEffect('duotone_filter'); // Configure effect with preset colors (convert hex to RGBA) if (preset.meta?.darkColor) { engine.block.setColor( duotoneEffect, 'effect/duotone_filter/darkColor', hexToRgba(preset.meta.darkColor as string) ); } if (preset.meta?.lightColor) { engine.block.setColor( duotoneEffect, 'effect/duotone_filter/lightColor', hexToRgba(preset.meta.lightColor as string) ); } engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.9 ); // Attach the configured effect to the image block engine.block.appendEffect(presetImageBlock, duotoneEffect); } // Create image block for custom duotone demonstration const customImageBlock = await engine.block.addImage(imageUri, { size: { width: 350, height: 250 } }); engine.block.appendChild(page, customImageBlock); engine.block.setPositionX(customImageBlock, 425); engine.block.setPositionY(customImageBlock, 25); // Create duotone with custom brand colors const customDuotone = engine.block.createEffect('duotone_filter'); // Dark color: deep navy blue (shadows) engine.block.setColor(customDuotone, 'effect/duotone_filter/darkColor', { r: 0.1, g: 0.15, b: 0.3, a: 1.0 }); // Light color: warm cream (highlights) engine.block.setColor(customDuotone, 'effect/duotone_filter/lightColor', { r: 0.95, g: 0.9, b: 0.8, a: 1.0 }); // Control effect strength (0.0 = original, 1.0 = full duotone) engine.block.setFloat( customDuotone, 'effect/duotone_filter/intensity', 0.85 ); engine.block.appendEffect(customImageBlock, customDuotone); // Create image block for combined effects demonstration const combinedImageBlock = await engine.block.addImage(imageUri, { size: { width: 350, height: 250 } }); engine.block.appendChild(page, combinedImageBlock); engine.block.setPositionX(combinedImageBlock, 225); engine.block.setPositionY(combinedImageBlock, 325); // Combine duotone with other effects // First, add adjustments for brightness and contrast const adjustments = engine.block.createEffect('adjustments'); engine.block.setFloat(adjustments, 'effect/adjustments/brightness', 0.1); engine.block.setFloat(adjustments, 'effect/adjustments/contrast', 0.15); engine.block.appendEffect(combinedImageBlock, adjustments); // Then add duotone on top const combinedDuotone = engine.block.createEffect('duotone_filter'); engine.block.setColor( combinedDuotone, 'effect/duotone_filter/darkColor', { r: 0.2, g: 0.1, b: 0.3, a: 1.0 } // Deep purple ); engine.block.setColor( combinedDuotone, 'effect/duotone_filter/lightColor', { r: 1.0, g: 0.85, b: 0.7, a: 1.0 } // Warm peach ); engine.block.setFloat( combinedDuotone, 'effect/duotone_filter/intensity', 0.75 ); engine.block.appendEffect(combinedImageBlock, combinedDuotone); // Get all effects currently applied to a block const appliedEffects = engine.block.getEffects(presetImageBlock); console.log(`Block has ${appliedEffects.length} effect(s) applied`); // Disable an effect without removing it if (appliedEffects.length > 0) { engine.block.setEffectEnabled(appliedEffects[0], false); // Check if an effect is currently enabled const isEnabled = engine.block.isEffectEnabled(appliedEffects[0]); console.log(`Effect enabled: ${isEnabled}`); // Re-enable the effect engine.block.setEffectEnabled(appliedEffects[0], true); } // Remove an effect at a specific index from a block const effectsOnCustom = engine.block.getEffects(customImageBlock); if (effectsOnCustom.length > 0) { engine.block.removeEffect(customImageBlock, 0); } // Destroy removed effect blocks to free memory if (effectsOnCustom.length > 0) { engine.block.destroy(effectsOnCustom[0]); } // Re-apply custom duotone after demonstration const newCustomDuotone = engine.block.createEffect('duotone_filter'); engine.block.setColor(newCustomDuotone, 'effect/duotone_filter/darkColor', { r: 0.1, g: 0.15, b: 0.3, a: 1.0 }); engine.block.setColor( newCustomDuotone, 'effect/duotone_filter/lightColor', { r: 0.95, g: 0.9, b: 0.8, a: 1.0 } ); engine.block.setFloat( newCustomDuotone, 'effect/duotone_filter/intensity', 0.85 ); engine.block.appendEffect(customImageBlock, newCustomDuotone); // Select the first image block to show the effects panel engine.block.select(presetImageBlock); console.log( 'Duotone guide initialized. Select any image to see the filters panel.' ); }} export default Example; ``` ## Understanding Duotone[#](#understanding-duotone) Unlike filters that simply tint or shift colors, duotone completely remaps the tonal range of an image. The effect analyzes each pixel’s luminosity and assigns a color based on where it falls between pure black and pure white: * **Dark tones** (shadows, blacks) adopt the dark color * **Light tones** (highlights, whites) adopt the light color * **Midtones** blend between the two colors This creates images with a consistent color palette regardless of the original colors, making duotone ideal for: * **Brand consistency** - Apply your brand’s color palette across diverse imagery * **Visual cohesion** - Unify photos from different sources in a design * **Vintage aesthetics** - Recreate classic print techniques like cyanotype or sepia * **Bold statements** - Create eye-catching visuals for social media or marketing The intensity property lets you blend between the original image and the full duotone effect, giving you creative control over how strongly the effect is applied. ## Using the Built-in Duotone UI[#](#using-the-built-in-duotone-ui) CE.SDK provides a filters panel where users can browse and apply duotone presets interactively. To enable this, use the Feature API: ``` // Enable duotone filters in the inspector panelcesdk.feature.enable('ly.img.filter'); ``` With filters enabled, users can: 1. Select any image block in the canvas 2. Open the inspector panel 3. Navigate to the Filters section 4. Browse available duotone presets and apply with a single click 5. Adjust the intensity slider to control effect strength ## Check Effect Support[#](#check-effect-support) Before applying effects programmatically, verify the block supports them. Only graphic blocks with image or video fills can have effects applied: ``` // Verify a block supports effects before applying themconst canApplyEffects = engine.block.supportsEffects(presetImageBlock);if (!canApplyEffects) { console.warn('Block does not support effects'); return;} ``` Attempting to apply effects to unsupported blocks (like text or shapes without fills) will result in an error. ## Applying Duotone Presets[#](#applying-duotone-presets) CE.SDK includes a library of professionally designed duotone presets. Each preset defines a dark/light color pair optimized for visual appeal. ### Query Built-in Presets[#](#query-built-in-presets) Use the Asset API to retrieve available duotone presets from the asset library: ``` // Query duotone presets from the asset libraryconst duotoneResults = await engine.asset.findAssets( 'ly.img.filter.duotone', { page: 0, perPage: 10 });const duotonePresets = duotoneResults.assets; ``` Preset metadata contains `darkColor` and `lightColor` as hex strings. Convert these to RGBA format (values 0-1) before passing to the effect API. ### Create Effect Block[#](#create-effect-block) Create a new duotone effect block that can be configured and attached to an image: ``` // Create a new duotone effect blockconst duotoneEffect = engine.block.createEffect('duotone_filter'); ``` ### Configure Preset Colors[#](#configure-preset-colors) Apply the preset’s color values to the effect using `setColor()` for colors and `setFloat()` for intensity: ``` // Configure effect with preset colors (convert hex to RGBA)if (preset.meta?.darkColor) { engine.block.setColor( duotoneEffect, 'effect/duotone_filter/darkColor', hexToRgba(preset.meta.darkColor as string) );}if (preset.meta?.lightColor) { engine.block.setColor( duotoneEffect, 'effect/duotone_filter/lightColor', hexToRgba(preset.meta.lightColor as string) );}engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.9); ``` ### Append Effect to Block[#](#append-effect-to-block) Attach the fully configured effect to an image block: ``` // Attach the configured effect to the image blockengine.block.appendEffect(presetImageBlock, duotoneEffect); ``` ## Creating Custom Colors[#](#creating-custom-colors) For brand-specific treatments or unique creative effects, define your own color combinations using `engine.block.setColor()`: ``` // Create duotone with custom brand colorsconst customDuotone = engine.block.createEffect('duotone_filter'); // Dark color: deep navy blue (shadows)engine.block.setColor(customDuotone, 'effect/duotone_filter/darkColor', { r: 0.1, g: 0.15, b: 0.3, a: 1.0}); // Light color: warm cream (highlights)engine.block.setColor(customDuotone, 'effect/duotone_filter/lightColor', { r: 0.95, g: 0.9, b: 0.8, a: 1.0}); // Control effect strength (0.0 = original, 1.0 = full duotone)engine.block.setFloat( customDuotone, 'effect/duotone_filter/intensity', 0.85); engine.block.appendEffect(customImageBlock, customDuotone); ``` ### Choosing Effective Color Pairs[#](#choosing-effective-color-pairs) The relationship between your dark and light colors determines the final aesthetic: | Color Relationship | Visual Effect | Example Use Case | | --- | --- | --- | | **High contrast** | Bold, graphic look | Social media, posters | | **Low contrast** | Subtle, sophisticated | Editorial, luxury brands | | **Warm to cool** | Dynamic temperature shift | Lifestyle, fashion | | **Monochromatic** | Tinted photography style | Vintage, noir aesthetic | **Classic combinations to try:** * **Cyanotype**: Deep blue (`#1a365d`) to light cyan (`#e0f7fa`) * **Sepia**: Dark brown (`#3e2723`) to cream (`#fff8e1`) * **Neon**: Deep purple (`#1a1a2e`) to hot pink (`#ff1493`) * **Corporate**: Navy (`#0d47a1`) to silver (`#eceff1`) Start with colors from your brand palette. Use your primary brand color as the light color for highlights, paired with a darker complementary shade for shadows. ## Combining with Other Effects[#](#combining-with-other-effects) Duotone can be stacked with other effects like brightness adjustments, contrast, or blur. Effects are applied in stack order, so the sequence affects the final result: ``` // Combine duotone with other effects// First, add adjustments for brightness and contrastconst adjustments = engine.block.createEffect('adjustments');engine.block.setFloat(adjustments, 'effect/adjustments/brightness', 0.1);engine.block.setFloat(adjustments, 'effect/adjustments/contrast', 0.15);engine.block.appendEffect(combinedImageBlock, adjustments); // Then add duotone on topconst combinedDuotone = engine.block.createEffect('duotone_filter');engine.block.setColor( combinedDuotone, 'effect/duotone_filter/darkColor', { r: 0.2, g: 0.1, b: 0.3, a: 1.0 } // Deep purple);engine.block.setColor( combinedDuotone, 'effect/duotone_filter/lightColor', { r: 1.0, g: 0.85, b: 0.7, a: 1.0 } // Warm peach);engine.block.setFloat( combinedDuotone, 'effect/duotone_filter/intensity', 0.75);engine.block.appendEffect(combinedImageBlock, combinedDuotone); ``` **Effect order matters**: In this example, brightness and contrast are applied first, then duotone maps the adjusted tones. Reversing the order would apply duotone first, then adjust the duotone colors’ brightness—producing a different result. Common combinations: * **Adjustments → Duotone**: Optimize image contrast before applying duotone for better tonal separation * **Duotone → Vignette**: Add depth with darkened edges after the color treatment * **Blur → Duotone**: Create dreamy, soft-focus duotone backgrounds ## Managing Duotone Effects[#](#managing-duotone-effects) Once effects are applied to a block, you can list, toggle, and remove them programmatically. ### List Applied Effects[#](#list-applied-effects) Retrieve all effect block IDs currently attached to a block: ``` // Get all effects currently applied to a blockconst appliedEffects = engine.block.getEffects(presetImageBlock); ``` ### Toggle Effect Visibility[#](#toggle-effect-visibility) Disable an effect temporarily without removing it from the block: ``` // Disable an effect without removing itif (appliedEffects.length > 0) { engine.block.setEffectEnabled(appliedEffects[0], false); // Check if an effect is currently enabled const isEnabled = engine.block.isEffectEnabled(appliedEffects[0]); console.log(`Effect enabled: ${isEnabled}`); // Re-enable the effect engine.block.setEffectEnabled(appliedEffects[0], true);} ``` ### Remove Effects[#](#remove-effects) Detach an effect from a block by specifying its index in the effect stack: ``` // Remove an effect at a specific index from a blockconst effectsOnCustom = engine.block.getEffects(customImageBlock);if (effectsOnCustom.length > 0) { engine.block.removeEffect(customImageBlock, 0);} ``` ### Clean Up Resources[#](#clean-up-resources) After removing an effect, destroy it to free memory: ``` // Destroy removed effect blocks to free memoryif (effectsOnCustom.length > 0) { engine.block.destroy(effectsOnCustom[0]);} ``` When removing effects, always destroy the effect block afterward to prevent memory leaks. Effects are independent blocks that persist until explicitly destroyed. ### Duotone Properties[#](#duotone-properties) | Property | Type | Range | Description | | --- | --- | --- | --- | | `effect/duotone_filter/darkColor` | Color | RGBA (0-1) | Color applied to shadows and dark tones | | `effect/duotone_filter/lightColor` | Color | RGBA (0-1) | Color applied to highlights and light tones | | `effect/duotone_filter/intensity` | Float | 0.0 - 1.0 | Effect strength (0 = original, 1 = full duotone) | **Intensity guidelines:** * `0.5 - 0.7`: Subtle tint, original image still recognizable * `0.8 - 0.9`: Strong duotone effect, ideal for most use cases * `1.0`: Full duotone, no original colors remain ## Best Practices[#](#best-practices) ### Performance Considerations[#](#performance-considerations) * **Batch effect creation**: When applying the same duotone to multiple images, create the effect once and clone it rather than creating new effects for each block * **Limit effect stacking**: Each additional effect increases render time; keep stacks minimal for real-time editing * **Clean up unused effects**: Always destroy effect blocks when they’re no longer needed to free GPU resources * **Disable rather than remove**: For temporary changes, use `setEffectEnabled(false)` instead of removing and re-creating effects ### Common Issues[#](#common-issues) **Duotone not visible**: Verify the block supports effects with `engine.block.supportsEffects(block)`. Only graphic blocks with image or video fills support effects. **Colors look wrong**: Ensure RGBA values are in the 0-1 range, not 0-255. For example, use `{ r: 0.5, g: 0.5, b: 0.5, a: 1.0 }` instead of `{ r: 128, g: 128, b: 128, a: 255 }`. **Effect too subtle or overwhelming**: Adjust the intensity property. Start at 0.85 and tune based on your image content and color choices. **Muddy midtones**: If midtones look flat, increase contrast between your dark and light colors, or add an adjustments effect before duotone to improve tonal separation. ## API Reference[#](#api-reference) ### Effect Methods[#](#effect-methods) | Method | Description | | --- | --- | | `engine.asset.findAssets(sourceId, query)` | Queries assets from an asset source | | `engine.block.supportsEffects(block)` | Returns `true` if the block can have effects applied | | `engine.block.createEffect(type)` | Creates a new effect block of the specified type | | `engine.block.setColor(block, property, color)` | Sets a color property on a block | | `engine.block.setFloat(block, property, value)` | Sets a float property on a block | | `engine.block.appendEffect(block, effect)` | Appends an effect to a block’s effect stack | | `engine.block.getEffects(block)` | Returns array of effect block IDs applied to a block | | `engine.block.setEffectEnabled(effect, enabled)` | Enables or disables an effect | | `engine.block.isEffectEnabled(effect)` | Returns `true` if the effect is enabled | | `engine.block.removeEffect(block, index)` | Removes an effect at the given index from a block | | `engine.block.destroy(block)` | Destroys a block and frees resources | --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/create-custom-lut-filter-6e3f49) --- # Create a Custom LUT Filter Apply custom LUT (Look-Up Table) filters to achieve brand-consistent color grading directly through CE.SDK’s effect API. ![Create Custom LUT Filter example showing an image with a custom LUT color grade applied](/docs/cesdk/_astro/browser.hero.CjBR8ke5_iTrIV.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-create-custom-lut-filter-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-create-custom-lut-filter-browser) LUT filters remap colors through a predefined transformation table, enabling cinematic color grading and consistent brand aesthetics. This guide shows how to apply your own LUT files directly to design elements using the effect API. For organizing collections of filters through asset sources, see [Create Custom Filters](vue/filters-and-effects/create-custom-filters-c796ba/) . ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Create Custom LUT Filter Guide * * Demonstrates applying custom LUT filters directly using the effect API: * - Creating a lut_filter effect * - Configuring the LUT file URI and tile dimensions * - Setting filter intensity * - Toggling the effect on and off */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'); } // Initialize CE.SDK with Design mode and load default assets await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = 800; const pageHeight = 600; engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); // Create a gradient background for the page const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.15, g: 0.1, b: 0.25, a: 1 }, stop: 0 }, { color: { r: 0.3, g: 0.15, b: 0.4, a: 1 }, stop: 0.5 }, { color: { r: 0.2, g: 0.1, b: 0.35, a: 1 }, stop: 1 } ]); 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); // Create a centered title text const titleText = engine.block.create('text'); engine.block.setString(titleText, 'text/text', 'Custom LUT Filter'); engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center'); engine.block.setTextFontSize(titleText, 96); engine.block.setTextColor(titleText, { r: 1, g: 1, b: 1, a: 1 }); engine.block.setWidthMode(titleText, 'Auto'); engine.block.setHeightMode(titleText, 'Auto'); engine.block.appendChild(page, titleText); // Create a subtext below the title const subText = engine.block.create('text'); engine.block.setString(subText, 'text/text', 'img.ly'); engine.block.setEnum(subText, 'text/horizontalAlignment', 'Center'); engine.block.setTextFontSize(subText, 64); engine.block.setTextColor(subText, { r: 0.8, g: 0.8, b: 0.8, a: 1 }); engine.block.setWidthMode(subText, 'Auto'); engine.block.setHeightMode(subText, 'Auto'); engine.block.appendChild(page, subText); // Get text dimensions for centering calculations const titleWidth = engine.block.getFrameWidth(titleText); const titleHeight = engine.block.getFrameHeight(titleText); const subTextWidth = engine.block.getFrameWidth(subText); const subTextHeight = engine.block.getFrameHeight(subText); // Image dimensions (smaller) const imageWidth = 200; const imageHeight = 150; // Calculate total content height and vertical centering const textGap = 8; const imagePadding = 60; const totalContentHeight = titleHeight + textGap + subTextHeight + imagePadding + imageHeight; const startY = (pageHeight - totalContentHeight) / 2; // Position title centered engine.block.setPositionX(titleText, (pageWidth - titleWidth) / 2); engine.block.setPositionY(titleText, startY); // Position subtext below title engine.block.setPositionX(subText, (pageWidth - subTextWidth) / 2); engine.block.setPositionY(subText, startY + titleHeight + textGap); // Add an image block to apply the LUT filter const imageY = startY + titleHeight + textGap + subTextHeight + imagePadding; const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri, { x: (pageWidth - imageWidth) / 2, y: imageY, size: { width: imageWidth, height: imageHeight } }); engine.block.appendChild(page, imageBlock); // Create a LUT filter effect const lutEffect = engine.block.createEffect('//ly.img.ubq/effect/lut_filter'); // Configure the LUT file URI - this is a tiled PNG containing the color lookup table const lutUrl = 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png'; engine.block.setString(lutEffect, 'effect/lut_filter/lutFileURI', lutUrl); // Set the tile grid dimensions - must match the LUT image structure engine.block.setInt(lutEffect, 'effect/lut_filter/horizontalTileCount', 5); engine.block.setInt(lutEffect, 'effect/lut_filter/verticalTileCount', 5); // Set filter intensity (0.0 = no effect, 1.0 = full effect) engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.8); // Apply the effect to the image block engine.block.appendEffect(imageBlock, lutEffect); // Register a custom button component to toggle the LUT filter cesdk.ui.registerComponent('lut.toggle', ({ builder }) => { const isEnabled = engine.block.isEffectEnabled(lutEffect); builder.Button('toggle-lut', { label: 'LUT Filter', icon: isEnabled ? '@imgly/ToggleIconOn' : '@imgly/ToggleIconOff', isActive: isEnabled, onClick: () => { engine.block.setEffectEnabled(lutEffect, !isEnabled); } }); }); // Add the toggle button to the navigation bar cesdk.ui.insertNavigationBarOrderComponent('last', 'lut.toggle'); // Retrieve all effects on the block const effects = engine.block.getEffects(imageBlock); console.log('Number of effects:', effects.length); // 1 // Check if block supports effects const supportsEffects = engine.block.supportsEffects(imageBlock); console.log('Supports effects:', supportsEffects); // true // Select the image to show it in the editor engine.block.select(imageBlock); console.log('Custom LUT filter applied successfully.'); }} export default Example; ``` ## Understanding LUT Image Format[#](#understanding-lut-image-format) CE.SDK uses a tiled PNG format where a 3D color cube is laid out as a 2D grid. Each tile represents a slice of the color cube along the blue axis. The LUT image requires two configuration values: * **`horizontalTileCount`** - Number of tiles across the image width * **`verticalTileCount`** - Number of tiles down the image height CE.SDK supports these tile configurations: * 5×5 tiles with 128px cube size * 8×8 tiles with 512px cube size Standard `.cube` files must be converted to this tiled PNG format using image processing tools. ## Creating LUT PNG Images[#](#creating-lut-png-images) ### Obtaining LUT Files[#](#obtaining-lut-files) LUT files are available from multiple sources: * **Color grading software** - Adobe Photoshop, DaVinci Resolve, and Affinity Photo can export 3D LUT files in `.cube` format * **Online LUT libraries** - Many free and commercial LUT packs are available for download * **LUT generators** - Tools that create custom color transformations from reference images ### Converting .cube to Tiled PNG[#](#converting-cube-to-tiled-png) CE.SDK requires LUTs in a specific tiled PNG format where each tile represents a slice of the 3D color cube along the blue axis. To convert a standard `.cube` file: 1. **Parse the .cube file** - Read the 3D color lookup table data 2. **Arrange slices as tiles** - Each blue channel value becomes a separate tile containing the red-green color plane 3. **Export as PNG** - Save the grid as a PNG image CE.SDK’s built-in LUTs follow a naming convention: `imgly_lut_{name}_{h}_{v}_{cubeSize}.png` where `h` and `v` are tile counts and `cubeSize` indicates the LUT precision. ### Using Python for Conversion[#](#using-python-for-conversion) You can write a Python script using PIL/Pillow and NumPy to convert `.cube` files: ``` --- # Pseudocode for .cube to tiled PNG conversion# 1. Parse the .cube file to extract the 3D LUT data# 2. Reshape data into (blue_slices, height, width, 3) array# 3. Arrange slices in a grid matching tile configuration# 4. Save as PNG with Image.fromarray() ``` ### Using CE.SDK’s Built-in LUTs[#](#using-cesdks-built-in-luts) The simplest approach is to use CE.SDK’s existing LUT assets as a starting point. The built-in filters use pre-generated tiled PNGs that you can reference for format verification. Check the filter extension at `ly.img.cesdk.filters.lut` for examples of properly formatted LUT images. ## Hosting LUT Files[#](#hosting-lut-files) LUT images must be served from an accessible URL. For production deployments, use HTTPS and enable CORS headers for cross-origin requests in browser environments. ## Creating the LUT Effect[#](#creating-the-lut-effect) Create a `lut_filter` effect instance using the effect API: ``` // Create a LUT filter effectconst lutEffect = engine.block.createEffect('//ly.img.ubq/effect/lut_filter'); ``` This creates an effect that can be configured and applied to image blocks. ## Configuring LUT Properties[#](#configuring-lut-properties) Set the LUT file URL and tile dimensions to match your LUT image: ``` // Configure the LUT file URI - this is a tiled PNG containing the color lookup tableconst lutUrl = 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png';engine.block.setString(lutEffect, 'effect/lut_filter/lutFileURI', lutUrl); // Set the tile grid dimensions - must match the LUT image structureengine.block.setInt(lutEffect, 'effect/lut_filter/horizontalTileCount', 5);engine.block.setInt(lutEffect, 'effect/lut_filter/verticalTileCount', 5); ``` The tile counts must match the actual LUT image grid structure. Using incorrect values produces distorted colors. ## Setting Filter Intensity[#](#setting-filter-intensity) Control the strength of the color transformation with intensity: ``` // Set filter intensity (0.0 = no effect, 1.0 = full effect)engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.8); ``` Values range from 0.0 (no effect) to 1.0 (full effect). Use intermediate values for subtle color grading. ## Applying the Effect[#](#applying-the-effect) Attach the configured effect to an image block: ``` // Apply the effect to the image blockengine.block.appendEffect(imageBlock, lutEffect); ``` The effect renders immediately after being applied. ## Toggling the Effect[#](#toggling-the-effect) Add a toggle button to the navigation bar for enabling and disabling the filter: ``` // Register a custom button component to toggle the LUT filtercesdk.ui.registerComponent('lut.toggle', ({ builder }) => { const isEnabled = engine.block.isEffectEnabled(lutEffect); builder.Button('toggle-lut', { label: 'LUT Filter', icon: isEnabled ? '@imgly/ToggleIconOn' : '@imgly/ToggleIconOff', isActive: isEnabled, onClick: () => { engine.block.setEffectEnabled(lutEffect, !isEnabled); } });}); // Add the toggle button to the navigation barcesdk.ui.insertNavigationBarOrderComponent('last', 'lut.toggle'); ``` The `registerComponent` function creates a custom UI component that tracks the effect’s enabled state. The `insertNavigationBarOrderComponent` adds it to the navigation bar. Clicking the button toggles the effect while preserving all settings. ## Managing Effects[#](#managing-effects) Retrieve and inspect effects applied to a block: ``` // Retrieve all effects on the blockconst effects = engine.block.getEffects(imageBlock);console.log('Number of effects:', effects.length); // 1 // Check if block supports effectsconst supportsEffects = engine.block.supportsEffects(imageBlock);console.log('Supports effects:', supportsEffects); // true ``` Use `getEffects()` to access all effects on a block and `supportsEffects()` to verify compatibility before applying. ## Troubleshooting[#](#troubleshooting) ### LUT Not Rendering[#](#lut-not-rendering) * Verify the LUT image URL is accessible and CORS-enabled * Confirm the image uses PNG format * Check that tile count values match the actual image grid ### Colors Look Wrong[#](#colors-look-wrong) * Verify tile counts match the LUT image structure * Ensure the LUT was generated with sRGB color space ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `engine.block.createEffect('//ly.img.ubq/effect/lut_filter')` | Create a LUT filter effect instance | | `engine.block.setString(effect, 'effect/lut_filter/lutFileURI', uri)` | Set the LUT image URL | | `engine.block.setInt(effect, 'effect/lut_filter/horizontalTileCount', count)` | Set horizontal tile count | | `engine.block.setInt(effect, 'effect/lut_filter/verticalTileCount', count)` | Set vertical tile count | | `engine.block.setFloat(effect, 'effect/lut_filter/intensity', value)` | Set filter intensity (0.0-1.0) | | `engine.block.appendEffect(block, effect)` | Apply effect to a block | | `engine.block.getEffects(block)` | Get all effects on a block | | `engine.block.setEffectEnabled(effect, enabled)` | Enable or disable an effect | | `engine.block.isEffectEnabled(effect)` | Check if effect is enabled | | `engine.block.removeEffect(block, index)` | Remove effect at index | | `engine.block.destroy(effect)` | Destroy an effect instance | | `engine.block.supportsEffects(block)` | Check if block supports effects | | `cesdk.ui.registerComponent(id, renderFn)` | Register a custom UI component | | `cesdk.ui.insertNavigationBarOrderComponent(matcher, id)` | Add a component to the navigation bar | ## Next Steps[#](#next-steps) * [Create Custom Filters](vue/filters-and-effects/create-custom-filters-c796ba/) \- Register custom LUT filters as asset sources * [Apply Filters and Effects](vue/filters-and-effects/apply-2764e4/) \- Learn more about the effects system * [Duotone Effects](vue/filters-and-effects/duotone-831fc5/) \- Create two-color artistic treatments --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/create-custom-filters-c796ba) --- # Create Custom Filters Extend CE.SDK with your own LUT filters by creating and registering custom filter asset sources for brand-specific color grading. ![Create Custom Filters example showing images with custom filters applied](/docs/cesdk/_astro/browser.hero.DNwiHTNZ_Z26y00f.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-add-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-add-browser) CE.SDK provides built-in LUT filters, but many applications need brand-specific color grading or custom filter collections. Custom filter asset sources let you register your own LUT filters that appear alongside or replace the defaults. Once registered, custom filters integrate seamlessly with the built-in UI and can be applied programmatically. ``` import type { EditorPlugin, EditorPluginContext, AssetSource, AssetQueryData, AssetsQueryResult, AssetResult} from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Create Custom Filters Guide * * Demonstrates creating and registering custom LUT filter asset sources: * - Creating a filter source with addSource() * - Defining filter assets with LUT metadata * - Loading filters from JSON configuration * - Querying custom filters * - Applying filters from custom 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'); } // Initialize CE.SDK with Design mode and load default assets await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Enable filters in the inspector panel using the Feature API cesdk.feature.enable('ly.img.filter'); // Add a custom filter to the built-in LUT filter source // The ID must follow the format //ly.img.cesdk.filters.lut/{name} // for the UI to display the label correctly engine.asset.addAssetToSource('ly.img.filter.lut', { id: '//ly.img.cesdk.filters.lut/mycustomfilter', label: { en: 'MY CUSTOM FILTER' }, tags: { en: ['custom', 'brand'] }, meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }); // Add translation for the custom filter label cesdk.i18n.setTranslations({ en: { 'property.lutFilter.mycustomfilter': 'MY CUSTOM FILTER' } }); // Create a custom filter asset source for organizing multiple filters const customFilterSource: AssetSource = { id: 'my-custom-filters', async findAssets( queryData: AssetQueryData ): Promise { // Define custom LUT filter assets const filters: AssetResult[] = [ { id: 'vintage-warm', label: 'Vintage Warm', tags: ['vintage', 'warm', 'retro'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'cool-cinema', label: 'Cool Cinema', tags: ['cinema', 'cool', 'film'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'bw-classic', label: 'B&W Classic', tags: ['black and white', 'classic', 'monochrome'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } } ]; // Filter by query if provided let filteredAssets = filters; if (queryData.query) { const searchTerm = queryData.query.toLowerCase(); filteredAssets = filters.filter( (asset) => asset.label?.toLowerCase().includes(searchTerm) || asset.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ); } // Filter by groups if provided if (queryData.groups && queryData.groups.length > 0) { filteredAssets = filteredAssets.filter((asset) => asset.tags?.some((tag) => queryData.groups?.includes(tag)) ); } // Handle pagination const page = queryData.page ?? 0; const perPage = queryData.perPage ?? 10; const startIndex = page * perPage; const paginatedAssets = filteredAssets.slice( startIndex, startIndex + perPage ); return { assets: paginatedAssets, total: filteredAssets.length, currentPage: page, nextPage: startIndex + perPage < filteredAssets.length ? page + 1 : undefined }; }, // Return available filter categories async getGroups(): Promise { return ['vintage', 'cinema', 'black and white']; } }; // Register the custom filter source for programmatic access engine.asset.addSource(customFilterSource); // Load filters from a JSON configuration string const filterConfigJSON = JSON.stringify({ version: '2.0.0', id: 'my-json-filters', assets: [ { id: 'sunset-glow', label: { en: 'Sunset Glow' }, tags: { en: ['warm', 'sunset', 'golden'] }, groups: ['Warm Tones'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'ocean-breeze', label: { en: 'Ocean Breeze' }, tags: { en: ['cool', 'blue', 'ocean'] }, groups: ['Cool Tones'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } } ] }); // Create asset source from JSON string const jsonSourceId = await engine.asset.addLocalAssetSourceFromJSONString(filterConfigJSON); console.log('Created JSON-based filter source:', jsonSourceId); // Query filters from our custom source for programmatic use const customFilterResults = await engine.asset.findAssets( 'my-custom-filters', { page: 0, perPage: 10 } ); // Create an image block to demonstrate applying a custom filter const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri, { x: 50, y: 150, size: { width: 300, height: 200 } }); engine.block.appendChild(page, imageBlock); // Get a filter from our custom source const filterAsset = customFilterResults.assets[0]; if (filterAsset && filterAsset.meta) { // Create and configure the LUT filter effect const lutEffect = engine.block.createEffect( '//ly.img.ubq/effect/lut_filter' ); // Set LUT file URI from asset metadata engine.block.setString( lutEffect, 'effect/lut_filter/lutFileURI', filterAsset.meta.uri as string ); // Configure LUT grid dimensions engine.block.setInt( lutEffect, 'effect/lut_filter/horizontalTileCount', parseInt(filterAsset.meta.horizontalTileCount as string, 10) ); engine.block.setInt( lutEffect, 'effect/lut_filter/verticalTileCount', parseInt(filterAsset.meta.verticalTileCount as string, 10) ); // Set filter intensity (0.0 to 1.0) engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.85); // Apply the effect to the image block engine.block.appendEffect(imageBlock, lutEffect); } // Create a second image without a filter for comparison const imageBlock2 = await engine.block.addImage(imageUri, { x: 450, y: 150, size: { width: 300, height: 200 } }); engine.block.appendChild(page, imageBlock2); // Select the filtered image to show the filter in the inspector engine.block.select(imageBlock); console.log( 'Custom filters guide initialized. Select an image to see filters in the inspector panel.' ); }} export default Example; ``` This guide covers how to create filter asset sources, define filter metadata, load filters from JSON configuration, and apply custom filters to design elements. ## Filter Asset Metadata[#](#filter-asset-metadata) LUT filters need these properties in the `meta` object: * **`uri`** - URL to the LUT image file (PNG format) * **`thumbUri`** - URL to the thumbnail preview image * **`horizontalTileCount`** - Number of horizontal tiles in the LUT grid (typically 5 or 8) * **`verticalTileCount`** - Number of vertical tiles in the LUT grid (typically 5 or 8) * **`blockType`** - Must be `//ly.img.ubq/effect/lut_filter` for LUT filters ## Adding a Custom Filter[#](#adding-a-custom-filter) We register a custom filter source using `engine.asset.addSource()` with a `findAssets` callback. This callback returns filter assets matching the query parameters. After registering the source, we use `cesdk.ui.updateAssetLibraryEntry()` to add our custom source to the filter inspector panel. ``` // Add a custom filter to the built-in LUT filter source// The ID must follow the format //ly.img.cesdk.filters.lut/{name}// for the UI to display the label correctlyengine.asset.addAssetToSource('ly.img.filter.lut', { id: '//ly.img.cesdk.filters.lut/mycustomfilter', label: { en: 'MY CUSTOM FILTER' }, tags: { en: ['custom', 'brand'] }, meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' }}); // Add translation for the custom filter labelcesdk.i18n.setTranslations({ en: { 'property.lutFilter.mycustomfilter': 'MY CUSTOM FILTER' }}); // Create a custom filter asset source for organizing multiple filtersconst customFilterSource: AssetSource = { id: 'my-custom-filters', async findAssets( queryData: AssetQueryData ): Promise { // Define custom LUT filter assets const filters: AssetResult[] = [ { id: 'vintage-warm', label: 'Vintage Warm', tags: ['vintage', 'warm', 'retro'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'cool-cinema', label: 'Cool Cinema', tags: ['cinema', 'cool', 'film'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'bw-classic', label: 'B&W Classic', tags: ['black and white', 'classic', 'monochrome'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } } ]; // Filter by query if provided let filteredAssets = filters; if (queryData.query) { const searchTerm = queryData.query.toLowerCase(); filteredAssets = filters.filter( (asset) => asset.label?.toLowerCase().includes(searchTerm) || asset.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ); } // Filter by groups if provided if (queryData.groups && queryData.groups.length > 0) { filteredAssets = filteredAssets.filter((asset) => asset.tags?.some((tag) => queryData.groups?.includes(tag)) ); } // Handle pagination const page = queryData.page ?? 0; const perPage = queryData.perPage ?? 10; const startIndex = page * perPage; const paginatedAssets = filteredAssets.slice( startIndex, startIndex + perPage ); return { assets: paginatedAssets, total: filteredAssets.length, currentPage: page, nextPage: startIndex + perPage < filteredAssets.length ? page + 1 : undefined }; }, // Return available filter categories async getGroups(): Promise { return ['vintage', 'cinema', 'black and white']; }}; // Register the custom filter source for programmatic accessengine.asset.addSource(customFilterSource); ``` The `findAssets` callback receives query parameters including pagination (`page`, `perPage`), search terms (`query`), and category filters (`groups`). We filter and paginate the results accordingly. The `updateAssetLibraryEntry()` call connects our custom source to the `ly.img.filter.lut` panel, making our filters appear alongside the built-in LUT filters when a user selects an image. ### Filter Asset Structure[#](#filter-asset-structure) Each filter asset returned by `findAssets` needs: * **`id`** - Unique identifier for the filter * **`label`** - Display name shown in the UI * **`tags`** - Keywords for search filtering * **`meta`** - Object containing LUT configuration (uri, thumbUri, tile counts, blockType) The optional `getGroups()` method returns available filter categories for the UI. ## Loading Filters from JSON Configuration[#](#loading-filters-from-json-configuration) For larger filter collections, we load definitions from JSON using `engine.asset.addLocalAssetSourceFromJSONString()`. This approach simplifies management of filter libraries. ``` // Load filters from a JSON configuration stringconst filterConfigJSON = JSON.stringify({ version: '2.0.0', id: 'my-json-filters', assets: [ { id: 'sunset-glow', label: { en: 'Sunset Glow' }, tags: { en: ['warm', 'sunset', 'golden'] }, groups: ['Warm Tones'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'ocean-breeze', label: { en: 'Ocean Breeze' }, tags: { en: ['cool', 'blue', 'ocean'] }, groups: ['Cool Tones'], meta: { uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } } ]}); // Create asset source from JSON stringconst jsonSourceId = await engine.asset.addLocalAssetSourceFromJSONString(filterConfigJSON);console.log('Created JSON-based filter source:', jsonSourceId); ``` ### JSON Structure for Filter Assets[#](#json-structure-for-filter-assets) The JSON format includes: * **`version`** - Schema version (use “2.0.0”) * **`id`** - Unique source identifier * **`assets`** - Array of filter definitions Each asset in the array contains: * **`id`** - Unique filter identifier * **`label`** - Localized label object (e.g., `{ "en": "Filter Name" }`) * **`tags`** - Localized tags for search * **`groups`** - Category assignments for UI organization * **`meta`** - LUT configuration properties For filters hosted on a CDN, use `engine.asset.addLocalAssetSourceFromJSONURI()` instead, which resolves relative URLs against the JSON file’s parent directory. ## Troubleshooting[#](#troubleshooting) ### Filters Not Appearing in UI[#](#filters-not-appearing-in-ui) * Verify the asset source is registered before loading the scene * Check that filter metadata includes all required fields (`uri`, `thumbUri`, tile counts) * Ensure the `blockType` is set to `//ly.img.ubq/effect/lut_filter` * Confirm thumbnails are accessible URLs ### LUT Not Rendering Correctly[#](#lut-not-rendering-correctly) * Verify tile count values match the actual LUT image grid dimensions * Check that the LUT image URL is CORS-enabled for cross-origin requests * Confirm the LUT image uses PNG format ### Filter Thumbnails Missing[#](#filter-thumbnails-missing) * Verify `thumbUri` points to an accessible image * Check that thumbnail URLs don’t have CORS restrictions * Ensure thumbnail dimensions are appropriate for UI display (typically 100-200px) ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `engine.asset.addSource(source)` | Register a custom asset source with findAssets callback | | `engine.asset.addLocalAssetSourceFromJSONString(json, basePath)` | Create asset source from inline JSON configuration | | `engine.asset.addLocalAssetSourceFromJSONURI(uri)` | Load asset source from remote JSON file | | `engine.asset.findAssets(sourceId, query)` | Query assets from a registered source | | `engine.asset.findAllSources()` | Get all registered asset source IDs | | `engine.asset.removeSource(id)` | Remove a registered asset source | | `cesdk.ui.updateAssetLibraryEntry(entryId, config)` | Add custom sources to filter inspector panel | | `engine.block.createEffect(type)` | Create effect instance (use `//ly.img.ubq/effect/lut_filter` for LUT filters) | | `engine.block.setString(effect, property, value)` | Set string property (LUT file URI) | | `engine.block.setInt(effect, property, value)` | Set integer property (tile counts) | | `engine.block.setFloat(effect, property, value)` | Set float property (intensity) | | `engine.block.appendEffect(block, effect)` | Add effect to block’s effect stack | ## Next Steps[#](#next-steps) Now that you understand how to create and register custom filter sources, explore related topics: * [Apply Filters and Effects](vue/filters-and-effects/apply-2764e4/) \- Learn to apply filters to design elements and manage effect stacks * [Create a Custom LUT Filter](vue/filters-and-effects/create-custom-lut-filter-6e3f49/) \- Understand LUT image format and create your own color grading filters * [Blur Effects](vue/filters-and-effects/blur-71d642/) \- Add blur effects to images and videos --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/chroma-key-green-screen-1e3e99) --- # Chroma Key (Green Screen) Replace specific colors with transparency using CE.SDK’s green screen effect for video compositing and virtual background applications. ![Chroma Key (Green Screen) example showing an image with color keying applied](/docs/cesdk/_astro/browser.hero.DH_s87lD_ZJ43yo.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-chroma-key-green-screen-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-chroma-key-green-screen-browser) The green screen effect (chroma key) replaces a specified color with transparency, enabling compositing workflows where foreground subjects appear over different backgrounds. While green is the most common key color due to its contrast with skin tones, the effect works with any solid color—blue screens, white backgrounds, or custom colors. CE.SDK processes chroma keying in real-time using GPU-accelerated shaders. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Chroma Key (Green Screen) Guide * * Demonstrates the green screen effect for color keying: * - Applying the green screen effect to an image * - Configuring the target color to key out * - Adjusting colorMatch, smoothness, and spill parameters * - Compositing with background layers * - Managing and toggling 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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Create an image block to apply the green screen effect const imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_4.jpg', { size: { width: 600, height: 450 } } ); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 100); engine.block.setPositionY(imageBlock, 75); // Create the green screen effect const greenScreenEffect = engine.block.createEffect('green_screen'); // Apply the effect to the image block engine.block.appendEffect(imageBlock, greenScreenEffect); // Set the target color to key out // Use off-white to remove the bright sky background // For traditional green screen footage, use { r: 0.0, g: 1.0, b: 0.0, a: 1.0 } engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', { r: 0.98, g: 0.98, b: 0.98, a: 1.0 }); // Adjust color matching tolerance // Higher values (closer to 1.0) key out more color variations // Lower values create more precise keying engine.block.setFloat( greenScreenEffect, 'effect/green_screen/colorMatch', 0.26 ); // Control edge smoothness for natural transitions // Higher values create softer edges that blend with backgrounds engine.block.setFloat( greenScreenEffect, 'effect/green_screen/smoothness', 1.0 ); // Remove color spill from reflective surfaces // Reduces color tint on edges near the keyed background engine.block.setFloat(greenScreenEffect, 'effect/green_screen/spill', 1.0); // Create a background layer for compositing const backgroundBlock = engine.block.create('graphic'); const backgroundShape = engine.block.createShape('rect'); engine.block.setShape(backgroundBlock, backgroundShape); // Create a solid color fill for the background const backgroundFill = engine.block.createFill('color'); engine.block.setColor(backgroundFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }); engine.block.setFill(backgroundBlock, backgroundFill); // Size and position the background to cover the page engine.block.setWidth(backgroundBlock, 800); engine.block.setHeight(backgroundBlock, 600); engine.block.setPositionX(backgroundBlock, 0); engine.block.setPositionY(backgroundBlock, 0); // Add to page and send to back engine.block.appendChild(page, backgroundBlock); engine.block.sendToBack(backgroundBlock); // Bring the keyed image to front engine.block.bringToFront(imageBlock); // Check if the effect is currently enabled const isEnabled = engine.block.isEffectEnabled(greenScreenEffect); console.log('Green screen effect enabled:', isEnabled); // Toggle the effect on or off engine.block.setEffectEnabled(greenScreenEffect, !isEnabled); console.log( 'Effect toggled:', engine.block.isEffectEnabled(greenScreenEffect) ); // Re-enable for demonstration engine.block.setEffectEnabled(greenScreenEffect, true); // Check if the block supports effects const supportsEffects = engine.block.supportsEffects(imageBlock); console.log('Block supports effects:', supportsEffects); // Get all effects applied to the block const effects = engine.block.getEffects(imageBlock); console.log('Number of effects:', effects.length); // Remove the effect from the block (by index) engine.block.removeEffect(imageBlock, 0); console.log('Effect removed from block'); // Destroy the effect instance to free resources engine.block.destroy(greenScreenEffect); console.log('Effect destroyed'); // Re-apply the effect for final display const finalEffect = engine.block.createEffect('green_screen'); engine.block.appendEffect(imageBlock, finalEffect); engine.block.setColor(finalEffect, 'effect/green_screen/fromColor', { r: 0.98, g: 0.98, b: 0.98, a: 1.0 }); engine.block.setFloat(finalEffect, 'effect/green_screen/colorMatch', 0.26); engine.block.setFloat(finalEffect, 'effect/green_screen/smoothness', 1.0); engine.block.setFloat(finalEffect, 'effect/green_screen/spill', 1.0); // Select the image block so the inspector panel shows it engine.block.setSelected(imageBlock, true); console.log( 'Chroma key guide initialized. Select the image to see effect parameters.' ); }} export default Example; ``` This guide covers how to apply the green screen effect programmatically, configure color selection and keying parameters, composite with background layers, and manage effects on blocks. ## Apply the Green Screen Effect[#](#apply-the-green-screen-effect) We start by creating an image block and applying the green screen effect to it. The effect immediately processes the target color, making matching pixels transparent. ``` // Create an image block to apply the green screen effectconst imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_4.jpg', { size: { width: 600, height: 450 } });engine.block.appendChild(page, imageBlock);engine.block.setPositionX(imageBlock, 100);engine.block.setPositionY(imageBlock, 75); // Create the green screen effectconst greenScreenEffect = engine.block.createEffect('green_screen'); // Apply the effect to the image blockengine.block.appendEffect(imageBlock, greenScreenEffect); ``` The `createEffect('green_screen')` method creates a new green screen effect instance. We then attach it to the image block using `appendEffect()`, which adds the effect to the block’s effect stack. ## Configure Color Selection[#](#configure-color-selection) The green screen effect targets a specific color to key out. We set this color using `setColor()` with the `effect/green_screen/fromColor` property. ``` // Set the target color to key out// Use off-white to remove the bright sky background// For traditional green screen footage, use { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', { r: 0.98, g: 0.98, b: 0.98, a: 1.0}); ``` The example uses off-white (`r: 0.98, g: 0.98, b: 0.98`) to key out a bright sky background. For traditional green screen footage, use pure green (`r: 0.0, g: 1.0, b: 0.0`). For blue screen footage, set the color to pure blue. Match the exact color you want to remove for best results. ## Adjust Color Matching Tolerance[#](#adjust-color-matching-tolerance) The `colorMatch` parameter controls how closely pixels must match the target color to be keyed out. We adjust this using `setFloat()`. ``` // Adjust color matching tolerance// Higher values (closer to 1.0) key out more color variations// Lower values create more precise keyingengine.block.setFloat( greenScreenEffect, 'effect/green_screen/colorMatch', 0.26); ``` Higher values (closer to 1.0) key out a wider range of similar colors, which is useful for footage with uneven lighting or color variations in the background. Lower values create more precise keying for well-lit footage with uniform backgrounds. ## Control Edge Smoothness[#](#control-edge-smoothness) The `smoothness` parameter controls the transition between opaque and transparent areas. This affects how sharp or soft the edges appear around keyed subjects. ``` // Control edge smoothness for natural transitions// Higher values create softer edges that blend with backgroundsengine.block.setFloat( greenScreenEffect, 'effect/green_screen/smoothness', 1.0); ``` Higher smoothness values create softer edges that blend naturally with new backgrounds, reducing harsh outlines. Lower values produce sharper edges, which may be preferable for high-contrast composites or when preserving fine detail. ## Remove Color Spill[#](#remove-color-spill) Color spill occurs when the key color reflects onto the foreground subject, creating a green or blue tint on edges. The `spill` parameter reduces this color cast. ``` // Remove color spill from reflective surfaces// Reduces color tint on edges near the keyed backgroundengine.block.setFloat(greenScreenEffect, 'effect/green_screen/spill', 1.0); ``` Increase the spill value when you notice the key color appearing on subject edges or reflective surfaces. This is common with shiny hair, glasses, or metallic objects near the screen. ## Composite with Background Layers[#](#composite-with-background-layers) After keying, we can layer the transparent content over backgrounds using block ordering. We create a background block and use `sendToBack()` to place it behind the keyed image. ``` // Create a background layer for compositingconst backgroundBlock = engine.block.create('graphic');const backgroundShape = engine.block.createShape('rect');engine.block.setShape(backgroundBlock, backgroundShape); // Create a solid color fill for the backgroundconst backgroundFill = engine.block.createFill('color');engine.block.setColor(backgroundFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 1.0});engine.block.setFill(backgroundBlock, backgroundFill); // Size and position the background to cover the pageengine.block.setWidth(backgroundBlock, 800);engine.block.setHeight(backgroundBlock, 600);engine.block.setPositionX(backgroundBlock, 0);engine.block.setPositionY(backgroundBlock, 0); // Add to page and send to backengine.block.appendChild(page, backgroundBlock);engine.block.sendToBack(backgroundBlock); // Bring the keyed image to frontengine.block.bringToFront(imageBlock); ``` The background appears through the transparent areas where the key color was removed. You can use image or video fills instead of solid colors for more dynamic backgrounds. ## Toggle the Effect[#](#toggle-the-effect) We can check whether an effect is enabled using `isEffectEnabled()`. ``` // Check if the effect is currently enabledconst isEnabled = engine.block.isEffectEnabled(greenScreenEffect); ``` To toggle the effect on or off, use `setEffectEnabled()`. This preserves the effect configuration while temporarily removing its visual impact. ``` // Toggle the effect on or offengine.block.setEffectEnabled(greenScreenEffect, !isEnabled); ``` Toggling effects is useful for before/after comparisons or conditional processing without removing and recreating the effect. ## Manage the Effect[#](#manage-the-effect) Beyond toggling, you can query, remove, and clean up effects. Use `supportsEffects()` to check if a block can have effects, `getEffects()` to list all applied effects, `removeEffect()` to detach an effect from a block, and `destroy()` to free the effect’s resources. ``` // Check if the block supports effectsconst supportsEffects = engine.block.supportsEffects(imageBlock);console.log('Block supports effects:', supportsEffects); // Get all effects applied to the blockconst effects = engine.block.getEffects(imageBlock);console.log('Number of effects:', effects.length); // Remove the effect from the block (by index)engine.block.removeEffect(imageBlock, 0);console.log('Effect removed from block'); // Destroy the effect instance to free resourcesengine.block.destroy(greenScreenEffect);console.log('Effect destroyed'); ``` When removing effects, use the index from `getEffects()` to specify which effect to remove. After removing an effect from a block, call `destroy()` on the effect instance to release its resources. This is important for memory management in long-running applications. ## Troubleshooting[#](#troubleshooting) ### Keying Results Appear Rough or Incomplete[#](#keying-results-appear-rough-or-incomplete) * Increase `colorMatch` value to capture more color variations * Ensure source footage has even lighting on the screen * Check that the target color accurately matches the screen color ### Edges Have Color Fringing[#](#edges-have-color-fringing) * Increase `spill` value to remove color cast * Adjust `smoothness` to soften hard edges * Consider using a higher `colorMatch` for gradual color transitions ### Transparent Areas Appear in Wrong Places[#](#transparent-areas-appear-in-wrong-places) * Decrease `colorMatch` to be more selective about which colors are keyed * Verify the `fromColor` matches only the intended background color * Check that foreground subjects don’t contain colors similar to the key color ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `block.createEffect('green_screen')` | Create a green screen effect instance | | `block.appendEffect(block, effect)` | Add effect to a block’s effect stack | | `block.setColor(effect, 'effect/green_screen/fromColor', color)` | Set the color to key out | | `block.setFloat(effect, 'effect/green_screen/colorMatch', value)` | Set color matching tolerance (0.0-1.0) | | `block.setFloat(effect, 'effect/green_screen/smoothness', value)` | Set edge smoothness (0.0-1.0) | | `block.setFloat(effect, 'effect/green_screen/spill', value)` | Set spill removal intensity (0.0-1.0) | | `block.isEffectEnabled(effect)` | Check if an effect is enabled | | `block.setEffectEnabled(effect, enabled)` | Enable or disable an effect | | `block.supportsEffects(block)` | Check if a block supports effects | | `block.getEffects(block)` | Get all effects applied to a block | | `block.removeEffect(block, index)` | Remove effect at specified index | | `block.destroy(effect)` | Destroy an effect instance | ## Next Steps[#](#next-steps) * [Apply Filters and Effects](vue/filters-and-effects/apply-2764e4/) \- Learn the fundamentals of the effect system * [Blur](vue/filters-and-effects/blur-71d642/) \- Add blur effects for depth of field * [Duotone](vue/filters-and-effects/duotone-831fc5/) \- Create two-color artistic treatments --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/blur-71d642) --- # Blur Effects Apply blur effects to design elements using CE.SDK’s dedicated blur system for creating depth, focus, and atmospheric effects. ![Blur Effects example showing an image with radial blur applied](/docs/cesdk/_astro/browser.hero.BkuQI2Z5_OxM9.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-blur-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-blur-browser) Unlike general effects that stack on elements, blur is a dedicated feature with its own API methods. Each block supports exactly one blur at a time, though the same blur instance can be shared across multiple blocks. CE.SDK provides four blur types: **uniform** for consistent softening, **linear** and **mirrored** for gradient-based effects along axes, and **radial** for circular focal points. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; class BlurPlugin implements EditorPlugin { name = 'BlurPlugin'; version = '1.0.0'; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Initialize scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design' }); await cesdk.createDesignScene(); const page = engine.block.findByType('page')[0]; // Get page dimensions to position content correctly const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); if (!engine.block.supportsBlur(page)) { console.log('Block does not support blur'); return; } // Create an image block const imageBlock = engine.block.create('graphic'); engine.block.setShape(imageBlock, engine.block.createShape('rect')); const imageFill = engine.block.createFill('image'); engine.block.setFill(imageBlock, imageFill); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); // Position image to fill the page engine.block.setWidth(imageBlock, pageWidth); engine.block.setHeight(imageBlock, pageHeight); engine.block.setPositionX(imageBlock, 0); engine.block.setPositionY(imageBlock, 0); engine.block.appendChild(page, imageBlock); const blur = engine.block.createBlur('//ly.img.ubq/blur/radial'); engine.block.setFloat(blur, 'blur/radial/blurRadius', 40); engine.block.setFloat(blur, 'blur/radial/radius', 100); engine.block.setFloat(blur, 'blur/radial/gradientRadius', 80); engine.block.setFloat(blur, 'blur/radial/x', 0.5); engine.block.setFloat(blur, 'blur/radial/y', 0.5); engine.block.setBlur(imageBlock, blur); engine.block.setBlurEnabled(imageBlock, true); const appliedBlur = engine.block.getBlur(imageBlock); const isEnabled = engine.block.isBlurEnabled(imageBlock); const blurType = engine.block.getType(appliedBlur); console.log('Blur type:', blurType, 'Enabled:', isEnabled); engine.block.setBlurEnabled(imageBlock, false); const nowEnabled = engine.block.isBlurEnabled(imageBlock); console.log('Blur now enabled:', nowEnabled); engine.block.setBlurEnabled(imageBlock, true); }} export default BlurPlugin; ``` This guide covers how to apply blur effects programmatically using the block API. ## Programmatic Blur Application[#](#programmatic-blur-application) ### Check Blur Support[#](#check-blur-support) Before applying blur to a block, verify it supports blur effects. Graphic blocks with shapes and pages support blur. ``` if (!engine.block.supportsBlur(page)) { console.log('Block does not support blur'); return;} ``` Always check support before creating and applying blur to avoid errors. ### Create and Apply Blur[#](#create-and-apply-blur) Create a blur instance using `createBlur()` with a blur type, then attach it to a block using `setBlur()`. Enable the blur with `setBlurEnabled()`. ``` const blur = engine.block.createBlur('//ly.img.ubq/blur/radial'); ``` CE.SDK provides four blur types: * **`//ly.img.ubq/blur/uniform`** - Even softening across the entire element * **`//ly.img.ubq/blur/linear`** - Gradient blur along a line defined by two control points * **`//ly.img.ubq/blur/mirrored`** - Band of focus with blur on both sides (tilt-shift style) * **`//ly.img.ubq/blur/radial`** - Circular blur pattern from a center point Omitting the prefix is also accepted, e.g., `'radial'` instead of `'//ly.img.ubq/blur/radial'`. ### Configure Blur Parameters[#](#configure-blur-parameters) Each blur type has specific parameters to control its appearance. Configure them using `setFloat()`. ``` engine.block.setFloat(blur, 'blur/radial/blurRadius', 40);engine.block.setFloat(blur, 'blur/radial/radius', 100);engine.block.setFloat(blur, 'blur/radial/gradientRadius', 80);engine.block.setFloat(blur, 'blur/radial/x', 0.5);engine.block.setFloat(blur, 'blur/radial/y', 0.5); ``` **Radial blur parameters:** * `blur/radial/blurRadius` - Blur intensity (default: 30) * `blur/radial/radius` - Size of the non-blurred center area (default: 75) * `blur/radial/gradientRadius` - Size of the blur transition zone (default: 50) * `blur/radial/x` - Center point x-value, 0.0 to 1.0 (default: 0.5) * `blur/radial/y` - Center point y-value, 0.0 to 1.0 (default: 0.5) **Uniform blur parameters:** * `blur/uniform/intensity` - Blur strength, 0.0 to 1.0 (default: 0.5) **Linear blur parameters:** * `blur/linear/blurRadius` - Blur intensity (default: 30) * `blur/linear/x1`, `blur/linear/y1` - Control point 1 (default: 0, 0.5) * `blur/linear/x2`, `blur/linear/y2` - Control point 2 (default: 1, 0.5) **Mirrored blur parameters:** * `blur/mirrored/blurRadius` - Blur intensity (default: 30) * `blur/mirrored/gradientSize` - Hardness of gradient transition (default: 50) * `blur/mirrored/size` - Size of the blurred area (default: 75) * `blur/mirrored/x1`, `blur/mirrored/y1` - Control point 1 (default: 0, 0.5) * `blur/mirrored/x2`, `blur/mirrored/y2` - Control point 2 (default: 1, 0.5) ### Apply Blur to Block[#](#apply-blur-to-block) After configuring the blur, apply it to the target block and enable it. ``` engine.block.setBlur(imageBlock, blur);engine.block.setBlurEnabled(imageBlock, true); ``` The blur takes effect immediately once enabled. You can modify parameters at any time and changes apply in real-time. ## Managing Blur[#](#managing-blur) ### Access Existing Blur[#](#access-existing-blur) Retrieve the blur applied to a block using `getBlur()`. You can then read or modify its properties. ``` const appliedBlur = engine.block.getBlur(imageBlock);const isEnabled = engine.block.isBlurEnabled(imageBlock);const blurType = engine.block.getType(appliedBlur);console.log('Blur type:', blurType, 'Enabled:', isEnabled); ``` ### Enable/Disable Blur[#](#enabledisable-blur) Toggle blur on and off without removing it using `setBlurEnabled()`. This preserves all blur parameters for quick before/after comparisons. ``` engine.block.setBlurEnabled(imageBlock, false);const nowEnabled = engine.block.isBlurEnabled(imageBlock);console.log('Blur now enabled:', nowEnabled);engine.block.setBlurEnabled(imageBlock, true); ``` When disabled, the blur remains attached to the block but doesn’t render until re-enabled. ### Share Blur Across Blocks[#](#share-blur-across-blocks) A single blur instance can be applied to multiple blocks. Create the blur once, then assign it to each block with `setBlur()`. ``` const sharedBlur = engine.block.createBlur('//ly.img.ubq/blur/uniform');engine.block.setFloat(sharedBlur, 'blur/uniform/intensity', 0.4); engine.block.setBlur(block1, sharedBlur);engine.block.setBlur(block2, sharedBlur);engine.block.setBlurEnabled(block1, true);engine.block.setBlurEnabled(block2, true); ``` Changes to the shared blur affect all blocks using it. ### Replace Blur[#](#replace-blur) To change the blur type on a block, create a new blur and assign it with `setBlur()`. The previous blur association is automatically removed. ``` const newBlur = engine.block.createBlur('//ly.img.ubq/blur/linear');engine.block.setBlur(block, newBlur);engine.block.setBlurEnabled(block, true); ``` If the old blur isn’t used elsewhere, destroy it with `engine.block.destroy(oldBlur)`. ## Troubleshooting[#](#troubleshooting) ### Blur Not Visible[#](#blur-not-visible) If blur doesn’t appear after applying: * Check the block supports blur with `supportsBlur()` * Verify blur is enabled with `isBlurEnabled()` * Ensure the blur instance is valid ### Blur Appears on Wrong Area[#](#blur-appears-on-wrong-area) For radial, linear, and mirrored blurs: * Verify control point coordinates are within 0.0 to 1.0 range * Check that x/y values match your intended focus area ### Blur Too Subtle or Too Strong[#](#blur-too-subtle-or-too-strong) * Increase or decrease `blurRadius` or `intensity` values * For radial blur, adjust `gradientRadius` to control the transition softness ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `block.createBlur(type)` | Create new blur instance | | `block.supportsBlur(block)` | Check if block supports blur | | `block.setBlur(block, blur)` | Apply blur to block | | `block.getBlur(block)` | Get blur from block | | `block.setBlurEnabled(block, enabled)` | Enable or disable blur | | `block.isBlurEnabled(block)` | Check if blur is enabled | | `block.setFloat(blur, property, value)` | Set blur float property | | `block.getFloat(blur, property)` | Get blur float property | | `block.getType(blur)` | Get blur type identifier | | `block.destroy(blur)` | Destroy unused blur instance | --- [Source](https:/img.ly/docs/cesdk/vue/filters-and-effects/apply-2764e4) --- # Apply Filters and Effects Apply professional color grading, blur effects, and artistic treatments to design elements using CE.SDK’s visual effects system. ![Apply Filters and Effects example showing images with various effects applied](/docs/cesdk/_astro/browser.hero.DS7M9OMT_Z2tR1OE.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-apply-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-apply-browser) While CE.SDK uses a unified effect API for both filters and effects, they serve different purposes. **Filters** typically apply color transformations like LUT filters and duotone, while **effects** apply visual modifications such as blur, pixelize, vignette, and image adjustments. You can combine multiple effects on a single element, creating complex visual treatments by stacking them in a customizable order. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json';import { calculateGridLayout, hexToRgba } from './utils'; /** * CE.SDK Plugin: Filters and Effects Guide * * Demonstrates applying various filters and effects to image blocks: * - Checking effect support * - Applying basic effects (blur) * - Configuring effect parameters (adjustments) * - Applying LUT filters * - Combining multiple effects * - Managing effect stacks */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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Enable effects and filters in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); // Enable all effects cesdk.feature.enable('ly.img.filter'); // Enable all filters cesdk.feature.enable('ly.img.blur'); // Enable blur effect cesdk.feature.enable('ly.img.adjustment'); // Enable adjustments // Calculate responsive grid layout based on page dimensions const layout = calculateGridLayout(pageWidth, pageHeight, 9); const { blockWidth, blockHeight, getPosition } = layout; // Use a sample image URL (this will load from demo assets) const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Query available LUT and Duotone filters from asset sources // These filters are provided by the demo asset sources loaded above const lutResults = await engine.asset.findAssets('ly.img.filter.lut', { page: 0, perPage: 10 }); const duotoneResults = await engine.asset.findAssets( 'ly.img.filter.duotone', { page: 0, perPage: 10 } ); const lutAssets = lutResults.assets; const duotoneAssets = duotoneResults.assets; // Pattern #2: Use Convenience APIs - addImage() simplifies block creation // Create a sample block to demonstrate effect support checking const blockSize = { width: blockWidth, height: blockHeight }; const sampleBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, sampleBlock); // Check if a block supports effects const supportsEffects = engine.block.supportsEffects(sampleBlock); console.log('Block supports effects:', supportsEffects); // true for graphics // Page blocks don't support effects const pageSupportsEffects = engine.block.supportsEffects(page); console.log('Page supports effects:', pageSupportsEffects); // false // Select this block so effects panel is visible engine.block.setSelected(sampleBlock, true); // Pattern #1: Demonstrate Individual Before Combined // Create a separate image block for blur demonstration const blurImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, blurImageBlock); // Create and apply a blur effect const blurEffect = engine.block.createEffect('extrude_blur'); engine.block.appendEffect(blurImageBlock, blurEffect); // Adjust blur intensity engine.block.setFloat(blurEffect, 'effect/extrude_blur/amount', 0.5); // Create a separate image block for adjustments demonstration const adjustmentsImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, adjustmentsImageBlock); // Create adjustments effect for brightness and contrast const adjustmentsEffect = engine.block.createEffect('adjustments'); engine.block.appendEffect(adjustmentsImageBlock, adjustmentsEffect); // Find all available properties for this effect const adjustmentProperties = engine.block.findAllProperties(adjustmentsEffect); console.log('Available adjustment properties:', adjustmentProperties); // Set brightness, contrast, and saturation engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/brightness', 0.2 ); engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/contrast', 0.15 ); engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/saturation', 0.1 ); // Demonstrate LUT filters by applying the first 2 from asset library // These filters are fetched from the demo asset sources (Grid positions 3-4) const lutImageBlocks = []; for (let i = 0; i < Math.min(2, lutAssets.length); i++) { const lutAsset = lutAssets[i]; const lutImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, lutImageBlock); lutImageBlocks.push(lutImageBlock); // Create LUT filter effect using the full effect type URI const lutEffect = engine.block.createEffect( '//ly.img.ubq/effect/lut_filter' ); // Use asset metadata for LUT configuration // The asset provides the LUT file URI and grid dimensions engine.block.setString( lutEffect, 'effect/lut_filter/lutFileURI', lutAsset.meta?.uri as string ); engine.block.setInt( lutEffect, 'effect/lut_filter/horizontalTileCount', parseInt(lutAsset.meta?.horizontalTileCount as string, 10) ); engine.block.setInt( lutEffect, 'effect/lut_filter/verticalTileCount', parseInt(lutAsset.meta?.verticalTileCount as string, 10) ); engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.85); engine.block.appendEffect(lutImageBlock, lutEffect); } // Demonstrate Duotone filters by applying the first 2 from asset library // Duotone filters create artistic two-color treatments (Grid positions 5-6) const duotoneImageBlocks = []; for (let i = 0; i < Math.min(2, duotoneAssets.length); i++) { const duotoneAsset = duotoneAssets[i]; const duotoneImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, duotoneImageBlock); duotoneImageBlocks.push(duotoneImageBlock); // Create Duotone filter effect using the full effect type URI const duotoneEffect = engine.block.createEffect( '//ly.img.ubq/effect/duotone_filter' ); // Convert hex colors from asset metadata to RGBA (0-1 range) const darkColor = hexToRgba(duotoneAsset.meta?.darkColor as string); engine.block.setColor( duotoneEffect, 'effect/duotone_filter/darkColor', darkColor ); const lightColor = hexToRgba(duotoneAsset.meta?.lightColor as string); engine.block.setColor( duotoneEffect, 'effect/duotone_filter/lightColor', lightColor ); engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.8 ); engine.block.appendEffect(duotoneImageBlock, duotoneEffect); } // Pattern #5: Progressive Complexity - now combining multiple effects // Create a separate image block to demonstrate combining multiple effects (Grid position 7) const combinedImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, combinedImageBlock); // Apply effects in order - the stack will contain: // 1. adjustments (brightness/contrast) - applied first // 2. blur - applied second // 3. duotone (color tinting) - applied third // 4. pixelize - applied last const combinedAdjustments = engine.block.createEffect('adjustments'); engine.block.appendEffect(combinedImageBlock, combinedAdjustments); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/brightness', 0.2 ); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/contrast', 0.15 ); const combinedBlur = engine.block.createEffect('extrude_blur'); engine.block.appendEffect(combinedImageBlock, combinedBlur); engine.block.setFloat(combinedBlur, 'effect/extrude_blur/amount', 0.3); const combinedDuotone = engine.block.createEffect('duotone_filter'); engine.block.appendEffect(combinedImageBlock, combinedDuotone); engine.block.setColor(combinedDuotone, 'duotone_filter/darkColor', { r: 0.1, g: 0.2, b: 0.4, a: 1.0 }); engine.block.setColor(combinedDuotone, 'duotone_filter/lightColor', { r: 0.9, g: 0.8, b: 0.6, a: 1.0 }); engine.block.setFloat(combinedDuotone, 'duotone_filter/intensity', 0.6); const pixelizeEffect = engine.block.createEffect('pixelize'); engine.block.appendEffect(combinedImageBlock, pixelizeEffect); engine.block.setInt(pixelizeEffect, 'pixelize/horizontalPixelSize', 8); engine.block.setInt(pixelizeEffect, 'pixelize/verticalPixelSize', 8); // Get all effects applied to the combined block const effects = engine.block.getEffects(combinedImageBlock); console.log('Applied effects:', effects); // Access properties of specific effects effects.forEach((effect, index) => { const effectType = engine.block.getType(effect); const isEnabled = engine.block.isEffectEnabled(effect); console.log(`Effect ${index}: ${effectType}, enabled: ${isEnabled}`); }); // Check if effect is enabled const isBlurEnabled = engine.block.isEffectEnabled(combinedBlur); console.log('Blur effect is enabled:', isBlurEnabled); // Create a temporary block to demonstrate effect removal const tempBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, tempBlock); const tempEffect = engine.block.createEffect('pixelize'); engine.block.appendEffect(tempBlock, tempEffect); engine.block.setInt(tempEffect, 'pixelize/horizontalPixelSize', 12); // Remove the effect 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); // ===== Position all blocks in grid layout ===== const blocks = [ sampleBlock, // Position 0 blurImageBlock, // Position 1 adjustmentsImageBlock, // Position 2 ...lutImageBlocks, // Positions 3-4 ...duotoneImageBlocks, // Positions 5-6 combinedImageBlock, // Position 7 tempBlock // Position 8 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Apply same effects to multiple blocks const allGraphics = engine.block.findByType('graphic'); allGraphics.forEach((graphic) => { if (engine.block.supportsEffects(graphic)) { // Only apply to blocks that don't already have effects const existingEffects = engine.block.getEffects(graphic); if (existingEffects.length === 0) { const effect = engine.block.createEffect('adjustments'); engine.block.appendEffect(graphic, effect); engine.block.setFloat(effect, 'effect/adjustments/brightness', 0.1); } } }); console.log( 'Effects guide initialized. Select any image to see effects panel.' ); }} export default Example; ``` This guide covers how to enable the built-in effects panel for interactive editing and how to apply and manage effects programmatically using the block API. ## Using the Built-in Effects UI[#](#using-the-built-in-effects-ui) ### Enable Effects Features[#](#enable-effects-features) To give users access to effects in the inspector panel, we enable the effects features using CE.SDK’s Feature API. Effects and filters appear in the **inspector bar** and **advanced inspector** when a user selects a supported element. ``` // Enable effects and filters in the inspector panel using the Feature APIcesdk.feature.enable('ly.img.effect'); // Enable all effectscesdk.feature.enable('ly.img.filter'); // Enable all filterscesdk.feature.enable('ly.img.blur'); // Enable blur effectcesdk.feature.enable('ly.img.adjustment'); // Enable adjustments ``` The Feature API controls which capabilities are available to users. By enabling `ly.img.effect` and `ly.img.filter`, the inspector panel displays effect and filter options when users select compatible blocks. You can also enable specific effects individually like `ly.img.blur` or `ly.img.adjustment` for more granular control. Effects are enabled by default for graphic blocks with image or video fills. The Feature API shown above allows you to control which specific effects appear in the inspector panel UI. ### User Workflow[#](#user-workflow) With effects features enabled, users can enhance their designs through a visual workflow in the inspector panel: 1. **Select an element** - Click on any image or supported graphic block in the canvas 2. **Access inspector** - The inspector panel shows available options for the selected element 3. **Find effects section** - Scroll to the effects and filters sections within the inspector 4. **Browse and apply** - Click through available effects to apply them 5. **Adjust parameters** - Use sliders and controls to fine-tune intensity and other effect properties 6. **Manage effects** - Toggle effects on/off, switch between effects, or reset effect parameters Effects are applied immediately when selected. CE.SDK does not currently support live preview mode when browsing effects before application. Effect reordering is not supported—use toggle on/off, switch, or reset operations to manage applied effects. This interactive approach is perfect for creative exploration and allows users to see results immediately without any coding knowledge. ## Programmatic Effect Application[#](#programmatic-effect-application) ### Initialize CE.SDK[#](#initialize-cesdk) For applications that need to apply effects programmatically—whether for automation, batch processing, or dynamic user experiences—we start by setting up CE.SDK with the proper configuration. ``` // Initialize CE.SDK with Design mode and load asset sourcesawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true});await cesdk.createDesignScene(); const engine = cesdk.engine;const page = engine.block.findByType('page')[0]; // Set page dimensionsengine.block.setWidth(page, 800);engine.block.setHeight(page, 600); const pageWidth = engine.block.getWidth(page);const pageHeight = engine.block.getHeight(page); ``` This initializes the full CE.SDK interface with the effects panel enabled, giving you both UI and API access to the effects system. ### Check Effect Support[#](#check-effect-support) Before applying effects to a block, we check whether it supports them. Not all block types can have effects applied—for example, page blocks and scene blocks do not support effects. ``` // Pattern #2: Use Convenience APIs - addImage() simplifies block creation// Create a sample block to demonstrate effect support checkingconst blockSize = { width: blockWidth, height: blockHeight };const sampleBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, sampleBlock); // Check if a block supports effectsconst supportsEffects = engine.block.supportsEffects(sampleBlock);console.log('Block supports effects:', supportsEffects); // true for graphics // Page blocks don't support effectsconst pageSupportsEffects = engine.block.supportsEffects(page);console.log('Page supports effects:', pageSupportsEffects); // false // Select this block so effects panel is visibleengine.block.setSelected(sampleBlock, true); ``` Effect support is available for: * **Graphic blocks** with image fills * **Graphic blocks** with video fills (with performance considerations) * **Shape blocks** with fills * **Text blocks** (with limited effect types) * **Page blocks** (particularly when they have fills applied, such as background fills) Always verify support before creating and applying effects to avoid errors and ensure a smooth user experience. ### Apply Basic Effects[#](#apply-basic-effects) Once we’ve confirmed a block supports effects, we can create and apply effects using the effect API. Here we create a separate image block using the convenience `addImage()` API and apply a blur effect to it. The example code uses the `engine.block.addImage()` convenience API throughout this guide. This built-in helper simplifies image block creation compared to manually constructing graphic blocks with image fills, and provides additional configuration options like positioning, sizing, corner radius, shadows, and timeline properties. ``` // Pattern #1: Demonstrate Individual Before Combined// Create a separate image block for blur demonstrationconst blurImageBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, blurImageBlock); // Create and apply a blur effectconst blurEffect = engine.block.createEffect('extrude_blur');engine.block.appendEffect(blurImageBlock, blurEffect); // Adjust blur intensityengine.block.setFloat(blurEffect, 'effect/extrude_blur/amount', 0.5); ``` CE.SDK provides several built-in effect types: * `extrude_blur` - Gaussian blur with configurable intensity * `adjustments` - Brightness, contrast, saturation, exposure * `pixelize` - Pixelation effect * `vignette` - Darkened corners * `half_tone` - Halftone pattern * `lut_filter` - Color grading with LUT files * `duotone` - Two-color tinting `extrude_blur` is the only blur available as an effect. CE.SDK also provides additional blur types in a separate blur category. Each effect type has its own set of configurable properties that control its visual appearance. ### Configure Effect Parameters[#](#configure-effect-parameters) After creating an effect, we can customize its appearance by setting properties. Each effect exposes different parameters depending on its type and capabilities. ``` // Create a separate image block for adjustments demonstrationconst adjustmentsImageBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, adjustmentsImageBlock); // Create adjustments effect for brightness and contrastconst adjustmentsEffect = engine.block.createEffect('adjustments');engine.block.appendEffect(adjustmentsImageBlock, adjustmentsEffect); // Find all available properties for this effectconst adjustmentProperties = engine.block.findAllProperties(adjustmentsEffect);console.log('Available adjustment properties:', adjustmentProperties); // Set brightness, contrast, and saturationengine.block.setFloat( adjustmentsEffect, 'effect/adjustments/brightness', 0.2);engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/contrast', 0.15);engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/saturation', 0.1); ``` CE.SDK provides typed setter methods for different parameter types: * **`setFloat()`** - For intensity, amount, and radius values (typically 0.0 to 1.0) * **`setInt()`** - For discrete values like pixel sizes * **`setString()`** - For file URIs (LUT files, image references) * **`setBool()`** - For enabling or disabling specific features Using the correct setter method ensures type safety and proper value validation. ### Apply LUT Filters[#](#apply-lut-filters) LUT (Look-Up Table) filters apply professional color grading by transforming colors through a predefined mapping. These are particularly useful for creating consistent brand aesthetics or applying cinematic color treatments. The example demonstrates querying LUT filters from the asset library using `engine.asset.findAssets('ly.img.filter.lut')`, then applying them using metadata from the asset results. This approach matches how CE.SDK’s built-in filter panel works. ``` // Demonstrate LUT filters by applying the first 2 from asset library// These filters are fetched from the demo asset sources (Grid positions 3-4)const lutImageBlocks = [];for (let i = 0; i < Math.min(2, lutAssets.length); i++) { const lutAsset = lutAssets[i]; const lutImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, lutImageBlock); lutImageBlocks.push(lutImageBlock); // Create LUT filter effect using the full effect type URI const lutEffect = engine.block.createEffect( '//ly.img.ubq/effect/lut_filter' ); // Use asset metadata for LUT configuration // The asset provides the LUT file URI and grid dimensions engine.block.setString( lutEffect, 'effect/lut_filter/lutFileURI', lutAsset.meta?.uri as string ); engine.block.setInt( lutEffect, 'effect/lut_filter/horizontalTileCount', parseInt(lutAsset.meta?.horizontalTileCount as string, 10) ); engine.block.setInt( lutEffect, 'effect/lut_filter/verticalTileCount', parseInt(lutAsset.meta?.verticalTileCount as string, 10) ); engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.85); engine.block.appendEffect(lutImageBlock, lutEffect);} ``` LUT filters are ideal for: * Creating consistent brand aesthetics across all designs * Applying cinematic or film-style color grading * Matching reference images or maintaining color continuity * Building curated filter collections for users **Asset metadata structure**: Each LUT asset provides `uri` (the LUT file URL), `horizontalTileCount`, and `verticalTileCount` describing the grid layout of color transformation cubes. ### Apply Duotone Filters[#](#apply-duotone-filters) Duotone filters create artistic two-color effects by mapping image tones to two colors (dark and light). This effect is popular for creating stylized visuals, vintage aesthetics, or brand-specific color treatments. The example queries duotone filters from the asset library, then applies them using color metadata. The `hexToRgba` utility converts hex color values from asset metadata to RGBA format required by the `setColorRGBA` API. ``` // Demonstrate Duotone filters by applying the first 2 from asset library// Duotone filters create artistic two-color treatments (Grid positions 5-6)const duotoneImageBlocks = [];for (let i = 0; i < Math.min(2, duotoneAssets.length); i++) { const duotoneAsset = duotoneAssets[i]; const duotoneImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, duotoneImageBlock); duotoneImageBlocks.push(duotoneImageBlock); // Create Duotone filter effect using the full effect type URI const duotoneEffect = engine.block.createEffect( '//ly.img.ubq/effect/duotone_filter' ); // Convert hex colors from asset metadata to RGBA (0-1 range) const darkColor = hexToRgba(duotoneAsset.meta?.darkColor as string); engine.block.setColor( duotoneEffect, 'effect/duotone_filter/darkColor', darkColor ); const lightColor = hexToRgba(duotoneAsset.meta?.lightColor as string); engine.block.setColor( duotoneEffect, 'effect/duotone_filter/lightColor', lightColor ); engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.8 ); engine.block.appendEffect(duotoneImageBlock, duotoneEffect);} ``` Duotone filters work by: * Mapping darker image tones to the **dark color** * Mapping lighter image tones to the **light color** * Blending between the two colors based on pixel brightness * Adjusting intensity to control the effect strength (0.0 to 1.0) **Asset metadata structure**: Each duotone asset provides `darkColor` and `lightColor` as hex strings (e.g., `"#1a2b3c"`) which must be converted to RGBA values for the effect API. ### Combine Multiple Effects[#](#combine-multiple-effects) One of the most powerful features of CE.SDK’s effect system is the ability to stack multiple effects on a single block. Each effect is applied sequentially, allowing you to build complex visual treatments. The example code demonstrates each effect type individually on separate image blocks before showing them combined. This educational approach helps you understand what each effect does before seeing them work together. In your production code, you can apply multiple effects directly to the same block without this separation. ``` // Pattern #5: Progressive Complexity - now combining multiple effects// Create a separate image block to demonstrate combining multiple effects (Grid position 7)const combinedImageBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, combinedImageBlock); // Apply effects in order - the stack will contain:// 1. adjustments (brightness/contrast) - applied first// 2. blur - applied second// 3. duotone (color tinting) - applied third// 4. pixelize - applied last const combinedAdjustments = engine.block.createEffect('adjustments');engine.block.appendEffect(combinedImageBlock, combinedAdjustments);engine.block.setFloat( combinedAdjustments, 'effect/adjustments/brightness', 0.2);engine.block.setFloat( combinedAdjustments, 'effect/adjustments/contrast', 0.15); const combinedBlur = engine.block.createEffect('extrude_blur');engine.block.appendEffect(combinedImageBlock, combinedBlur);engine.block.setFloat(combinedBlur, 'effect/extrude_blur/amount', 0.3); const combinedDuotone = engine.block.createEffect('duotone_filter');engine.block.appendEffect(combinedImageBlock, combinedDuotone);engine.block.setColor(combinedDuotone, 'duotone_filter/darkColor', { r: 0.1, g: 0.2, b: 0.4, a: 1.0});engine.block.setColor(combinedDuotone, 'duotone_filter/lightColor', { r: 0.9, g: 0.8, b: 0.6, a: 1.0});engine.block.setFloat(combinedDuotone, 'duotone_filter/intensity', 0.6); const pixelizeEffect = engine.block.createEffect('pixelize');engine.block.appendEffect(combinedImageBlock, pixelizeEffect);engine.block.setInt(pixelizeEffect, 'pixelize/horizontalPixelSize', 8);engine.block.setInt(pixelizeEffect, 'pixelize/verticalPixelSize', 8); ``` **Effect ordering matters**: Effects are applied from the bottom of the stack to the top. In this example: 1. First, we adjust brightness and contrast 2. Then, we apply blur 3. Then, we apply color grading with a LUT filter 4. Finally, we add stylization with pixelization Experiment with different orderings to achieve the desired visual result—changing the order can significantly impact the final appearance. ## Managing Applied Effects[#](#managing-applied-effects) ### List and Access Effects[#](#list-and-access-effects) We can retrieve all effects applied to a block and inspect their properties. This is useful for building effect management interfaces or debugging effect configurations. ``` // Get all effects applied to the combined blockconst effects = engine.block.getEffects(combinedImageBlock);console.log('Applied effects:', effects); // Access properties of specific effectseffects.forEach((effect, index) => { const effectType = engine.block.getType(effect); const isEnabled = engine.block.isEffectEnabled(effect); console.log(`Effect ${index}: ${effectType}, enabled: ${isEnabled}`);}); ``` This allows you to iterate through all applied effects, read their properties, and make modifications as needed. ### Enable/Disable Effects[#](#enabledisable-effects) CE.SDK allows you to temporarily toggle effects on and off without removing them from the block. This is particularly useful for before/after comparisons or when you need to optimize rendering performance during interactive editing sessions. ``` // Check if effect is enabledconst isBlurEnabled = engine.block.isEffectEnabled(combinedBlur);console.log('Blur effect is enabled:', isBlurEnabled); ``` When you disable an effect, it remains attached to the block but won’t be rendered until you enable it again. This preserves all effect parameters while giving you full control over when the effect is applied. You can use this feature to create interactive preview modes, implement undo-like functionality, or conditionally apply effects based on user preferences or device capabilities. ### Remove Effects[#](#remove-effects) When you no longer need an effect, you can remove it from the effect stack and free its resources. Always destroy effects that are no longer in use to prevent memory leaks. ``` // Create a temporary block to demonstrate effect removalconst tempBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, tempBlock); const tempEffect = engine.block.createEffect('pixelize');engine.block.appendEffect(tempBlock, tempEffect);engine.block.setInt(tempEffect, 'pixelize/horizontalPixelSize', 12); // Remove the effectconst tempEffects = engine.block.getEffects(tempBlock);const effectIndex = tempEffects.indexOf(tempEffect);if (effectIndex !== -1) { engine.block.removeEffect(tempBlock, effectIndex);} // Destroy the removed effect to free memoryengine.block.destroy(tempEffect); ``` The `removeEffect()` method takes an index position, so you can remove effects selectively from any position in the stack. After removal, destroy the effect instance to ensure proper cleanup. ## Additional Techniques[#](#additional-techniques) ### Batch Processing[#](#batch-processing) For applications that need to apply the same effects to multiple elements, we can iterate through a collection of blocks and apply effects efficiently. ``` // Apply same effects to multiple blocksconst allGraphics = engine.block.findByType('graphic'); allGraphics.forEach((graphic) => { if (engine.block.supportsEffects(graphic)) { // Only apply to blocks that don't already have effects const existingEffects = engine.block.getEffects(graphic); if (existingEffects.length === 0) { const effect = engine.block.createEffect('adjustments'); engine.block.appendEffect(graphic, effect); engine.block.setFloat(effect, 'effect/adjustments/brightness', 0.1); } }}); ``` When batch processing, check effect support before creating effects to avoid unnecessary work. You can also reuse effect instances when applying the same configuration to multiple blocks, though be careful to destroy them properly when done. ### Dynamic Effects in Video Mode[#](#dynamic-effects-in-video-mode) When working in Video mode (not Design mode), you can combine effects with CE.SDK’s built-in animation system to create dynamic visual treatments that change over time. Effect parameters are static properties and cannot be animated using JavaScript timers like `setInterval` or `requestAnimationFrame`. For animated content, use CE.SDK’s built-in animation blocks (`createAnimation()`, `setInAnimation()`, etc.) in Video mode or refer to the animation guides. For dynamic visual effects in video projects, explore CE.SDK’s animation system which provides professionally designed transitions and effects that integrate seamlessly with the rendering pipeline. ### Custom Effect Combinations[#](#custom-effect-combinations) Creating reusable effect presets allows you to maintain consistent styling across your application and speed up common effect applications. Here’s a pattern for building reusable effect configurations: ``` // Create a reusable preset functionasync function applyVintagePreset(engine: CreativeEngine, imageBlock: number) { // Apply LUT filter const lutEffect = engine.block.createEffect('lut_filter'); engine.block.setString( lutEffect, 'lut_filter/lutFileURI', 'https://img.ly/static/ubq_luts/vintage.png', ); engine.block.appendEffect(imageBlock, lutEffect); // Add vignette const vignetteEffect = engine.block.createEffect('vignette'); engine.block.setFloat(vignetteEffect, 'vignette/intensity', 0.5); engine.block.appendEffect(imageBlock, vignetteEffect); return { lutEffect, vignetteEffect };} // Use the presetconst effects = await applyVintagePreset(engine, myImageBlock); ``` Preset strategies include: * **Brand filters** - Maintain a consistent look across campaigns * **Style templates** - Provide quick application of complex multi-effect treatments * **User favorites** - Allow users to save and recall their preferred settings ## Performance Considerations[#](#performance-considerations) CE.SDK’s effect system is optimized for real-time performance, but understanding these considerations helps you build responsive applications: * **GPU acceleration**: Effects leverage GPU rendering for smooth performance on modern devices * **Mobile optimization**: Limit effects to 2-3 per element on mobile devices to maintain responsiveness * **Effect complexity**: Blur and LUT filters are computationally expensive compared to simple adjustments * **Video effects**: Apply effects sparingly to video blocks to maintain smooth playback * **Real-time editing**: Temporarily disable effects during intensive editing operations for better interactivity Test your effect combinations on target devices early in development to ensure acceptable performance. ## Troubleshooting[#](#troubleshooting) ### Effect Not Visible[#](#effect-not-visible) If an effect doesn’t appear after applying it, check these common issues: * Verify the block type supports effects using `supportsEffects()` * Check that the effect is enabled with `isEffectEnabled()` * Ensure effect parameters are in valid ranges (e.g., intensity values between 0.0 and 1.0) * Confirm the effect is in the effect stack with `getEffects()` ### Performance Degradation[#](#performance-degradation) If you experience slow rendering or laggy interactions: * Reduce the number of effects per element (aim for 2-3 maximum on mobile) * Lower blur radius values or use smaller LUT files * Temporarily disable effects during editing with `setEffectEnabled()` * Test on target devices early to identify performance bottlenecks ### Effects Not Persisting[#](#effects-not-persisting) Effects should save automatically with the scene, but verify: * You’re not destroying effects prematurely before saving * Save/load operations complete successfully * Effect URIs (LUT files, images) remain accessible after loading ### Incompatible Block Types[#](#incompatible-block-types) If you can’t apply an effect: * Remember that graphic blocks (with image or video fills), shape blocks, and text blocks support effects * Page blocks themselves don’t support effects directly, but page fills (such as background fills) do support effects * Scene blocks cannot have effects applied * Check the block type with `block.getType()` and use `block.supportsEffects()` before attempting to apply effects ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `block.supportsEffects(block)` | Check if a block supports effects | | `block.createEffect(type)` | Create a new effect instance | | `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.removeEffect(block, index)` | Remove effect at the specified index | | `block.getEffects(block)` | Get all effects applied to a block | | `block.setEffectEnabled(effect, enabled)` | Enable or disable an effect | | `block.isEffectEnabled(effect)` | Check if an effect is currently enabled | | `block.findAllProperties(effect)` | Get all available properties for an effect | | `block.setFloat(effect, property, value)` | Set a floating-point property value | | `block.setInt(effect, property, value)` | Set an integer property value | | `block.setString(effect, property, value)` | Set a string property value | | `block.setBool(effect, property, value)` | Set a boolean property value | | `block.destroy(effect)` | Destroy an unused effect instance | ## About the Example Code[#](#about-the-example-code) The example code accompanying this guide follows educational design patterns to help you learn effectively: * **Individual demonstrations**: Each effect type is demonstrated on its own image block before showing combinations, making it easier to understand what each effect does * **Convenience API usage**: The code uses `engine.block.addImage()` instead of manual block construction—this is the recommended approach for simplicity and maintainability * **Spatial layout**: Image blocks are positioned in a grid layout (x/y coordinates) so you can visually see the results of each effect when running the example * **Progressive complexity**: The example starts with simple single effects and gradually builds to complex multi-effect combinations In your production code, you can apply multiple effects directly to the same block without creating separate demonstration blocks. The example structure is optimized for learning, not production usage. ## Next Steps[#](#next-steps) Now that you understand how to apply and manage filters and effects, explore specific effect types and advanced techniques: * **LUT Filters** - Create custom color grading filters for cinematic looks * **Blur Effects** - Apply depth of field and motion blur techniques * **Duotone Effects** - Create striking two-color artistic treatments * **Adjustments** - Fine-tune brightness, contrast, and saturation * **Effect Combinations** - Build sophisticated multi-effect visual treatments --- [Source](https:/img.ly/docs/cesdk/vue/fills/video-ec7f9f) --- # Video Fills Apply motion content to design elements by filling shapes, backgrounds, and text with videos using CE.SDK’s video fill system. ![CE.SDK video fills example showing a 3x3 grid with video content applied to different blocks including rectangles, ellipse, and opacity variations](/docs/cesdk/_astro/browser.hero.CgrDjad3_1Sw6WA.webp) 15 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-fills-video-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-fills-video-browser) Understanding the distinction between **video fills** and **video blocks** is essential. Video fills are fill objects that can be applied to any block supporting fills—shapes, text, backgrounds—to paint them with video content. Video blocks, created with `addVideo()`, are dedicated timeline elements with full editing capabilities like trimming and duration control. Video fills focus on applying video as a visual treatment, while video blocks provide complete video editing functionality. ``` import type { CreativeEngine, EditorPlugin, EditorPluginContext} from '@cesdk/cesdk-js';import packageJson from './package.json';import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Video Fills Guide * * Demonstrates video fills in CE.SDK: * - Creating video fills * - Setting video sources (single URI and source sets) * - Applying video fills to blocks * - Content fill modes (Cover, Contain) * - Loading video resources * - Getting video thumbnails * - Different use cases (backgrounds, shapes, text) */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'); } // Video fills require Video mode and video features enabled cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.fill'); // Load assets and create video scene (required for video fills) await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine as CreativeEngine; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : engine.scene.get(); // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 8); const { blockWidth, blockHeight, getPosition } = layout; // Use a sample video URL from demo assets const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; // Create a sample block to demonstrate fill support checking const sampleBlock = engine.block.create('graphic'); engine.block.setShape(sampleBlock, engine.block.createShape('rect')); // Check if the block supports fills const supportsFills = engine.block.supportsFill(sampleBlock); console.log('Block supports fills:', supportsFills); // true for graphic blocks // Verify we're in Video mode (required for video fills) const sceneMode = engine.scene.getMode(); if (sceneMode !== 'Video') { throw new Error('Video fills require Video mode.'); } console.log('Scene mode:', sceneMode); // "Video" // Pattern #1: Demonstrate Individual Before Combined // Create a basic video fill demonstration const basicBlock = engine.block.create('graphic'); engine.block.setShape(basicBlock, engine.block.createShape('rect')); engine.block.setWidth(basicBlock, blockWidth); engine.block.setHeight(basicBlock, blockHeight); engine.block.appendChild(page, basicBlock); // Create a video fill const basicVideoFill = engine.block.createFill('video'); // or using full type name: engine.block.createFill('//ly.img.ubq/fill/video'); // Set the video source URI engine.block.setString(basicVideoFill, 'fill/video/fileURI', videoUri); // Apply the fill to the block engine.block.setFill(basicBlock, basicVideoFill); // Get and verify the current fill const fillId = engine.block.getFill(basicBlock); const fillType = engine.block.getType(fillId); console.log('Fill type:', fillType); // '//ly.img.ubq/fill/video' // Pattern #2: Content fill mode - Cover // Cover mode fills entire block, may crop video to fit const coverBlock = engine.block.create('graphic'); engine.block.setShape(coverBlock, engine.block.createShape('rect')); engine.block.setWidth(coverBlock, blockWidth); engine.block.setHeight(coverBlock, blockHeight); engine.block.appendChild(page, coverBlock); const coverVideoFill = engine.block.createFill('video'); engine.block.setString(coverVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(coverBlock, coverVideoFill); // Set content fill mode to Cover engine.block.setEnum(coverBlock, 'contentFill/mode', 'Cover'); // Get current fill mode const coverMode = engine.block.getEnum(coverBlock, 'contentFill/mode'); console.log('Cover block fill mode:', coverMode); // 'Cover' // Content fill mode - Contain // Contain mode fits entire video, may leave empty space const containBlock = engine.block.create('graphic'); engine.block.setShape(containBlock, engine.block.createShape('rect')); engine.block.setWidth(containBlock, blockWidth); engine.block.setHeight(containBlock, blockHeight); engine.block.appendChild(page, containBlock); const containVideoFill = engine.block.createFill('video'); engine.block.setString(containVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(containBlock, containVideoFill); // Set content fill mode to Contain engine.block.setEnum(containBlock, 'contentFill/mode', 'Contain'); // Force load video resource to access metadata const resourceBlock = engine.block.create('graphic'); engine.block.setShape(resourceBlock, engine.block.createShape('rect')); engine.block.setWidth(resourceBlock, blockWidth); engine.block.setHeight(resourceBlock, blockHeight); engine.block.appendChild(page, resourceBlock); const resourceVideoFill = engine.block.createFill('video'); engine.block.setString(resourceVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(resourceBlock, resourceVideoFill); // Force load the video resource before accessing metadata await engine.block.forceLoadAVResource(resourceVideoFill); // Now we can access video metadata const totalDuration = engine.block.getDouble( resourceVideoFill, 'fill/video/totalDuration' ); console.log('Video total duration:', totalDuration, 'seconds'); // Use case: Video as shape fill - Ellipse const ellipseBlock = engine.block.create('graphic'); const ellipseShape = engine.block.createShape('//ly.img.ubq/shape/ellipse'); engine.block.setShape(ellipseBlock, ellipseShape); engine.block.setWidth(ellipseBlock, blockWidth); engine.block.setHeight(ellipseBlock, blockHeight); engine.block.appendChild(page, ellipseBlock); const ellipseVideoFill = engine.block.createFill('video'); engine.block.setString(ellipseVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(ellipseBlock, ellipseVideoFill); // Advanced: Video fill with opacity const opacityBlock = engine.block.create('graphic'); engine.block.setShape(opacityBlock, engine.block.createShape('rect')); engine.block.setWidth(opacityBlock, blockWidth); engine.block.setHeight(opacityBlock, blockHeight); engine.block.appendChild(page, opacityBlock); const opacityVideoFill = engine.block.createFill('video'); engine.block.setString(opacityVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(opacityBlock, opacityVideoFill); // Set block opacity to 70% engine.block.setFloat(opacityBlock, 'opacity', 0.7); // Advanced: Share one video fill between multiple blocks const sharedFill = engine.block.createFill('video'); engine.block.setString(sharedFill, 'fill/video/fileURI', videoUri); // First block using shared fill const sharedBlock1 = engine.block.create('graphic'); engine.block.setShape(sharedBlock1, engine.block.createShape('rect')); engine.block.setWidth(sharedBlock1, blockWidth); engine.block.setHeight(sharedBlock1, blockHeight); engine.block.appendChild(page, sharedBlock1); engine.block.setFill(sharedBlock1, sharedFill); // Second block using the same shared fill const sharedBlock2 = engine.block.create('graphic'); engine.block.setShape(sharedBlock2, engine.block.createShape('rect')); engine.block.setWidth(sharedBlock2, blockWidth * 0.8); // Slightly smaller engine.block.setHeight(sharedBlock2, blockHeight * 0.8); engine.block.appendChild(page, sharedBlock2); engine.block.setFill(sharedBlock2, sharedFill); console.log( 'Shared fill - Two blocks using the same video fill instance for memory efficiency' ); // ===== Position all blocks in grid layout ===== const blocks = [ basicBlock, // Position 0 coverBlock, // Position 1 containBlock, // Position 2 resourceBlock, // Position 3 ellipseBlock, // Position 4 opacityBlock, // Position 5 sharedBlock1, // Position 6 sharedBlock2 // Position 7 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Select the first block so users can see the fill in action engine.block.setSelected(basicBlock, true); // Set playback time to 2 seconds to show video content engine.block.setPlaybackTime(page, 2); // Start playback automatically try { engine.block.setPlaying(page, true); console.log( 'Video fills guide initialized. Playback started. Demonstrating various video fill techniques across the grid.' ); } catch (error) { console.log( 'Video fills guide initialized. Click play to start video playback (browser autoplay restriction).' ); } }} export default Example; ``` This guide covers how to create video fills, apply them to blocks, configure fill modes, and work with video resources programmatically. ## Understanding Video Fills[#](#understanding-video-fills) ### What is a Video Fill?[#](#what-is-a-video-fill) A video fill is a fill object that paints a design block with video content. Like color and image fills, video fills are part of CE.SDK’s broader fill system. Video fills are identified by the type `'//ly.img.ubq/fill/video'` or the short form `'video'`. They contain properties for the video source, positioning, scaling, and playback behavior. ### Video Fill vs Video Blocks[#](#video-fill-vs-video-blocks) **Video fills** are fill objects created with `createFill('video')` and applied to blocks with `setFill()`. You can use them to fill shapes with video content, create video backgrounds, or add video textures to text. **Video blocks** are created with the convenience method `addVideo()` and come pre-configured with timeline integration, trim support, and playback controls. Use video blocks when building video editors or when you need features like trimming, duration adjustment, and precise playback control. For this guide, we focus on video fills—applying video content as a fill to design elements. For video editing workflows, see the [Trim Video guide](vue/edit-video/trim-4f688b/) . ### Video Mode Requirement[#](#video-mode-requirement) Video fills can only be created in Video mode scenes. Design mode doesn’t support video fills. You must initialize CE.SDK with `createVideoScene()` instead of `createDesignScene()`. ``` // Create Video mode scene (required for video fills)await cesdk.createVideoScene(); // Verify scene modeconst mode = engine.scene.getMode();console.log(mode); // "Video" ``` ## Checking Video Fill Support[#](#checking-video-fill-support) Before applying video fills, verify that blocks support fills and that you’re in the correct scene mode. ``` // Create a sample block to demonstrate fill support checkingconst sampleBlock = engine.block.create('graphic');engine.block.setShape(sampleBlock, engine.block.createShape('rect')); // Check if the block supports fillsconst supportsFills = engine.block.supportsFill(sampleBlock);console.log('Block supports fills:', supportsFills); // true for graphic blocks // Verify we're in Video mode (required for video fills)const sceneMode = engine.scene.getMode();if (sceneMode !== 'Video') { throw new Error('Video fills require Video mode.');}console.log('Scene mode:', sceneMode); // "Video" ``` Graphic blocks, shapes, and text blocks typically support fills. Pages and scenes don’t. Always check `supportsFill()` before attempting to apply video fills to prevent errors. ## Creating Video Fills[#](#creating-video-fills) ### Creating Video Fills[#](#creating-video-fills-1) Creating a video fill involves three steps: create the fill object, set the video source, and apply it to a block. ``` // Pattern #1: Demonstrate Individual Before Combined// Create a basic video fill demonstrationconst basicBlock = engine.block.create('graphic');engine.block.setShape(basicBlock, engine.block.createShape('rect'));engine.block.setWidth(basicBlock, blockWidth);engine.block.setHeight(basicBlock, blockHeight);engine.block.appendChild(page, basicBlock); // Create a video fillconst basicVideoFill = engine.block.createFill('video');// or using full type name: engine.block.createFill('//ly.img.ubq/fill/video'); // Set the video source URIengine.block.setString(basicVideoFill, 'fill/video/fileURI', videoUri); // Apply the fill to the blockengine.block.setFill(basicBlock, basicVideoFill); ``` The video fill exists independently until you attach it to a block. This allows you to configure the fill completely before applying it. Once applied, the fill paints the block with the video content. ### Getting Current Fill Information[#](#getting-current-fill-information) We can retrieve the current fill from a block and inspect its type to verify it’s a video fill. ``` // Get and verify the current fillconst fillId = engine.block.getFill(basicBlock);const fillType = engine.block.getType(fillId);console.log('Fill type:', fillType); // '//ly.img.ubq/fill/video' ``` This is useful when building UIs that need to adapt based on the current fill type or when implementing undo/redo functionality that tracks fill changes. ## Content Fill Modes[#](#content-fill-modes) Content fill modes control how video scales and positions within blocks. The two primary modes are Cover and Contain, each suited to different use cases. ### Cover Mode[#](#cover-mode) Cover mode fills the entire block with video while maintaining the video’s aspect ratio. If the aspect ratios don’t match, CE.SDK crops portions of the video to ensure no empty space appears in the block. ``` // Pattern #2: Content fill mode - Cover// Cover mode fills entire block, may crop video to fitconst coverBlock = engine.block.create('graphic');engine.block.setShape(coverBlock, engine.block.createShape('rect'));engine.block.setWidth(coverBlock, blockWidth);engine.block.setHeight(coverBlock, blockHeight);engine.block.appendChild(page, coverBlock); const coverVideoFill = engine.block.createFill('video');engine.block.setString(coverVideoFill, 'fill/video/fileURI', videoUri);engine.block.setFill(coverBlock, coverVideoFill); // Set content fill mode to Coverengine.block.setEnum(coverBlock, 'contentFill/mode', 'Cover'); // Get current fill modeconst coverMode = engine.block.getEnum(coverBlock, 'contentFill/mode');console.log('Cover block fill mode:', coverMode); // 'Cover' ``` Use Cover mode for background videos, full-frame video content, and situations where visual consistency matters more than showing the entire video. It guarantees no empty space but may crop content. ### Contain Mode[#](#contain-mode) Contain mode fits the entire video within the block while maintaining aspect ratio. If aspect ratios don’t match, CE.SDK adds empty space to preserve the full video visibility. ``` // Content fill mode - Contain// Contain mode fits entire video, may leave empty spaceconst containBlock = engine.block.create('graphic');engine.block.setShape(containBlock, engine.block.createShape('rect'));engine.block.setWidth(containBlock, blockWidth);engine.block.setHeight(containBlock, blockHeight);engine.block.appendChild(page, containBlock); const containVideoFill = engine.block.createFill('video');engine.block.setString(containVideoFill, 'fill/video/fileURI', videoUri);engine.block.setFill(containBlock, containVideoFill); // Set content fill mode to Containengine.block.setEnum(containBlock, 'contentFill/mode', 'Contain'); ``` Use Contain mode when the entire video must remain visible—presentations, product demos, or content where cropping would lose important information. Empty space is acceptable to preserve complete visibility. ## Loading Video Resources[#](#loading-video-resources) Before accessing video metadata like duration or dimensions, you must force load the video resource. This ensures CE.SDK has downloaded the necessary information. ``` // Force load video resource to access metadataconst resourceBlock = engine.block.create('graphic');engine.block.setShape(resourceBlock, engine.block.createShape('rect'));engine.block.setWidth(resourceBlock, blockWidth);engine.block.setHeight(resourceBlock, blockHeight);engine.block.appendChild(page, resourceBlock); const resourceVideoFill = engine.block.createFill('video');engine.block.setString(resourceVideoFill, 'fill/video/fileURI', videoUri);engine.block.setFill(resourceBlock, resourceVideoFill); // Force load the video resource before accessing metadataawait engine.block.forceLoadAVResource(resourceVideoFill); // Now we can access video metadataconst totalDuration = engine.block.getDouble( resourceVideoFill, 'fill/video/totalDuration');console.log('Video total duration:', totalDuration, 'seconds'); ``` Skipping this step causes errors when trying to access metadata. Videos load asynchronously, so `forceLoadAVResource` ensures the metadata is available before you query it. Once loaded, you can access properties like `fill/video/totalDuration` to get the video length in seconds. This information helps you build UI previews or validate user input. ## Common Use Cases[#](#common-use-cases) ### Video as Shape Fill[#](#video-as-shape-fill) Video fills aren’t limited to rectangles. You can fill any shape with video content. ``` // Use case: Video as shape fill - Ellipseconst ellipseBlock = engine.block.create('graphic');const ellipseShape = engine.block.createShape('//ly.img.ubq/shape/ellipse');engine.block.setShape(ellipseBlock, ellipseShape);engine.block.setWidth(ellipseBlock, blockWidth);engine.block.setHeight(ellipseBlock, blockHeight);engine.block.appendChild(page, ellipseBlock); const ellipseVideoFill = engine.block.createFill('video');engine.block.setString(ellipseVideoFill, 'fill/video/fileURI', videoUri);engine.block.setFill(ellipseBlock, ellipseVideoFill); ``` Ellipse shapes, polygons, stars, and custom paths all support video fills. The video content fills the shape boundary, masking the video. ### Video with Opacity[#](#video-with-opacity) Control the transparency of video-filled blocks to create overlay effects or blend video content with backgrounds. ``` // Advanced: Video fill with opacityconst opacityBlock = engine.block.create('graphic');engine.block.setShape(opacityBlock, engine.block.createShape('rect'));engine.block.setWidth(opacityBlock, blockWidth);engine.block.setHeight(opacityBlock, blockHeight);engine.block.appendChild(page, opacityBlock); const opacityVideoFill = engine.block.createFill('video');engine.block.setString(opacityVideoFill, 'fill/video/fileURI', videoUri);engine.block.setFill(opacityBlock, opacityVideoFill); // Set block opacity to 70%engine.block.setFloat(opacityBlock, 'opacity', 0.7); ``` Opacity affects the entire block, including its video fill. This technique creates semi-transparent video overlays, watermarks, or layered compositions where video content blends with other elements. ## Additional Techniques[#](#additional-techniques) ### Sharing Video Fills[#](#sharing-video-fills) Memory efficiency improves when multiple blocks share a single video fill instance. Changes to the shared fill affect all blocks using it. ``` // Advanced: Share one video fill between multiple blocksconst sharedFill = engine.block.createFill('video');engine.block.setString(sharedFill, 'fill/video/fileURI', videoUri); // First block using shared fillconst sharedBlock1 = engine.block.create('graphic');engine.block.setShape(sharedBlock1, engine.block.createShape('rect'));engine.block.setWidth(sharedBlock1, blockWidth);engine.block.setHeight(sharedBlock1, blockHeight);engine.block.appendChild(page, sharedBlock1);engine.block.setFill(sharedBlock1, sharedFill); // Second block using the same shared fillconst sharedBlock2 = engine.block.create('graphic');engine.block.setShape(sharedBlock2, engine.block.createShape('rect'));engine.block.setWidth(sharedBlock2, blockWidth * 0.8); // Slightly smallerengine.block.setHeight(sharedBlock2, blockHeight * 0.8);engine.block.appendChild(page, sharedBlock2);engine.block.setFill(sharedBlock2, sharedFill); console.log( 'Shared fill - Two blocks using the same video fill instance for memory efficiency'); ``` This pattern reduces memory usage when the same video appears multiple times in a composition. It’s particularly useful for repeated elements like watermarks or background patterns. Shared fills play back synchronized—all blocks display the same frame at the same time during playback. This ensures visual consistency across multiple elements. ## Troubleshooting[#](#troubleshooting) ### Video Not Visible[#](#video-not-visible) If your video fill doesn’t appear, check several common causes. Verify the fill is enabled with `isFillEnabled(block)`. Ensure the video URL is accessible—CORS restrictions on web platforms can block video loading. Confirm the block has valid dimensions (width and height greater than zero) and exists in the scene hierarchy. Check that the video format is supported on your platform. MP4 with H.264 encoding works reliably across platforms, while other codecs may have limited support. ### Cannot Create Video Fill[#](#cannot-create-video-fill) If creating a video fill throws an error, verify you’re in Video mode. Design mode doesn’t support video fills. Use `engine.scene.getMode()` to check the current mode. If it returns “Design”, you need to create a video scene instead. Call `await cesdk.createVideoScene()` during initialization rather than `createDesignScene()` to enable video capabilities. ### Video Not Loading[#](#video-not-loading) When videos fail to load, verify network connectivity for remote URLs. Check CORS headers—web browsers enforce cross-origin restrictions that can block video access. Validate the URI format uses `https://` for remote videos or appropriate schemes for local files. Test with a known working video URL to isolate whether the issue is with your specific video or a broader configuration problem. Check the browser console for detailed error messages. ### Memory Leaks[#](#memory-leaks) Always destroy replaced fills to prevent memory leaks. When changing a block’s fill, retrieve the old fill with `getFill()`, assign the new fill with `setFill()`, then destroy the old fill with `destroy()`. Don’t create fills without attaching them to blocks—unattached fills remain in memory indefinitely. Clean up shared fills when no blocks reference them anymore. ### Performance Issues[#](#performance-issues) Video playback is resource-intensive. Use appropriately sized videos—avoid massive files that strain decoding hardware. Consider lower resolutions for editing with high-resolution sources reserved for export. Limit the number of simultaneously playing videos, especially on mobile devices. Too many concurrent video decodes overwhelm device capabilities. Compress videos before use to reduce file sizes and improve loading times. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `createFill('video')` | Create a new video fill object | | `setFill(block, fill)` | Assign fill to a block | | `getFill(block)` | Get the fill ID from a block | | `setString(fill, property, value)` | Set video URI property | | `getString(fill, property)` | Get current video URI | | `setSourceSet(fill, property, sources)` | Set responsive video sources | | `getSourceSet(fill, property)` | Get current source set | | `setEnum(block, property, value)` | Set content fill mode | | `getEnum(block, property)` | Get current fill mode | | `setFillEnabled(block, enabled)` | Enable or disable fill rendering | | `isFillEnabled(block)` | Check if fill is enabled | | `supportsFill(block)` | Check if block supports fills | | `forceLoadAVResource(fill)` | Force load video metadata | | `getVideoFillThumbnail(fill, height)` | Get single thumbnail frame | | `adjustCropToFillFrame(block, fillIndex)` | Adjust crop to fill frame | ### Video Fill Properties[#](#video-fill-properties) | Property | Type | Description | | --- | --- | --- | | `fill/video/fileURI` | String | Single video URI (URL, data URI, file path) | | `fill/video/sourceSet` | SourceSet\[\] | Array of responsive video sources | | `fill/video/totalDuration` | Double | Total duration of video in seconds | ### Content Fill Properties[#](#content-fill-properties) | Property | Type | Values | Description | | --- | --- | --- | --- | | `contentFill/mode` | Enum | ’Cover’, ‘Contain’ | How video scales within block | --- [Source](https:/img.ly/docs/cesdk/vue/fills/overview-3895ee) --- # Fills Some [design blocks](vue/concepts/blocks-90241e/) in CE.SDK allow you to modify or replace their fill. The fill is an object that defines the contents within the shape of a block. CreativeEditor SDK supports many different types of fills, such as images, solid colors, gradients and videos. Similarly to blocks, each fill has a numeric id which can be used to query and [modify its properties](vue/concepts/blocks-90241e/) . We currently support the following fill types: * `'//ly.img.ubq/fill/color'` * `'//ly.img.ubq/fill/gradient/linear'` * `'//ly.img.ubq/fill/gradient/radial'` * `'//ly.img.ubq/fill/gradient/conical'` * `'//ly.img.ubq/fill/image'` * `'//ly.img.ubq/fill/video'` * `'//ly.img.ubq/fill/pixelStream'` Note: short types are also accepted, e.g. ‘color’ instead of ‘//ly.img.ubq/fill/color’. ## Accessing Fills[#](#accessing-fills) Not all types of design blocks support fills, so you should always first call the `supportsFill(id: number): boolean` API before accessing any of the following APIs. ``` engine.block.supportsFill(scene); // Returns falseengine.block.supportsFill(block); // Returns true ``` In order to receive the fill id of a design block, call the `getFill(id: number): number` API. You can now pass this id into other APIs in order to query more information about the fill, e.g. its type via the `getType(id: number): ObjectType` API. ``` const colorFill = engine.block.getFill(block);const defaultRectFillType = engine.block.getType(colorFill); ``` ## Fill Properties[#](#fill-properties) Just like design blocks, fills with different types have different properties that you can query and modify via the API. Use `findAllProperties(id: number): string[]` in order to get a list of all properties of a given fill. For the solid color fill in this example, the call would return `["fill/color/value", "type"]`. Please refer to the [design blocks](vue/concepts/blocks-90241e/) for a complete list of all available properties for each type of fill. ``` const allFillProperties = engine.block.findAllProperties(colorFill); ``` Once we know the property keys of a fill, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `setColor(id: number, property: string, value: Color): void` in order to change the color of the fill to red. Once we do this, our graphic block with rect shape will be filled with solid red. ``` engine.block.setColor(colorFill, 'fill/color/value', { r: 1.0, g: 0.0, b: 0.0, a: 1.0}); ``` ## Disabling Fills[#](#disabling-fills) You can disable and enable a fill using the `setFillEnabled(id: number, enabled: boolean): void` API, for example in cases where the design block should only have a stroke but no fill. Notice that you have to pass the id of the design block and not of the fill to the API. ``` engine.block.setFillEnabled(block, false);engine.block.setFillEnabled(block, !engine.block.isFillEnabled(block)); ``` ## Changing Fill Types[#](#changing-fill-types) All design blocks that support fills allow you to also exchange their current fill for any other type of fill. In order to do this, you need to first create a new fill object using `createFill(type: FillType): number`. ``` const imageFill = engine.block.createFill('image');engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg'); ``` In order to assign a fill to a design block, simply call `setFill(id: number, fill: number): void`. Make sure to delete the previous fill of the design block first if you don’t need it any more, otherwise we will have leaked it into the scene and won’t be able to access it any more, because we don’t know its id. Notice that we don’t use the `appendChild` API here, which only works with design blocks and not fills. When a fill is attached to one design block, it will be automatically destroyed when the block itself gets destroyed. ``` engine.block.destroy(engine.block.getFill(block));engine.block.setFill(block, imageFill); /* The following line would also destroy imageFill */// engine.block.destroy(circle); ``` ## Duplicating Fills[#](#duplicating-fills) If we duplicate a design block with a fill that is only attached to this block, the fill will automatically be duplicated as well. In order to modify the properties of the duplicate fill, we have to query its id from the duplicate block. ``` const duplicateBlock = engine.block.duplicate(block);engine.block.setPositionX(duplicateBlock, 450);const autoDuplicateFill = engine.block.getFill(duplicateBlock);engine.block.setString( autoDuplicateFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg'); // const manualDuplicateFill = engine.block.duplicate(autoDuplicateFill);// /* We could now assign this fill to another block. */// engine.block.destroy(manualDuplicateFill); ``` ## Sharing Fills[#](#sharing-fills) It is also possible to share a single fill instance between multiple design blocks. In that case, changing the properties of the fill will apply to all of the blocks that it’s attached to at once. Destroying a block with a shared fill will not destroy the fill until there are no other design blocks left that still use that fill. ``` const sharedFillBlock = engine.block.create('graphic');engine.block.setShape(sharedFillBlock, engine.block.createShape('rect'));engine.block.setPositionX(sharedFillBlock, 350);engine.block.setPositionY(sharedFillBlock, 400);engine.block.setWidth(sharedFillBlock, 100);engine.block.setHeight(sharedFillBlock, 100);engine.block.appendChild(page, sharedFillBlock); engine.block.setFill(sharedFillBlock, engine.block.getFill(block)); ``` --- [Source](https:/img.ly/docs/cesdk/vue/fills/image-e9cb5c) --- # Image Fills Fill shapes, text, and design blocks with photos and images from URLs, uploads, or asset libraries using CE.SDK’s versatile image fill system. ![Image Fills example showing multiple images applied to design blocks with different fill modes](/docs/cesdk/_astro/browser.hero.DjAVsBFL_19kaeF.webp) 15 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-fills-image-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-fills-image-browser) Image fills paint design blocks with raster or vector image content, supporting various formats including PNG, JPEG, WebP, and SVG. You can load images from remote URLs, local files, data URIs, and asset libraries, with built-in support for responsive images through source sets and multiple content fill modes for flexible positioning. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json';import { calculateGridLayout } from './utils'; 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'); } // Fill features are enabled by default in CE.SDK // You can check and control fill feature availability: const isFillEnabled = cesdk.feature.isEnabled('ly.img.fill', { engine: cesdk.engine }); console.log('Fill feature enabled:', isFillEnabled); await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Calculate responsive grid layout for demonstrations const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 7); const { blockWidth, blockHeight, getPosition } = layout; const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const blockSize = { width: blockWidth, height: blockHeight }; // ===== Section 1: Check Fill Support ===== // Check if a block supports fills before accessing fill APIs const testBlock = engine.block.create('graphic'); const canHaveFill = engine.block.supportsFill(testBlock); console.log('Block supports fills:', canHaveFill); engine.block.destroy(testBlock); // ===== Section 2: Create and Apply Image Fill ===== // Create a new image fill using the convenience API const coverImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, coverImageBlock); // Or create manually for more control const manualBlock = engine.block.create('graphic'); engine.block.setShape(manualBlock, engine.block.createShape('rect')); engine.block.setWidth(manualBlock, blockWidth); engine.block.setHeight(manualBlock, blockHeight); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); engine.block.setFill(manualBlock, imageFill); engine.block.appendChild(page, manualBlock); // Get the current fill from a block const currentFill = engine.block.getFill(coverImageBlock); const fillType = engine.block.getType(currentFill); console.log('Fill type:', fillType); // '//ly.img.ubq/fill/image' // ===== Section 3: Content Fill Modes ===== // Cover mode: Fill entire block, may crop image const coverBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: blockSize } ); engine.block.appendChild(page, coverBlock); engine.block.setEnum(coverBlock, 'contentFill/mode', 'Cover'); // Contain mode: Fit entire image, may leave empty space const containBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_4.jpg', { size: blockSize } ); engine.block.appendChild(page, containBlock); engine.block.setEnum(containBlock, 'contentFill/mode', 'Contain'); // Get current fill mode const currentMode = engine.block.getEnum(containBlock, 'contentFill/mode'); console.log('Current fill mode:', currentMode); // ===== Section 4: Source Sets (Responsive Images) ===== // Use source sets for responsive images const responsiveBlock = engine.block.create('graphic'); engine.block.setShape(responsiveBlock, engine.block.createShape('rect')); engine.block.setWidth(responsiveBlock, blockWidth); engine.block.setHeight(responsiveBlock, blockHeight); const responsiveFill = engine.block.createFill('image'); engine.block.setSourceSet(responsiveFill, 'fill/image/sourceSet', [ { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 512, height: 341 }, { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 1024, height: 683 }, { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 2048, height: 1366 } ]); engine.block.setFill(responsiveBlock, responsiveFill); engine.block.appendChild(page, responsiveBlock); // Get current source set const sourceSet = engine.block.getSourceSet( responsiveFill, 'fill/image/sourceSet' ); console.log('Source set entries:', sourceSet.length); // ===== Section 5: Data URI / Base64 Images ===== // Use data URI for embedded images (small SVG example) const svgContent = ` SVG `; const svgDataUri = `data:image/svg+xml;base64,${btoa(svgContent)}`; const dataUriBlock = engine.block.create('graphic'); engine.block.setShape(dataUriBlock, engine.block.createShape('rect')); engine.block.setWidth(dataUriBlock, blockWidth); engine.block.setHeight(dataUriBlock, blockHeight); const dataUriFill = engine.block.createFill('image'); engine.block.setString(dataUriFill, 'fill/image/imageFileURI', svgDataUri); engine.block.setFill(dataUriBlock, dataUriFill); engine.block.appendChild(page, dataUriBlock); // ===== Section 6: Opacity ===== // Control opacity for transparency effects const opacityBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_6.jpg', { size: blockSize } ); engine.block.appendChild(page, opacityBlock); engine.block.setFloat(opacityBlock, 'opacity', 0.6); // ===== Position all blocks in grid layout ===== const blocks = [ coverImageBlock, // Position 0 manualBlock, // Position 1 coverBlock, // Position 2 containBlock, // Position 3 responsiveBlock, // Position 4 dataUriBlock, // Position 5 opacityBlock // Position 6 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Zoom to show all content await engine.scene.zoomToBlock(page); }} export default Example; ``` This guide covers how to create and apply image fills programmatically, configure content fill modes, work with responsive images, and load images from different sources. ## Understanding Image Fills[#](#understanding-image-fills) Image fills are one of the fundamental fill types in CE.SDK, identified by the type `'//ly.img.ubq/fill/image'` or simply `'image'`. Unlike color fills that provide solid colors or gradient fills that create color transitions, image fills paint blocks with photographic or graphic content from image files. CE.SDK supports common image formats including PNG, JPEG, JPG, GIF, WebP, SVG, and BMP, with transparency support in formats like PNG, WebP, and SVG. The image fill system handles content scaling, positioning, and optimization automatically while giving you full programmatic control when needed. ## Checking Image Fill Support[#](#checking-image-fill-support) Before working with fills, we should verify that a block supports fill operations. Not all blocks in CE.SDK can have fills—for example, scenes and pages typically don’t support fills, while graphic blocks, shapes, and text blocks do. ``` // Check if a block supports fills before accessing fill APIsconst testBlock = engine.block.create('graphic');const canHaveFill = engine.block.supportsFill(testBlock);console.log('Block supports fills:', canHaveFill);engine.block.destroy(testBlock); ``` The `supportsFill()` method returns `true` if the block can have a fill assigned to it. Always check this before attempting to access fill APIs to avoid errors. ## Creating Image Fills[#](#creating-image-fills) CE.SDK provides two approaches for creating image fills: a convenience API for quick block creation, and manual creation for more control over the fill configuration. ### Using the Convenience API[#](#using-the-convenience-api) The fastest way to create a block with an image fill is using the `addImage()` method, which creates a graphic block, configures the image fill, and adds it to the scene in one operation: ``` // Create a new image fill using the convenience APIconst coverImageBlock = await engine.block.addImage(imageUri, { size: blockSize});engine.block.appendChild(page, coverImageBlock); ``` This convenience method handles all the underlying setup automatically, including creating the graphic block, shape, fill, and positioning. ### Manual Image Fill Creation[#](#manual-image-fill-creation) For more control over the fill configuration or to apply fills to existing blocks, you can create fills manually: ``` // Or create manually for more controlconst manualBlock = engine.block.create('graphic');engine.block.setShape(manualBlock, engine.block.createShape('rect'));engine.block.setWidth(manualBlock, blockWidth);engine.block.setHeight(manualBlock, blockHeight); const imageFill = engine.block.createFill('image');engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg');engine.block.setFill(manualBlock, imageFill);engine.block.appendChild(page, manualBlock); ``` When creating fills manually, the fill exists independently until you attach it to a block using `setFill()`. If you create a fill but don’t attach it to a block, you must destroy it manually to avoid memory leaks. ### Getting the Current Fill[#](#getting-the-current-fill) You can retrieve the fill from any block and inspect its type to verify it’s an image fill: ``` // Get the current fill from a blockconst currentFill = engine.block.getFill(coverImageBlock);const fillType = engine.block.getType(currentFill);console.log('Fill type:', fillType); // '//ly.img.ubq/fill/image' ``` The `getFill()` method returns the fill’s block ID, which you can then use to query the fill’s type and properties. ## Configuring Content Fill Modes[#](#configuring-content-fill-modes) Content fill modes control how images scale and position within their containing blocks. CE.SDK provides two primary modes: Cover and Contain, each optimized for different use cases. ### Cover Mode[#](#cover-mode) Cover mode ensures the image fills the entire block while maintaining its aspect ratio. Parts of the image may be cropped if the aspect ratios don’t match, but there will never be empty space in the block: ``` // Cover mode: Fill entire block, may crop imageconst coverBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: blockSize });engine.block.appendChild(page, coverBlock);engine.block.setEnum(coverBlock, 'contentFill/mode', 'Cover'); ``` Cover mode is ideal for backgrounds, hero images, and photo frames where you want the block completely filled with image content. The image is scaled to cover the entire area, and any overflow is cropped. ### Contain Mode[#](#contain-mode) Contain mode fits the entire image within the block while maintaining its aspect ratio. This may leave empty space if the aspect ratios don’t match, but the entire image will always be visible: ``` // Contain mode: Fit entire image, may leave empty spaceconst containBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_4.jpg', { size: blockSize });engine.block.appendChild(page, containBlock);engine.block.setEnum(containBlock, 'contentFill/mode', 'Contain'); ``` Contain mode is best for logos, product images, and situations where preserving the complete image visibility is more important than filling the entire block. ### Getting the Current Fill Mode[#](#getting-the-current-fill-mode) You can query the current fill mode to understand how the image is being displayed: ``` // Get current fill modeconst currentMode = engine.block.getEnum(containBlock, 'contentFill/mode');console.log('Current fill mode:', currentMode); ``` This returns either `'Cover'` or `'Contain'` depending on the current configuration. ## Working with Source Sets[#](#working-with-source-sets) Source sets enable responsive images by providing multiple resolutions of the same image. The engine automatically selects the most appropriate size based on the current display context, optimizing both performance and visual quality. ### Setting Up a Source Set[#](#setting-up-a-source-set) A source set is an array of image sources, each with a URI and dimensions: ``` // Use source sets for responsive imagesconst responsiveBlock = engine.block.create('graphic');engine.block.setShape(responsiveBlock, engine.block.createShape('rect'));engine.block.setWidth(responsiveBlock, blockWidth);engine.block.setHeight(responsiveBlock, blockHeight); const responsiveFill = engine.block.createFill('image');engine.block.setSourceSet(responsiveFill, 'fill/image/sourceSet', [ { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 512, height: 341 }, { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 1024, height: 683 }, { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 2048, height: 1366 }]);engine.block.setFill(responsiveBlock, responsiveFill);engine.block.appendChild(page, responsiveBlock); ``` Each entry in the source set specifies a URI and the image’s width and height in pixels. The engine calculates the current drawing size and selects the source with the closest size that exceeds the required dimensions. Source sets are particularly valuable for optimizing bandwidth usage during preview while ensuring high-resolution output during export. The engine automatically uses the highest resolution available when exporting. ### Retrieving Source Sets[#](#retrieving-source-sets) You can get the current source set from a fill to inspect or modify it: ``` // Get current source setconst sourceSet = engine.block.getSourceSet( responsiveFill, 'fill/image/sourceSet');console.log('Source set entries:', sourceSet.length); ``` ## Loading Images from Different Sources[#](#loading-images-from-different-sources) CE.SDK’s image fills support multiple image source types, giving you flexibility in how you provide image content to your designs. ### Data URIs and Base64[#](#data-uris-and-base64) You can embed image data directly using data URIs, which is particularly useful for small images, icons, or dynamically generated graphics: ``` // Use data URI for embedded images (small SVG example)const svgContent = ` SVG `;const svgDataUri = `data:image/svg+xml;base64,${btoa(svgContent)}`; const dataUriBlock = engine.block.create('graphic');engine.block.setShape(dataUriBlock, engine.block.createShape('rect'));engine.block.setWidth(dataUriBlock, blockWidth);engine.block.setHeight(dataUriBlock, blockHeight); const dataUriFill = engine.block.createFill('image');engine.block.setString(dataUriFill, 'fill/image/imageFileURI', svgDataUri);engine.block.setFill(dataUriBlock, dataUriFill);engine.block.appendChild(page, dataUriBlock); ``` Data URIs embed the entire image within the URI string itself, eliminating the need for network requests. However, this increases the scene file size, so it’s best reserved for smaller images or cases where you need guaranteed availability without network dependencies. ## Additional Techniques[#](#additional-techniques) ### Controlling Opacity[#](#controlling-opacity) You can control the overall opacity of blocks with image fills, affecting the entire block including its fill: ``` // Control opacity for transparency effectsconst opacityBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_6.jpg', { size: blockSize });engine.block.appendChild(page, opacityBlock);engine.block.setFloat(opacityBlock, 'opacity', 0.6); ``` The opacity value ranges from 0 (fully transparent) to 1 (fully opaque). This affects the entire block, including the image fill. For transparency within the image itself, use image formats that support alpha channels like PNG, WebP, or SVG. Opacity is a block property, not a fill property. It affects the entire block including any strokes, effects, or other visual properties applied to the block. ## API Reference[#](#api-reference) ### Core Methods[#](#core-methods) | Method | Description | | --- | --- | | `createFill('image')` | Create a new image fill object | | `setFill(block, fill)` | Assign an image fill to a block | | `getFill(block)` | Get the fill ID from a block | | `setString(fill, property, value)` | Set the image URI | | `getString(fill, property)` | Get the current image URI | | `setSourceSet(fill, property, sources)` | Set responsive image sources | | `getSourceSet(fill, property)` | Get current source set | | `setEnum(block, property, value)` | Set content fill mode | | `getEnum(block, property)` | Get current fill mode | | `supportsFill(block)` | Check if block supports fills | | `addImage(url, options)` | Convenience method to create image block with fill | ### Image Fill Properties[#](#image-fill-properties) | Property | Type | Description | | --- | --- | --- | | `fill/image/imageFileURI` | String | Single image URI (URL, data URI, or file path) | | `fill/image/sourceSet` | SourceSet\[\] | Array of responsive image sources with dimensions | ### Content Fill Properties[#](#content-fill-properties) | Property | Type | Values | Description | | --- | --- | --- | --- | | `contentFill/mode` | Enum | ’Cover’, ‘Contain’ | How the image scales within its block | ### SourceSet Interface[#](#sourceset-interface) ``` interface SourceSetEntry { uri: string; // Image URI width: number; // Image width in pixels height: number; // Image height in pixels} ``` --- [Source](https:/img.ly/docs/cesdk/vue/fills/color-7129cd) --- # Color Fills Apply uniform solid colors to shapes, text, and design blocks using CE.SDK’s comprehensive color fill system with support for multiple color spaces. ![Color Fills example showing various colored shapes with RGB, CMYK, and Spot Colors](/docs/cesdk/_astro/browser.hero.BRZqHVJH_cpGsT.webp) 15 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-fills-color-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-fills-color-browser) Color fills are one of the fundamental fill types in CE.SDK, allowing you to paint design blocks with solid, uniform colors. Unlike gradient fills that transition between colors or image fills that display photo content, color fills apply a single color across the entire block. The color fill system supports multiple color spaces including RGB for screen display, CMYK for print workflows, and Spot Colors for brand consistency. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json';import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Color Fills Guide * * This example demonstrates: * - Creating and applying color fills * - Working with RGB, CMYK, and Spot Colors * - Managing fill properties * - Enabling/disabling fills * - Sharing fills between 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'); } // Create a design scene using CE.SDK cesdk method await cesdk.createDesignScene(); 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 dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Set page background to light gray const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { r: 0.96, g: 0.96, b: 0.96, a: 1.0 }); // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 12); const { blockWidth, blockHeight, getPosition } = layout; // Helper function to create a shape with a fill const createShapeWithFill = ( shapeType: 'rect' | 'ellipse' | 'polygon' | 'star' = 'rect' ): { block: number; fill: number } => { const block = engine.block.create('graphic'); const shape = engine.block.createShape(shapeType); engine.block.setShape(block, shape); // Set size engine.block.setWidth(block, blockWidth); engine.block.setHeight(block, blockHeight); // Append to page engine.block.appendChild(page, block); // Check if block supports fills const canHaveFill = engine.block.supportsFill(block); if (!canHaveFill) { throw new Error('Block does not support fills'); } // Create a color fill const colorFill = engine.block.createFill('color'); // Apply the fill to the block engine.block.setFill(block, colorFill); return { block, fill: colorFill }; }; // Example 1: RGB Color Fill (Red) const { block: rgbBlock, fill: rgbFill } = createShapeWithFill(); engine.block.setColor(rgbFill, 'fill/color/value', { r: 1.0, // Red (0.0 to 1.0) g: 0.0, // Green b: 0.0, // Blue a: 1.0 // Alpha (opacity) }); // Example 2: RGB Color Fill (Green with transparency) const { block: transparentBlock, fill: transparentFill } = createShapeWithFill(); engine.block.setColor(transparentFill, 'fill/color/value', { r: 0.0, g: 0.8, b: 0.2, a: 0.5 // 50% opacity }); // Example 3: RGB Color Fill (Blue) const { block: blueBlock, fill: blueFill } = createShapeWithFill(); engine.block.setColor(blueFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }); // Get the current fill from a block const currentFill = engine.block.getFill(blueBlock); const fillType = engine.block.getType(currentFill); console.log('Fill type:', fillType); // '//ly.img.ubq/fill/color' // Get the current color value const currentColor = engine.block.getColor(blueFill, 'fill/color/value'); console.log('Current color:', currentColor); // Example 4: CMYK Color Fill (Magenta) const { block: cmykBlock, fill: cmykFill } = createShapeWithFill('ellipse'); engine.block.setColor(cmykFill, 'fill/color/value', { c: 0.0, // Cyan (0.0 to 1.0) m: 1.0, // Magenta y: 0.0, // Yellow k: 0.0, // Key/Black tint: 1.0 // Tint value (0.0 to 1.0) }); // Example 5: Print-Ready CMYK Color const { block: printBlock, fill: printFill } = createShapeWithFill('ellipse'); engine.block.setColor(printFill, 'fill/color/value', { c: 0.0, m: 0.85, y: 1.0, k: 0.0, tint: 1.0 }); // Example 6: Spot Color (Brand Color) // First define the spot color globally engine.editor.setSpotColorRGB('BrandRed', 0.9, 0.1, 0.1); engine.editor.setSpotColorRGB('BrandBlue', 0.1, 0.3, 0.9); // Then apply to fill const { block: spotBlock, fill: spotFill } = createShapeWithFill('ellipse'); engine.block.setColor(spotFill, 'fill/color/value', { name: 'BrandRed', tint: 1.0, externalReference: '' // Optional reference system }); // Example 7: Brand Color Application // Apply brand color to multiple elements const { block: headerBlock, fill: headerFill } = createShapeWithFill('star'); const brandColor = { name: 'BrandBlue', tint: 1.0, externalReference: '' }; engine.block.setColor(headerFill, 'fill/color/value', brandColor); // Example 8: Second element with same brand color const { block: buttonBlock, fill: buttonFill } = createShapeWithFill('star'); engine.block.setColor(buttonFill, 'fill/color/value', brandColor); // Example 9: Toggle fill visibility const { block: toggleBlock, fill: toggleFill } = createShapeWithFill('star'); engine.block.setColor(toggleFill, 'fill/color/value', { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }); // Check fill state const isEnabled = engine.block.isFillEnabled(toggleBlock); console.log('Fill enabled:', isEnabled); // true // Example 10: Shared Fill const block1 = engine.block.create('graphic'); const shape1 = engine.block.createShape('rect'); engine.block.setShape(block1, shape1); engine.block.setWidth(block1, blockWidth); engine.block.setHeight(block1, blockHeight / 2); engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, blockWidth); engine.block.setHeight(block2, blockHeight / 2); engine.block.appendChild(page, block2); // Create one fill const sharedFill = engine.block.createFill('color'); engine.block.setColor(sharedFill, 'fill/color/value', { r: 0.5, g: 0.0, b: 0.5, a: 1.0 }); // Apply to both blocks engine.block.setFill(block1, sharedFill); engine.block.setFill(block2, sharedFill); // Example 11: Yellow Star const { block: replaceBlock, fill: replaceFill } = createShapeWithFill('star'); engine.block.setColor(replaceFill, 'fill/color/value', { r: 0.9, g: 0.9, b: 0.1, a: 1.0 }); // Example 12: Color Space Conversion (for demonstration) const rgbColor = { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }; // Convert to CMYK const cmykColor = engine.editor.convertColorToColorSpace(rgbColor, 'CMYK'); console.log('Converted CMYK color:', cmykColor); // ===== Position all blocks in grid layout ===== const blocks = [ rgbBlock, // Position 0 transparentBlock, // Position 1 blueBlock, // Position 2 cmykBlock, // Position 3 printBlock, // Position 4 spotBlock, // Position 5 headerBlock, // Position 6 buttonBlock, // Position 7 toggleBlock, // Position 8 block1, // Position 9 block2, // Position 10 replaceBlock // Position 11 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Zoom to fit all content await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); }} export default Example; ``` This guide demonstrates how to create, apply, and modify color fills programmatically, work with different color spaces, and manage fill properties for various design elements. ## Understanding Color Fills[#](#understanding-color-fills) ### What is a Color Fill?[#](#what-is-a-color-fill) A color fill is a fill object identified by the type `'//ly.img.ubq/fill/color'` (or its shorthand `'color'`) that paints a design block with a single, uniform color. Color fills are part of the broader fill system in CE.SDK and contain a `fill/color/value` property that defines the actual color using various color space formats. Color fills differ from other fill types available in CE.SDK: * **Color fills**: Solid, uniform color across the entire block * **Gradient fills**: Color transitions (linear, radial, conical) * **Image fills**: Photo or raster content * **Video fills**: Animated video content ### Supported Color Spaces[#](#supported-color-spaces) CE.SDK’s color fill system supports multiple color spaces to accommodate different design and production workflows: * **RGB/sRGB**: Red, Green, Blue with alpha channel (standard for screen display) * **CMYK**: Cyan, Magenta, Yellow, Key (black) with tint (for print production) * **Spot Colors**: Named colors with RGB/CMYK approximations (for brand consistency) Each color space serves specific use cases—use RGB for digital designs, CMYK for print-ready content, and Spot Colors to maintain brand standards across projects. ## Checking Color Fill Support[#](#checking-color-fill-support) ### Verifying Block Compatibility[#](#verifying-block-compatibility) Before applying color fills to a block, verify that the block type supports fills. Not all block types can have fills—for example, scene and page blocks typically don’t support fills. ``` // Check if block supports fillsconst canHaveFill = engine.block.supportsFill(block);if (!canHaveFill) { throw new Error('Block does not support fills');} ``` Graphic blocks, shapes, and text blocks typically support fills. Always check `supportsFill()` before accessing fill APIs to avoid runtime errors and ensure smooth operation. ## Creating Color Fills[#](#creating-color-fills) ### Creating a New Color Fill[#](#creating-a-new-color-fill) Create a new color fill instance using the `createFill()` method with the type `'color'` or the full type name `'//ly.img.ubq/fill/color'`. ``` // Create a color fillconst colorFill = engine.block.createFill('color'); ``` The `createFill()` method returns a numeric fill ID. The fill exists independently until you attach it to a block using `setFill()`. If you create a fill but don’t attach it to a block, you must destroy it manually to prevent memory leaks. ### Default Color Fill Properties[#](#default-color-fill-properties) New color fills have default properties—typically white or transparent. You can discover all available properties using `findAllProperties()`: ``` const properties = engine.block.findAllProperties(colorFillId);console.log(properties); // ["fill/color/value", "type"] ``` ## Applying Color Fills[#](#applying-color-fills) ### Setting a Fill on a Block[#](#setting-a-fill-on-a-block) Once you’ve created a color fill, attach it to a block using `setFill()`: ``` // Apply the fill to the blockengine.block.setFill(block, colorFill); ``` This example creates a graphic block with a rectangle shape and applies the color fill to it. The block will now render with the fill’s color. ### Getting the Current Fill[#](#getting-the-current-fill) Retrieve the current fill attached to a block using `getFill()` and inspect its type: ``` // Get the current fill from a blockconst currentFill = engine.block.getFill(blueBlock);const fillType = engine.block.getType(currentFill);console.log('Fill type:', fillType); // '//ly.img.ubq/fill/color' ``` ## Modifying Color Fill Properties[#](#modifying-color-fill-properties) ### Setting RGB Colors[#](#setting-rgb-colors) Set the fill color using RGB values with the `setColor()` method. RGB values are normalized floats from 0.0 to 1.0, and the alpha channel controls opacity. ``` const { block: rgbBlock, fill: rgbFill } = createShapeWithFill();engine.block.setColor(rgbFill, 'fill/color/value', { r: 1.0, // Red (0.0 to 1.0) g: 0.0, // Green b: 0.0, // Blue a: 1.0 // Alpha (opacity)}); ``` The alpha channel (a) controls opacity: 1.0 is fully opaque, 0.0 is fully transparent. This allows you to create semi-transparent overlays and layered color effects. ### Setting CMYK Colors[#](#setting-cmyk-colors) For print workflows, use CMYK color space with the `setColor()` method. CMYK values are also normalized from 0.0 to 1.0, and include a tint value for partial color application. ``` const { block: cmykBlock, fill: cmykFill } = createShapeWithFill('ellipse');engine.block.setColor(cmykFill, 'fill/color/value', { c: 0.0, // Cyan (0.0 to 1.0) m: 1.0, // Magenta y: 0.0, // Yellow k: 0.0, // Key/Black tint: 1.0 // Tint value (0.0 to 1.0)}); ``` The tint value allows partial application of the color, useful for creating lighter variations without changing the base CMYK values. ### Setting Spot Colors[#](#setting-spot-colors) Spot colors are named colors that must be defined before use. They’re ideal for maintaining brand consistency and can have both RGB and CMYK approximations for different output scenarios. ``` // First define the spot color globallyengine.editor.setSpotColorRGB('BrandRed', 0.9, 0.1, 0.1);engine.editor.setSpotColorRGB('BrandBlue', 0.1, 0.3, 0.9); // Then apply to fillconst { block: spotBlock, fill: spotFill } = createShapeWithFill('ellipse');engine.block.setColor(spotFill, 'fill/color/value', { name: 'BrandRed', tint: 1.0, externalReference: '' // Optional reference system}); ``` First, define the spot color globally using `setSpotColorRGB()` or `setSpotColorCMYK()`, then apply it to your fill using the color name. The tint value controls intensity from 0.0 to 1.0. ### Getting Current Color Value[#](#getting-current-color-value) Retrieve the current color value from a fill using `getColor()`: ``` // Get the current color valueconst currentColor = engine.block.getColor(blueFill, 'fill/color/value');console.log('Current color:', currentColor); ``` ## Enabling and Disabling Color Fills[#](#enabling-and-disabling-color-fills) ### Toggle Fill Visibility[#](#toggle-fill-visibility) You can temporarily disable a fill without removing it from the block. This preserves all fill properties while making the block transparent: ``` const { block: toggleBlock, fill: toggleFill } = createShapeWithFill('star');engine.block.setColor(toggleFill, 'fill/color/value', { r: 1.0, g: 0.5, b: 0.0, a: 1.0}); // Check fill stateconst isEnabled = engine.block.isFillEnabled(toggleBlock);console.log('Fill enabled:', isEnabled); // true ``` Disabling fills is useful for creating stroke-only designs or for temporarily hiding fills during interactive editing sessions. The fill properties remain intact and can be re-enabled at any time. ## Additional Techniques[#](#additional-techniques) ### Sharing Color Fills[#](#sharing-color-fills) You can share a single fill instance between multiple blocks. Changes to the shared fill affect all blocks using it: ``` const block1 = engine.block.create('graphic');const shape1 = engine.block.createShape('rect');engine.block.setShape(block1, shape1);engine.block.setWidth(block1, blockWidth);engine.block.setHeight(block1, blockHeight / 2);engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic');const shape2 = engine.block.createShape('rect');engine.block.setShape(block2, shape2);engine.block.setWidth(block2, blockWidth);engine.block.setHeight(block2, blockHeight / 2);engine.block.appendChild(page, block2); // Create one fillconst sharedFill = engine.block.createFill('color');engine.block.setColor(sharedFill, 'fill/color/value', { r: 0.5, g: 0.0, b: 0.5, a: 1.0}); // Apply to both blocksengine.block.setFill(block1, sharedFill);engine.block.setFill(block2, sharedFill); ``` With shared fills, modifying the fill’s color updates all blocks simultaneously. The fill is only destroyed when the last block referencing it is destroyed. ### Color Space Conversion[#](#color-space-conversion) Convert colors between different color spaces using `convertColorToColorSpace()`: ``` const rgbColor = { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }; // Convert to CMYKconst cmykColor = engine.editor.convertColorToColorSpace(rgbColor, 'CMYK');console.log('Converted CMYK color:', cmykColor); ``` This is useful when you need to ensure color consistency across different output mediums (screen vs. print). ## Common Use Cases[#](#common-use-cases) ### Brand Color Application[#](#brand-color-application) Define and apply brand colors as spot colors to maintain consistency across all design elements: ``` // Apply brand color to multiple elementsconst { block: headerBlock, fill: headerFill } = createShapeWithFill('star');const brandColor = { name: 'BrandBlue', tint: 1.0, externalReference: '' };engine.block.setColor(headerFill, 'fill/color/value', brandColor); ``` Using spot colors ensures brand consistency and makes it easy to update all instances of a brand color by modifying the spot color definition. ### Transparency Effects[#](#transparency-effects) Create semi-transparent overlays and visual effects by adjusting the alpha channel: ``` const { block: transparentBlock, fill: transparentFill } = createShapeWithFill();engine.block.setColor(transparentFill, 'fill/color/value', { r: 0.0, g: 0.8, b: 0.2, a: 0.5 // 50% opacity}); ``` ### Print-Ready Colors[#](#print-ready-colors) Use CMYK color space for designs destined for print production: ``` const { block: printBlock, fill: printFill } = createShapeWithFill('ellipse');engine.block.setColor(printFill, 'fill/color/value', { c: 0.0, m: 0.85, y: 1.0, k: 0.0, tint: 1.0}); ``` ## Troubleshooting[#](#troubleshooting) ### Fill Not Visible[#](#fill-not-visible) If your fill doesn’t appear: * Check if fill is enabled: `engine.block.isFillEnabled(block)` * Verify alpha channel is not 0: Check the `a` property in RGBA colors * Ensure block has valid dimensions (width and height > 0) * Confirm block is in the scene hierarchy ### Color Looks Different Than Expected[#](#color-looks-different-than-expected) If colors don’t match expectations: * Verify you’re using the correct color space (RGB vs CMYK) * Check if spot color is properly defined before use * Review tint values (should be 0.0-1.0) * Consider color space conversion for your output medium ### Memory Leaks[#](#memory-leaks) To prevent memory leaks: * Always destroy replaced fills: `engine.block.destroy(oldFill)` * Don’t create fills without attaching them to blocks * Clean up shared fills when they’re no longer needed ### Cannot Apply Color to Block[#](#cannot-apply-color-to-block) If you can’t apply a color fill: * Verify block supports fills: `engine.block.supportsFill(block)` * Check if block has a shape: Some blocks require shapes before fills work * Ensure fill object is valid and not already destroyed ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `createFill('color')` | Create a new color fill object | | `setFill(block, fill)` | Assign fill to a block | | `getFill(block)` | Get the fill ID from a block | | `setColor(fill, property, color)` | Set color value (RGB, CMYK, or Spot) | | `getColor(fill, property)` | Get current color value | | `setFillEnabled(block, enabled)` | Enable or disable fill rendering | | `isFillEnabled(block)` | Check if fill is enabled | | `supportsFill(block)` | Check if block supports fills | | `findAllProperties(fill)` | List all properties of the fill | | `convertColorToColorSpace(color, space)` | Convert between color spaces | | `setSpotColorRGB(name, r, g, b)` | Define spot color with RGB approximation | | `setSpotColorCMYK(name, c, m, y, k)` | Define spot color with CMYK approximation | ## Next Steps[#](#next-steps) Now that you understand color fills, explore other fill types and color management features: * Learn about Gradient Fills for color transitions * Explore Image Fills for photo content * Understand Fill Overview for the comprehensive fill system * Review Apply Colors for color management across properties * Study Blocks Concept for understanding the block system --- [Source](https:/img.ly/docs/cesdk/vue/export-save-publish/store-custom-metadata-337248) --- # Store Custom Metadata Attach custom key-value metadata to design blocks in CE.SDK for tracking asset origins, storing application state, or linking to external systems. ![Store Custom Metadata example showing an image block with metadata](/docs/cesdk/_astro/browser.hero.D7VjuxYX_Z1280Ny.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-store-custom-metadata-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-store-custom-metadata-browser) Metadata lets you attach arbitrary string key-value pairs to any design block. This data is invisible to end users but persists with the scene through save/load operations. Common use cases include tracking asset origins, storing application-specific state, and linking blocks to external databases or content management systems. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Store Custom Metadata Guide * * Demonstrates how to attach, retrieve, and manage custom metadata on design blocks: * - Setting metadata key-value pairs * - Getting metadata values by key * - Checking if metadata exists * - Listing all metadata keys * - Removing metadata * - Storing structured data as 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'); } // Initialize CE.SDK with Design mode await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Create an image block to attach metadata to const imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_1.jpg', { size: { width: 400, height: 300 } } ); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 200); engine.block.setPositionY(imageBlock, 150); // Set metadata key-value pairs on the block engine.block.setMetadata(imageBlock, 'externalId', 'asset-12345'); engine.block.setMetadata(imageBlock, 'source', 'user-upload'); engine.block.setMetadata(imageBlock, 'uploadedBy', 'user@example.com'); console.log('Set metadata: externalId, source, uploadedBy'); // Retrieve a metadata value by key if (engine.block.hasMetadata(imageBlock, 'externalId')) { const externalId = engine.block.getMetadata(imageBlock, 'externalId'); console.log('External ID:', externalId); } // List all metadata keys on the block const allKeys = engine.block.findAllMetadata(imageBlock); console.log('All metadata keys:', allKeys); // Log all key-value pairs for (const key of allKeys) { const value = engine.block.getMetadata(imageBlock, key); console.log(` ${key}: ${value}`); } // Store structured data as JSON const generationInfo = { source: 'ai-generated', model: 'stable-diffusion', timestamp: Date.now() }; engine.block.setMetadata( imageBlock, 'generationInfo', JSON.stringify(generationInfo) ); // Retrieve and parse structured data const retrievedJson = engine.block.getMetadata( imageBlock, 'generationInfo' ); const parsedInfo = JSON.parse(retrievedJson); console.log('Parsed generation info:', parsedInfo); // Remove a metadata key engine.block.removeMetadata(imageBlock, 'uploadedBy'); console.log('Removed metadata key: uploadedBy'); // Verify the key was removed const hasUploadedBy = engine.block.hasMetadata(imageBlock, 'uploadedBy'); console.log('Has uploadedBy after removal:', hasUploadedBy); // List remaining keys const remainingKeys = engine.block.findAllMetadata(imageBlock); console.log('Remaining metadata keys:', remainingKeys); // Select the image block to show it in focus engine.block.select(imageBlock); console.log( 'Metadata guide initialized. Check the console for metadata operations.' ); }} export default Example; ``` This guide covers how to set, retrieve, list, and remove metadata on blocks, as well as how to store structured data as JSON strings. ## Initialize CE.SDK[#](#initialize-cesdk) We start by initializing CE.SDK with a basic configuration. The metadata APIs are available on the `engine.block` namespace. ``` // Initialize CE.SDK with Design modeawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true});await cesdk.createDesignScene(); const engine = cesdk.engine;const page = engine.block.findByType('page')[0]; // Set page dimensionsengine.block.setWidth(page, 800);engine.block.setHeight(page, 600); // Create an image block to attach metadata toconst imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_1.jpg', { size: { width: 400, height: 300 } });engine.block.appendChild(page, imageBlock);engine.block.setPositionX(imageBlock, 200);engine.block.setPositionY(imageBlock, 150); ``` ## Set Metadata[#](#set-metadata) Use `engine.block.setMetadata()` to attach a key-value pair to a block. Both the key and value must be strings. If the key already exists, the value is overwritten. ``` // Set metadata key-value pairs on the blockengine.block.setMetadata(imageBlock, 'externalId', 'asset-12345');engine.block.setMetadata(imageBlock, 'source', 'user-upload');engine.block.setMetadata(imageBlock, 'uploadedBy', 'user@example.com');console.log('Set metadata: externalId, source, uploadedBy'); ``` You can attach multiple metadata entries to a single block. Each entry is independent and can be accessed, modified, or removed separately. ## Get Metadata[#](#get-metadata) Use `engine.block.getMetadata()` to retrieve a value by its key. This method throws an error if the key doesn’t exist, so always check with `hasMetadata()` first for conditional access. ``` // Retrieve a metadata value by keyif (engine.block.hasMetadata(imageBlock, 'externalId')) { const externalId = engine.block.getMetadata(imageBlock, 'externalId'); console.log('External ID:', externalId);} ``` The `hasMetadata()` method returns `true` if the block has metadata for the specified key, and `false` otherwise. This pattern prevents runtime errors when accessing metadata that may not be set. ## List All Metadata Keys[#](#list-all-metadata-keys) Use `engine.block.findAllMetadata()` to get an array of all metadata keys stored on a block. ``` // List all metadata keys on the blockconst allKeys = engine.block.findAllMetadata(imageBlock);console.log('All metadata keys:', allKeys); // Log all key-value pairsfor (const key of allKeys) { const value = engine.block.getMetadata(imageBlock, key); console.log(` ${key}: ${value}`);} ``` This is useful for iterating through all metadata on a block or debugging what metadata is attached. ## Store Structured Data[#](#store-structured-data) Since metadata values must be strings, you can store structured data by serializing it to JSON. Parse the JSON when retrieving the data. ``` // Store structured data as JSONconst generationInfo = { source: 'ai-generated', model: 'stable-diffusion', timestamp: Date.now()};engine.block.setMetadata( imageBlock, 'generationInfo', JSON.stringify(generationInfo)); // Retrieve and parse structured dataconst retrievedJson = engine.block.getMetadata( imageBlock, 'generationInfo');const parsedInfo = JSON.parse(retrievedJson);console.log('Parsed generation info:', parsedInfo); ``` This pattern lets you store complex objects like configuration settings, generation parameters, or any structured information that can be serialized to JSON. ## Remove Metadata[#](#remove-metadata) Use `engine.block.removeMetadata()` to delete a key-value pair from a block. This method does not throw an error if the key doesn’t exist. ``` // Remove a metadata keyengine.block.removeMetadata(imageBlock, 'uploadedBy');console.log('Removed metadata key: uploadedBy'); ``` After removal, you can verify the key was deleted by checking with `hasMetadata()`. ``` // Verify the key was removedconst hasUploadedBy = engine.block.hasMetadata(imageBlock, 'uploadedBy');console.log('Has uploadedBy after removal:', hasUploadedBy); // List remaining keysconst remainingKeys = engine.block.findAllMetadata(imageBlock);console.log('Remaining metadata keys:', remainingKeys); ``` ## Metadata Persistence[#](#metadata-persistence) Metadata is automatically preserved when saving scenes with `engine.scene.saveToString()` or `engine.scene.saveToArchive()`. When loading a saved scene, all metadata is restored to the blocks. Metadata is only preserved when saving the scene data. Exporting to image or video formats (PNG, JPEG, MP4) does not include metadata since these are final output formats. ## Troubleshooting[#](#troubleshooting) ### getMetadata Throws Error[#](#getmetadata-throws-error) If `getMetadata()` throws an error, the key doesn’t exist on the block. Always use `hasMetadata()` to check before retrieving: ``` if (engine.block.hasMetadata(block, 'myKey')) { const value = engine.block.getMetadata(block, 'myKey');} ``` ### Metadata Lost After Load[#](#metadata-lost-after-load) Ensure you’re saving with `saveToString()` or `saveToArchive()`, not exporting to image/video formats. Only scene saves preserve metadata. ### Large Metadata Values[#](#large-metadata-values) Metadata is designed for small strings. Very large values may impact performance during save/load operations. For large data, consider storing a reference (like a URL or ID) rather than the data itself. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `engine.block.setMetadata(block, key, value)` | Set a metadata key-value pair on a block | | `engine.block.getMetadata(block, key)` | Get the value for a metadata key | | `engine.block.hasMetadata(block, key)` | Check if a metadata key exists | | `engine.block.findAllMetadata(block)` | List all metadata keys on a block | | `engine.block.removeMetadata(block, key)` | Remove a metadata key-value pair | --- [Source](https:/img.ly/docs/cesdk/vue/export-save-publish/save-c8b124) --- # Save Save and serialize designs in CE.SDK for later retrieval, sharing, or storage using string or archive formats. ![Save designs showing different save format options](/docs/cesdk/_astro/browser.hero.DorB6Lk1_LBkkq.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) CE.SDK provides two formats for persisting designs. Choose the format based on your storage and portability requirements. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Save Designs Guide * * Demonstrates how to save and serialize designs in CE.SDK: * - Saving scenes to string format for database storage * - Saving scenes to archive format with embedded assets * - Using built-in save actions and customization */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'); } const engine = cesdk.engine; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage(); if (page == null) { throw new Error('No page found in scene'); } engine.scene.zoomToBlock(page, { padding: 40 }); cesdk.actions.register('saveScene', async () => { const sceneString = await engine.scene.saveToString(); // Send to your backend API console.log('Custom save:', sceneString.length, 'bytes'); }); // Button: Save Scene & Download const handleSaveScene = async () => { const sceneString = await engine.scene.saveToString(); const sceneBlob = new Blob([sceneString], { type: 'application/octet-stream' }); await cesdk.utils.downloadFile(sceneBlob, 'application/octet-stream'); cesdk.ui.showNotification({ message: `Scene downloaded (${(sceneString.length / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Button: Save to Archive & Download const handleSaveToArchive = async () => { const archiveBlob = await engine.scene.saveToArchive(); await cesdk.utils.downloadFile(archiveBlob, 'application/zip'); cesdk.ui.showNotification({ message: `Archive downloaded (${(archiveBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; const handleLoadScene = async () => { await cesdk.actions.run('importScene', { format: 'scene' }); }; const handleLoadArchive = async () => { await cesdk.actions.run('importScene', { format: 'archive' }); const loadedPage = engine.scene.getCurrentPage(); if (loadedPage != null) { engine.scene.zoomToBlock(loadedPage, { padding: 40 }); } }; cesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'save-scene', label: 'Save Scene', icon: '@imgly/Save', onClick: handleSaveScene }, { id: 'ly.img.action.navigationBar', key: 'save-archive', label: 'Save Archive', icon: '@imgly/Download', onClick: handleSaveToArchive }, { id: 'ly.img.action.navigationBar', key: 'load-scene', label: 'Load Scene', icon: '@imgly/Upload', onClick: handleLoadScene }, { id: 'ly.img.action.navigationBar', key: 'load-archive', label: 'Load Archive', icon: '@imgly/Upload', onClick: handleLoadArchive } ] }); }} export default Example; ``` ## Save Format Comparison[#](#save-format-comparison) | Format | Method | Assets | Best For | | --- | --- | --- | --- | | String | `saveToString()` | Referenced by URL | Database storage, cloud sync | | Archive | `saveToArchive()` | Embedded in ZIP | Offline use, file sharing | **String format** produces a lightweight Base64-encoded string where assets remain as URL references. Use this when asset URLs will remain accessible. **Archive format** creates a self-contained ZIP with all assets embedded. Use this for portable designs that work offline. ## Save to String[#](#save-to-string) Serialize the current scene to a Base64-encoded string suitable for database storage. ``` const sceneString = await engine.scene.saveToString(); ``` The string contains the complete scene structure but references assets by their original URLs. ## Save to Archive[#](#save-to-archive) Create a self-contained ZIP file with the scene and all embedded assets. ``` const archiveBlob = await engine.scene.saveToArchive(); ``` The archive includes all pages, elements, and asset data in a single portable file. ## Download to User Device[#](#download-to-user-device) Use `cesdk.utils.downloadFile()` to trigger a browser download with the correct MIME type. For scene strings, convert to a Blob first: ``` const sceneBlob = new Blob([sceneString], { type: 'application/octet-stream'});await cesdk.utils.downloadFile(sceneBlob, 'application/octet-stream'); ``` For archive blobs, pass directly to the download utility: ``` await cesdk.utils.downloadFile(archiveBlob, 'application/zip'); ``` This utility handles creating and revoking object URLs automatically. ## Load Scene from File[#](#load-scene-from-file) Use the built-in `importScene` action to open a file picker for `.scene` files. This restores a previously saved design from its serialized string format. ``` const handleLoadScene = async () => { await cesdk.actions.run('importScene', { format: 'scene' });}; ``` Scene files are lightweight but require the original asset URLs to remain accessible. ## Load Archive from File[#](#load-archive-from-file) Load a self-contained `.zip` archive that includes all embedded assets. ``` const handleLoadArchive = async () => { await cesdk.actions.run('importScene', { format: 'archive' }); ``` Archives are portable and work offline since all assets are bundled within the file. ## Built-in Save Action[#](#built-in-save-action) CE.SDK includes a built-in `saveScene` action that integrates with the navigation bar. ### Running an Action[#](#running-an-action) Trigger the default save behavior programmatically using `actions.run()`: ``` await cesdk.actions.run('saveScene'); ``` This executes the registered handler for `saveScene`, which by default downloads the scene file. ### Customizing an Action[#](#customizing-an-action) Override the default behavior by registering a custom handler: ``` cesdk.actions.register('saveScene', async () => { const sceneString = await engine.scene.saveToString(); // Send to your backend API console.log('Custom save:', sceneString.length, 'bytes');}); ``` The registered handler runs when the built-in save button is clicked or when the action is triggered via `actions.run()`. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `engine.scene.saveToString()` | Serialize scene to Base64 string | | `engine.scene.saveToArchive()` | Save scene with assets as ZIP blob | | `engine.scene.loadFromString()` | Load scene from serialized string | | `engine.scene.loadFromArchive()` | Load scene from archive blob | | `engine.scene.loadFromURL()` | Load scene from remote URL | | `engine.scene.loadFromArchiveURL()` | Load scene from remote ZIP archive | | `cesdk.utils.downloadFile()` | Download blob or string to user device | | `cesdk.actions.run()` | Execute a registered action with parameters | | `cesdk.actions.register()` | Register or override an action handler | ## Next Steps[#](#next-steps) * [Export Overview](vue/export-save-publish/export/overview-9ed3a8/) \- Export designs to image, PDF, and video formats * [Load Scene](vue/open-the-editor/load-scene-478833/) \- Load scenes from remote URLs and archives * [Store Custom Metadata](vue/export-save-publish/store-custom-metadata-337248/) \- Attach metadata like tags or version info to designs * [Partial Export](vue/export-save-publish/export/partial-export-89aaf6/) \- Export individual blocks or selections --- [Source](https:/img.ly/docs/cesdk/vue/export-save-publish/for-social-media-0e8a92) --- # Export for Social Media Export vertical video designs for social media platforms with the correct dimensions, formats, and quality settings. Configure video exports with appropriate resolution, framerate, and bitrate optimized for Instagram Reels, TikTok, and YouTube Shorts. ![Export for Social Media example showing CE.SDK with export buttons for different platforms](/docs/cesdk/_astro/browser.hero.CDmxBtPC_Wf5yU.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-for-social-media-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-for-social-media-browser) Short-form vertical video has become the dominant format for social media. Instagram Reels, TikTok, and YouTube Shorts all use the 9:16 aspect ratio at 1080×1920 pixels. This guide demonstrates how to create and export vertical video content with the correct settings for these platforms. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Export for Social Media Guide * * This example demonstrates: * - Creating a vertical video scene (9:16) for Instagram Reels, TikTok, YouTube Shorts * - Exporting videos with resolution, framerate, and bitrate settings * - Tracking video export progress */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 vertical video scene (9:16) for Instagram Reels, TikTok, YouTube Shorts await cesdk.createVideoScene({ width: 1080, height: 1920, unit: 'Pixel' }); const page = engine.scene.getCurrentPage(); if (!page) { throw new Error('No page found'); } // Add a video clip that fills the vertical frame const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const videoBlock = await engine.block.addVideo( 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4', pageWidth, pageHeight ); engine.block.fillParent(videoBlock); // Export video for Instagram Reels / TikTok / YouTube Shorts (9:16) const exportVideo = async () => { cesdk.ui.showNotification({ message: 'Exporting video...', type: 'info' }); const currentPage = engine.scene.getCurrentPage(); if (!currentPage) return; const videoBlob = await engine.block.exportVideo(currentPage, { mimeType: 'video/mp4', targetWidth: 1080, targetHeight: 1920, framerate: 30, videoBitrate: 8_000_000, // 8 Mbps onProgress: (renderedFrames, encodedFrames, totalFrames) => { const percent = Math.round((encodedFrames / totalFrames) * 100); console.log( `Export progress: ${percent}% (${encodedFrames}/${totalFrames} frames)` ); } }); await cesdk.utils.downloadFile(videoBlob, 'video/mp4'); cesdk.ui.showNotification({ message: `Video exported: ${(videoBlob.size / 1024 / 1024).toFixed(1)} MB (1080×1920)`, type: 'success' }); }; // Configure navigation bar with export button cesdk.ui.setNavigationBarOrder([ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', { id: 'ly.img.action.navigationBar', onClick: exportVideo, key: 'export-video', label: 'Export Video', icon: '@imgly/Video', variant: 'plain', color: 'accent' } ]); }} export default Example; ``` This guide covers creating a vertical video scene, exporting with resolution, framerate, and bitrate settings, and tracking export progress. ## Creating a Video Scene[#](#creating-a-video-scene) Create a video scene with the correct dimensions for vertical video. Use `createVideoScene()` with explicit pixel dimensions to ensure your content matches platform requirements. ``` // Create a vertical video scene (9:16) for Instagram Reels, TikTok, YouTube Shortsawait cesdk.createVideoScene({ width: 1080, height: 1920, unit: 'Pixel'}); const page = engine.scene.getCurrentPage();if (!page) { throw new Error('No page found');} // Add a video clip that fills the vertical frameconst pageWidth = engine.block.getWidth(page);const pageHeight = engine.block.getHeight(page);const videoBlock = await engine.block.addVideo( 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4', pageWidth, pageHeight);engine.block.fillParent(videoBlock); ``` The scene uses 1080×1920 pixels (9:16 aspect ratio), which is the standard resolution for Instagram Reels, TikTok, and YouTube Shorts. The video block fills the entire page dimensions. ## Exporting Videos[#](#exporting-videos) Export video content using `engine.block.exportVideo()`. Configure resolution, framerate, and bitrate for optimal quality and file size. ``` const videoBlob = await engine.block.exportVideo(currentPage, { mimeType: 'video/mp4', targetWidth: 1080, targetHeight: 1920, framerate: 30, videoBitrate: 8_000_000, // 8 Mbps onProgress: (renderedFrames, encodedFrames, totalFrames) => { const percent = Math.round((encodedFrames / totalFrames) * 100); console.log( `Export progress: ${percent}% (${encodedFrames}/${totalFrames} frames)` ); }}); ``` Key video export settings: * **mimeType**: `video/mp4` for broad platform compatibility * **targetWidth/targetHeight**: Output resolution (1080×1920 for vertical) * **framerate**: 30 frames per second (standard for social media) * **videoBitrate**: 8 Mbps provides good quality while keeping file sizes reasonable Higher bitrates produce better quality but larger files. For short-form vertical video, 8 Mbps (8,000,000 bits per second) balances quality and upload speed. ## Tracking Export Progress[#](#tracking-export-progress) Video exports can take time, especially for longer content. Use the `onProgress` callback to provide users with feedback during export. ``` onProgress: (renderedFrames, encodedFrames, totalFrames) => { const percent = Math.round((encodedFrames / totalFrames) * 100); console.log( `Export progress: ${percent}% (${encodedFrames}/${totalFrames} frames)` );} ``` The callback receives three parameters: * **renderedFrames**: Number of frames rendered so far * **encodedFrames**: Number of frames encoded to the output file * **totalFrames**: Total frames to be exported The encoding stage typically trails rendering slightly. Calculate progress percentage from `encodedFrames / totalFrames` for an accurate completion indicator. ## Downloading Exported Files[#](#downloading-exported-files) After export, use `cesdk.utils.downloadFile()` to trigger a browser download: ``` const videoBlob = await engine.block.exportVideo(page, { /* options */ });await cesdk.utils.downloadFile(videoBlob, 'video/mp4'); ``` This utility handles the download process automatically, including memory cleanup. For server uploads, pass the Blob directly to your upload function or FormData. ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `cesdk.createVideoScene()` | Create a video scene with specified dimensions | | `engine.block.exportVideo()` | Export block as video (MP4) | | `engine.scene.getCurrentPage()` | Get the active page for export | | `cesdk.utils.downloadFile()` | Trigger browser download for exported files | ### Export Options (Videos)[#](#export-options-videos) | Option | Type | Description | | --- | --- | --- | | `mimeType` | `string` | Output format: `video/mp4` | | `targetWidth` | `number` | Output width in pixels | | `targetHeight` | `number` | Output height in pixels | | `framerate` | `number` | Frames per second | | `videoBitrate` | `number` | Video bitrate in bits per second | | `onProgress` | `function` | Progress callback with frame counts | ## Next Steps[#](#next-steps) * [Export Overview](vue/export-save-publish/export/overview-9ed3a8/) \- Complete export options including H.264 profiles and advanced settings --- [Source](https:/img.ly/docs/cesdk/vue/export-save-publish/pre-export-validation-3a2cba) --- # Pre-Export Validation Validate your design before export by detecting elements outside the page, protruding content, obscured text, and other issues that could affect the final output quality. ![Pre-Export Validation example showing validation workflow](/docs/cesdk/_astro/browser.hero.C-6FGCPL_1RtfQn.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-pre-export-validation-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-pre-export-validation-browser) Pre-export validation catches layout and quality issues before export, preventing problems like cropped content, hidden text, and elements missing from the final output. Production-quality designs require elements to be properly positioned within the page boundaries. ``` import type { CreativeEngine, EditorPlugin, EditorPluginContext} from '@cesdk/cesdk-js';import type CreativeEditorSDK from '@cesdk/cesdk-js';import packageJson from './package.json'; type ValidationSeverity = 'error' | 'warning'; interface ValidationIssue { type: | 'outside_page' | 'protruding' | 'text_obscured' | 'unfilled_placeholder'; severity: ValidationSeverity; blockId: number; blockName: string; message: string;} interface ValidationResult { errors: ValidationIssue[]; warnings: ValidationIssue[];} 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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; // Set role to adopter so placeholders can be replaced engine.editor.setRole('Adopter'); const page = engine.block.findByType('page')[0]; engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); await this.createDemoScene(engine, page); this.overrideExportAction(cesdk, engine); } private async createDemoScene( engine: CreativeEngine, page: number ): Promise { const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const centerY = pageHeight / 2; // Row 1: Main validation examples (horizontally aligned) const row1Y = centerY - 120; const elementWidth = 150; const elementHeight = 100; const spacing = 20; // Calculate positions for 4 elements in a row const totalRowWidth = elementWidth * 4 + spacing * 3; const startX = (pageWidth - totalRowWidth) / 2; // Create an image that's outside the page (will trigger error) // Positioned to the left of the page - completely outside const outsideImage = engine.block.create('graphic'); engine.block.setName(outsideImage, 'Outside Image'); engine.block.setShape(outsideImage, engine.block.createShape('rect')); const outsideFill = engine.block.createFill('image'); engine.block.setString( outsideFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(outsideImage, outsideFill); engine.block.setWidth(outsideImage, elementWidth); engine.block.setHeight(outsideImage, elementHeight); engine.block.setPositionX(outsideImage, -elementWidth - 10); // Left of the page engine.block.setPositionY(outsideImage, row1Y); engine.block.appendChild(page, outsideImage); // Create a properly placed image for reference (first in row) const validImage = engine.block.create('graphic'); engine.block.setName(validImage, 'Valid Image'); engine.block.setShape(validImage, engine.block.createShape('rect')); const validFill = engine.block.createFill('image'); engine.block.setString( validFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_3.jpg' ); engine.block.setFill(validImage, validFill); engine.block.setWidth(validImage, elementWidth); engine.block.setHeight(validImage, elementHeight); engine.block.setPositionX(validImage, startX); engine.block.setPositionY(validImage, row1Y); engine.block.appendChild(page, validImage); // Create unfilled placeholder (second in row - triggers error) const placeholder = engine.block.create('graphic'); engine.block.setName(placeholder, 'Unfilled Placeholder'); engine.block.setShape(placeholder, engine.block.createShape('rect')); const placeholderFill = engine.block.createFill('image'); engine.block.setFill(placeholder, placeholderFill); engine.block.setWidth(placeholder, elementWidth); engine.block.setHeight(placeholder, elementHeight); engine.block.setPositionX(placeholder, startX + elementWidth + spacing); engine.block.setPositionY(placeholder, row1Y); engine.block.appendChild(page, placeholder); engine.block.setScopeEnabled(placeholder, 'fill/change', true); engine.block.setPlaceholderBehaviorEnabled(placeholderFill, true); engine.block.setPlaceholderEnabled(placeholder, true); engine.block.setPlaceholderControlsOverlayEnabled(placeholder, true); engine.block.setPlaceholderControlsButtonEnabled(placeholder, true); // Create text that will be partially obscured (third in row) const obscuredText = engine.block.create('text'); engine.block.setName(obscuredText, 'Obscured Text'); engine.block.setPositionX( obscuredText, startX + (elementWidth + spacing) * 2 ); engine.block.setPositionY(obscuredText, row1Y); engine.block.setWidth(obscuredText, elementWidth); engine.block.setHeight(obscuredText, elementHeight); engine.block.replaceText(obscuredText, 'Hidden'); engine.block.setFloat(obscuredText, 'text/fontSize', 48); engine.block.appendChild(page, obscuredText); // Create a shape that overlaps the text (added after text = on top) const overlappingShape = engine.block.create('graphic'); engine.block.setName(overlappingShape, 'Overlapping Shape'); engine.block.setShape(overlappingShape, engine.block.createShape('rect')); const shapeFill = engine.block.createFill('color'); engine.block.setColor(shapeFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 0.8 }); engine.block.setFill(overlappingShape, shapeFill); engine.block.setWidth(overlappingShape, elementWidth); engine.block.setHeight(overlappingShape, elementHeight); engine.block.setPositionX( overlappingShape, startX + (elementWidth + spacing) * 2 ); engine.block.setPositionY(overlappingShape, row1Y); engine.block.appendChild(page, overlappingShape); // Create an image that protrudes from the page (fourth in row - will trigger warning) // Extends past right page boundary const protrudingImage = engine.block.create('graphic'); engine.block.setName(protrudingImage, 'Protruding Image'); engine.block.setShape(protrudingImage, engine.block.createShape('rect')); const protrudingFill = engine.block.createFill('image'); engine.block.setString( protrudingFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); engine.block.setFill(protrudingImage, protrudingFill); engine.block.setWidth(protrudingImage, elementWidth); engine.block.setHeight(protrudingImage, elementHeight); engine.block.setPositionX(protrudingImage, pageWidth - elementWidth / 2); // Extends 75px past right engine.block.setPositionY(protrudingImage, row1Y); engine.block.appendChild(page, protrudingImage); // Add explanatory text below the row const explanationText = engine.block.create('text'); engine.block.setPositionX(explanationText, 50); engine.block.setPositionY(explanationText, row1Y + elementHeight + 40); engine.block.setWidth(explanationText, pageWidth - 100); engine.block.setHeightMode(explanationText, 'Auto'); engine.block.replaceText( explanationText, 'Click Export to run validation. Move elements to fix issues.' ); engine.block.setFloat(explanationText, 'text/fontSize', 48); engine.block.setEnum(explanationText, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, explanationText); } private getBoundingBox( engine: CreativeEngine, blockId: number ): [number, number, number, number] { const x = engine.block.getGlobalBoundingBoxX(blockId); const y = engine.block.getGlobalBoundingBoxY(blockId); const width = engine.block.getGlobalBoundingBoxWidth(blockId); const height = engine.block.getGlobalBoundingBoxHeight(blockId); return [x, y, x + width, y + height]; } private calculateOverlap( box1: [number, number, number, number], box2: [number, number, number, number] ): number { const [ax1, ay1, ax2, ay2] = box1; const [bx1, by1, bx2, by2] = box2; const overlapWidth = Math.max(0, Math.min(ax2, bx2) - Math.max(ax1, bx1)); const overlapHeight = Math.max(0, Math.min(ay2, by2) - Math.max(ay1, by1)); const overlapArea = overlapWidth * overlapHeight; const box1Area = (ax2 - ax1) * (ay2 - ay1); if (box1Area === 0) return 0; return overlapArea / box1Area; } private getBlockName(engine: CreativeEngine, blockId: number): string { const name = engine.block.getName(blockId); if (name) return name; const kind = engine.block.getKind(blockId); return kind.charAt(0).toUpperCase() + kind.slice(1); } private getRelevantBlocks(engine: CreativeEngine): number[] { return [ ...engine.block.findByType('text'), ...engine.block.findByType('graphic') ]; } private findOutsideBlocks( engine: CreativeEngine, page: number ): ValidationIssue[] { const issues: ValidationIssue[] = []; const pageBounds = this.getBoundingBox(engine, page); for (const blockId of this.getRelevantBlocks(engine)) { if (!engine.block.isValid(blockId)) continue; const blockBounds = this.getBoundingBox(engine, blockId); const overlap = this.calculateOverlap(blockBounds, pageBounds); if (overlap === 0) { // Element is completely outside the page issues.push({ type: 'outside_page', severity: 'error', blockId, blockName: this.getBlockName(engine, blockId), message: 'Element is completely outside the visible page area' }); } } return issues; } private findProtrudingBlocks( engine: CreativeEngine, page: number ): ValidationIssue[] { const issues: ValidationIssue[] = []; const pageBounds = this.getBoundingBox(engine, page); for (const blockId of this.getRelevantBlocks(engine)) { if (!engine.block.isValid(blockId)) continue; // Compare element bounds against page bounds const blockBounds = this.getBoundingBox(engine, blockId); const overlap = this.calculateOverlap(blockBounds, pageBounds); // Protruding: partially inside (overlap > 0) but not fully inside (overlap < 1) if (overlap > 0 && overlap < 0.99) { issues.push({ type: 'protruding', severity: 'warning', blockId, blockName: this.getBlockName(engine, blockId), message: 'Element extends beyond page boundaries' }); } } return issues; } private findObscuredText( engine: CreativeEngine, page: number ): ValidationIssue[] { const issues: ValidationIssue[] = []; const children = engine.block.getChildren(page); const textBlocks = engine.block.findByType('text'); for (const textId of textBlocks) { if (!engine.block.isValid(textId)) continue; const textIndex = children.indexOf(textId); if (textIndex === -1) continue; // Elements later in children array are rendered on top const blocksAbove = children.slice(textIndex + 1); for (const aboveId of blocksAbove) { // Skip text blocks - they don't typically obscure other text if (engine.block.getType(aboveId) === '//ly.img.ubq/text') continue; const overlap = this.calculateOverlap( this.getBoundingBox(engine, textId), this.getBoundingBox(engine, aboveId) ); if (overlap > 0) { // Text is obscured by element above it issues.push({ type: 'text_obscured', severity: 'warning', blockId: textId, blockName: this.getBlockName(engine, textId), message: 'Text may be partially hidden by overlapping elements' }); break; } } } return issues; } private findUnfilledPlaceholders(engine: CreativeEngine): ValidationIssue[] { const issues: ValidationIssue[] = []; const placeholders = engine.block.findAllPlaceholders(); for (const blockId of placeholders) { if (!engine.block.isValid(blockId)) continue; if (!this.isPlaceholderFilled(engine, blockId)) { issues.push({ type: 'unfilled_placeholder', severity: 'error', blockId, blockName: this.getBlockName(engine, blockId), message: 'Placeholder has not been filled with content' }); } } return issues; } private isPlaceholderFilled( engine: CreativeEngine, blockId: number ): boolean { const fillId = engine.block.getFill(blockId); if (!fillId || !engine.block.isValid(fillId)) return false; const fillType = engine.block.getType(fillId); // Check image fill - empty URI means unfilled placeholder if (fillType === '//ly.img.ubq/fill/image') { const imageUri = engine.block.getString(fillId, 'fill/image/imageFileURI'); return imageUri !== '' && imageUri !== undefined; } // Other fill types are considered filled return true; } private validateDesign(engine: CreativeEngine): ValidationResult { const page = engine.block.findByType('page')[0]; const outsideIssues = this.findOutsideBlocks(engine, page); const protrudingIssues = this.findProtrudingBlocks(engine, page); const obscuredIssues = this.findObscuredText(engine, page); const placeholderIssues = this.findUnfilledPlaceholders(engine); const allIssues = [ ...outsideIssues, ...protrudingIssues, ...obscuredIssues, ...placeholderIssues ]; return { errors: allIssues.filter((i) => i.severity === 'error'), warnings: allIssues.filter((i) => i.severity === 'warning') }; } private overrideExportAction( cesdk: CreativeEditorSDK, engine: CreativeEngine ): void { cesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: ['ly.img.exportImage.navigationBar'] }); const exportDesign = cesdk.actions.get('exportDesign'); cesdk.actions.register('exportDesign', async () => { const result = this.validateDesign(engine); const hasErrors = result.errors.length > 0; const hasWarnings = result.warnings.length > 0; // Log all issues to console for debugging if (hasErrors || hasWarnings) { console.log('Validation Results:', result); } // Block export for errors if (hasErrors) { cesdk.ui.showNotification({ message: `${result.errors.length} error(s) found - export blocked`, type: 'error', duration: 'long' }); // Select first problematic block const firstError = result.errors[0]; if (engine.block.isValid(firstError.blockId)) { engine.block.select(firstError.blockId); } return; } // Show warning but allow export if (hasWarnings) { cesdk.ui.showNotification({ message: `${result.warnings.length} warning(s) found - proceeding with export`, type: 'warning', duration: 'medium' }); } // Proceed with export exportDesign(); }); }} export default Example; ``` This guide demonstrates how to detect elements outside the page, find protruding content, identify obscured text, and integrate validation into the export workflow. ## Getting Element Bounds[#](#getting-element-bounds) To detect spatial issues, we need to get the bounding box of elements in global coordinates. The `getGlobalBoundingBox*` methods return positions that account for all transformations: ``` const x = engine.block.getGlobalBoundingBoxX(blockId);const y = engine.block.getGlobalBoundingBoxY(blockId);const width = engine.block.getGlobalBoundingBoxWidth(blockId);const height = engine.block.getGlobalBoundingBoxHeight(blockId);return [x, y, x + width, y + height]; ``` This returns coordinates as `[x1, y1, x2, y2]` representing the top-left and bottom-right corners of the element. The overlap between element and page bounds is calculated as the intersection area divided by the element’s total area. An overlap of 0 means completely outside, while 1 (100%) means fully inside. ## Detecting Elements Outside the Page[#](#detecting-elements-outside-the-page) Elements completely outside the page won’t appear in the exported output. We find these by checking for blocks with zero overlap with the page bounds: ``` const blockBounds = this.getBoundingBox(engine, blockId);const overlap = this.calculateOverlap(blockBounds, pageBounds); if (overlap === 0) { // Element is completely outside the page ``` These issues are categorized as errors because the content is completely missing from the export. ## Detecting Protruding Elements[#](#detecting-protruding-elements) Elements that extend beyond the page boundaries will be partially cropped in the export. For each block, compare its bounds against the page bounds and calculate the overlap ratio: ``` // Compare element bounds against page boundsconst blockBounds = this.getBoundingBox(engine, blockId);const overlap = this.calculateOverlap(blockBounds, pageBounds); // Protruding: partially inside (overlap > 0) but not fully inside (overlap < 1)if (overlap > 0 && overlap < 0.99) { ``` An overlap between 0% and 100% indicates the element is partially inside the page. These issues are warnings because the content is partially visible but may not appear as intended. ## Finding Obscured Text[#](#finding-obscured-text) Text hidden behind other elements may be unreadable in the final export. First, get the stacking order and all text blocks: ``` const children = engine.block.getChildren(page);const textBlocks = engine.block.findByType('text'); ``` The `getChildren()` method returns blocks in stacking order - elements later in the array are rendered on top. For each text block, check if any non-text element above it overlaps with its bounds: ``` // Elements later in children array are rendered on topconst blocksAbove = children.slice(textIndex + 1); for (const aboveId of blocksAbove) { // Skip text blocks - they don't typically obscure other text if (engine.block.getType(aboveId) === '//ly.img.ubq/text') continue; const overlap = this.calculateOverlap( this.getBoundingBox(engine, textId), this.getBoundingBox(engine, aboveId) ); if (overlap > 0) { // Text is obscured by element above it ``` We skip text-on-text comparisons since transparent text backgrounds don’t typically obscure other text. When overlap is detected, we flag the text as potentially obscured. ## Checking Placeholder Content[#](#checking-placeholder-content) Placeholders mark areas where users must add content before export. First, find all placeholder blocks in the design: ``` const placeholders = engine.block.findAllPlaceholders(); ``` Then inspect each placeholder’s fill to determine if content has been added. Get the fill block and check its type to determine the validation logic: ``` const fillId = engine.block.getFill(blockId);if (!fillId || !engine.block.isValid(fillId)) return false; const fillType = engine.block.getType(fillId); // Check image fill - empty URI means unfilled placeholderif (fillType === '//ly.img.ubq/fill/image') { const imageUri = engine.block.getString(fillId, 'fill/image/imageFileURI'); return imageUri !== '' && imageUri !== undefined;} ``` For image placeholders, check if the `fill/image/imageFileURI` property has a value. An empty or undefined URI indicates the placeholder hasn’t been filled. Unfilled placeholders are treated as errors that block export, ensuring users complete all required content before exporting. ## Overriding the Export Action[#](#overriding-the-export-action) Intercept the navigation bar export action using `cesdk.actions.get()` and `cesdk.actions.register()`. The action ID `'exportDesign'` controls the export button in the CE.SDK interface: ``` cesdk.actions.register('exportDesign', async () => { const result = this.validateDesign(engine); const hasErrors = result.errors.length > 0; const hasWarnings = result.warnings.length > 0; // Log all issues to console for debugging if (hasErrors || hasWarnings) { console.log('Validation Results:', result); } // Block export for errors if (hasErrors) { cesdk.ui.showNotification({ message: `${result.errors.length} error(s) found - export blocked`, type: 'error', duration: 'long' }); // Select first problematic block const firstError = result.errors[0]; if (engine.block.isValid(firstError.blockId)) { engine.block.select(firstError.blockId); } return; } // Show warning but allow export if (hasWarnings) { cesdk.ui.showNotification({ message: `${result.warnings.length} warning(s) found - proceeding with export`, type: 'warning', duration: 'medium' }); } // Proceed with export exportDesign();}); ``` This override runs validation before export, shows notifications for errors or warnings, and selects the first problematic block to help users locate issues. Export proceeds only when validation passes. ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `engine.block.getGlobalBoundingBoxX(id)` | Get element’s global X position | | `engine.block.getGlobalBoundingBoxY(id)` | Get element’s global Y position | | `engine.block.getGlobalBoundingBoxWidth(id)` | Get element’s global width | | `engine.block.getGlobalBoundingBoxHeight(id)` | Get element’s global height | | `engine.block.findByType(type)` | Find all blocks of a specific type | | `engine.block.getChildren(id)` | Get child blocks in stacking order | | `engine.block.getType(id)` | Get the block’s type string | | `engine.block.getName(id)` | Get the block’s display name | | `engine.block.isValid(id)` | Check if block exists | | `engine.block.select(id)` | Select a block in the editor | | `engine.block.findAllPlaceholders()` | Find all placeholder blocks | | `engine.block.getFill(id)` | Get the fill block | | `engine.block.getString(id, property)` | Get a string property value | | `cesdk.actions.get(actionId)` | Get an action function (browser) | | `cesdk.actions.register(actionId, fn)` | Register an action (browser) | | `cesdk.ui.showNotification(options)` | Display notification (browser) | --- [Source](https:/img.ly/docs/cesdk/vue/export-save-publish/for-printing-bca896) --- # Export for Printing Export print-ready PDFs from CE.SDK with options for high compatibility mode, underlayers for special media like fabric or glass, and configurable output resolution. ![Export for Printing example showing PDF export options](/docs/cesdk/_astro/browser.hero.DUuMNxM4_Z13iEVs.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-for-printing-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-for-printing-browser) CE.SDK exports designs as PDFs, but professional print workflows require specific configurations beyond standard export. This guide covers PDF export options for print, including high compatibility mode for complex designs, underlayers for printing on special media, and output resolution settings. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Export for Printing Guide * * This example demonstrates: * - Exporting designs as print-ready PDFs * - Configuring high compatibility mode for complex designs * - Generating underlayers for special media (DTF, fabric, glass) * - Setting scene DPI for print resolution */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 template scene - this will be our print design await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene' ); // Get the scene and page const scene = engine.scene.get(); if (!scene) { throw new Error('No scene found'); } const page = engine.scene.getCurrentPage(); if (!page) { throw new Error('No page found'); } // Set print resolution (DPI) on the scene // 300 DPI is standard for high-quality print output engine.block.setFloat(scene, 'scene/dpi', 300); // Helper function to download blob const downloadBlob = (blob: Blob, filename: string) => { const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; anchor.click(); URL.revokeObjectURL(url); }; // Export PDF with high compatibility mode const exportWithHighCompatibility = async () => { // Enable high compatibility mode for consistent rendering across PDF viewers // This rasterizes complex elements like gradients with transparency at the scene's DPI const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); downloadBlob(pdfBlob, 'print-high-compatibility.pdf'); cesdk.ui.showNotification({ message: `PDF exported with high compatibility (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export PDF without high compatibility (faster, smaller files) const exportStandardPdf = async () => { // Disable high compatibility for faster exports when targeting modern PDF viewers // Complex elements remain as vectors but may render differently across viewers const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: false }); downloadBlob(pdfBlob, 'print-standard.pdf'); cesdk.ui.showNotification({ message: `Standard PDF exported (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Define underlayer spot color and export with underlayer const exportWithUnderlayer = async () => { // Define the underlayer spot color before export // This creates a named spot color that will be used for the underlayer ink // The 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 enabled for DTF or special media printing // The underlayer generates a shape behind design elements filled with the spot color const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks the underlayer inward to prevent visible edges underlayerOffset: -2.0 }); downloadBlob(pdfBlob, 'print-with-underlayer.pdf'); cesdk.ui.showNotification({ message: `PDF exported with underlayer (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export with custom target size const exportWithTargetSize = async () => { // Export with specific dimensions for print output // targetWidth and targetHeight control the exported PDF dimensions in pixels const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, targetWidth: 2480, // A4 at 300 DPI (210mm) targetHeight: 3508 // A4 at 300 DPI (297mm) }); downloadBlob(pdfBlob, 'print-a4-300dpi.pdf'); cesdk.ui.showNotification({ message: `A4 PDF exported (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Configure navigation bar with export buttons cesdk.ui.setNavigationBarOrder([ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', { id: 'ly.img.action.navigationBar', onClick: exportWithHighCompatibility, key: 'export-high-compat', label: 'High Compat PDF', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportStandardPdf, key: 'export-standard', label: 'Standard PDF', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportWithUnderlayer, key: 'export-underlayer', label: 'With Underlayer', icon: '@imgly/Save', variant: 'plain', color: 'accent' }, { id: 'ly.img.action.navigationBar', onClick: exportWithTargetSize, key: 'export-a4', label: 'A4 @ 300 DPI', icon: '@imgly/Save', variant: 'plain' } ]); cesdk.ui.showNotification({ message: 'Use the export buttons to export print-ready PDFs with different options', type: 'info', duration: 'infinite' }); }} export default Example; ``` ## Default PDF Color Behavior[#](#default-pdf-color-behavior) CE.SDK exports PDFs in RGB color space. CMYK or spot colors defined in your design convert to RGB during standard export. For CMYK output with ICC profiles, use the **Print Ready PDF plugin**. The base `engine.block.export()` method provides print compatibility options, but full CMYK workflow requires the plugin. ## Setting Up for Print Export[#](#setting-up-for-print-export) Before exporting, configure your scene with appropriate print settings. Set the scene’s DPI to control print resolution—300 DPI is standard for high-quality print output. ``` // Load a template scene - this will be our print designawait engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene'); // Get the scene and pageconst scene = engine.scene.get();if (!scene) { throw new Error('No scene found');}const page = engine.scene.getCurrentPage();if (!page) { throw new Error('No page found');} // Set print resolution (DPI) on the scene// 300 DPI is standard for high-quality print outputengine.block.setFloat(scene, 'scene/dpi', 300); ``` ## PDF Export Options for Print[#](#pdf-export-options-for-print) Export a page as PDF using `engine.block.export()` with `mimeType: 'application/pdf'`. ### High Compatibility Mode[#](#high-compatibility-mode) The `exportPdfWithHighCompatibility` option rasterizes complex elements like gradients with transparency at the scene’s DPI. Enable this when: * Designs use gradients with transparency * Effects or blend modes render inconsistently across PDF viewers * Maximum compatibility across print RIPs matters more than vector precision ``` // Enable high compatibility mode for consistent rendering across PDF viewers// This rasterizes complex elements like gradients with transparency at the scene's DPIconst pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true}); ``` Disabling high compatibility produces faster exports with smaller file sizes but may cause rendering inconsistencies in some PDF viewers. ### Standard PDF Export[#](#standard-pdf-export) When targeting modern PDF viewers where file size and export speed matter more than universal compatibility: ``` // Disable high compatibility for faster exports when targeting modern PDF viewers// Complex elements remain as vectors but may render differently across viewersconst pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: false}); ``` ## Underlayers for Special Media[#](#underlayers-for-special-media) Underlayers provide a base ink layer (typically white) for printing on: * Transparent or non-white substrates * DTF (Direct-to-Film) transfers * Fabric, glass, or dark materials ### Define the Underlayer Spot Color[#](#define-the-underlayer-spot-color) Before exporting with an underlayer, define the spot color that represents the underlayer ink. Use `engine.editor.setSpotColorRGB()` to create a named spot color with RGB preview values. ``` // Define the underlayer spot color before export// This creates a named spot color that will be used for the underlayer ink// The RGB values (0.8, 0.8, 0.8) provide a preview representationengine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); ``` ### Export with Underlayer[#](#export-with-underlayer) Enable `exportPdfWithUnderlayer` and specify the `underlayerSpotColorName` to generate an underlayer from design contours. The underlayer offset controls the size adjustment—negative values shrink the underlayer inward to prevent visible edges from print misalignment. ``` // Export with underlayer enabled for DTF or special media printing// The underlayer generates a shape behind design elements filled with the spot colorconst pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks the underlayer inward to prevent visible edges underlayerOffset: -2.0}); ``` ### Underlayer Offset[#](#underlayer-offset) The `underlayerOffset` option adjusts the underlayer size in design units. Negative values shrink the underlayer inward, which prevents visible white edges when the print layers don’t align perfectly. Start with values like `-1.0` to `-3.0` and adjust based on your print equipment’s alignment accuracy. ## Export with Target Size[#](#export-with-target-size) Control the exported PDF dimensions using `targetWidth` and `targetHeight`. These values are in pixels and work together with the scene’s DPI setting to determine physical print size. ``` // Export with specific dimensions for print output// targetWidth and targetHeight control the exported PDF dimensions in pixelsconst pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, targetWidth: 2480, // A4 at 300 DPI (210mm) targetHeight: 3508 // A4 at 300 DPI (297mm)}); ``` ## CMYK PDFs with ICC Profiles[#](#cmyk-pdfs-with-icc-profiles) For CMYK color space and ICC profile embedding, use the **Print Ready PDF plugin**. This plugin post-processes exports to convert RGB to CMYK with embedded ICC profiles. See the [Print Ready PDF Plugin](vue/plugins/print-ready-pdf-iroalu/) for setup and usage. ## Troubleshooting[#](#troubleshooting) ### PDF Not Opening Correctly in Print Software[#](#pdf-not-opening-correctly-in-print-software) Enable `exportPdfWithHighCompatibility: true` to rasterize complex elements that may not render correctly in prepress software. ### Underlayer Not Visible in PDF Viewer[#](#underlayer-not-visible-in-pdf-viewer) Standard PDF viewers may not display spot colors. Use professional print software like Adobe Acrobat Pro or prepress tools to verify the underlayer separation. ### Colors Look Different After Printing[#](#colors-look-different-after-printing) Standard export uses RGB. Use the Print Ready PDF plugin with appropriate ICC profiles for accurate CMYK reproduction. ### White Edges on Special Media[#](#white-edges-on-special-media) Increase the negative `underlayerOffset` value to shrink the underlayer further from design edges. Try values like `-2.0` or `-3.0` depending on your equipment’s alignment tolerance. ## API Reference[#](#api-reference) | Method/Option | Purpose | | --- | --- | | `engine.block.export(block, options)` | Export block to PDF | | `mimeType: 'application/pdf'` | Specify PDF output format | | `targetWidth` | Target width for exported PDF in pixels | | `targetHeight` | Target height for exported PDF in pixels | | `exportPdfWithHighCompatibility` | Rasterize bitmap images and gradients at scene DPI (default: `true`) | | `exportPdfWithUnderlayer` | Generate underlayer from contours (default: `false`) | | `underlayerSpotColorName` | Spot color name for underlayer ink | | `underlayerOffset` | Size adjustment in design units (negative shrinks) | | `engine.editor.setSpotColorRGB(name, r, g, b)` | Define spot color for underlayer | | `engine.block.setFloat(scene, 'scene/dpi', value)` | Set scene DPI for print resolution | ## Next Steps[#](#next-steps) * [Print Ready PDF Plugin](vue/plugins/print-ready-pdf-iroalu/) \- CMYK PDFs with ICC profiles * [CMYK Colors](vue/colors/for-print/cmyk-8a1334/) \- Configure CMYK colors * [Spot Colors](vue/colors/for-print/spot-c3a150/) \- Define and use spot colors * [Export to PDF](vue/export-save-publish/export/to-pdf-95e04b/) \- General PDF export options --- [Source](https:/img.ly/docs/cesdk/vue/export-save-publish/export-82f968) --- # Export --- [Source](https:/img.ly/docs/cesdk/vue/export-save-publish/create-thumbnail-749be1) --- # Create Thumbnail Generate thumbnail preview images from CE.SDK scenes by exporting with target dimensions for galleries and design management. ![Create Thumbnail hero image](/docs/cesdk/_astro/browser.hero.Digz9ahH_AG98A.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-create-thumbnail-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-create-thumbnail-browser) Thumbnails provide visual previews of designs without loading the full editor. Use `engine.block.export()` with `targetWidth` and `targetHeight` options to scale content while maintaining aspect ratio. Supported formats include PNG, JPEG, and WebP. ``` import type CreativeEditorSDK from '@cesdk/cesdk-js';import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v1/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 }); // Setup thumbnail export functionality await this.setupThumbnailActions(cesdk, page); } private async setupThumbnailActions( cesdk: CreativeEditorSDK, page: number ): Promise { const engine = cesdk.engine; // Add thumbnail export buttons to navigation bar cesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-thumbnail-small', label: 'Small Thumbnail', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/jpeg', targetWidth: 150, targetHeight: 150, jpegQuality: 0.8 }); await cesdk.utils.downloadFile(blob, 'image/jpeg'); console.log( `✓ Small thumbnail: ${(blob.size / 1024).toFixed(1)} KB` ); } }, { id: 'ly.img.action.navigationBar', key: 'export-thumbnail-medium', label: 'Medium Thumbnail', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/jpeg', targetWidth: 400, targetHeight: 300, jpegQuality: 0.85 }); await cesdk.utils.downloadFile(blob, 'image/jpeg'); console.log( `✓ Medium thumbnail: ${(blob.size / 1024).toFixed(1)} KB` ); } }, { id: 'ly.img.action.navigationBar', key: 'export-thumbnail-png', label: 'PNG Thumbnail', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 400, targetHeight: 300, pngCompressionLevel: 6 }); await cesdk.utils.downloadFile(blob, 'image/png'); console.log(`✓ PNG thumbnail: ${(blob.size / 1024).toFixed(1)} KB`); } }, { id: 'ly.img.action.navigationBar', key: 'export-thumbnail-webp', label: 'WebP Thumbnail', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/webp', targetWidth: 400, targetHeight: 300, webpQuality: 0.8 }); await cesdk.utils.downloadFile(blob, 'image/webp'); console.log( `✓ WebP thumbnail: ${(blob.size / 1024).toFixed(1)} KB` ); } } ] }); }} export default Example; ``` This guide covers exporting thumbnails at specific dimensions, choosing formats, optimizing quality and file size, and generating multiple thumbnail sizes. ## Export a Thumbnail[#](#export-a-thumbnail) Call `engine.block.export()` with target dimensions to create a scaled thumbnail. Both `targetWidth` and `targetHeight` must be set together for scaling to work. ``` const blob = await engine.block.export(page, { mimeType: 'image/jpeg', targetWidth: 150, targetHeight: 150, jpegQuality: 0.8}); ``` The block renders large enough to fill the target size while maintaining aspect ratio. If aspect ratios differ, the output extends beyond the target on one axis. ## Choose Thumbnail Format[#](#choose-thumbnail-format) Select the format via the `mimeType` option based on your needs: * **`'image/jpeg'`** — Smaller files, good for photos, no transparency * **`'image/png'`** — Lossless quality, supports transparency * **`'image/webp'`** — Best compression, modern browsers only ### JPEG Thumbnails[#](#jpeg-thumbnails) JPEG works well for photographic content. Control file size with `jpegQuality` (0-1, default 0.9). Values between 0.75-0.85 balance quality and size for thumbnails. ``` const blob = await engine.block.export(page, { mimeType: 'image/jpeg', targetWidth: 400, targetHeight: 300, jpegQuality: 0.85}); ``` ### PNG Thumbnails[#](#png-thumbnails) PNG provides lossless quality with transparency support. Control encoding speed vs. file size with `pngCompressionLevel` (0-9, default 5). ``` const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 400, targetHeight: 300, pngCompressionLevel: 6}); ``` ### WebP Thumbnails[#](#webp-thumbnails) WebP offers the best compression for modern browsers. Control quality with `webpQuality` (0-1, default 1.0 for lossless). ``` const blob = await engine.block.export(page, { mimeType: 'image/webp', targetWidth: 400, targetHeight: 300, webpQuality: 0.8}); ``` ## Common Thumbnail Sizes[#](#common-thumbnail-sizes) Standard sizes for different use cases: | Size | Dimensions | Use Case | | --- | --- | --- | | Small | 150×150 | Grid galleries, file browsers | | Medium | 400×300 | Preview panels, cards | | Large | 800×600 | Full previews, detail views | ## Optimize Thumbnail Quality[#](#optimize-thumbnail-quality) Balance quality with file size using format-specific options: | Format | Option | Range | Default | Notes | | --- | --- | --- | --- | --- | | JPEG | `jpegQuality` | 0-1 | 0.9 | Lower = smaller files, visible artifacts | | PNG | `pngCompressionLevel` | 0-9 | 5 | Higher = smaller files, slower encoding | | WebP | `webpQuality` | 0-1 | 1.0 | 1.0 = lossless, lower = lossy compression | For thumbnails, JPEG quality of 0.8 or WebP quality of 0.75-0.85 typically provides good results with small file sizes. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `engine.block.export(blockId, options)` | Export a block as image with format and dimension options | | `engine.scene.getCurrentPage()` | Get the current page block ID | | `cesdk.utils.downloadFile(blob, mimeType)` | Download a blob to the user’s device | --- [Source](https:/img.ly/docs/cesdk/vue/edit-video/trim-4f688b) --- # Trim Video Clips Control video playback timing by trimming clips to specific start points and durations using CE.SDK’s timeline UI and programmatic trim API. ![Video Trim example showing timeline with video clips and trim controls](/docs/cesdk/_astro/browser.hero.CS2ktFlb_Z1I2Vcs.webp) 12 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-trim-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-trim-browser) Understanding the difference between **fill-level trimming** and **block-level timing** is essential. Fill-level trimming (`setTrimOffset`, `setTrimLength`) controls which portion of the source media plays, while block-level timing (`setTimeOffset`, `setDuration`) controls when and how long the block appears in your timeline. These two systems work together to give you complete control over video playback. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json';import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Trim Video Guide * * Demonstrates trimming video clips in CE.SDK: * - Loading video resources with forceLoadAVResource * - Basic video trimming with setTrimOffset/setTrimLength * - Getting current trim values * - Coordinating trim with block duration * - Trimming with looping enabled * - Checking trim support * - Frame-accurate trimming * - Batch trimming multiple videos */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 in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene (required for trimming) await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 8); const { blockWidth, blockHeight, getPosition } = layout; // Use a sample video URL from demo assets const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; // Create a sample video block to demonstrate trim support checking const sampleVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the video fill - trim operations are applied to the fill, not the block const videoFill = engine.block.getFill(sampleVideo); // Check if the fill supports trim operations const supportsTrim = engine.block.supportsTrim(videoFill); console.log('Video fill supports trim:', supportsTrim); // true for video fills // Select this block so timeline controls are visible engine.block.setSelected(sampleVideo, true); // Pattern: Always load video resource before accessing trim properties // This ensures metadata (duration, frame rate, etc.) is available await engine.block.forceLoadAVResource(videoFill); // Now we can safely access video metadata const totalDuration = engine.block.getDouble( videoFill, 'fill/video/totalDuration' ); console.log('Total video duration:', totalDuration, 'seconds'); // Pattern #1: Demonstrate Individual Before Combined // Create a separate video block for basic trim demonstration const basicTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the fill to apply trim operations const basicTrimFill = engine.block.getFill(basicTrimVideo); // Load resource before trimming await engine.block.forceLoadAVResource(basicTrimFill); // Trim video to start at 2 seconds and play for 5 seconds engine.block.setTrimOffset(basicTrimFill, 2.0); engine.block.setTrimLength(basicTrimFill, 5.0); // Get current trim values to verify or modify const currentOffset = engine.block.getTrimOffset(basicTrimFill); const currentLength = engine.block.getTrimLength(basicTrimFill); console.log( `Basic trim - Offset: ${currentOffset}s, Length: ${currentLength}s` ); // Pattern #5: Progressive Complexity - coordinating trim with block duration // Create a video block demonstrating trim + duration coordination const durationTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const durationTrimFill = engine.block.getFill(durationTrimVideo); await engine.block.forceLoadAVResource(durationTrimFill); // Set trim: play portion from 3s to 8s (5 seconds of content) engine.block.setTrimOffset(durationTrimFill, 3.0); engine.block.setTrimLength(durationTrimFill, 5.0); // Set block duration: how long this block appears in the timeline // When duration equals trim length, the entire trimmed portion plays once engine.block.setDuration(durationTrimVideo, 5.0); console.log( 'Trim+Duration - Block will play trimmed 5s exactly once in timeline' ); // Create a video block with trim + looping const loopingTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const loopingTrimFill = engine.block.getFill(loopingTrimVideo); await engine.block.forceLoadAVResource(loopingTrimFill); // Trim to a short 3-second segment engine.block.setTrimOffset(loopingTrimFill, 1.0); engine.block.setTrimLength(loopingTrimFill, 3.0); // Enable looping so the 3-second segment repeats engine.block.setLooping(loopingTrimFill, true); // Verify looping is enabled const isLooping = engine.block.isLooping(loopingTrimFill); console.log('Looping enabled:', isLooping); // Set duration longer than trim length - the trim will loop to fill it engine.block.setDuration(loopingTrimVideo, 9.0); console.log( 'Looping trim - 3s segment will loop 3 times to fill 9s duration' ); // Pattern #6: Descriptive naming - frame-accurate trim demonstration // Create a video block for frame-accurate trimming const frameAccurateTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const frameFill = engine.block.getFill(frameAccurateTrimVideo); await engine.block.forceLoadAVResource(frameFill); // Note: Frame rate is not directly accessible via the API // For this example, we'll assume a common frame rate of 30fps const frameRate = 30; // Calculate trim offset based on specific frame number // Example: Start at frame 60 for a 30fps video = 2.0 seconds const startFrame = 60; const trimOffsetSeconds = startFrame / frameRate; // Trim for exactly 150 frames = 5.0 seconds at 30fps const trimFrames = 150; const trimLengthSeconds = trimFrames / frameRate; engine.block.setTrimOffset(frameFill, trimOffsetSeconds); engine.block.setTrimLength(frameFill, trimLengthSeconds); console.log( `Frame-accurate trim - Frame rate: ${frameRate}fps (assumed), Start frame: ${startFrame}, Duration: ${trimFrames} frames` ); // Pattern: Batch processing multiple video clips // Create multiple video blocks to demonstrate batch trimming const batchVideoUris = [ 'https://img.ly/static/ubq_video_samples/bbb.mp4', 'https://img.ly/static/ubq_video_samples/bbb.mp4', 'https://img.ly/static/ubq_video_samples/bbb.mp4' ]; const batchVideos = []; for (let i = 0; i < batchVideoUris.length; i++) { const batchVideo = await engine.block.addVideo( batchVideoUris[i], blockWidth, blockHeight ); batchVideos.push(batchVideo); // Get the fill for trim operations const batchFill = engine.block.getFill(batchVideo); // Load resource before trimming await engine.block.forceLoadAVResource(batchFill); // Apply consistent trim: first 4 seconds of each video engine.block.setTrimOffset(batchFill, 0.0); engine.block.setTrimLength(batchFill, 4.0); // Set consistent duration engine.block.setDuration(batchVideo, 4.0); } console.log('Batch trim - Applied consistent 4s trim to 3 video blocks'); // ===== Position all blocks in grid layout ===== const blocks = [ sampleVideo, // Position 0 basicTrimVideo, // Position 1 durationTrimVideo, // Position 2 loopingTrimVideo, // Position 3 frameAccurateTrimVideo, // Position 4 ...batchVideos // Positions 5-7 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Start playback automatically when the example loads try { engine.block.setPlaying(page, true); console.log( 'Video trim guide initialized. Playback started automatically. Use timeline controls to adjust trim handles.' ); } catch (error) { console.log( 'Video trim guide initialized. Click play button to start playback.' ); } }} export default Example; ``` This guide covers how to use the built-in timeline UI for visual trimming and how to trim videos programmatically using the Engine API. ## Understanding Trim Concepts[#](#understanding-trim-concepts) ### Fill-Level Trimming[#](#fill-level-trimming) When we trim a video, we’re adjusting properties of the video’s fill, not the block itself. The fill represents the media source—the actual video file. Fill-level trimming determines which portion of that source media will play. `setTrimOffset` specifies where playback starts within the source media. A trim offset of `2.0` skips the first two seconds of the video file. `setTrimLength` defines how much of the source media plays from the trim offset point. A trim length of 5.0 will play 5 seconds of the source. Combined with a trim offset of 2.0, the video plays from 2 seconds to 7 seconds of the original file. This trimming is completely non-destructive—the source video file remains unchanged. You can adjust trim values at any time to show different portions of the same media. Audio blocks use the same trim API (`setTrimOffset`, `setTrimLength`) as video blocks. The concepts are identical, though this guide focuses on video. ### Block-Level Timing[#](#block-level-timing) Block-level timing is separate from trimming and controls when and how long a block exists in the timeline. `setTimeOffset` determines when the block becomes active in the composition timeline (useful for track-based layouts). `setDuration` controls how long the block appears in the timeline. The _trim_ controls what plays from the source media, while the _duration_ controls how long that playback appears in your timeline. If the duration exceeds the trim length and if looping is disabled, the trimmed portion will play once and then hold the last frame for the remaining duration. ### Common Use Cases[#](#common-use-cases) Trimming enables many video editing workflows: * **Remove unwanted segments** - Cut intro or outro portions to keep videos concise * **Extract key moments** - Isolate specific segments from longer source media * **Sync audio to video** - Trim audio and video independently for perfect alignment * **Create loops** - Trim to a specific length and enable loop mode for seamless repeating content * **Uniform compositions** - Batch trim multiple clips to consistent lengths ## Trimming Video via UI[#](#trimming-video-via-ui) ### Accessing Trim Controls[#](#accessing-trim-controls) When you select a video block in the timeline, CE.SDK reveals trim handles at the edges of the clip. These visual controls appear as draggable handles on the left and right sides of the video block in the timeline panel. The trimmed portion of your video is visually distinguished from the untrimmed regions on either side that represent portions of the source media that won’t play due to trim settings. This visual feedback makes it immediately clear which part of your video will be included in the final composition. ### Using Trim Handles[#](#using-trim-handles) We adjust trimming by dragging the handles. The left handle controls the trim offset—dragging it right increases the offset, skipping more of the beginning. Dragging left decreases the offset, including more from the start of the video. The right handle adjusts the trim length by changing where the video stops playing. Dragging left shortens the trim length, ending playback earlier. Dragging right extends it, playing more of the source media. For frame-accurate control, many CE.SDK interfaces provide numeric input fields where you can type exact time values in seconds. This precision is essential when you need to trim to specific frames or match exact durations. The icon on the trim handle turns into an outward-pointing arrow. ### Preview During Trimming[#](#preview-during-trimming) Scrubbing the playhead through your trimmed content shows exactly what will play. This immediate feedback loop makes it easy to find the perfect trim points visually. If your video extends beyond the page duration, out-of-bounds content is indicated with a blue overlay in the timeline and won’t be visible in the final output. ### Constraints and Limitations[#](#constraints-and-limitations) CE.SDK enforces a minimum trim duration to prevent creating zero-length or extremely short clips that could cause playback issues. If you try to drag handles closer than this minimum, the handle will resist further movement. When clips extend beyond page duration boundaries, grey visual indicators show which portions fall outside. While the video block may be longer than the page, only content within the page duration will appear in exports or final compositions. ## Programmatic Video Trimming[#](#programmatic-video-trimming) ### Prerequisites and Setup[#](#prerequisites-and-setup) For applications that need to apply trimming programmatically—whether for automation, batch processing, or dynamic user experiences—we start by setting up CE.SDK in Video mode with the proper configuration. ``` // Enable video editing features in CE.SDKcesdk.feature.enable('ly.img.video');cesdk.feature.enable('ly.img.timeline');cesdk.feature.enable('ly.img.playback'); ``` Video mode is required for trimming operations. Design mode doesn’t provide timeline-based editing capabilities, so we must use `createVideoScene()` to access trim functionality. ### Loading Video Resources[#](#loading-video-resources) Before accessing trim properties or setting trim values, we must load the video resource metadata using `forceLoadAVResource`. This critical step ensures CE.SDK has downloaded information about the video’s duration, frame rate, and other properties needed for accurate trimming. ``` // Pattern: Always load video resource before accessing trim properties// This ensures metadata (duration, frame rate, etc.) is availableawait engine.block.forceLoadAVResource(videoFill); // Now we can safely access video metadataconst totalDuration = engine.block.getDouble( videoFill, 'fill/video/totalDuration');console.log('Total video duration:', totalDuration, 'seconds'); ``` Skipping this step is a common source of errors. Without loading the resource first, trim operations may fail silently or produce unexpected results. Always await `forceLoadAVResource` before calling any trim methods. Once loaded, we can access metadata like `totalDuration` and `frameRate` from the video fill. This information helps us calculate valid trim ranges and ensures we don’t try to trim beyond the available media. ### Checking Trim Support[#](#checking-trim-support) Before applying trim operations, we verify that a block supports trimming. While video blocks typically support trimming, other block types like pages and scenes do not. ``` // Create a sample video block to demonstrate trim support checkingconst sampleVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); // Get the video fill - trim operations are applied to the fill, not the blockconst videoFill = engine.block.getFill(sampleVideo); // Check if the fill supports trim operationsconst supportsTrim = engine.block.supportsTrim(videoFill);console.log('Video fill supports trim:', supportsTrim); // true for video fills // Select this block so timeline controls are visibleengine.block.setSelected(sampleVideo, true); ``` Checking support prevents runtime errors and allows you to build robust interfaces that only show trim controls for compatible blocks. Graphic blocks with video fills also support trimming, not just top-level video blocks. ### Trimming Video[#](#trimming-video) Once we’ve confirmed trim support and loaded the resource, we can apply trimming. Here we create a video block and trim it to start 2 seconds into the source media and play for 5 seconds. ``` // Pattern #1: Demonstrate Individual Before Combined// Create a separate video block for basic trim demonstrationconst basicTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); // Get the fill to apply trim operationsconst basicTrimFill = engine.block.getFill(basicTrimVideo); // Load resource before trimmingawait engine.block.forceLoadAVResource(basicTrimFill); // Trim video to start at 2 seconds and play for 5 secondsengine.block.setTrimOffset(basicTrimFill, 2.0);engine.block.setTrimLength(basicTrimFill, 5.0); ``` The trim offset of 2.0 skips the first 2 seconds of the video. The trim length of 5.0 means exactly 5 seconds of video will play, starting from that offset point. So this video plays from the 2-second mark to the 7-second mark of the original file. ### Getting Current Trim Values[#](#getting-current-trim-values) We can retrieve the current trim settings to verify values, build UI controls, or make relative adjustments based on existing settings. ``` // Get current trim values to verify or modifyconst currentOffset = engine.block.getTrimOffset(basicTrimFill);const currentLength = engine.block.getTrimLength(basicTrimFill);console.log( `Basic trim - Offset: ${currentOffset}s, Length: ${currentLength}s`); ``` These getter methods return the current trim offset and length in seconds. Use them to populate UI inputs, calculate remaining media duration, or create undo/redo functionality in your application. ## Additional Trimming Techniques[#](#additional-trimming-techniques) ### Trimming with Block Duration[#](#trimming-with-block-duration) Take a look at this example to understand how trim length and block duration interact: ``` // Pattern #5: Progressive Complexity - coordinating trim with block duration// Create a video block demonstrating trim + duration coordinationconst durationTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); const durationTrimFill = engine.block.getFill(durationTrimVideo);await engine.block.forceLoadAVResource(durationTrimFill); // Set trim: play portion from 3s to 8s (5 seconds of content)engine.block.setTrimOffset(durationTrimFill, 3.0);engine.block.setTrimLength(durationTrimFill, 5.0); // Set block duration: how long this block appears in the timeline// When duration equals trim length, the entire trimmed portion plays onceengine.block.setDuration(durationTrimVideo, 5.0); console.log( 'Trim+Duration - Block will play trimmed 5s exactly once in timeline'); ``` In this example, we trim the video to a 5-second segment (from 3s to 8s of the source) and set the block duration to exactly 5 seconds. This means the entire trimmed portion plays once, then stops. The block duration matches the trim length, so there’s no looping or holding on the last frame. If the block duration is less than the trim length, only part of the trimmed segment will play. If duration exceeds trim length without looping enabled, the video plays the trimmed portion once and holds on the last frame for the remaining time. ### Trimming with Looping[#](#trimming-with-looping) Looping allows a trimmed video segment to repeat seamlessly. We enable looping and set a block duration longer than the trim length to create repeating playback. ``` // Create a video block with trim + loopingconst loopingTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); const loopingTrimFill = engine.block.getFill(loopingTrimVideo);await engine.block.forceLoadAVResource(loopingTrimFill); // Trim to a short 3-second segmentengine.block.setTrimOffset(loopingTrimFill, 1.0);engine.block.setTrimLength(loopingTrimFill, 3.0); // Enable looping so the 3-second segment repeatsengine.block.setLooping(loopingTrimFill, true); // Verify looping is enabledconst isLooping = engine.block.isLooping(loopingTrimFill);console.log('Looping enabled:', isLooping); // Set duration longer than trim length - the trim will loop to fill itengine.block.setDuration(loopingTrimVideo, 9.0); console.log( 'Looping trim - 3s segment will loop 3 times to fill 9s duration'); ``` Here we trim to a 3-second segment and enable looping. The block duration of 9 seconds means this 3-second segment will loop 3 times to fill the entire duration. This technique is perfect for creating background loops, repeated motion graphics, or extending short clips. When looping is enabled, CE.SDK automatically restarts playback from the trim offset when it reaches the end of the trim length. ### Frame-Accurate Trimming[#](#frame-accurate-trimming) For precise editing, we often need to trim to specific frame boundaries rather than arbitrary time values. Using the video’s frame rate metadata, we can calculate exact frame-based trim points. ``` // Pattern #6: Descriptive naming - frame-accurate trim demonstration// Create a video block for frame-accurate trimmingconst frameAccurateTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); const frameFill = engine.block.getFill(frameAccurateTrimVideo);await engine.block.forceLoadAVResource(frameFill); // Note: Frame rate is not directly accessible via the API// For this example, we'll assume a common frame rate of 30fpsconst frameRate = 30; // Calculate trim offset based on specific frame number// Example: Start at frame 60 for a 30fps video = 2.0 secondsconst startFrame = 60;const trimOffsetSeconds = startFrame / frameRate; // Trim for exactly 150 frames = 5.0 seconds at 30fpsconst trimFrames = 150;const trimLengthSeconds = trimFrames / frameRate; engine.block.setTrimOffset(frameFill, trimOffsetSeconds);engine.block.setTrimLength(frameFill, trimLengthSeconds); console.log( `Frame-accurate trim - Frame rate: ${frameRate}fps (assumed), Start frame: ${startFrame}, Duration: ${trimFrames} frames`); ``` We first retrieve the frame rate from the video fill metadata. Then we convert frame numbers to time offsets by dividing by the frame rate. Starting at frame 60 with a 30fps video gives us exactly 2.0 seconds. Trimming for 150 frames provides exactly 5.0 seconds of playback. This technique ensures frame-accurate edits, which is essential for professional video editing workflows. Remember that codec compression may affect true frame accuracy—for critical applications, test with your target codecs to verify precision. ### Batch Processing Multiple Videos[#](#batch-processing-multiple-videos) When working with multiple video clips that need consistent trimming, we can iterate through collections and apply the same trim settings programmatically. ``` // Pattern: Batch processing multiple video clips// Create multiple video blocks to demonstrate batch trimmingconst batchVideoUris = [ 'https://img.ly/static/ubq_video_samples/bbb.mp4', 'https://img.ly/static/ubq_video_samples/bbb.mp4', 'https://img.ly/static/ubq_video_samples/bbb.mp4']; const batchVideos = [];for (let i = 0; i < batchVideoUris.length; i++) { const batchVideo = await engine.block.addVideo( batchVideoUris[i], blockWidth, blockHeight ); batchVideos.push(batchVideo); // Get the fill for trim operations const batchFill = engine.block.getFill(batchVideo); // Load resource before trimming await engine.block.forceLoadAVResource(batchFill); // Apply consistent trim: first 4 seconds of each video engine.block.setTrimOffset(batchFill, 0.0); engine.block.setTrimLength(batchFill, 4.0); // Set consistent duration engine.block.setDuration(batchVideo, 4.0);} console.log('Batch trim - Applied consistent 4s trim to 3 video blocks'); ``` We create multiple video blocks and apply identical trim settings to each one. This ensures consistency across clips—perfect for creating video montages, multi-angle compositions, or any scenario where uniform clip lengths are required. When batch processing, always load each video’s resources before trimming. Don’t assume all videos have the same duration—check total duration to ensure your trim values don’t exceed available media. ## Trim vs Duration Interaction[#](#trim-vs-duration-interaction) ### How setDuration Affects Playback[#](#how-setduration-affects-playback) The relationship between trim length and block duration determines playback behavior. When block duration equals trim length, the video plays the trimmed portion exactly once. When duration is less than trim length, playback stops before the trimmed portion finishes. When duration exceeds trim length with looping disabled, the video plays once and holds on the last frame. With looping enabled, exceeding trim length causes the trimmed segment to repeat until the block duration is filled. This creates seamless loops as long as the content loops visually. ### Best Practices[#](#best-practices) For predictable behavior, always consider both trim and duration together. Set trim values first to define the source media segment you want. Then set duration to control timeline length. If you want the entire trimmed segment to play once, match duration to trim length. For looping content, enable looping before setting a longer duration. When building UIs, update both values together when users adjust trim handles. This prevents confusion about why a video isn’t playing the full trimmed length (duration too short) or why it’s holding on the last frame (duration too long without looping). ## Performance Considerations[#](#performance-considerations) CE.SDK’s video system is optimized for real-time editing, but understanding these performance factors helps you build responsive applications: * **Resource loading**: Use `forceLoadAVResource` judiciously. Loading resources has overhead, so batch loads when possible rather than loading repeatedly. * **Trim adjustments**: Changing trim values is lightweight—CE.SDK updates the playback range without reprocessing the video. You can adjust trim interactively without performance concerns. * **Mobile devices**: Video decoding is more expensive on mobile. Limit the number of simultaneous video blocks and consider lower resolution sources for editing (high resolution for export). * **Long videos**: Very long source videos (30+ minutes) may have slower seeking to trim offsets. Consider pre-cutting extremely long videos into shorter segments. Test your trim operations on target devices early in development to ensure acceptable performance for your users. ## Troubleshooting[#](#troubleshooting) ### Trim Not Applied[#](#trim-not-applied) If setting trim values has no visible effect, the most common cause is forgetting to await `forceLoadAVResource`. The resource must be loaded before trim values take effect. Always load resources first. Another possibility is confusing time offset with trim offset. `setTimeOffset` controls when the block appears in the timeline, while `setTrimOffset` controls where in the source media playback starts. Make sure you’re using the correct method. ### Incorrect Trim Calculation[#](#incorrect-trim-calculation) If trim values seem offset or produce unexpected results, verify you’re calculating based on the source media duration, not the block duration. Use `getTotalDuration` from the fill metadata to understand the available media length. Also check that you’re not exceeding the total available duration. Trim offset plus trim length should never exceed total duration. CE.SDK may clamp values automatically, but it’s better to validate before setting. ### Playback Beyond Trim Length[#](#playback-beyond-trim-length) If video plays past the intended trim length, check that block duration doesn’t exceed trim length. When duration is longer and looping is disabled, the video will hold on the last frame for the excess duration. Ensure looping is set correctly for your use case. If you want playback to stop at the trim length, set duration equal to trim length or enable looping. ### Audio/Video Desync[#](#audiovideo-desync) When trimming both audio and video independently, desynchronization can occur if offset and duration values aren’t coordinated carefully. Calculate both trim offsets to maintain the original relationship between audio and video timing. Consider the original sync point between audio and video in the source media. If they were perfectly synced at 0 seconds originally, maintaining the same offset difference preserves that sync. ### Frame-Accurate Trim Issues[#](#frame-accurate-trim-issues) If frame-accurate trimming doesn’t land on exact frames, remember that floating-point precision can cause tiny discrepancies. Round your calculated values to a reasonable precision (e.g., 3 decimal places). Also understand codec limitations. Variable frame rate videos don’t have perfectly uniform frame timing, so true frame accuracy may not be possible. Use constant frame rate sources for critical frame-accurate applications. ## Best Practices[#](#best-practices-1) ### Workflow Recommendations[#](#workflow-recommendations) 1. Always `await forceLoadAVResource()` before accessing trim properties 2. Check `supportsTrim()` before applying trim operations 3. Coordinate trim length with block duration for predictable behavior 4. Use TypeScript for type safety with CE.SDK API 5. Preview trimmed content before final export 6. Validate trim values don’t exceed total media duration ### Code Organization[#](#code-organization) * Separate media loading from trim logic * Create helper functions for common trim patterns (e.g., `trimToFrames`, `trimToPercentage`) * Handle errors gracefully with try-catch blocks around `forceLoadAVResource` * Document complex trim calculations with comments explaining frame math ### Performance Optimization[#](#performance-optimization) * Avoid redundant `forceLoadAVResource` calls—load once, trim multiple times * Use appropriate preview quality settings during editing to maintain responsiveness * Test on target devices early to identify performance bottlenecks ## API Reference[#](#api-reference) | Method | Description | Parameters | Returns | | --- | --- | --- | --- | | `getFill(id)` | Get the fill block for a block | `id: DesignBlockId` | `DesignBlockId` | | `forceLoadAVResource(id)` | Force load media resource metadata | `id: DesignBlockId` | `Promise` | | `supportsTrim(id)` | Check if block supports trimming | `id: DesignBlockId` | `boolean` | | `setTrimOffset(id, offset)` | Set start point of media playback | `id: DesignBlockId, offset: number` | `void` | | `getTrimOffset(id)` | Get current trim offset | `id: DesignBlockId` | `number` | | `setTrimLength(id, length)` | Set duration of trimmed media | `id: DesignBlockId, length: number` | `void` | | `getTrimLength(id)` | Get current trim length | `id: DesignBlockId` | `number` | | `getAVResourceTotalDuration(id)` | Get total duration of source media | `id: DesignBlockId` | `number` | | `setLooping(id, enabled)` | Enable/disable media looping | `id: DesignBlockId, enabled: boolean` | `void` | | `isLooping(id)` | Check if media looping is enabled | `id: DesignBlockId` | `boolean` | | `setDuration(id, duration)` | Set block playback duration | `id: DesignBlockId, duration: number` | `void` | | `getDuration(id)` | Get block duration | `id: DesignBlockId` | `number` | | `setTimeOffset(id, offset)` | Set when block becomes active | `id: DesignBlockId, offset: number` | `void` | | `getTimeOffset(id)` | Get block time offset | `id: DesignBlockId` | `number` | --- [Source](https:/img.ly/docs/cesdk/vue/edit-video/transform-369f28) --- # Transform --- [Source](https:/img.ly/docs/cesdk/vue/edit-video/split-464167) --- # Split Video and Audio Split video and audio clips at specific time points using CE.SDK’s timeline UI and programmatic split API to create independent segments. ![Video Split example showing timeline with video clips and split controls](/docs/cesdk/_astro/browser.hero.V0W2tjV5_1k33vL.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-split-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-split-browser) Clip splitting divides one block into two at a specified time. The original block ends at the split point; a new block starts there. Both blocks reference the same source media with independent timing. This differs from trimming, which adjusts a single block’s playback range without creating new blocks. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json';import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Split Video Guide * * Demonstrates splitting video clips in CE.SDK: * - Basic splitting at specific time points * - Configuring split options (attachToParent, selectNewBlock, createParentTrackIfNeeded) * - Splitting at playhead position * - Understanding split results (trim properties) * - Splitting multiple tracks at timeline position * - Split and delete workflow */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 in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene (required for splitting) await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Get a video from demo asset sources const videoAssets = await engine.asset.findAssets('ly.img.video', { page: 0, perPage: 1 }); const videoUri = videoAssets.assets[0]?.payload?.sourceSet?.[0]?.uri ?? 'https://img.ly/static/ubq_video_samples/bbb.mp4'; // Create a video block to demonstrate basic splitting const basicSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the video fill and load resource to access duration const basicFill = engine.block.getFill(basicSplitVideo); await engine.block.forceLoadAVResource(basicFill); // Set block duration for the timeline engine.block.setDuration(basicSplitVideo, 10.0); // Split the video block at 5 seconds // Returns the ID of the newly created block (second segment) const splitResultBlock = engine.block.split(basicSplitVideo, 5.0); console.log( `Basic split - Original block: ${basicSplitVideo}, New block: ${splitResultBlock}` ); // Create another video block to demonstrate split options const optionsSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const optionsFill = engine.block.getFill(optionsSplitVideo); await engine.block.forceLoadAVResource(optionsFill); engine.block.setDuration(optionsSplitVideo, 10.0); // Split with custom options const optionsSplitResult = engine.block.split(optionsSplitVideo, 4.0, { attachToParent: true, // Attach new block to same parent (default: true) createParentTrackIfNeeded: false, // Don't create track if needed (default: false) selectNewBlock: false // Don't select the new block (default: true) }); console.log( `Split with options - New block: ${optionsSplitResult}, selectNewBlock: false` ); // Create a video block to demonstrate playhead-based splitting const playheadSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const playheadFill = engine.block.getFill(playheadSplitVideo); await engine.block.forceLoadAVResource(playheadFill); engine.block.setDuration(playheadSplitVideo, 10.0); // Get the clip's start time on the timeline const clipStartTime = engine.block.getTimeOffset(playheadSplitVideo); // Simulate a playhead position (in a real app, use engine.block.getPlaybackTime(page)) const simulatedPlayheadTime = clipStartTime + 3.0; // Calculate split time relative to the clip const splitTime = simulatedPlayheadTime - clipStartTime; // Perform the split at the calculated time const playheadSplitResult = engine.block.split( playheadSplitVideo, splitTime ); console.log( `Playhead split - Split at ${splitTime}s into clip, New block: ${playheadSplitResult}` ); // Create a video block to examine split results const resultsSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const resultsFill = engine.block.getFill(resultsSplitVideo); await engine.block.forceLoadAVResource(resultsFill); engine.block.setDuration(resultsSplitVideo, 10.0); // Get trim values before split const originalTrimOffset = engine.block.getTrimOffset(resultsFill); const originalTrimLength = engine.block.getTrimLength(resultsFill); // Split at 6 seconds const resultsNewBlock = engine.block.split(resultsSplitVideo, 6.0); const newBlockFill = engine.block.getFill(resultsNewBlock); // Examine trim properties after split const originalAfterSplitOffset = engine.block.getTrimOffset(resultsFill); const originalAfterSplitLength = engine.block.getTrimLength(resultsFill); const newBlockTrimOffset = engine.block.getTrimOffset(newBlockFill); const newBlockTrimLength = engine.block.getTrimLength(newBlockFill); console.log('Split results:'); console.log( ` Original before: offset=${originalTrimOffset}, length=${originalTrimLength}` ); console.log( ` Original after: offset=${originalAfterSplitOffset}, length=${originalAfterSplitLength}` ); console.log( ` New block: offset=${newBlockTrimOffset}, length=${newBlockTrimLength}` ); // Create a video block to demonstrate split-and-delete workflow const deleteWorkflowVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const deleteFill = engine.block.getFill(deleteWorkflowVideo); await engine.block.forceLoadAVResource(deleteFill); engine.block.setDuration(deleteWorkflowVideo, 10.0); // Remove middle section: split at start of section to remove (2s) const middleBlock = engine.block.split(deleteWorkflowVideo, 2.0); // Split again at the end of the section to remove (at 3s into middle block = 5s total) const endBlock = engine.block.split(middleBlock, 3.0); // Delete the middle segment engine.block.destroy(middleBlock); console.log( `Split and delete - Removed middle 3s section, kept blocks: ${deleteWorkflowVideo}, ${endBlock}` ); // Create a video block to demonstrate split time validation const validateVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const validateFill = engine.block.getFill(validateVideo); await engine.block.forceLoadAVResource(validateFill); engine.block.setDuration(validateVideo, 8.0); // Get block duration to validate split time const blockDuration = engine.block.getDuration(validateVideo); const desiredSplitTime = 4.0; // Validate split time is within bounds (must be > 0 and < duration) if (desiredSplitTime > 0 && desiredSplitTime < blockDuration) { const validatedSplitResult = engine.block.split( validateVideo, desiredSplitTime ); console.log( `Validated split - Duration: ${blockDuration}s, Split at: ${desiredSplitTime}s, New block: ${validatedSplitResult}` ); } else { console.log('Split time out of range'); } // ===== Position all blocks in grid layout ===== // Note: Some original blocks were modified by splits, position remaining visible blocks const blocks = [ basicSplitVideo, splitResultBlock, optionsSplitVideo, optionsSplitResult, playheadSplitVideo, playheadSplitResult ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Select first block so timeline controls are visible engine.block.setSelected(basicSplitVideo, true); // Set playback time to 8 seconds for hero image engine.block.setPlaybackTime(page, 8.0); // Start playback automatically when the example loads try { engine.block.setPlaying(page, true); console.log( 'Video split guide initialized. Playback started. Use timeline to see split results.' ); } catch (error) { console.log( 'Video split guide initialized. Click play button to start playback.' ); } }} export default Example; ``` This guide covers how to use the built-in timeline UI for visual splitting and how to split clips programmatically using the Engine API. ## Splitting Clips via the UI[#](#splitting-clips-via-the-ui) When you select a video or audio block in the timeline, CE.SDK provides split functionality through the toolbar. Position the playhead at the desired split point by clicking on the timeline ruler or dragging the playhead indicator. With the clip selected and playhead positioned, click the Split button in the timeline toolbar. CE.SDK divides the clip at the playhead position, creating two independent segments. Visual feedback shows the resulting segments as separate blocks on the timeline. The timeline enforces minimum duration constraints that prevent splitting too close to clip edges. If the playhead is positioned within this minimum boundary, the split operation will not be available. ## Programmatic Splitting[#](#programmatic-splitting) For applications that need to split clips programmatically—whether for automation, batch processing, or dynamic editing—CE.SDK provides the `engine.block.split()` method. ### Prerequisites and Setup[#](#prerequisites-and-setup) Before splitting, ensure video features are enabled and a video scene is created. ``` // Enable video editing features in CE.SDKcesdk.feature.enable('ly.img.video');cesdk.feature.enable('ly.img.timeline');cesdk.feature.enable('ly.img.playback'); ``` Video mode is required for split operations. Design mode doesn’t provide timeline-based editing capabilities, so use `createVideoScene()` to access split functionality. ### Basic Splitting at a Specific Time[#](#basic-splitting-at-a-specific-time) Split a block by providing the block ID and the split time in seconds. The time parameter is relative to the block’s own timeline, accounting for the block’s time offset. ``` // Create a video block to demonstrate basic splittingconst basicSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); // Get the video fill and load resource to access durationconst basicFill = engine.block.getFill(basicSplitVideo);await engine.block.forceLoadAVResource(basicFill); // Set block duration for the timelineengine.block.setDuration(basicSplitVideo, 10.0); // Split the video block at 5 seconds// Returns the ID of the newly created block (second segment)const splitResultBlock = engine.block.split(basicSplitVideo, 5.0); console.log( `Basic split - Original block: ${basicSplitVideo}, New block: ${splitResultBlock}`); ``` The `split()` method returns the ID of the newly created block. The original block becomes the first segment (before the split point), and the returned block is the second segment (after the split point). ### Configuring Split Options[#](#configuring-split-options) The `SplitOptions` object controls split behavior with three optional properties: * **`attachToParent`** (default: `true`): Whether to attach the new block to the same parent as the original * **`createParentTrackIfNeeded`** (default: `false`): Creates a parent track if needed and adds both blocks to it * **`selectNewBlock`** (default: `true`): Whether to select the newly created block after splitting ``` // Create another video block to demonstrate split optionsconst optionsSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); const optionsFill = engine.block.getFill(optionsSplitVideo);await engine.block.forceLoadAVResource(optionsFill);engine.block.setDuration(optionsSplitVideo, 10.0); // Split with custom optionsconst optionsSplitResult = engine.block.split(optionsSplitVideo, 4.0, { attachToParent: true, // Attach new block to same parent (default: true) createParentTrackIfNeeded: false, // Don't create track if needed (default: false) selectNewBlock: false // Don't select the new block (default: true)}); console.log( `Split with options - New block: ${optionsSplitResult}, selectNewBlock: false`); ``` Use `selectNewBlock: false` when splitting multiple clips programmatically to avoid changing selection state between operations. ### Splitting at the Current Playhead Position[#](#splitting-at-the-current-playhead-position) To implement playhead-based splitting like the built-in UI, get the current playback time from the page and convert it to block-relative time. ``` // Create a video block to demonstrate playhead-based splittingconst playheadSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); const playheadFill = engine.block.getFill(playheadSplitVideo);await engine.block.forceLoadAVResource(playheadFill);engine.block.setDuration(playheadSplitVideo, 10.0); // Get the clip's start time on the timelineconst clipStartTime = engine.block.getTimeOffset(playheadSplitVideo); // Simulate a playhead position (in a real app, use engine.block.getPlaybackTime(page))const simulatedPlayheadTime = clipStartTime + 3.0; // Calculate split time relative to the clipconst splitTime = simulatedPlayheadTime - clipStartTime; // Perform the split at the calculated timeconst playheadSplitResult = engine.block.split( playheadSplitVideo, splitTime); console.log( `Playhead split - Split at ${splitTime}s into clip, New block: ${playheadSplitResult}`); ``` The playhead position from `getPlaybackTime(page)` is in absolute timeline time. Subtract the clip’s `getTimeOffset()` to convert to block-relative time before passing to `split()`. ## Understanding Split Results[#](#understanding-split-results) After a split operation, both the original and new blocks are configured with updated trim properties. ### Trim Properties After Split[#](#trim-properties-after-split) ``` // Create a video block to examine split resultsconst resultsSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); const resultsFill = engine.block.getFill(resultsSplitVideo);await engine.block.forceLoadAVResource(resultsFill);engine.block.setDuration(resultsSplitVideo, 10.0); // Get trim values before splitconst originalTrimOffset = engine.block.getTrimOffset(resultsFill);const originalTrimLength = engine.block.getTrimLength(resultsFill); // Split at 6 secondsconst resultsNewBlock = engine.block.split(resultsSplitVideo, 6.0);const newBlockFill = engine.block.getFill(resultsNewBlock); // Examine trim properties after splitconst originalAfterSplitOffset = engine.block.getTrimOffset(resultsFill);const originalAfterSplitLength = engine.block.getTrimLength(resultsFill);const newBlockTrimOffset = engine.block.getTrimOffset(newBlockFill);const newBlockTrimLength = engine.block.getTrimLength(newBlockFill); console.log('Split results:');console.log( ` Original before: offset=${originalTrimOffset}, length=${originalTrimLength}`);console.log( ` Original after: offset=${originalAfterSplitOffset}, length=${originalAfterSplitLength}`);console.log( ` New block: offset=${newBlockTrimOffset}, length=${newBlockTrimLength}`); ``` The original block keeps its trim offset unchanged, but its trim length is reduced to the split point. The new block has its trim offset advanced by the split time and trim length set to cover the remaining duration. Both blocks reference the same source media—splitting is non-destructive. ### Timeline Positioning[#](#timeline-positioning) The original block keeps its `getTimeOffset()`. When `attachToParent` is true, the new block is positioned immediately after the original on the same parent. Both blocks stay on the same track unless `createParentTrackIfNeeded` creates a new track structure. ## Split and Delete Workflow[#](#split-and-delete-workflow) Remove a middle section from a clip by splitting at both boundaries and deleting the middle segment. ``` // Create a video block to demonstrate split-and-delete workflowconst deleteWorkflowVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); const deleteFill = engine.block.getFill(deleteWorkflowVideo);await engine.block.forceLoadAVResource(deleteFill);engine.block.setDuration(deleteWorkflowVideo, 10.0); // Remove middle section: split at start of section to remove (2s)const middleBlock = engine.block.split(deleteWorkflowVideo, 2.0); // Split again at the end of the section to remove (at 3s into middle block = 5s total)const endBlock = engine.block.split(middleBlock, 3.0); // Delete the middle segmentengine.block.destroy(middleBlock); console.log( `Split and delete - Removed middle 3s section, kept blocks: ${deleteWorkflowVideo}, ${endBlock}`); ``` This workflow is useful for removing unwanted sections, such as cutting out pauses, mistakes, or irrelevant portions from a recording. ## Validating Split Time[#](#validating-split-time) Always validate that the split time is within valid bounds before calling `split()`. The split time must be greater than 0 and less than the block’s duration. ``` // Create a video block to demonstrate split time validationconst validateVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight); const validateFill = engine.block.getFill(validateVideo);await engine.block.forceLoadAVResource(validateFill);engine.block.setDuration(validateVideo, 8.0); // Get block duration to validate split timeconst blockDuration = engine.block.getDuration(validateVideo);const desiredSplitTime = 4.0; // Validate split time is within bounds (must be > 0 and < duration)if (desiredSplitTime > 0 && desiredSplitTime < blockDuration) { const validatedSplitResult = engine.block.split( validateVideo, desiredSplitTime ); console.log( `Validated split - Duration: ${blockDuration}s, Split at: ${desiredSplitTime}s, New block: ${validatedSplitResult}` );} else { console.log('Split time out of range');} ``` Attempting to split at an invalid time (at the beginning, end, or outside the block’s duration) will fail or produce unexpected results. ## Troubleshooting[#](#troubleshooting) ### Split Returns Unexpected Block[#](#split-returns-unexpected-block) If the returned block ID doesn’t behave as expected, remember that the original block becomes the first segment (before split point) and the returned block is the second segment (after split point). ### Split Time Out of Range[#](#split-time-out-of-range) If split fails or produces unexpected results, verify the split time is within bounds. Use `getDuration()` to check the valid range before splitting. ### Clip Not Splitting[#](#clip-not-splitting) If `split()` has no visible effect, check that the block type supports splitting. Verify `supportsTrim()` returns true for the block’s fill. For video and audio fills, ensure `forceLoadAVResource()` has been awaited before attempting to split. ## API Reference[#](#api-reference) | Method | Description | Parameters | Returns | | --- | --- | --- | --- | | `split(id, atTime, options?)` | Split a block at the specified time | `id: DesignBlockId, atTime: number, options?: SplitOptions` | `DesignBlockId` | | `getTimeOffset(id)` | Get time offset relative to parent | `id: DesignBlockId` | `number` | | `getDuration(id)` | Get playback duration | `id: DesignBlockId` | `number` | | `getPlaybackTime(id)` | Get current playback time | `id: DesignBlockId` | `number` | | `getTrimOffset(id)` | Get trim offset of media content | `id: DesignBlockId` | `number` | | `getTrimLength(id)` | Get trim length of media content | `id: DesignBlockId` | `number` | | `supportsTrim(id)` | Check if block supports trim properties | `id: DesignBlockId` | `boolean` | | `forceLoadAVResource(id)` | Force load media resource metadata | `id: DesignBlockId` | `Promise` | | `destroy(id)` | Destroy a block | `id: DesignBlockId` | `void` | --- [Source](https:/img.ly/docs/cesdk/vue/edit-video/redaction-cf6d03) --- # Redact Sensitive Content in Videos Redact sensitive video content using blur, pixelization, or solid overlays for privacy protection. ![Video Redaction example showing video clips with blur, pixelization, and overlay effects](/docs/cesdk/_astro/browser.hero.CoIOU8UK_ZPIgUX.webp) 15 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-redaction-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-redaction-browser) CE.SDK applies effects to blocks themselves, not as overlays affecting content beneath. This means redaction involves applying effects directly to the block for complete obscuration. Four techniques cover most privacy scenarios: full-block blur, radial blur, pixelization, and solid overlays. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; // Video URLs for demonstrating different redaction scenariosconst VIDEOS = { surfer: 'https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4', lifestyle1: 'https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-taryn-elliott-7108793.mp4', lifestyle2: 'https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-taryn-elliott-7108801.mp4', nature1: 'https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-taryn-elliott-8713109.mp4', nature2: 'https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-taryn-elliott-8713114.mp4'}; // Labels for each redaction techniqueconst LABELS = [ 'Radial Blur', 'Full-Block Blur', 'Pixelization', 'Solid Overlay', 'Time-Based']; // Duration for each video segment (in seconds)const SEGMENT_DURATION = 5.0; /** * CE.SDK Plugin: Video Redaction Guide * * Demonstrates video redaction techniques in CE.SDK: * - Full-block blur for complete video obscuration * - Radial blur for circular redaction patterns * - Pixelization for mosaic-style censoring * - Solid overlays for complete blocking * - Time-based redactions */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'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); cesdk.feature.enable('ly.img.blur'); cesdk.feature.enable('ly.img.effect'); // Load assets and create a video scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Set 16:9 page dimensions (1920x1080) const pageWidth = 1920; const pageHeight = 1080; engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); // Load all videos simultaneously const videoUrls = [ VIDEOS.nature2, VIDEOS.surfer, VIDEOS.lifestyle1, VIDEOS.lifestyle2, VIDEOS.nature1 ]; const videos = await Promise.all( videoUrls.map((url) => engine.block.addVideo(url, pageWidth, pageHeight)) ); const [ radialVideo, fullBlurVideo, pixelVideo, , // Base video for overlay segment (overlay is created separately) timedVideo ] = videos; // Position all videos at origin (they'll play sequentially) videos.forEach((video, index) => { engine.block.setPositionX(video, 0); engine.block.setPositionY(video, 0); engine.block.setDuration(video, SEGMENT_DURATION); engine.block.setTimeOffset(video, index * SEGMENT_DURATION); engine.block.appendChild(page, video); }); // Full-Block Blur: Apply blur to entire video // Use this when the entire video content needs obscuring // Check if the block supports blur const supportsBlur = engine.block.supportsBlur(fullBlurVideo); console.log('Video supports blur:', supportsBlur); // Create and apply uniform blur to entire video const uniformBlur = engine.block.createBlur('uniform'); engine.block.setFloat(uniformBlur, 'blur/uniform/intensity', 0.7); engine.block.setBlur(fullBlurVideo, uniformBlur); engine.block.setBlurEnabled(fullBlurVideo, true); // Pixelization: Apply mosaic effect for clearly intentional censoring // Check if the block supports effects if (engine.block.supportsEffects(pixelVideo)) { // Create and apply pixelize effect const pixelizeEffect = engine.block.createEffect('pixelize'); engine.block.setInt(pixelizeEffect, 'effect/pixelize/horizontalPixelSize', 24); engine.block.setInt(pixelizeEffect, 'effect/pixelize/verticalPixelSize', 24); engine.block.appendEffect(pixelVideo, pixelizeEffect); engine.block.setEffectEnabled(pixelizeEffect, true); } // Solid Overlay: Create opaque shape for complete blocking // Best for highly sensitive information like documents or credentials // Create a solid rectangle overlay const overlay = engine.block.create('//ly.img.ubq/graphic'); const rectShape = engine.block.createShape('//ly.img.ubq/shape/rect'); engine.block.setShape(overlay, rectShape); // Create solid black fill const solidFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(solidFill, 'fill/color/value', { r: 0.1, g: 0.1, b: 0.1, a: 1.0 }); engine.block.setFill(overlay, solidFill); // Position and size the overlay engine.block.setWidth(overlay, pageWidth * 0.4); engine.block.setHeight(overlay, pageHeight * 0.3); engine.block.setPositionX(overlay, pageWidth * 0.55); engine.block.setPositionY(overlay, pageHeight * 0.65); engine.block.appendChild(page, overlay); engine.block.setTimeOffset(overlay, 3 * SEGMENT_DURATION); engine.block.setDuration(overlay, SEGMENT_DURATION); // Time-Based Redaction: Redaction appears only during specific time range // Apply blur to the video const timedBlur = engine.block.createBlur('uniform'); engine.block.setFloat(timedBlur, 'blur/uniform/intensity', 0.9); engine.block.setBlur(timedVideo, timedBlur); engine.block.setBlurEnabled(timedVideo, true); // The video is already timed to appear at a specific offset (set earlier) // You can adjust timeOffset and duration to control when redaction is visible engine.block.setTimeOffset(timedVideo, 4 * SEGMENT_DURATION); engine.block.setDuration(timedVideo, SEGMENT_DURATION); // Radial Blur: Use radial blur for face-like regions // Apply radial blur for circular redaction effect const radialBlur = engine.block.createBlur('radial'); engine.block.setFloat(radialBlur, 'blur/radial/blurRadius', 50); engine.block.setFloat(radialBlur, 'blur/radial/radius', 25); engine.block.setFloat(radialBlur, 'blur/radial/gradientRadius', 35); engine.block.setFloat(radialBlur, 'blur/radial/x', 0.5); engine.block.setFloat(radialBlur, 'blur/radial/y', 0.45); engine.block.setBlur(radialVideo, radialBlur); engine.block.setBlurEnabled(radialVideo, true); // Add text labels for each video segment (positioned top-right) const labelWidth = 400; const labelHeight = 60; const labelPadding = 20; const labelMargin = 30; videos.forEach((_, index) => { const label = LABELS[index]; // Add background first (so it's behind text) const labelBg = engine.block.create('//ly.img.ubq/graphic'); const labelBgShape = engine.block.createShape('//ly.img.ubq/shape/rect'); engine.block.setShape(labelBg, labelBgShape); const labelBgFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(labelBgFill, 'fill/color/value', { r: 0.0, g: 0.0, b: 0.0, a: 0.8 }); engine.block.setFill(labelBg, labelBgFill); engine.block.setWidth(labelBg, labelWidth); engine.block.setHeight(labelBg, labelHeight + labelPadding); engine.block.setPositionX(labelBg, pageWidth - labelWidth - labelMargin); engine.block.setPositionY(labelBg, labelMargin); engine.block.setTimeOffset(labelBg, index * SEGMENT_DURATION); engine.block.setDuration(labelBg, SEGMENT_DURATION); engine.block.appendChild(page, labelBg); // Create text block for label const textBlock = engine.block.create('//ly.img.ubq/text'); engine.block.setString(textBlock, 'text/text', label); engine.block.setFloat(textBlock, 'text/fontSize', 48); engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(textBlock, 'text/verticalAlignment', 'Center'); // Set white text color engine.block.setTextColor(textBlock, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Position label at top right engine.block.setWidth(textBlock, labelWidth); engine.block.setHeight(textBlock, labelHeight); engine.block.setPositionX(textBlock, pageWidth - labelWidth - labelMargin); engine.block.setPositionY(textBlock, labelMargin + labelPadding / 2); engine.block.setTimeOffset(textBlock, index * SEGMENT_DURATION); engine.block.setDuration(textBlock, SEGMENT_DURATION); engine.block.appendChild(page, textBlock); }); // Enable auto-fit to keep page in view cesdk.engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); // Select first video to show timeline controls engine.block.select(fullBlurVideo); console.log( 'Video redaction guide initialized. Videos play sequentially - press play to see each redaction technique.' ); }} export default Example; ``` This guide covers how to use the built-in UI for blur and pixelization effects, and how to apply redaction programmatically using blur, pixelization, solid overlays, and time-based controls. ## Understanding Redaction in CE.SDK[#](#understanding-redaction-in-cesdk) ### How Effects Work[#](#how-effects-work) Effects in CE.SDK modify the block’s appearance directly rather than creating transparent overlays that affect content beneath. When you blur a video block, the entire block becomes blurred—not just a region on top of the video. ### Choosing a Redaction Technique[#](#choosing-a-redaction-technique) Select your technique based on privacy requirements and visual impact: * **Full-block blur**: Complete obscuration for backgrounds or placeholder content * **Radial blur**: Circular blur patterns ideal for face-like regions * **Pixelization**: Clearly intentional censoring that’s faster to render than heavy blur * **Solid overlays**: Complete blocking for highly sensitive information like documents or credentials ## Using the Built-in UI[#](#using-the-built-in-ui) ### Accessing Blur Controls[#](#accessing-blur-controls) When you select a video block, the inspector panel provides access to blur settings. Navigate to the blur section to find controls for different blur types including uniform, radial, linear, and mirrored. The uniform blur applies consistent intensity across the entire block. Adjust the intensity slider to control how strongly the content is obscured. Higher values create stronger privacy protection but may affect visual quality. ### Accessing Pixelization Controls[#](#accessing-pixelization-controls) The effects panel contains pixelization settings. Select your video block, open the effects section, and add a pixelize effect. Configure the horizontal and vertical pixel sizes to control the mosaic block dimensions. Larger pixel sizes create stronger obscuration but are more visually disruptive. Values between 15-30 pixels work well for standard redaction scenarios. ## Programmatic Redaction[#](#programmatic-redaction) ### Full-Block Blur[#](#full-block-blur) When the entire video needs obscuring, apply blur directly to the original block without duplication. This approach works well for background content or privacy placeholders. ``` // Check if the block supports blurconst supportsBlur = engine.block.supportsBlur(fullBlurVideo);console.log('Video supports blur:', supportsBlur); // Create and apply uniform blur to entire videoconst uniformBlur = engine.block.createBlur('uniform');engine.block.setFloat(uniformBlur, 'blur/uniform/intensity', 0.7);engine.block.setBlur(fullBlurVideo, uniformBlur);engine.block.setBlurEnabled(fullBlurVideo, true); ``` We first check that the block supports blur with `supportsBlur()`. Then we create a uniform blur, configure its intensity, attach it to the video block with `setBlur()`, and enable it with `setBlurEnabled()`. The intensity value ranges from 0.0 to 1.0, where higher values create stronger blur. ### Pixelization[#](#pixelization) Pixelization creates a mosaic effect that’s clearly intentional and renders faster than heavy blur. We use the effect system rather than the blur system for pixelization. ``` // Check if the block supports effectsif (engine.block.supportsEffects(pixelVideo)) { // Create and apply pixelize effect const pixelizeEffect = engine.block.createEffect('pixelize'); engine.block.setInt(pixelizeEffect, 'effect/pixelize/horizontalPixelSize', 24); engine.block.setInt(pixelizeEffect, 'effect/pixelize/verticalPixelSize', 24); engine.block.appendEffect(pixelVideo, pixelizeEffect); engine.block.setEffectEnabled(pixelizeEffect, true);} ``` We check `supportsEffects()` before creating the pixelize effect. The horizontal and vertical pixel sizes control the mosaic block dimensions—larger values create stronger obscuration. ### Solid Overlays[#](#solid-overlays) For complete blocking without any visual hint of the underlying content, create an opaque shape overlay. This approach doesn’t require block duplication. ``` // Create a solid rectangle overlayconst overlay = engine.block.create('//ly.img.ubq/graphic');const rectShape = engine.block.createShape('//ly.img.ubq/shape/rect');engine.block.setShape(overlay, rectShape); // Create solid black fillconst solidFill = engine.block.createFill('//ly.img.ubq/fill/color');engine.block.setColor(solidFill, 'fill/color/value', { r: 0.1, g: 0.1, b: 0.1, a: 1.0});engine.block.setFill(overlay, solidFill); // Position and size the overlayengine.block.setWidth(overlay, pageWidth * 0.4);engine.block.setHeight(overlay, pageHeight * 0.3);engine.block.setPositionX(overlay, pageWidth * 0.55);engine.block.setPositionY(overlay, pageHeight * 0.65);engine.block.appendChild(page, overlay); ``` We create a graphic block with a rectangle shape and solid color fill. The overlay uses absolute page coordinates for positioning. Set the alpha to 1.0 for complete opacity. ### Time-Based Redaction[#](#time-based-redaction) Redactions can appear only during specific portions of the video timeline. We use `setTimeOffset()` and `setDuration()` to control when the redaction is visible. ``` // Apply blur to the videoconst timedBlur = engine.block.createBlur('uniform');engine.block.setFloat(timedBlur, 'blur/uniform/intensity', 0.9);engine.block.setBlur(timedVideo, timedBlur);engine.block.setBlurEnabled(timedVideo, true); // The video is already timed to appear at a specific offset (set earlier)// You can adjust timeOffset and duration to control when redaction is visibleengine.block.setTimeOffset(timedVideo, 4 * SEGMENT_DURATION);engine.block.setDuration(timedVideo, SEGMENT_DURATION); ``` The time offset specifies when the redaction appears (in seconds from the start), and the duration controls how long it remains visible. This is useful for redacting faces or information that only appears briefly. ### Radial Blur[#](#radial-blur) For face-like regions, radial blur creates a circular blur pattern that works well with rounded subjects. ``` // Apply radial blur for circular redaction effectconst radialBlur = engine.block.createBlur('radial');engine.block.setFloat(radialBlur, 'blur/radial/blurRadius', 50);engine.block.setFloat(radialBlur, 'blur/radial/radius', 25);engine.block.setFloat(radialBlur, 'blur/radial/gradientRadius', 35);engine.block.setFloat(radialBlur, 'blur/radial/x', 0.5);engine.block.setFloat(radialBlur, 'blur/radial/y', 0.45);engine.block.setBlur(radialVideo, radialBlur);engine.block.setBlurEnabled(radialVideo, true); ``` Radial blur properties control the blur center (`x`, `y` from 0.0-1.0), the unblurred center area (`radius`), the blur transition zone (`gradientRadius`), and the blur strength (`blurRadius`). ## Performance Considerations[#](#performance-considerations) Different redaction techniques have different performance impacts: * **Solid overlays**: Minimal impact, can create many without significant overhead * **Pixelization**: Faster than blur, larger pixel sizes have minimal impact * **Blur effects**: Higher intensity values increase rendering time For complex scenes with multiple redactions, consider using solid overlays where blur isn’t necessary, or reduce blur intensity to maintain smooth playback. ## Troubleshooting[#](#troubleshooting) ### Redaction Not Visible[#](#redaction-not-visible) If your redaction doesn’t appear, verify that: * The overlay is a child of the page with `appendChild()` * Blur is enabled with `setBlurEnabled()` after setting it with `setBlur()` * Effects are enabled with `setEffectEnabled()` after appending with `appendEffect()` ### Performance Issues[#](#performance-issues) Reduce blur intensity, use pixelization instead of heavy blur, or switch to solid overlays for some redactions. ## Best Practices[#](#best-practices) * **Preview thoroughly**: Scrub the entire timeline to verify all sensitive content is covered * **Add safety margins**: Make redaction regions slightly larger than the sensitive area * **Test at export resolution**: Higher resolutions may need stronger blur settings * **Archive originals**: Exported redactions are permanent and cannot be reversed * **Document redactions**: For compliance requirements, maintain records of what was redacted ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `block.supportsBlur(id)` | Check if block supports blur effects | | `block.createBlur(type)` | Create blur instance (uniform, radial, linear, mirrored) | | `block.setBlur(id, blur)` | Apply blur to block | | `block.setBlurEnabled(id, enabled)` | Enable or disable blur | | `block.supportsEffects(id)` | Check if block supports effects | | `block.createEffect(type)` | Create effect instance (pixelize, etc.) | | `block.appendEffect(id, effect)` | Add effect to block | | `block.setEffectEnabled(effect, enabled)` | Enable or disable effect | | `block.setTimeOffset(id, offset)` | Set when block appears in timeline | | `block.setDuration(id, duration)` | Set block duration in timeline | | `block.create(type)` | Create block of specified type | | `block.createShape(type)` | Create shape for graphic blocks | | `block.setShape(id, shape)` | Apply shape to graphic block | | `block.createFill(type)` | Create fill (color, image, video, gradient) | | `block.setFill(id, fill)` | Apply fill to block | | `block.setFloat(id, property, value)` | Set float property value | | `block.setInt(id, property, value)` | Set integer property value | | `block.setColor(id, property, color)` | Set color property value | --- [Source](https:/img.ly/docs/cesdk/vue/edit-video/join-and-arrange-3bbc30) --- # Join and Arrange Video Clips Combine multiple video clips into sequences and organize them on the timeline using CE.SDK’s track system and programmatic APIs. ![Join and Arrange Video Clips example showing timeline with video clips organized in tracks](/docs/cesdk/_astro/browser.hero.C_GyG3d8_Z274QXT.webp) 12 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-join-and-arrange-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-join-and-arrange-browser) Video compositions in CE.SDK use a hierarchy: **Scene → Page → Track → Clip**. Tracks organize clips for sequential playback—when you add clips to a track, they play one after another. You can control precise timing using time offsets and create layered compositions by adding multiple tracks to a page. In CE.SDK’s block-based architecture, a **clip is a graphic block with a video fill**. This means video clips share the same APIs and capabilities as other blocks—you can position, rotate, scale, and apply effects to video just like images or shapes. The `addVideo()` helper creates this structure automatically and loads the video metadata. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Join and Arrange Video Clips Guide * * Demonstrates combining multiple video clips into sequences: * - Creating video scenes and tracks * - Adding clips to tracks for sequential playback * - Reordering clips within a track * - Controlling clip timing with time offsets * - Creating multi-track compositions */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 in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); // Create a video scene - required for timeline-based editing await cesdk.createVideoScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page to 16:9 landscape (1920x1080 is standard HD video resolution) engine.block.setWidth(page, 1920); engine.block.setHeight(page, 1080); // Set page duration to accommodate all clips (15 seconds total) engine.block.setDuration(page, 15); // Sample video URL for the demonstration const videoUrl = 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4'; // Create video clips using the addVideo helper method // Each clip is sized to fill the canvas (1920x1080 is standard video resolution) const clipA = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 0 } }); const clipB = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 5 } }); const clipC = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 10 } }); // Create a track and add it to the page // Tracks organize clips for sequential playback on the timeline const track = engine.block.create('track'); engine.block.appendChild(page, track); // Add clips to the track engine.block.appendChild(track, clipA); engine.block.appendChild(track, clipB); engine.block.appendChild(track, clipC); // Resize all track children to fill the page dimensions engine.block.fillParent(track); // Query track children to verify order const trackClips = engine.block.getChildren(track); console.log('Track clip count:', trackClips.length, 'clips'); // Set durations for each clip engine.block.setDuration(clipA, 5); engine.block.setDuration(clipB, 5); engine.block.setDuration(clipC, 5); // Set time offsets to position clips sequentially on the timeline engine.block.setTimeOffset(clipA, 0); engine.block.setTimeOffset(clipB, 5); engine.block.setTimeOffset(clipC, 10); console.log('Track offsets set: Clip A: 0s, Clip B: 5s, Clip C: 10s'); // Reorder clips: move Clip C to the beginning (index 0) // This demonstrates using insertChild for precise positioning engine.block.insertChild(track, clipC, 0); // After reordering, update time offsets to reflect the new sequence engine.block.setTimeOffset(clipC, 0); engine.block.setTimeOffset(clipA, 5); engine.block.setTimeOffset(clipB, 10); console.log('After reorder - updated offsets: C=0s, A=5s, B=10s'); // Get all clips in the track to verify arrangement const finalClips = engine.block.getChildren(track); console.log('Final track arrangement:'); finalClips.forEach((clipId, index) => { const offset = engine.block.getTimeOffset(clipId); const duration = engine.block.getDuration(clipId); console.log( ` Clip ${index + 1}: offset=${offset}s, duration=${duration}s` ); }); // Create a second track for layered compositions // Track order determines z-index: last track renders on top const overlayTrack = engine.block.create('track'); engine.block.appendChild(page, overlayTrack); // Create an overlay clip for picture-in-picture effect (1/4 size) const overlayClip = await engine.block.addVideo( videoUrl, 1920 / 4, 1080 / 4, { timeline: { duration: 5, timeOffset: 2 } } ); engine.block.appendChild(overlayTrack, overlayClip); // Position overlay in bottom-right corner with padding engine.block.setPositionX(overlayClip, 1920 - 1920 / 4 - 40); engine.block.setPositionY(overlayClip, 1080 - 1080 / 4 - 40); console.log('Multi-track composition created with overlay starting at 2s'); // Select the first clip in the main track to show timeline controls engine.block.select(clipC); // Seek to 2.5s to show both main clip and overlay visible // (overlay starts at 2s, so 2.5s shows both elements) engine.block.setPlaybackTime(page, 2.5); console.log( 'Join and Arrange guide initialized. Use timeline to view clip arrangement.' ); }} export default Example; ``` This guide covers how to join clips using the built-in timeline UI, how to programmatically add and arrange clips in tracks, and how to create multi-track compositions. ## Joining Clips via UI[#](#joining-clips-via-ui) CE.SDK’s timeline UI provides visual tools for arranging video clips. Select the Video mode to access timeline-based editing. ### Adding Clips to Timeline[#](#adding-clips-to-timeline) Drag clips from the asset panel directly onto the timeline. When you drop a clip on an existing track, it joins the sequence. Dropping on an empty area creates a new track for that clip. The timeline displays clip duration visually—longer clips take more horizontal space. You can see at a glance how clips relate to each other in time. ### Reordering Clips[#](#reordering-clips) Drag clips within a track to reorder them. As you drag, CE.SDK shows where the clip will land. Release to confirm the new position. The timeline UI updates time offsets when you reorder clips via drag-and-drop, positioning clips sequentially without gaps. ### Creating Additional Tracks[#](#creating-additional-tracks) Add multiple tracks to create layered compositions. Tracks stack vertically in the timeline, and clips on upper tracks render on top of clips below. This enables picture-in-picture effects, overlays, and complex multi-layer edits. ## Programmatic Clip Joining[#](#programmatic-clip-joining) ### Prerequisites and Setup[#](#prerequisites-and-setup) For applications that need to join clips programmatically—whether for automation, batch processing, or dynamic compositions—we start by setting up CE.SDK in Video mode. ``` // Enable video editing features in CE.SDKcesdk.feature.enable('ly.img.video');cesdk.feature.enable('ly.img.timeline');cesdk.feature.enable('ly.img.playback'); ``` Video mode enables timeline features and playback controls. The `ly.img.timeline` feature provides the timeline panel, and `ly.img.playback` enables play/pause controls. ### Creating a Video Scene[#](#creating-a-video-scene) We create a video scene to access timeline-based editing capabilities. Design mode doesn’t support tracks and sequential playback. ``` // Create a video scene - required for timeline-based editingawait cesdk.createVideoScene(); const engine = cesdk.engine;const page = engine.block.findByType('page')[0]; // Set page to 16:9 landscape (1920x1080 is standard HD video resolution)engine.block.setWidth(page, 1920);engine.block.setHeight(page, 1080); // Set page duration to accommodate all clips (15 seconds total)engine.block.setDuration(page, 15); ``` The page duration determines how long the composition plays. Set it to accommodate all your clips—in this example, 15 seconds for three 5-second clips. ### Creating Video Clips[#](#creating-video-clips) We create video clips as graphic blocks with video fills. Each clip needs a video fill that references the source media. ``` // Create video clips using the addVideo helper method// Each clip is sized to fill the canvas (1920x1080 is standard video resolution)const clipA = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 0 }}); const clipB = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 5 }}); const clipC = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 10 }}); ``` The `addVideo` helper method creates a graphic block with an attached video fill and automatically loads the video resource metadata. We set width and height to control how the clip appears in the composition. The `timeline` options let us set duration and time offset in one call. ### Creating Tracks[#](#creating-tracks) Tracks organize clips for sequential playback. We create a track and attach it to the page. ``` // Create a track and add it to the page// Tracks organize clips for sequential playback on the timelineconst track = engine.block.create('track');engine.block.appendChild(page, track); ``` A track acts as a container for clips. When you add clips to a track, they play in the order they were added. ### Adding Clips to Track[#](#adding-clips-to-track) We add clips to the track using `appendChild`. Clips join the sequence in the order they’re added. ``` // Add clips to the trackengine.block.appendChild(track, clipA);engine.block.appendChild(track, clipB);engine.block.appendChild(track, clipC); // Resize all track children to fill the page dimensionsengine.block.fillParent(track); // Query track children to verify orderconst trackClips = engine.block.getChildren(track);console.log('Track clip count:', trackClips.length, 'clips'); ``` After adding clips, you can query the track’s children to verify the order. `getChildren` returns an array of clip IDs in playback order. ### Setting Clip Durations[#](#setting-clip-durations) Each clip needs a duration that determines how long it plays in the timeline. ``` // Set durations for each clipengine.block.setDuration(clipA, 5);engine.block.setDuration(clipB, 5);engine.block.setDuration(clipC, 5); ``` Duration is measured in seconds. A 5-second duration means the clip occupies 5 seconds of timeline space. ## Arranging Clips[#](#arranging-clips) ### Time Offsets[#](#time-offsets) Time offsets control when each clip starts playing. We set offsets to position clips at specific points in the timeline. ``` // Set time offsets to position clips sequentially on the timelineengine.block.setTimeOffset(clipA, 0);engine.block.setTimeOffset(clipB, 5);engine.block.setTimeOffset(clipC, 10); console.log('Track offsets set: Clip A: 0s, Clip B: 5s, Clip C: 10s'); ``` Clip A starts at 0 seconds, Clip B at 5 seconds, and Clip C at 10 seconds. Combined with 5-second durations, this creates a continuous 15-second sequence with no gaps. ### Reordering Clips[#](#reordering-clips-1) Use `insertChild` to move clips to specific positions within a track. This moves an existing child to a new index. ``` // Reorder clips: move Clip C to the beginning (index 0)// This demonstrates using insertChild for precise positioningengine.block.insertChild(track, clipC, 0); // After reordering, update time offsets to reflect the new sequenceengine.block.setTimeOffset(clipC, 0);engine.block.setTimeOffset(clipA, 5);engine.block.setTimeOffset(clipB, 10); console.log('After reorder - updated offsets: C=0s, A=5s, B=10s'); ``` When we insert Clip C at index 0, it becomes the first clip. The order changes from A-B-C to C-A-B. We update time offsets to match the new sequence. ### Querying Track Children[#](#querying-track-children) Use `getChildren` to inspect the current clip order and verify arrangements. ``` // Get all clips in the track to verify arrangementconst finalClips = engine.block.getChildren(track);console.log('Final track arrangement:');finalClips.forEach((clipId, index) => { const offset = engine.block.getTimeOffset(clipId); const duration = engine.block.getDuration(clipId); console.log( ` Clip ${index + 1}: offset=${offset}s, duration=${duration}s` );}); ``` This loop outputs each clip’s position, time offset, and duration—useful for debugging or building custom timeline UIs. ## Multi-Track Compositions[#](#multi-track-compositions) ### Adding Multiple Tracks[#](#adding-multiple-tracks) Create layered compositions by adding multiple tracks to a page. Track order determines rendering order—clips in later tracks appear on top. ``` // Create a second track for layered compositions// Track order determines z-index: last track renders on topconst overlayTrack = engine.block.create('track');engine.block.appendChild(page, overlayTrack); // Create an overlay clip for picture-in-picture effect (1/4 size)const overlayClip = await engine.block.addVideo( videoUrl, 1920 / 4, 1080 / 4, { timeline: { duration: 5, timeOffset: 2 } });engine.block.appendChild(overlayTrack, overlayClip); // Position overlay in bottom-right corner with paddingengine.block.setPositionX(overlayClip, 1920 - 1920 / 4 - 40);engine.block.setPositionY(overlayClip, 1080 - 1080 / 4 - 40); console.log('Multi-track composition created with overlay starting at 2s'); ``` The overlay track contains a smaller clip positioned in the corner. It starts at 2 seconds and lasts 5 seconds, creating a picture-in-picture effect during that time range. ### Track Rendering Order[#](#track-rendering-order) CE.SDK renders tracks from first to last. The first track added appears at the bottom, and subsequent tracks layer on top. Use this to create: * **Background layers**: Full-screen videos or images on the first track * **Overlays**: Smaller clips positioned on upper tracks * **Titles**: Text or graphics that appear over video content ## Troubleshooting[#](#troubleshooting) ### Clips Not Appearing[#](#clips-not-appearing) If clips don’t show on the timeline, verify they’re attached to a track that’s attached to the page. Use `getParent` and `getChildren` to inspect the hierarchy: ``` const parent = engine.block.getParent(clipId);const children = engine.block.getChildren(trackId); ``` ### Wrong Playback Order[#](#wrong-playback-order) If clips play in unexpected order, check time offsets. Clips play based on their time offset values, not their order in the children array. Set explicit offsets when precise timing matters. ### Video Not Loading[#](#video-not-loading) If video content doesn’t appear when using `addVideo`, check that the video URL is accessible and the format is supported. The `addVideo` helper automatically loads video metadata. ## API Reference[#](#api-reference) | Method | Description | Parameters | Returns | | --- | --- | --- | --- | | `block.addVideo(uri, width, height, options)` | Create video clip with automatic resource loading | `uri: string, width: number, height: number, options?: { timeline: { duration, timeOffset } }` | `Promise` | | `block.create('track')` | Create a new track | `type: 'track'` | `DesignBlockId` | | `block.appendChild(parent, child)` | Add child to parent | `parent: DesignBlockId, child: DesignBlockId` | `void` | | `block.insertChild(parent, child, index)` | Insert child at specific position | `parent: DesignBlockId, child: DesignBlockId, index: number` | `void` | | `block.getChildren(id)` | Get all children of a block | `id: DesignBlockId` | `DesignBlockId[]` | | `block.setTimeOffset(id, offset)` | Set when block starts in timeline | `id: DesignBlockId, offset: number` | `void` | | `block.getTimeOffset(id)` | Get block’s time offset | `id: DesignBlockId` | `number` | | `block.setDuration(id, duration)` | Set block’s duration | `id: DesignBlockId, duration: number` | `void` | | `block.getDuration(id)` | Get block’s duration | `id: DesignBlockId` | `number` | ## Next Steps[#](#next-steps) Now that you understand how to join and arrange clips, explore related video editing features: * [Trim Video Clips](vue/edit-video/trim-4f688b/) \- Control which portion of media plays back * [Control Audio and Video](vue/create-video/control-daba54/) \- Master playback timing and audio mixing * [Video Timeline Overview](vue/create-video/timeline-editor-912252/) \- Understand the complete timeline editing system --- [Source](https:/img.ly/docs/cesdk/vue/edit-video/add-captions-f67565) --- # Add Captions Add synchronized captions to video projects using CE.SDK’s caption system, with support for importing subtitle files, styling with presets, and burning captions into video exports. ![Video captions example showing timeline with caption track and styled captions](/docs/cesdk/_astro/browser.hero.CjKUtOI4_1O0icd.webp) 15 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-add-captions-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-add-captions-browser) Captions in CE.SDK follow a hierarchy: **Page → CaptionTrack → Caption blocks**. Each caption has text, timing (time offset and duration), and styling properties. Captions appear and disappear based on their timing, synchronized with video playback. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Add Captions Guide * * Demonstrates adding synchronized captions to video projects: * - Importing captions from SRT/VTT files * - Creating and styling captions programmatically * - Applying caption presets * - Controlling caption timing and positioning * - Adding animations to captions */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 including captions cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); // Create a video scene for caption overlay await cesdk.createVideoScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions and duration engine.block.setWidth(page, 1920); engine.block.setHeight(page, 1080); engine.block.setDuration(page, 40); // Add a video clip as the base content const videoUrl = 'https://cdn.img.ly/assets/demo/v2/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: 40, timeOffset: 0 } }); engine.block.appendChild(track, videoClip); engine.block.fillParent(track); // Import captions from SRT file // createCaptionsFromURI parses SRT/VTT and creates caption blocks with timing const captionSrtUrl = 'https://img.ly/static/examples/captions.srt'; const captionBlocks = await engine.block.createCaptionsFromURI(captionSrtUrl); console.log(`Imported ${captionBlocks.length} captions from SRT file`); // Adjust caption timing to start at the beginning of the video // The SRT file may have different timing, so we reset to start at 0 let currentOffset = 0; for (const captionId of captionBlocks) { const duration = engine.block.getDuration(captionId); engine.block.setTimeOffset(captionId, currentOffset); currentOffset += duration; } // Create a caption track and add captions to it // Caption tracks organize captions in the timeline const captionTrack = engine.block.create('//ly.img.ubq/captionTrack'); engine.block.appendChild(page, captionTrack); // Add each caption block to the track for (const captionId of captionBlocks) { engine.block.appendChild(captionTrack, captionId); } console.log(`Caption track created with ${captionBlocks.length} captions`); // Apply a caption preset for consistent styling // Caption presets provide pre-configured styles (fonts, colors, backgrounds) const captionPresetsSourceId = 'ly.img.captionPresets'; const comicPresetId = '//ly.img.captionPresets/comic'; // Fetch the preset asset const comicPreset = await engine.asset.fetchAsset( captionPresetsSourceId, comicPresetId ); // Apply preset to the first caption (styling syncs across all captions) if (comicPreset && captionBlocks.length > 0) { await engine.asset.applyToBlock( captionPresetsSourceId, comicPreset, captionBlocks[0] ); console.log('Applied comic preset to captions'); } // Position captions at the bottom of the video frame // Caption position and size sync across all captions, so we only set it once if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Use percentage-based positioning for responsive layout engine.block.setPositionXMode(firstCaption, 'Percent'); engine.block.setPositionYMode(firstCaption, 'Percent'); engine.block.setWidthMode(firstCaption, 'Percent'); engine.block.setHeightMode(firstCaption, 'Percent'); // Position at bottom center with padding engine.block.setPositionX(firstCaption, 0.05); // 5% from left engine.block.setPositionY(firstCaption, 0.8); // 80% from top (near bottom) engine.block.setWidth(firstCaption, 0.9); // 90% width engine.block.setHeight(firstCaption, 0.15); // 15% height } // Modify a specific caption's text and timing if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Get current text const currentText = engine.block.getString( firstCaption, 'caption/text' ); console.log('First caption text:', currentText); // Get timing info const offset = engine.block.getTimeOffset(firstCaption); const duration = engine.block.getDuration(firstCaption); console.log(`First caption: offset=${offset}s, duration=${duration}s`); } // Add fade-in animation to the first caption if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Create and apply entry animation const fadeIn = engine.block.createAnimation('fade'); engine.block.setDuration(fadeIn, 0.3); engine.block.setInAnimation(firstCaption, fadeIn); console.log('Added fade-in animation to first caption'); } // Select the first caption to show it in the inspector if (captionBlocks.length > 0) { engine.block.select(captionBlocks[0]); } // Seek to show the first caption at 1 second engine.block.setPlaybackTime(page, 1); // Open the caption inspector panel cesdk.ui.openPanel('//ly.img.panel/inspector/caption'); console.log( 'Add Captions guide initialized. Captions imported and styled.' ); }} export default Example; ``` This guide covers how to import captions from SRT/VTT files, style them using presets and custom properties, create captions programmatically, and export videos with burned-in captions. ## Understanding Caption Structure[#](#understanding-caption-structure) ### Caption Hierarchy[#](#caption-hierarchy) CE.SDK organizes captions in a parent-child hierarchy. The page contains one or more caption tracks, and each caption track contains individual caption blocks. This structure allows for multiple caption tracks (for different languages or purposes) while keeping captions organized. When you import captions from a subtitle file, CE.SDK automatically creates the caption track and populates it with caption blocks. Each caption block stores its text content, start time, duration, and styling properties. ### Caption Timing[#](#caption-timing) Each caption has two timing properties: **time offset** (when the caption appears) and **duration** (how long it stays visible). These values are in seconds and synchronize with the video timeline. A caption with a time offset of 2.0 and duration of 3.0 appears at the 2-second mark and disappears at the 5-second mark. ## Importing Captions from Subtitle Files[#](#importing-captions-from-subtitle-files) ### Using createCaptionsFromURI[#](#using-createcaptionsfromuri) The fastest way to add captions is importing from an SRT or VTT subtitle file. CE.SDK parses the file and creates caption blocks with timing already configured. ``` // Import captions from SRT file// createCaptionsFromURI parses SRT/VTT and creates caption blocks with timingconst captionSrtUrl = 'https://img.ly/static/examples/captions.srt';const captionBlocks = await engine.block.createCaptionsFromURI(captionSrtUrl); console.log(`Imported ${captionBlocks.length} captions from SRT file`); // Adjust caption timing to start at the beginning of the video// The SRT file may have different timing, so we reset to start at 0let currentOffset = 0;for (const captionId of captionBlocks) { const duration = engine.block.getDuration(captionId); engine.block.setTimeOffset(captionId, currentOffset); currentOffset += duration;} ``` The `createCaptionsFromURI` method downloads the subtitle file, parses the timing and text, and creates a caption track with all captions positioned correctly. It returns an array of caption block IDs for the imported captions. ### Creating the Caption Track[#](#creating-the-caption-track) After importing captions, create a caption track to organize them in the timeline. The caption track manages caption positioning and display. ``` // Create a caption track and add captions to it// Caption tracks organize captions in the timelineconst captionTrack = engine.block.create('//ly.img.ubq/captionTrack');engine.block.appendChild(page, captionTrack); // Add each caption block to the trackfor (const captionId of captionBlocks) { engine.block.appendChild(captionTrack, captionId);} console.log(`Caption track created with ${captionBlocks.length} captions`); ``` Create a caption track with `engine.block.create('//ly.img.ubq/captionTrack')` and append it to the page. Then add each caption block to the track using `appendChild`. ## Using the Built-in Caption UI[#](#using-the-built-in-caption-ui) ### Caption Panel[#](#caption-panel) CE.SDK provides a caption panel in the inspector for visual caption management. When you select a caption, the panel shows timing controls, text editing, and styling options. Users can drag caption edges in the timeline to adjust timing or double-click to edit text. ### Importing via UI[#](#importing-via-ui) The caption panel includes an import button for uploading SRT or VTT files. The interface guides users through file selection and automatically extracts timing information. ### Styling with Presets[#](#styling-with-presets) Caption presets provide pre-configured styling combinations including font, color, background, and animations. Select a caption and choose from available presets to apply consistent styling. Presets are especially useful for maintaining brand consistency across videos. ### Editing Text and Timing[#](#editing-text-and-timing) Double-click a caption in the timeline or panel to edit its text. Drag the edges of caption blocks in the timeline to adjust start time and duration. The timeline provides visual feedback showing caption positions relative to video content. ## Creating Captions Programmatically[#](#creating-captions-programmatically) ### Caption Track Setup[#](#caption-track-setup) For full control over captions, create them programmatically. First, create a caption track and append it to the page. ``` const captionTrack = engine.block.create('//ly.img.ubq/captionTrack');engine.block.appendChild(page, captionTrack); ``` ### Creating Caption Blocks[#](#creating-caption-blocks) Create individual captions with text and timing. ``` const caption = engine.block.create('//ly.img.ubq/caption');engine.block.appendChild(captionTrack, caption); // Set caption textengine.block.setString(caption, 'caption/text', 'Hello, world!'); // Set timing - appears at 2 seconds for 3 secondsengine.block.setTimeOffset(caption, 2);engine.block.setDuration(caption, 3); ``` Set the caption text using `setString` with the `caption/text` property. Position the caption in time using `setTimeOffset` (when it appears) and `setDuration` (how long it shows). ## Styling Captions[#](#styling-captions) ### Applying Presets[#](#applying-presets) The fastest way to style captions is using presets. Presets provide pre-configured styling including fonts, colors, backgrounds, and effects. ``` // Apply a caption preset for consistent styling// Caption presets provide pre-configured styles (fonts, colors, backgrounds)const captionPresetsSourceId = 'ly.img.captionPresets';const comicPresetId = '//ly.img.captionPresets/comic'; // Fetch the preset assetconst comicPreset = await engine.asset.fetchAsset( captionPresetsSourceId, comicPresetId); // Apply preset to the first caption (styling syncs across all captions)if (comicPreset && captionBlocks.length > 0) { await engine.asset.applyToBlock( captionPresetsSourceId, comicPreset, captionBlocks[0] ); console.log('Applied comic preset to captions');} ``` Fetch a preset using `engine.asset.fetchAsset` and apply it with `engine.asset.applyToBlock`. Caption styling automatically syncs across all captions, so applying a preset to one caption styles them all. ### Positioning Captions[#](#positioning-captions) Position captions at the bottom of the video frame using percentage-based positioning for responsive layout. ``` // Position captions at the bottom of the video frame// Caption position and size sync across all captions, so we only set it onceif (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Use percentage-based positioning for responsive layout engine.block.setPositionXMode(firstCaption, 'Percent'); engine.block.setPositionYMode(firstCaption, 'Percent'); engine.block.setWidthMode(firstCaption, 'Percent'); engine.block.setHeightMode(firstCaption, 'Percent'); // Position at bottom center with padding engine.block.setPositionX(firstCaption, 0.05); // 5% from left engine.block.setPositionY(firstCaption, 0.8); // 80% from top (near bottom) engine.block.setWidth(firstCaption, 0.9); // 90% width engine.block.setHeight(firstCaption, 0.15); // 15% height} ``` Use percentage mode (`setPositionXMode`, `setPositionYMode`) for positions that adapt to different video resolutions. Caption position and size sync across all captions automatically. ### Background[#](#background) Enable a background behind caption text for better readability over video content. Use `setBool` to enable `backgroundColor/enabled` and `setColor` to set `backgroundColor/color` with RGBA values. A semi-transparent black background (alpha 0.7) is common for video captions. ### Automatic Font Sizing[#](#automatic-font-sizing) CE.SDK can automatically adjust font size to fit caption text within bounds. Enable automatic sizing and set minimum and maximum size limits. ``` engine.block.setBool(captionId, 'caption/automaticFontSizeEnabled', true);engine.block.setFloat(captionId, 'caption/minAutomaticFontSize', 24);engine.block.setFloat(captionId, 'caption/maxAutomaticFontSize', 72); ``` This prevents text from overflowing while maintaining readability. ## Applying Presets Programmatically[#](#applying-presets-programmatically) ### Finding Available Presets[#](#finding-available-presets) Query available caption presets from the asset library. ``` const presetsResult = await engine.asset.findAssets('ly.img.captionPresets', { page: 0, perPage: 100}); const presets = presetsResult.assets; ``` The `findAssets` method returns preset metadata including IDs and preview thumbnails. ### Applying a Preset[#](#applying-a-preset) Apply a preset to a caption using `applyToBlock`. ``` const preset = presets[0];await engine.asset.applyToBlock('ly.img.captionPresets', preset, captionId); ``` The preset applies all styling properties at once—font, colors, background, and any animations defined in the preset. ## Caption Animations[#](#caption-animations) ### Adding Entry Animations[#](#adding-entry-animations) Make captions more engaging by adding entry animations. ``` // Add fade-in animation to the first captionif (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Create and apply entry animation const fadeIn = engine.block.createAnimation('fade'); engine.block.setDuration(fadeIn, 0.3); engine.block.setInAnimation(firstCaption, fadeIn); console.log('Added fade-in animation to first caption');} ``` Create an animation using `createAnimation` with types like ‘fade’, ‘slide’, or ‘scale’. Set the animation duration and apply it with `setInAnimation`. ### Animation Types[#](#animation-types) CE.SDK supports several animation types for captions: * **fade** - Opacity transition * **slide** - Position movement * **scale** - Size change * **blur** - Focus effect Set loop animations with `setLoopAnimation` for continuous effects, or exit animations with `setOutAnimation` for departure transitions. ## Reading Caption Properties[#](#reading-caption-properties) ### Getting Text and Timing[#](#getting-text-and-timing) Retrieve caption properties to display in custom UI or for processing. ``` // Modify a specific caption's text and timingif (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Get current text const currentText = engine.block.getString( firstCaption, 'caption/text' ); console.log('First caption text:', currentText); // Get timing info const offset = engine.block.getTimeOffset(firstCaption); const duration = engine.block.getDuration(firstCaption); console.log(`First caption: offset=${offset}s, duration=${duration}s`);} ``` Use `getString` for text, `getTimeOffset` for start time, and `getDuration` for display length. These values are useful for building custom caption editors or synchronization tools. ## Exporting Videos with Captions[#](#exporting-videos-with-captions) ### Burned-In Captions[#](#burned-in-captions) When you export a video, captions are burned into the video as pixels. They become part of the video image and cannot be turned off by viewers. This ensures captions display correctly on any platform. ``` const videoBlob = await engine.block.exportVideo(page, { mimeType: 'video/mp4'}); ``` The export process renders each frame with captions overlaid at the correct timing. Export time depends on video length and resolution. For accessibility, consider also providing separate subtitle files (SRT/VTT) alongside burned-in captions. This allows viewers to customize caption appearance in their video player. ## Troubleshooting[#](#troubleshooting) | Issue | Cause | Solution | | --- | --- | --- | | Captions not visible | Not in caption track hierarchy | Check `getParent()`: page → captionTrack → caption | | Wrong timing | Time offset/duration incorrect | Verify `getTimeOffset()` and `getDuration()` | | Import fails | Unsupported format | Use valid SRT or VTT file | | Styling not applying | Property path wrong | Use `caption/` prefix for caption properties | ### Captions Not Appearing[#](#captions-not-appearing) If captions don’t show in the preview, verify the caption hierarchy. Each caption must be a child of a caption track, which must be a child of the page. Use `getParent()` to trace the hierarchy. Also check that the playhead position matches caption timing. Captions only appear during their time offset and duration window. ### Import Errors[#](#import-errors) If `createCaptionsFromURI` fails, verify the URL is accessible and returns valid SRT or VTT content. Common issues include CORS restrictions and malformed subtitle files. Test the URL in a browser to confirm accessibility. ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `engine.block.createCaptionsFromURI(uri)` | Import captions from SRT/VTT file | | `engine.block.create('//ly.img.ubq/captionTrack')` | Create caption track container | | `engine.block.create('//ly.img.ubq/caption')` | Create caption block | | `engine.block.setString(id, property, value)` | Set caption text | | `engine.block.setTimeOffset(id, offset)` | Set caption start time | | `engine.block.setDuration(id, duration)` | Set caption display duration | | `engine.block.setFloat(id, property, value)` | Set font size, spacing | | `engine.block.setEnum(id, property, value)` | Set alignment | | `engine.block.setBool(id, property, value)` | Enable background | | `engine.block.setColor(id, property, value)` | Set colors | | `engine.block.createAnimation(type)` | Create animation | | `engine.block.setInAnimation(id, animation)` | Set entry animation | | `engine.block.exportVideo(id, options)` | Export video with captions | | `engine.asset.findAssets(sourceId, params)` | Find presets | | `engine.asset.applyToBlock(sourceId, asset, block)` | Apply preset | --- [Source](https:/img.ly/docs/cesdk/vue/edit-video/add-watermark-762ce6) --- # Add Watermark Add text and image watermarks to video content for copyright protection, branding, and content attribution using CE.SDK’s timeline-aware block system. ![Add Watermark example showing video with text and logo watermarks](/docs/cesdk/_astro/browser.hero.DOoz-wzg_ZbI97d.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-add-watermark-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-add-watermark-browser) Video watermarks in CE.SDK are design blocks positioned over video content. **Text watermarks** display copyright notices, URLs, or branding text, while **image watermarks** show logos or graphics. Both watermark types require timeline management to ensure they remain visible throughout video playback. The key difference from static image watermarking is setting the watermark’s `duration` to match the video duration. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Add Watermark to Video Guide * * Demonstrates adding text and image watermarks to videos: * - Creating text watermarks with styling * - Creating image watermarks from logos * - Positioning watermarks on the canvas * - Setting watermark duration to match video * - Adding drop shadows for visibility * - Configuring opacity and blend modes */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 in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); // Create a video scene from a sample video const videoUrl = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; await cesdk.engine.scene.createFromVideo(videoUrl); const engine = cesdk.engine; const page = engine.scene.getCurrentPage()!; // Get page dimensions for watermark positioning const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Get the page duration (set automatically from the video) const videoDuration = engine.block.getDuration(page); console.log('Video duration from page:', videoDuration); // ===== TEXT WATERMARK ===== // Create a text watermark for copyright notice const textWatermark = engine.block.create('text'); // Use Auto sizing so the text block grows to fit its content engine.block.setWidthMode(textWatermark, 'Auto'); engine.block.setHeightMode(textWatermark, 'Auto'); // Set the watermark text content using replaceText engine.block.replaceText(textWatermark, 'All rights reserved © 2025'); // Position in bottom-left corner with padding const textPadding = 20; engine.block.setPositionX(textWatermark, textPadding); engine.block.setPositionY(textWatermark, pageHeight - textPadding - 20); // Style the text watermark with a subtle font size engine.block.setFloat(textWatermark, 'text/fontSize', 4); engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // White text // Set text alignment to left engine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left'); // Set watermark opacity for subtle appearance engine.block.setOpacity(textWatermark, 0.7); // Add drop shadow for visibility across different backgrounds engine.block.setDropShadowEnabled(textWatermark, true); engine.block.setDropShadowColor(textWatermark, { r: 0, g: 0, b: 0, a: 0.8 }); engine.block.setDropShadowOffsetX(textWatermark, 2); engine.block.setDropShadowOffsetY(textWatermark, 2); engine.block.setDropShadowBlurRadiusX(textWatermark, 4); engine.block.setDropShadowBlurRadiusY(textWatermark, 4); // Set the text watermark duration to match the video engine.block.setDuration(textWatermark, videoDuration); engine.block.setTimeOffset(textWatermark, 0); // Add the text watermark to the page engine.block.appendChild(page, textWatermark); // ===== IMAGE WATERMARK (LOGO) ===== // Create a graphic block for the logo watermark const logoWatermark = engine.block.create('graphic'); // Create a rectangular shape for the logo const rectShape = engine.block.createShape('rect'); engine.block.setShape(logoWatermark, rectShape); // Create an image fill with the logo const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(logoWatermark, imageFill); // Set content fill mode to contain so the logo fits within bounds engine.block.setContentFillMode(logoWatermark, 'Contain'); // Size and position the logo in the top-right corner const logoSize = 80; const logoPadding = 20; engine.block.setWidth(logoWatermark, logoSize); engine.block.setHeight(logoWatermark, logoSize); engine.block.setPositionX(logoWatermark, pageWidth - logoSize - logoPadding); engine.block.setPositionY(logoWatermark, logoPadding); // Set opacity for the logo watermark engine.block.setOpacity(logoWatermark, 0.6); // Set blend mode for better integration with video content engine.block.setBlendMode(logoWatermark, 'Normal'); // Set the logo watermark duration to match the video engine.block.setDuration(logoWatermark, videoDuration); engine.block.setTimeOffset(logoWatermark, 0); // Add the logo watermark to the page engine.block.appendChild(page, logoWatermark); // Select the page to show the timeline engine.block.setSelected(page, true); // Zoom to fit the page and enable auto-fit for responsive resizing await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); engine.scene.enableZoomAutoFit(page, 'Horizontal', 40, 40); // Start playback automatically try { engine.block.setPlaying(page, true); console.log( 'Video watermark guide initialized. Playback started with text and logo watermarks visible.' ); } catch (error) { console.log( 'Video watermark guide initialized. Click play button to start playback.' ); } }} export default Example; ``` This guide covers how to create text and image watermarks programmatically, position them on the canvas, style them for visibility, and configure their timeline duration to span the entire video. ## Setting Up Video Mode[#](#setting-up-video-mode) Before adding watermarks, we configure CE.SDK for video editing. Video mode enables timeline features required for watermark duration control. ``` // Enable video editing features in CE.SDKcesdk.feature.enable('ly.img.video');cesdk.feature.enable('ly.img.timeline');cesdk.feature.enable('ly.img.playback'); ``` We enable three features: `ly.img.video` for video support, `ly.img.timeline` for timeline controls, and `ly.img.playback` for video playback. These features must be enabled before creating a video scene. ## Creating a Video Scene[#](#creating-a-video-scene) We create a video scene from a video URL. This automatically sets up the timeline and page dimensions based on the video. ``` // Create a video scene from a sample videoconst videoUrl = 'https://img.ly/static/ubq_video_samples/bbb.mp4';await cesdk.engine.scene.createFromVideo(videoUrl); ``` The `createFromVideo` method loads the video, creates a scene, and sets the page dimensions to match the video’s aspect ratio. The video becomes a fill block on the timeline with its duration already set. ## Creating a Text Watermark[#](#creating-a-text-watermark) Text watermarks display copyright notices, branding text, or URLs. We create a text block and position it on the canvas. ``` // Create a text watermark for copyright noticeconst textWatermark = engine.block.create('text'); // Use Auto sizing so the text block grows to fit its contentengine.block.setWidthMode(textWatermark, 'Auto');engine.block.setHeightMode(textWatermark, 'Auto'); // Set the watermark text content using replaceTextengine.block.replaceText(textWatermark, 'All rights reserved © 2025'); // Position in bottom-left corner with paddingconst textPadding = 20;engine.block.setPositionX(textWatermark, textPadding);engine.block.setPositionY(textWatermark, pageHeight - textPadding - 20); ``` We create a text block with `block.create('text')` and configure it with auto-sizing using `setWidthMode('Auto')` and `setHeightMode('Auto')`. This lets the text block grow to fit its content. We set the text content using `replaceText()` and position the watermark in the bottom-left corner with padding from the edges. ## Styling Text Watermarks[#](#styling-text-watermarks) Style the text for readability across different video backgrounds. ``` // Style the text watermark with a subtle font sizeengine.block.setFloat(textWatermark, 'text/fontSize', 4);engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // White text // Set text alignment to leftengine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left'); // Set watermark opacity for subtle appearanceengine.block.setOpacity(textWatermark, 0.7); ``` We set the font size using `setFloat()` with the `'text/fontSize'` property for a subtle watermark appearance. White text color ensures visibility, left alignment positions the text naturally, and 70% opacity creates a semi-transparent appearance that’s visible but not distracting. ## Adding Drop Shadow for Visibility[#](#adding-drop-shadow-for-visibility) Drop shadows ensure text remains readable over both light and dark video backgrounds. ``` // Add drop shadow for visibility across different backgroundsengine.block.setDropShadowEnabled(textWatermark, true);engine.block.setDropShadowColor(textWatermark, { r: 0, g: 0, b: 0, a: 0.8 });engine.block.setDropShadowOffsetX(textWatermark, 2);engine.block.setDropShadowOffsetY(textWatermark, 2);engine.block.setDropShadowBlurRadiusX(textWatermark, 4);engine.block.setDropShadowBlurRadiusY(textWatermark, 4); ``` We enable the drop shadow and configure its appearance. The black shadow color with 80% opacity provides contrast. Offset values (2px in each direction) separate the shadow from the text, while blur radius values (4px) create a soft shadow edge. ## Setting Text Watermark Duration[#](#setting-text-watermark-duration) The watermark must persist throughout video playback. We set its duration to match the video duration. ``` // Set the text watermark duration to match the videoengine.block.setDuration(textWatermark, videoDuration);engine.block.setTimeOffset(textWatermark, 0); // Add the text watermark to the pageengine.block.appendChild(page, textWatermark); ``` `setDuration` controls how long the block appears in the timeline. `setTimeOffset` of 0 ensures it starts at the beginning. We then append the watermark to the page, placing it above the video content. ## Creating an Image Watermark[#](#creating-an-image-watermark) Image watermarks display logos or graphics. We create a graphic block with an image fill. ``` // Create a graphic block for the logo watermarkconst logoWatermark = engine.block.create('graphic'); // Create a rectangular shape for the logoconst rectShape = engine.block.createShape('rect');engine.block.setShape(logoWatermark, rectShape); // Create an image fill with the logoconst imageFill = engine.block.createFill('image');engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg');engine.block.setFill(logoWatermark, imageFill); // Set content fill mode to contain so the logo fits within boundsengine.block.setContentFillMode(logoWatermark, 'Contain'); ``` We create a graphic block, assign it a rectangular shape, and fill it with an image. The `fill/image/imageFileURI` property specifies the logo URL. We set the content fill mode to ‘Contain’ so the logo fits within its bounds without cropping. This pattern—graphic block with shape and fill—is standard for displaying images in CE.SDK. ## Positioning Image Watermarks[#](#positioning-image-watermarks) Position the logo in a corner that doesn’t obstruct video content. ``` // Size and position the logo in the top-right cornerconst logoSize = 80;const logoPadding = 20;engine.block.setWidth(logoWatermark, logoSize);engine.block.setHeight(logoWatermark, logoSize);engine.block.setPositionX(logoWatermark, pageWidth - logoSize - logoPadding);engine.block.setPositionY(logoWatermark, logoPadding); ``` We size the logo at 80x80 pixels—large enough to be recognizable but not dominating. Position values place it in the top-right corner with 20px padding from the edges. ## Configuring Opacity and Blend Mode[#](#configuring-opacity-and-blend-mode) Control how the watermark integrates with the video. ``` // Set opacity for the logo watermarkengine.block.setOpacity(logoWatermark, 0.6); // Set blend mode for better integration with video contentengine.block.setBlendMode(logoWatermark, 'Normal'); ``` We set 60% opacity for a subtle but visible watermark. The blend mode ‘Normal’ displays the logo as-is. Other modes like ‘Multiply’ or ‘Screen’ create different visual effects depending on the logo and video content. ## Setting Image Watermark Duration[#](#setting-image-watermark-duration) Like text watermarks, image watermarks need duration configuration. ``` // Set the logo watermark duration to match the videoengine.block.setDuration(logoWatermark, videoDuration);engine.block.setTimeOffset(logoWatermark, 0); // Add the logo watermark to the pageengine.block.appendChild(page, logoWatermark); ``` We set the same duration and time offset as the text watermark so both appear throughout the video. The `appendChild` call adds the logo to the page above existing content. ## Watermark Positioning Strategies[#](#watermark-positioning-strategies) Choose watermark positions based on your use case: * **Bottom-right corner**: Most common for copyright notices. Less intrusive but clearly visible. * **Top-right corner**: Good for logos. Doesn’t interfere with typical video framing. * **Bottom-left corner**: Alternative for text when bottom-right conflicts with video content. * **Center**: Strong protection but obstructs content. Use for draft or preview watermarks. Calculate positions dynamically based on page dimensions to handle different video aspect ratios. ## Best Practices[#](#best-practices) ### Visibility[#](#visibility) * Use drop shadows on text watermarks for contrast against varying backgrounds * Set opacity between 50-70% for subtle but visible branding * Choose appropriate font sizes based on your use case (smaller for subtle branding, larger for prominent notices) * Test watermarks against different scenes in your video ### Timeline Management[#](#timeline-management) * Always match watermark duration to video duration * Set time offset to 0 for watermarks that should appear from the start * For time-based watermarks, calculate offsets based on video sections ### Performance[#](#performance) * Use appropriately sized logo images (avoid oversized source files) * Limit the number of watermark blocks to minimize rendering overhead * Consider combining multiple watermarks into a single image if they’re always used together ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `block.create('text')` | Create a text block for text watermarks | | `block.create('graphic')` | Create a graphic block for image watermarks | | `block.setWidthMode(id, mode)` | Set width sizing mode (‘Auto’, ‘Absolute’, ‘Percent’) | | `block.setHeightMode(id, mode)` | Set height sizing mode (‘Auto’, ‘Absolute’, ‘Percent’) | | `block.replaceText(id, text)` | Set text content for text blocks | | `block.setFloat(id, property, value)` | Set numeric properties like font size | | `block.createShape('rect')` | Create a rectangular shape for graphics | | `block.createFill('image')` | Create an image fill for logo watermarks | | `block.setString(id, property, value)` | Set string properties like image URI | | `block.setContentFillMode(id, mode)` | Set content fill mode (‘Crop’, ‘Cover’, ‘Contain’) | | `block.setDuration(id, duration)` | Set watermark timeline duration | | `block.setTimeOffset(id, offset)` | Set watermark start time | | `block.setOpacity(id, opacity)` | Set watermark transparency (0.0-1.0) | | `block.setDropShadowEnabled(id, enabled)` | Enable/disable drop shadow | | `block.setDropShadowColor(id, color)` | Set shadow color | | `block.setDropShadowOffsetX/Y(id, offset)` | Set shadow position | | `block.setDropShadowBlurRadiusX/Y(id, radius)` | Set shadow blur | | `block.setBlendMode(id, mode)` | Set blend mode (‘Normal’, ‘Multiply’, etc.) | | `block.appendChild(parent, child)` | Add watermark to page | --- [Source](https:/img.ly/docs/cesdk/vue/edit-image/vectorize-2b4c7f) --- # Vectorize Convert raster images into scalable vector graphics that resize without quality loss using CE.SDK’s vectorizer plugin. ![Vectorize Images example showing an image ready for vectorization](/docs/cesdk/_astro/browser.hero.BQUfCQLZ_Z81Def.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-vectorize-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-vectorize-browser) Vectorization transforms pixel-based images into vector paths that can be scaled to any size without losing quality. The `@imgly/plugin-vectorizer-web` plugin provides one-click UI conversion directly in the canvas menu. Common use cases include converting logos for scalable branding, creating cutout outlines from photographs, and extracting editable paths from illustrations. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import VectorizerPlugin from '@imgly/plugin-vectorizer-web';import packageJson from './package.json'; /** * CE.SDK Plugin: Vectorize Images Guide * * Demonstrates converting raster images to vector graphics: * - Using the vectorizer plugin for UI-based conversion * - Programmatically vectorizing with createCutoutFromBlocks() * - Configuring threshold parameters for quality control */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'); } // Load asset sources for the editor await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); const engine = cesdk.engine; // Add the vectorizer plugin with configuration options await cesdk.addPlugin( VectorizerPlugin({ // Display the vectorize button in the canvas menu ui: { locations: 'canvasMenu' }, // Set processing timeout to 30 seconds timeout: 30000, // Combine paths into a single shape when exceeding 500 paths groupingThreshold: 500 }) ); // Show only the vectorizer button in the canvas menu cesdk.ui.setCanvasMenuOrder(['@imgly/plugin-vectorizer-web.canvasMenu']); // Create a design scene with a page 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); // Create an image block to vectorize const imageBlock = engine.block.create('graphic'); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); // Load a sample image with clear contours for vectorization const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setContentFillMode(imageBlock, 'Contain'); // Center the image on the page const imageWidth = 400; const imageHeight = 300; engine.block.setWidth(imageBlock, imageWidth); engine.block.setHeight(imageBlock, imageHeight); engine.block.setPositionX(imageBlock, (800 - imageWidth) / 2); engine.block.setPositionY(imageBlock, (600 - imageHeight) / 2); engine.block.appendChild(page, imageBlock); // Select the image to reveal the vectorize button in the canvas menu engine.block.select(imageBlock); // Zoom to fit the page in view await engine.scene.zoomToBlock(page, { padding: 40 }); engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); }} export default Example; ``` This guide covers how to install and configure the vectorizer plugin, customize the canvas menu, and troubleshoot common vectorization issues. ## Using the Vectorizer Plugin[#](#using-the-vectorizer-plugin) The `@imgly/plugin-vectorizer-web` plugin adds a vectorize button to the canvas menu when you select an image block. Processing runs entirely in the browser using the [@imgly/vectorizer](https://www.npmjs.com/package/@imgly/vectorizer) library. ### Installation[#](#installation) Install the plugin via npm or yarn: Terminal window ``` yarn add @imgly/plugin-vectorizer-webnpm install @imgly/plugin-vectorizer-web ``` ### Adding the Plugin[#](#adding-the-plugin) We register the plugin using `cesdk.addPlugin()` with the `ui.locations` option to display the vectorize button in the canvas menu. To show only the vectorizer button, we use `setCanvasMenuOrder()` to filter out other menu items. ``` // Add the vectorizer plugin with configuration optionsawait cesdk.addPlugin( VectorizerPlugin({ // Display the vectorize button in the canvas menu ui: { locations: 'canvasMenu' }, // Set processing timeout to 30 seconds timeout: 30000, // Combine paths into a single shape when exceeding 500 paths groupingThreshold: 500 })); // Show only the vectorizer button in the canvas menucesdk.ui.setCanvasMenuOrder(['@imgly/plugin-vectorizer-web.canvasMenu']); ``` ### Configuration Options[#](#configuration-options) You can customize the plugin behavior with two configuration options: * **timeout**: Processing time limit in milliseconds (default: 30000). Increase this for complex images that take longer to process. * **groupingThreshold**: Maximum path count before combining into a single shape (default: 500). Lower values combine paths earlier, reducing selectable elements. ## Programmatic Vectorization[#](#programmatic-vectorization) For automation workflows, you can create cutout blocks from source blocks using `engine.block.createCutoutFromBlocks()`. This method traces rasterized content or extracts existing vector paths. ### Threshold Parameters[#](#threshold-parameters) The `createCutoutFromBlocks()` method accepts three parameters that control vectorization quality: * **vectorizeDistanceThreshold** (default: 2): Maximum contour deviation during tracing. Lower values increase accuracy but produce more complex paths. * **simplifyDistanceThreshold** (default: 4): Maximum deviation for path smoothing. Set to 0 to disable smoothing entirely. * **useExistingShapeInformation** (default: true): When true, extracts existing vector paths from shapes and SVGs without re-tracing. ### Threshold Recommendations[#](#threshold-recommendations) Start with the default values (2, 4) and adjust based on your source content: | Content Type | vectorizeDistanceThreshold | simplifyDistanceThreshold | | --- | --- | --- | | Photographs | 4-8 | 6-10 | | Logos and icons | 1-2 | 2-4 | | Illustrations | 2-4 | 4-6 | Lower thresholds increase path complexity and processing time. For photographs with many details, higher thresholds reduce the number of paths while maintaining overall shape recognition. ## Troubleshooting[#](#troubleshooting) Common issues and solutions: * **Processing timeout**: Increase the `timeout` option or use higher threshold values to reduce complexity. * **Jagged edges**: Increase `simplifyDistanceThreshold` to smooth the paths. * **Lost details**: Decrease both threshold values to capture finer contours. * **Vectorize button not appearing**: Verify `ui: { locations: 'canvasMenu' }` is set and that you’ve selected an image block. * **Memory issues with complex images**: Increase `groupingThreshold` to combine more paths into single shapes. ## API Reference[#](#api-reference) | Method | Category | Purpose | | --- | --- | --- | | `cesdk.addPlugin(VectorizerPlugin(options))` | Plugin | Register the vectorizer plugin | | `cesdk.ui.setCanvasMenuOrder(ids)` | UI | Control which items appear in the canvas menu | | `engine.block.createCutoutFromBlocks(ids, vectorizeDistanceThreshold?, simplifyDistanceThreshold?, useExistingShapeInformation?)` | Block | Create a cutout from block contours | --- [Source](https:/img.ly/docs/cesdk/vue/edit-image/transform-9d189b) --- # Transform --- [Source](https:/img.ly/docs/cesdk/vue/edit-image/replace-colors-6ede17) --- # Replace Colors Transform images by swapping specific colors using the Recolor effect or remove backgrounds with the Green Screen effect in CE.SDK. ![Replace Colors example showing color replacement and background removal](/docs/cesdk/_astro/browser.hero.CiWZDZK-_ZLQEXi.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-replace-colors-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-replace-colors-browser) CE.SDK offers two color replacement effects. The **Recolor** effect swaps one color for another while preserving image details. The **Green Screen** effect removes background colors with transparency. Both effects provide precise control over color matching, edge smoothness, and intensity. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); 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'); // Enable all effects including recolor and green screen // 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); 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]); 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 shows how to enable the built-in effects UI for interactive color replacement and apply effects programmatically using the block API. ## Using the Built-in Effects UI[#](#using-the-built-in-effects-ui) ### Enable Effects Panel[#](#enable-effects-panel) We enable the effects feature using CE.SDK’s Feature API. The effects panel appears in the inspector when users select a compatible graphic block. ``` // Enable effects in the inspector panel using the Feature APIcesdk.feature.enable('ly.img.effect'); // Enable all effects including recolor and green screen ``` Enabling `ly.img.effect` makes all effect options available in the inspector panel, including Recolor and Green Screen. ### User Workflow[#](#user-workflow) With effects enabled, users can replace colors through the inspector panel: 1. **Select an image block** - Click an image or graphic block on the canvas 2. **Open inspector** - The inspector shows available options for the selected element 3. **Find effects section** - Scroll to the effects section 4. **Choose Recolor or Green Screen** - Click the desired effect 5. **Select colors** - Use the color picker to specify source and target colors 6. **Adjust parameters** - Fine-tune color matching, smoothness, and intensity 7. **Toggle effects** - Enable or disable effects to compare results Users can experiment with color replacements and see results immediately. ## Programmatic Color Replacement[#](#programmatic-color-replacement) ### Initialize CE.SDK[#](#initialize-cesdk) To apply color replacement programmatically, we set up CE.SDK with the proper configuration. ``` // Initialize CE.SDK with Design mode and load asset sourcesawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true});await cesdk.createDesignScene(); const engine = cesdk.engine;const page = engine.block.findByType('page')[0]; // Set page dimensionsengine.block.setWidth(page, 800);engine.block.setHeight(page, 600); const pageWidth = engine.block.getWidth(page);const pageHeight = engine.block.getHeight(page); ``` This initializes CE.SDK with the effects panel enabled, providing both UI and API access. ### Creating and Applying Recolor Effects[#](#creating-and-applying-recolor-effects) The Recolor effect swaps one color for another throughout an image. We create a Recolor effect using `engine.block.createEffect('recolor')` and specify the source and target colors. ``` // Create a Recolor effect to swap red colors to blueconst 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 colorengine.block.setColor(recolorEffect, 'effect/recolor/toColor', { r: 0.0, g: 0.5, b: 1.0, a: 1.0}); // Blue target colorengine.block.appendEffect(block1, recolorEffect); // Select this block to show the effects panelengine.block.setSelected(block1, true); ``` The Recolor effect identifies pixels matching the source color (fromColor) and replaces them with the target color (toColor). Color values use RGBA format with values from 0.0 to 1.0. The example code uses the `engine.block.addImage()` convenience API. This simplifies image block creation compared to manually constructing graphic blocks with image fills. ### Configuring Color Matching[#](#configuring-color-matching) We adjust the matching tolerance and smoothness parameters to control how precisely colors must match before replacement. ``` // Configure color matching precision for Recolor effectconst 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 sourceengine.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 toleranceengine.block.setFloat( recolorEffect2, 'effect/recolor/brightnessMatch', 0.2);// Adjust edge smoothnessengine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1);engine.block.appendEffect(block2, recolorEffect2); ``` The Recolor effect provides these parameters: * **colorMatch** (0-1) - How closely colors must match the source. Lower values match exact colors, higher values match broader ranges * **brightnessMatch** (0-1) - Tolerance for brightness variations * **smoothness** (0-1) - Edge blending to reduce artifacts These parameters help handle images where colors vary due to lighting, shadows, or compression. ### Creating and Applying Green Screen Effects[#](#creating-and-applying-green-screen-effects) The Green Screen effect removes backgrounds by making specific colors transparent. ``` // Create a Green Screen effect to remove green backgroundsconst 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); ``` The Green Screen effect identifies pixels matching the specified color (fromColor) and makes them transparent. This works best with solid-color backgrounds like green screens or blue screens. ### Fine-Tuning Green Screen Removal[#](#fine-tuning-green-screen-removal) We adjust the color matching tolerance, edge smoothness, and spill suppression parameters. ``` // Fine-tune Green Screen removal parametersconst 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 toleranceengine.block.setFloat( greenScreenEffect2, 'effect/green_screen/colorMatch', 0.4);// Adjust edge smoothness for cleaner removalengine.block.setFloat( greenScreenEffect2, 'effect/green_screen/smoothness', 0.2);// Reduce color spill from green backgroundengine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5);engine.block.appendEffect(block4, greenScreenEffect2); ``` The Green Screen effect provides these parameters: * **colorMatch** (0-1) - Tolerance for color variations in the background * **smoothness** (0-1) - Edge feathering for natural transitions * **spill** (0-1) - Reduces color spill from the background onto foreground objects These parameters help create clean composites without harsh edges or color artifacts. ## Managing Multiple Effects[#](#managing-multiple-effects) We can apply multiple color replacement effects to the same block. CE.SDK maintains an effect stack for each block, applying effects in the order they were added. ``` // Demonstrate managing multiple effects on a blockconst block5 = await engine.block.addImage(imageUri, { size: blockSize });engine.block.appendChild(page, block5); // Add multiple effects to the same blockconst 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 blockconst effects = engine.block.getEffects(block5);console.log('Number of effects:', effects.length); // 2 // Disable the first effect without removing itengine.block.setEffectEnabled(effects[0], false); // Check if effect is enabledconst isEnabled = engine.block.isEffectEnabled(effects[0]);console.log('First effect enabled:', isEnabled); // false ``` Effect management capabilities: * **Get effects** - Retrieve all effects with `engine.block.getEffects()` * **Enable/disable** - Toggle effects with `engine.block.setEffectEnabled()` without removing them * **Check status** - Query effect state with `engine.block.isEffectEnabled()` * **Remove effects** - Delete effects by index with `engine.block.removeEffect()` Disabling effects is useful for before/after comparisons or performance optimization. ## Batch Processing Multiple Images[#](#batch-processing-multiple-images) We can loop through all image blocks in a scene and apply the same effect configuration to each. ``` // Apply consistent color replacement across multiple blocksconst block6 = await engine.block.addImage(imageUri, { size: blockSize });engine.block.appendChild(page, block6); // Find all image blocks in the sceneconst allBlocks = engine.block.findByType('//ly.img.ubq/graphic'); // Apply a consistent recolor effect to each blockallBlocks.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);}); ``` Batch processing use cases: * **Product variations** - Generate multiple color variants * **Brand consistency** - Apply consistent color corrections * **Automated workflows** - Process multiple images with the same adjustments The `engine.block.findByType()` method locates all graphic blocks in the scene. ## Troubleshooting[#](#troubleshooting) Common issues and solutions when working with color replacement effects: **Effect not visible** * Verify the effect is enabled with `engine.block.isEffectEnabled()` * Check that the effect is attached to the correct block using `engine.block.getEffects()` * Ensure the block type supports effects with `engine.block.supportsEffects()` **Wrong colors being replaced** * Decrease `colorMatch` for more precise matching * Increase `colorMatch` to capture broader color ranges * Adjust `brightnessMatch` for Recolor effects with lighting variations **Harsh edges or artifacts** * Increase `smoothness` to blend edges more gradually * For Green Screen, adjust `spill` to reduce color contamination * Use higher resolution images for smoother results **Performance issues** * Limit active effects on a single block * Use `engine.block.setEffectEnabled(false)` to disable effects during editing * Process effects sequentially rather than simultaneously ## API Reference[#](#api-reference) | Method | Category | Purpose | | --- | --- | --- | | `engine.block.createEffect()` | Block | Create a new Recolor or Green Screen effect block | | `engine.block.appendEffect()` | Block | Attach an effect to an image block | | `engine.block.insertEffect()` | Block | Insert an effect at a specific position in the effect stack | | `engine.block.removeEffect()` | Block | Remove an effect from a block by index | | `engine.block.getEffects()` | Block | Get all effects attached to a block | | `engine.block.supportsEffects()` | Block | Check if a block can render effects | | `engine.block.setColor()` | Block | Set color properties on effect blocks | | `engine.block.getColor()` | Block | Get color properties from effect blocks | | `engine.block.setFloat()` | Block | Set numeric effect properties | | `engine.block.getFloat()` | Block | Get numeric effect properties | | `engine.block.setEffectEnabled()` | Block | Enable or disable an effect | | `engine.block.isEffectEnabled()` | Block | Check if an effect is enabled | | `engine.block.findByType()` | Block | Find all blocks of a specific type | ## Next Steps[#](#next-steps) * [Apply Filters and Effects](vue/filters-and-effects/apply-2764e4/) — Explore other visual effects available in CE.SDK * [Export Designs](vue/export-save-publish/export-82f968/) — Save your color-replaced images in various formats --- [Source](https:/img.ly/docs/cesdk/vue/edit-image/remove-bg-9dfcf7) --- # Remove Background Remove image backgrounds to isolate subjects for compositing, product photography, or creating transparent overlays. ![Remove Background example showing an image with its background removed](/docs/cesdk/_astro/browser.hero.C0PR9trj_Z1d9iSk.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-remove-bg-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-remove-bg-browser) The `@imgly/plugin-background-removal-web` plugin adds AI-powered background removal directly to the CE.SDK editor. Processing runs locally in the browser using WebAssembly and WebGPU, ensuring privacy since images never leave the client. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web';import packageJson from './package.json'; /** * CE.SDK Browser Guide: Remove Background with Plugin * * Demonstrates adding and configuring the background removal plugin * for the CE.SDK editor with various UI placement options. */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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true, }); await cesdk.createDesignScene(); // Get page and set dimensions const page = engine.block.findByType('page')[0]; const pageWidth = 800; const pageHeight = 600; engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); // Add the background removal plugin with canvas menu button await cesdk.addPlugin( BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu'], }, }) ); // Create a gradient background (deep teal to soft purple) const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { stop: 0, color: { r: 0.08, g: 0.22, b: 0.35, a: 1 } }, // Deep teal { stop: 1, color: { r: 0.35, g: 0.2, b: 0.45, a: 1 } }, // Soft purple ]); 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); // Create centered title text const titleBlock = engine.block.create('text'); engine.block.setString(titleBlock, 'text/text', 'Remove Background'); engine.block.setFloat(titleBlock, 'text/fontSize', 140); engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(titleBlock, pageWidth); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); engine.block.setTextColor(titleBlock, { r: 1, g: 1, b: 1, a: 1 }); // Create image block with a portrait photo const imageBlock = engine.block.create('graphic'); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); 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.setContentFillMode(imageBlock, 'Cover'); const imageWidth = 202; const imageHeight = 230; engine.block.setWidth(imageBlock, imageWidth); engine.block.setHeight(imageBlock, imageHeight); engine.block.appendChild(page, imageBlock); // Create img.ly logo at bottom center const logoBlock = engine.block.create('graphic'); const logoShape = engine.block.createShape('rect'); engine.block.setShape(logoBlock, 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(logoBlock, logoFill); engine.block.setContentFillMode(logoBlock, 'Contain'); const logoWidth = 72; const logoHeight = 45; engine.block.setWidth(logoBlock, logoWidth); engine.block.setHeight(logoBlock, logoHeight); engine.block.setOpacity(logoBlock, 0.9); engine.block.appendChild(page, logoBlock); // Position elements const titleHeight = engine.block.getFrameHeight(titleBlock); const imageGap = 30; const padding = 20; // Calculate vertical layout - title and image centered const totalContentHeight = titleHeight + imageGap + imageHeight; const startY = (pageHeight - totalContentHeight) / 2; // Position title at top of content area engine.block.setPositionX(titleBlock, 0); engine.block.setPositionY(titleBlock, startY); // Position image centered below title engine.block.setPositionX(imageBlock, (pageWidth - imageWidth) / 2); engine.block.setPositionY(imageBlock, startY + titleHeight + imageGap); // Position logo at bottom center engine.block.setPositionX(logoBlock, (pageWidth - logoWidth) / 2); engine.block.setPositionY(logoBlock, pageHeight - logoHeight - padding); // Select the image to show the canvas menu with BG Removal button engine.block.select(imageBlock); // Zoom to fit await engine.scene.zoomToBlock(page, { padding: 40 }); engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); }} export default Example; ``` This guide covers installing the plugin, configuring UI placement, and customizing the background removal process. ## Installing the Plugin[#](#installing-the-plugin) Install the background removal plugin and its ONNX runtime peer dependency: Terminal window ``` npm install @imgly/plugin-background-removal-web onnxruntime-web@1.21.0 ``` Import the plugin in your application: ``` import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web'; ``` ## Initializing the Editor[#](#initializing-the-editor) Set up the CE.SDK editor with asset sources before adding the plugin: ``` await cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true,});await cesdk.createDesignScene(); ``` ## Adding the Plugin[#](#adding-the-plugin) Add the plugin to the editor using `cesdk.addPlugin()`. The `ui.locations` option controls where the background removal button appears: ``` // Add the background removal plugin with canvas menu buttonawait cesdk.addPlugin( BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu'], }, })); ``` When a user selects an image and clicks the button, the plugin handles the entire workflow: exporting the image, processing it through the AI model, and applying the result back to the scene. ![A BG Removal button added to the Canvas Menu](/docs/cesdk/_astro/screenshot-bg-removal-button-v1.43.0.CwNZXOB3_Zlu7Pl.webp) The plugin requires a correctly configured upload setting. ‘local’ works for testing but production requires stable storage. See the [Upload Images](vue/import-media/from-local-source/user-upload-c6c7d9/) guide for details. ## UI Placement Options[#](#ui-placement-options) The plugin supports multiple UI locations: | Location | Description | | --- | --- | | `'canvasMenu'` | Context menu when selecting an image on canvas | | `'dock'` | Panel in the left dock sidebar | | `'inspectorBar'` | Top bar of the inspector panel | | `'navigationBar'` | Main navigation bar | | `'canvasBarTop'` | Top bar above the canvas | | `'canvasBarBottom'` | Bottom bar below the canvas | You can specify a single location or an array: ``` BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu', 'dock'] }}) ``` ## Provider Configuration[#](#provider-configuration) Configure the underlying background removal library through the `provider` option: ``` BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu'] }, provider: { type: '@imgly/background-removal', configuration: { model: 'medium', // 'small' | 'medium' | 'large' output: { format: 'image/png', quality: 0.9 } } }}) ``` | Option | Type | Description | | --- | --- | --- | | `model` | `'small'` | `'medium'` | `'large'` | Model size for quality/speed trade-off | | `output.format` | string | Output format: `'image/png'`, `'image/webp'` | | `output.quality` | number | Quality for lossy formats (0-1) | The `'medium'` model provides the best balance of quality and speed. Use `'small'` for faster processing or `'large'` for maximum edge quality. ## Custom Provider[#](#custom-provider) For advanced use cases, implement a custom background removal provider: ``` BackgroundRemovalPlugin({ provider: { type: 'custom', processImageFileURI: async (imageFileURI: string) => { // Call your own background removal service const response = await fetch('/api/remove-background', { method: 'POST', body: JSON.stringify({ imageUrl: imageFileURI }) }); const { processedUrl } = await response.json(); return processedUrl; }, processSourceSet: async (sourceSet) => { // Handle multi-resolution source sets // Process the highest resolution and resize for others return sourceSet; } }}) ``` ## Creating an Image Block[#](#creating-an-image-block) Add an image to the scene for background removal: ``` // Create image block with a portrait photoconst imageBlock = engine.block.create('graphic');const rectShape = engine.block.createShape('rect');engine.block.setShape(imageBlock, rectShape); 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.setContentFillMode(imageBlock, 'Cover'); const imageWidth = 202;const imageHeight = 230;engine.block.setWidth(imageBlock, imageWidth);engine.block.setHeight(imageBlock, imageHeight);engine.block.appendChild(page, imageBlock); ``` Select the image block to display the canvas menu with the background removal button. ## Performance Considerations[#](#performance-considerations) The first background removal operation downloads AI models (~40MB) which are then cached: * **Model caching**: First run fetches models; subsequent runs use the cache * **GPU acceleration**: WebGPU provides faster processing than WebGL fallback * **CORS headers**: For optimal performance, configure these headers on your server: ``` Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp ``` ## Troubleshooting[#](#troubleshooting) | Issue | Solution | | --- | --- | | Model download slow | First run fetches models; subsequent runs use cache | | Poor edge quality | Use higher resolution input or ‘medium’/‘large’ model | | Out of memory | Reduce image size before processing | | WebGL errors | Check browser WebGL support; try different device setting | | Plugin not showing | Verify plugin added and UI location configured | | Result not transparent | Ensure export uses PNG format (JPEG doesn’t support transparency) | ## Next Steps[#](#next-steps) * [Export Overview](vue/export-save-publish/export/overview-9ed3a8/) \- Export options for images with transparency * [Vectorize Images](vue/edit-image/vectorize-2b4c7f/) \- Convert images to vector graphics * [Replace Colors](vue/edit-image/replace-colors-6ede17/) \- Replace specific colors in images --- [Source](https:/img.ly/docs/cesdk/vue/edit-image/overview-5249ea) --- # Image Editor SDK The CreativeEditor SDK (CE.SDK) offers powerful image editing capabilities designed for seamless integration into your application. You can give your users full control through an intuitive user interface or implement fully automated workflows via the SDK’s programmatic API. Image editing with CE.SDK is fully client-side, ensuring fast performance, data privacy, and offline compatibility. Whether you’re building a photo editor, design tool, or automation workflow, CE.SDK provides everything you need—plus the flexibility to integrate AI tools for tasks like adding or removing objects, swapping backgrounds, or creating variants. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## Core Capabilities[#](#core-capabilities) CE.SDK includes a wide range of image editing features accessible both through the UI and programmatically. Key capabilities include: * **Transformations**: Crop, rotate, resize, scale, and flip images. * **Adjustments and effects**: Apply filters, control brightness and contrast, add vignettes, pixelization, and more. * **Background removal**: Automatically remove backgrounds from images using plugin integrations. * **Color tools**: Replace colors, apply gradients, adjust palettes, and convert to black and white. * **Vectorization**: Convert raster images into vector format (SVG). * **Programmatic editing**: Make all edits via API—ideal for automation and bulk processing. All operations are optimized for in-app performance and align with real-time editing needs. ## AI-powered Editing[#](#ai-powered-editing) CE.SDK allows you to easily integrate AI tools directly into your editing workflow. Users can generate or edit images from simple prompts — all from within the editor’s task bar, without switching tools or uploading external assets. [ Launch AI Editor Demo ](https://img.ly/showcases/cesdk/ai-editor/web) Typical AI use cases include: * **Text-to-image**: Generate visuals from user prompts. * **Background removal**: Automatically extract subjects from photos. * **Style transfer**: Apply the look of one image to another. * **Variant generation**: Create multiple versions of a design or product image. * **Text-to-graphics**: Render typographic compositions from plain text. * **Object add/remove**: Modify compositions by adding or erasing visual elements. You can bring your own models or third-party APIs with minimal setup. AI tools can be added as standalone plugins, contextual buttons, or task bar actions. ## Supported Input Formats[#](#supported-input-formats) The SDK supports a broad range of image input types: | Category | Supported Formats | | --- | --- | | **Images** | `.png`, `.jpeg`, `.jpg`, `.gif`, `.webp`, `.svg`, `.bmp` | | **Video** | `.mp4` (H.264/AVC, H.265/HEVC), `.mov` (H.264/AVC, H.265/HEVC), `.webm` (VP8, VP9, AV1) | | **Audio** | `.mp3`, `.m4a`, `.mp4` (AAC or MP3), `.mov` (AAC or MP3) | | **Animation** | `.json` (Lottie) | Need to import a format not listed here? CE.SDK allows you to create custom importers for any file type by using our Scene and Block APIs programmatically. ## Output and export options[#](#output-and-export-options) Export edited images in the following formats: | Category | Supported Formats | | --- | --- | | **Images** | `.png` (with transparency), `.jpeg`, `.webp`, `.tga` | | **Video** | `.mp4` (H.264 or H.265 on supported platforms with limited transparency support) | | **Print** | `.pdf` (supports underlayer printing and spot colors) | | **Scene** | `.scene` (description of the scene without any assets) | | **Archive** | `.zip` (fully self-contained archive that bundles the `.scene` file with all assets) | Our custom cross-platform C++ based rendering and layout engine ensures consistent output quality across devices. You can define export resolution, compression level, and file metadata. CE.SDK also supports exporting with transparent backgrounds, underlayers, or color masks. ## UI-Based vs. Programmatic Editing[#](#ui-based-vs-programmatic-editing) CE.SDK provides two equally powerful ways to perform image edits: * **UI-based editing**: The built-in editor includes a customizable toolbar, side panels, and inspector views. End users can directly manipulate elements through the visual interface. * **Programmatic editing**: Every image transformation, effect, or layout operation can be executed via the SDK’s API. This is ideal for bulk operations, automated design workflows, or serverless rendering. You can freely combine both approaches in a single application. ## Customization[#](#customization) The CE.SDK image editor is fully customizable: * **Tool configuration**: Enable, disable, or reorder individual editing tools. * **Panel visibility**: Show or hide interface elements like inspectors, docks, and canvas menus. * **Themes and styling**: Customize the UI appearance with brand colors, fonts, and icons. * **Localization**: Translate all interface text via the internationalization API. You can also add custom buttons, inject quick actions, or build your own interface on top of the engine using the headless mode. --- [Source](https:/img.ly/docs/cesdk/vue/edit-image/add-watermark-679de0) --- # Add Watermark Add text and image watermarks to designs programmatically using CE.SDK’s block API. ![Add Watermark example showing a design with text and logo watermarks](/docs/cesdk/_astro/browser.hero.C0vdLnln_12EYHw.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-add-watermark-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-add-watermark-browser) Watermarks protect intellectual property, indicate ownership, add branding, or mark content as drafts. CE.SDK supports two types of watermarks: **text watermarks** created from text blocks for copyright notices and brand names, and **image watermarks** created from graphic blocks with image fills for logos and symbols. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Add Watermark Guide * * Demonstrates adding text and image watermarks to designs: * - Creating text watermarks with custom styling * - Creating logo watermarks using graphic blocks * - Positioning watermarks side by side at the bottom * - Applying drop shadows for visibility * - Exporting watermarked 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'); } // Load asset sources for the editor await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); const engine = cesdk.engine; // Create a scene with custom page dimensions 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 pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create a gradient background for the page const gradientFill = engine.block.createFill('gradient/linear'); // Set a modern purple-to-cyan gradient engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.39, g: 0.4, b: 0.95, a: 1 }, stop: 0 }, // Indigo { color: { r: 0.02, g: 0.71, b: 0.83, a: 1 }, stop: 1 } // Cyan ]); // Set diagonal gradient direction (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); // Apply gradient to page engine.block.setFill(page, gradientFill); // Create a centered title text const titleText = engine.block.create('text'); engine.block.setString(titleText, 'text/text', 'Add Watermark'); engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, titleText); // Style the title engine.block.setTextFontSize(titleText, 14); engine.block.setTextColor(titleText, { r: 1, g: 1, b: 1, a: 1 }); engine.block.setWidthMode(titleText, 'Auto'); engine.block.setHeightMode(titleText, 'Auto'); // Center the title on the page const titleWidth = engine.block.getFrameWidth(titleText); const titleHeight = engine.block.getFrameHeight(titleText); engine.block.setPositionX(titleText, (pageWidth - titleWidth) / 2); engine.block.setPositionY(titleText, (pageHeight - titleHeight) / 2); // Create a text block for the watermark const textWatermark = engine.block.create('text'); // Set the watermark text content engine.block.setString(textWatermark, 'text/text', '© 2024 img.ly'); // Left-align the text for the watermark engine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left'); // Add the text block to the page engine.block.appendChild(page, textWatermark); // Set font size for the watermark engine.block.setTextFontSize(textWatermark, 4); // Set text color to white for contrast engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // Set opacity to make it semi-transparent engine.block.setOpacity(textWatermark, 0.8); // Set width mode to auto so text fits its content engine.block.setWidthMode(textWatermark, 'Auto'); engine.block.setHeightMode(textWatermark, 'Auto'); // Create a graphic block for the logo watermark const logoWatermark = engine.block.create('graphic'); // Create a rect shape for the logo const rectShape = engine.block.createShape('rect'); engine.block.setShape(logoWatermark, rectShape); // Create an image fill with a logo const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); // Apply the fill to the graphic block engine.block.setFill(logoWatermark, imageFill); // Set content fill mode to contain the image within bounds engine.block.setContentFillMode(logoWatermark, 'Contain'); // Add to page engine.block.appendChild(page, logoWatermark); // Size the logo watermark const logoWidth = 80; const logoHeight = 50; engine.block.setWidth(logoWatermark, logoWidth); engine.block.setHeight(logoWatermark, logoHeight); // Set opacity for the logo watermark engine.block.setOpacity(logoWatermark, 0.8); // Position padding from edges const padding = 15; // Position text watermark at bottom-left engine.block.setPositionX(textWatermark, padding); engine.block.setPositionY(textWatermark, pageHeight - padding - 20); // Position logo watermark at top-right engine.block.setPositionX(logoWatermark, pageWidth - padding - logoWidth); engine.block.setPositionY(logoWatermark, padding); // Add drop shadow to text watermark for better visibility engine.block.setDropShadowEnabled(textWatermark, true); engine.block.setDropShadowOffsetX(textWatermark, 1); engine.block.setDropShadowOffsetY(textWatermark, 1); engine.block.setDropShadowBlurRadiusX(textWatermark, 2); engine.block.setDropShadowBlurRadiusY(textWatermark, 2); engine.block.setDropShadowColor(textWatermark, { r: 0, g: 0, b: 0, a: 0.5 }); // Add drop shadow to logo watermark engine.block.setDropShadowEnabled(logoWatermark, true); engine.block.setDropShadowOffsetX(logoWatermark, 1); engine.block.setDropShadowOffsetY(logoWatermark, 1); engine.block.setDropShadowBlurRadiusX(logoWatermark, 2); engine.block.setDropShadowBlurRadiusY(logoWatermark, 2); engine.block.setDropShadowColor(logoWatermark, { r: 0, g: 0, b: 0, a: 0.5 }); // Add export button to the navigation bar cesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-watermarked', label: 'Export', icon: '@imgly/Download', onClick: async () => { // Export the watermarked design const blob = await engine.block.export(page, { mimeType: 'image/png' }); // Download the watermarked image await cesdk.utils.downloadFile(blob, 'image/png'); } } ] }); // Zoom to fit the page in view with padding and enable auto-fit await engine.scene.zoomToBlock(page, { padding: 40 }); engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); }} export default Example; ``` This guide covers how to create text and logo watermarks, position them on a design, style them for visibility, and export the watermarked result. ## Setup and Prerequisites[#](#setup-and-prerequisites) We start by initializing CE.SDK, loading asset sources, and creating a scene with a custom page size. The page provides the canvas where we’ll add our watermarks. ``` // Load asset sources for the editorawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true}); const engine = cesdk.engine; // Create a scene with custom page dimensionsconst 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 pageWidth = engine.block.getWidth(page);const pageHeight = engine.block.getHeight(page); ``` We use `engine.scene.create('VerticalStack', {...})` to create a scene with custom page dimensions. The page dimensions are retrieved for positioning calculations later. ## Creating Text Watermarks[#](#creating-text-watermarks) Text watermarks display copyright notices, URLs, or brand names. We create a text block, set its content, and add it to the page. ``` // Create a text block for the watermarkconst textWatermark = engine.block.create('text'); // Set the watermark text contentengine.block.setString(textWatermark, 'text/text', '© 2024 img.ly'); // Left-align the text for the watermarkengine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left'); // Add the text block to the pageengine.block.appendChild(page, textWatermark); ``` The `engine.block.create('text')` method creates a new text block. We set the text content using `engine.block.setString()` with the `'text/text'` property. ### Styling the Text[#](#styling-the-text) We configure the font size, color, and opacity to make the watermark visible but unobtrusive. ``` // Set font size for the watermarkengine.block.setTextFontSize(textWatermark, 4); // Set text color to white for contrastengine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // Set opacity to make it semi-transparentengine.block.setOpacity(textWatermark, 0.8); // Set width mode to auto so text fits its contentengine.block.setWidthMode(textWatermark, 'Auto');engine.block.setHeightMode(textWatermark, 'Auto'); ``` Key styling options: * **Font size** - Use sizes between 14-18px for subtle watermarks * **Text color** - White or black depending on the image background * **Opacity** - Values between 0.5-0.7 provide a balanced, semi-transparent appearance * **Size mode** - Set to `'Auto'` so the block automatically fits its text content ### Positioning the Watermarks[#](#positioning-the-watermarks) We calculate the watermark positions based on the page dimensions and place the logo and text side-by-side at the bottom center of the page. ``` // Position padding from edgesconst padding = 15; // Position text watermark at bottom-leftengine.block.setPositionX(textWatermark, padding);engine.block.setPositionY(textWatermark, pageHeight - padding - 20); // Position logo watermark at top-rightengine.block.setPositionX(logoWatermark, pageWidth - padding - logoWidth);engine.block.setPositionY(logoWatermark, padding); ``` We retrieve the rendered frame dimensions using `engine.block.getFrameWidth()` and `engine.block.getFrameHeight()`, calculate the total width of both watermarks with spacing, then center them horizontally. The vertical position places them near the bottom with padding from the edge. ## Creating Logo Watermarks[#](#creating-logo-watermarks) Logo watermarks use graphic blocks with image fills to display brand symbols or company logos. ``` // Create a graphic block for the logo watermarkconst logoWatermark = engine.block.create('graphic'); // Create a rect shape for the logoconst rectShape = engine.block.createShape('rect');engine.block.setShape(logoWatermark, rectShape); // Create an image fill with a logoconst imageFill = engine.block.createFill('image');engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg'); // Apply the fill to the graphic blockengine.block.setFill(logoWatermark, imageFill); // Set content fill mode to contain the image within boundsengine.block.setContentFillMode(logoWatermark, 'Contain'); // Add to pageengine.block.appendChild(page, logoWatermark); ``` We create a graphic block, assign a rect shape, then create an image fill with the logo URI. The fill is applied to the graphic block before adding it to the page. ### Sizing the Logo[#](#sizing-the-logo) We set fixed dimensions for the logo and apply opacity to match the text watermark. ``` // Size the logo watermarkconst logoWidth = 80;const logoHeight = 50;engine.block.setWidth(logoWatermark, logoWidth);engine.block.setHeight(logoWatermark, logoHeight); // Set opacity for the logo watermarkengine.block.setOpacity(logoWatermark, 0.8); ``` A good rule is to size logos to 10-20% of the page width. This keeps them visible without dominating the design. ## Enhancing Visibility with Drop Shadows[#](#enhancing-visibility-with-drop-shadows) Drop shadows improve watermark readability against varied backgrounds by adding contrast. ``` // Add drop shadow to text watermark for better visibilityengine.block.setDropShadowEnabled(textWatermark, true);engine.block.setDropShadowOffsetX(textWatermark, 1);engine.block.setDropShadowOffsetY(textWatermark, 1);engine.block.setDropShadowBlurRadiusX(textWatermark, 2);engine.block.setDropShadowBlurRadiusY(textWatermark, 2);engine.block.setDropShadowColor(textWatermark, { r: 0, g: 0, b: 0, a: 0.5}); // Add drop shadow to logo watermarkengine.block.setDropShadowEnabled(logoWatermark, true);engine.block.setDropShadowOffsetX(logoWatermark, 1);engine.block.setDropShadowOffsetY(logoWatermark, 1);engine.block.setDropShadowBlurRadiusX(logoWatermark, 2);engine.block.setDropShadowBlurRadiusY(logoWatermark, 2);engine.block.setDropShadowColor(logoWatermark, { r: 0, g: 0, b: 0, a: 0.5}); ``` Drop shadow parameters: * **Offset X/Y** - Distance from the block (2-4 pixels works well) * **Blur Radius X/Y** - Softness of the shadow (4-8 pixels for subtle effect) * **Color** - Black with 0.5 alpha provides soft contrast without being harsh ## Exporting Watermarked Images[#](#exporting-watermarked-images) After adding watermarks, we add an export button to the navigation bar that downloads the watermarked image when clicked. ``` // Add export button to the navigation barcesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-watermarked', label: 'Export', icon: '@imgly/Download', onClick: async () => { // Export the watermarked design const blob = await engine.block.export(page, { mimeType: 'image/png' }); // Download the watermarked image await cesdk.utils.downloadFile(blob, 'image/png'); } } ]}); ``` We use `cesdk.ui.insertNavigationBarOrderComponent()` to add a custom button to the editor’s navigation bar. When clicked, `engine.block.export()` renders the page with all watermarks and returns a blob that `cesdk.utils.downloadFile()` downloads to the user’s device. Supported formats include PNG, JPEG, and WebP. ## Troubleshooting[#](#troubleshooting) **Watermark not visible** * Verify the block is within page bounds using position values * Check opacity is between 0.3-1.0 * Ensure `engine.block.appendChild()` was called to add the block to the page **Position appears incorrect** * Recalculate positions using current `pageWidth` and `pageHeight` * Account for watermark dimensions when calculating corner positions * Remember that coordinates start from the top-left corner **Text not legible** * Increase font size to at least 36px * Add a drop shadow for contrast against complex backgrounds * Increase opacity if the watermark is too faint **Logo quality issues** * Use a higher resolution source image for the logo * Avoid scaling the logo beyond its original dimensions ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `engine.block.create(type)` | Create text or graphic blocks | | `engine.block.createShape(type)` | Create shapes for graphic blocks | | `engine.block.createFill(type)` | Create image fills for logos | | `engine.block.setString(id, property, value)` | Set text content or image URI | | `engine.block.setTextFontSize(id, size)` | Set text font size | | `engine.block.setTextColor(id, color)` | Set text color | | `engine.block.setOpacity(id, opacity)` | Set block transparency | | `engine.block.setPositionX(id, value)` | Set horizontal position | | `engine.block.setPositionY(id, value)` | Set vertical position | | `engine.block.setWidth(id, value)` | Set block width | | `engine.block.setHeight(id, value)` | Set block height | | `engine.block.getFrameWidth(id)` | Get rendered frame width | | `engine.block.getFrameHeight(id)` | Get rendered frame height | | `engine.block.setDropShadowEnabled(id, enabled)` | Enable drop shadow | | `engine.block.setDropShadowOffsetX(id, offset)` | Set shadow X offset | | `engine.block.setDropShadowOffsetY(id, offset)` | Set shadow Y offset | | `engine.block.setDropShadowBlurRadiusX(id, radius)` | Set shadow blur | | `engine.block.setDropShadowColor(id, color)` | Set shadow color | | `engine.block.export(id, options)` | Export block to blob | | `cesdk.ui.insertNavigationBarOrderComponent(position, config)` | Add custom button to navigation bar | | `cesdk.utils.downloadFile(blob, mimeType)` | Download blob as file | ## Next Steps[#](#next-steps) * [Text Styling](vue/text/styling-269c48/) — Style text blocks with fonts, colors, and effects * [Export Overview](vue/export-save-publish/export/overview-9ed3a8/) — Export options and formats for watermarked images * [Crop Images](vue/edit-image/transform/crop-f67a47/) — Transform images before watermarking --- [Source](https:/img.ly/docs/cesdk/vue/create-video/update-caption-presets-e9c385) --- # Update Caption Presets Extend CE.SDK’s video caption feature with custom caption presets by updating the content.json file. Caption presets let your users apply predefined styles to video captions with a single click. ![Update Caption Presets example showing a styled neon glow caption preset](/docs/cesdk/_astro/browser.hero.D2NxRBEo_Z1SeR6D.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-update-caption-presets-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-update-caption-presets-browser) Video captions have become an essential part of digital content, improving accessibility and engagement. With CE.SDK’s caption presets feature, you can offer your users a selection of predefined caption styles that they can apply with a single click. This guide shows you how to create styled text blocks, serialize them as preset files, and structure the content.json to make them available in the caption presets panel. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Update Caption Presets Guide * * Demonstrates creating custom caption presets in CE.SDK: * - Creating a styled text block as a preset base * - Applying neon glow styling with colors and drop shadow * - Serializing the block for use as a preset file * - Understanding the content.json structure for caption presets */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 for caption presets cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine; // Create a text block to use as the preset base // Text blocks support all the styling properties needed for captions const textBlock = engine.block.create('text'); // Set sample caption text engine.block.setString(textBlock, 'text/text', 'NEON GLOW'); // Position and size the text block engine.block.setPositionX(textBlock, 50); engine.block.setPositionY(textBlock, 200); engine.block.setWidth(textBlock, 600); engine.block.setHeightMode(textBlock, 'Auto'); // Style the text with a bright neon cyan color // This will be the fill/solid/color property in the preset engine.block.setColor(textBlock, 'fill/solid/color', { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }); // Set font properties for the caption style engine.block.setFloat(textBlock, 'text/fontSize', 48); // Use a bold font for better visibility // Load and set a typeface const typefaceResult = await engine.asset.findAssets('ly.img.typeface', { query: 'Roboto', page: 0, perPage: 10 }); if (typefaceResult.assets.length > 0) { const typefaceAsset = typefaceResult.assets[0]; const typeface = typefaceAsset.payload?.typeface; if (typeface && typeface.fonts?.[0]?.uri) { engine.block.setFont(textBlock, typeface.fonts[0].uri, typeface); } } // Add a glowing drop shadow effect for the neon look // This creates the characteristic neon glow effect engine.block.setDropShadowEnabled(textBlock, true); // Set glow color (bright cyan to match text) engine.block.setColor(textBlock, 'dropShadow/color', { r: 0.0, g: 1.0, b: 1.0, a: 0.8 }); // Configure shadow properties for a soft glow engine.block.setFloat(textBlock, 'dropShadow/blurRadius/x', 20); engine.block.setFloat(textBlock, 'dropShadow/blurRadius/y', 20); engine.block.setFloat(textBlock, 'dropShadow/offset/x', 0); engine.block.setFloat(textBlock, 'dropShadow/offset/y', 0); // Optionally add a semi-transparent dark background // This helps the caption stand out against video content engine.block.setBackgroundColorEnabled(textBlock, true); engine.block.setColor(textBlock, 'backgroundColor/color', { r: 0.0, g: 0.0, b: 0.1, a: 0.7 }); // Add the styled text block to the page const pages = engine.block.findByType('page'); if (pages.length > 0) { engine.block.appendChild(pages[0], textBlock); } // Select the block and zoom to it so it's visible in the editor engine.block.select(textBlock); await engine.scene.zoomToBlock(textBlock, { padding: 40 }); // Serialize the styled text block to create a preset file // This serialized string can be saved as a .blocks or .preset file // Include 'bundle' scheme to allow serialization of blocks with bundled fonts const serializedPreset = await engine.block.saveToString( [textBlock], ['buffer', 'http', 'https', 'bundle'] ); console.log('=== Serialized Preset ==='); console.log('Save this as a .preset file (e.g., neon-glow.preset):'); console.log(serializedPreset); // Example content.json entry for the custom preset // This shows the structure needed to add the preset to content.json const contentJsonEntry = { id: '//ly.img.captionPresets/neon-glow', label: { en: 'Neon Glow' }, meta: { uri: '{{base_url}}/ly.img.captionPresets/presets/neon-glow.preset', thumbUri: '{{base_url}}/ly.img.captionPresets/thumbnails/neon-glow.png', mimeType: 'application/ubq-blocks-string' }, payload: { properties: [ { type: 'Color', property: 'fill/solid/color', value: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }, defaultValue: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 } }, { type: 'Color', property: 'dropShadow/color', value: { r: 0.0, g: 1.0, b: 1.0, a: 0.8 }, defaultValue: { r: 0.0, g: 1.0, b: 1.0, a: 0.8 } }, { type: 'Color', property: 'backgroundColor/color', value: { r: 0.0, g: 0.0, b: 0.1, a: 0.7 }, defaultValue: { r: 0.0, g: 0.0, b: 0.1, a: 0.7 } } ] } }; console.log('\n=== content.json Entry ==='); console.log('Add this entry to your content.json assets array:'); console.log(JSON.stringify(contentJsonEntry, null, 2)); // Example of a complete content.json file structure const completeContentJson = { version: '3.0.0', id: 'ly.img.captionPresets', assets: [contentJsonEntry] }; console.log('\n=== Complete content.json Example ==='); console.log(JSON.stringify(completeContentJson, null, 2)); console.log('\n=== Caption Preset Guide ==='); console.log( 'The styled text block above demonstrates a "Neon Glow" caption preset.' ); console.log('To use this preset:'); console.log('1. Save the serialized preset string as a .preset file'); console.log('2. Create a thumbnail image showing the preset appearance'); console.log('3. Add the content.json entry to your assets folder'); console.log('4. Configure CE.SDK baseURL to point to your assets location'); }} export default Example; ``` This guide covers how to understand the caption presets folder structure, create custom caption styles from text blocks, serialize presets for hosting, define customizable properties, and configure CE.SDK to load your custom presets. ## Understanding the Caption Presets Structure[#](#understanding-the-caption-presets-structure) ### Folder Organization[#](#folder-organization) CE.SDK’s caption presets use a specific directory structure that the engine expects when loading presets. The base path is `assets/v4/ly.img.captionPresets/` and contains: ``` assets/v4/ly.img.captionPresets/├── content.json # Master index of all presets├── presets/ # Folder containing preset files│ ├── my-custom-preset.preset # Serialized caption block with styling│ └── ...└── thumbnails/ # Folder containing preview images ├── my-custom-preset.png # Preview image for preset └── ... ``` The main `content.json` file acts as an index that lists all available presets with their metadata. When CE.SDK loads caption presets, it reads this file to discover available presets and their locations. ### content.json Format[#](#contentjson-format) The content.json file follows a specific format with version, asset source ID, and an assets array: ``` { "version": "3.0.0", "id": "ly.img.captionPresets", "assets": [ { "id": "//ly.img.captionPresets/my-preset", "label": { "en": "My Preset" }, "meta": { "uri": "{{base_url}}/ly.img.captionPresets/presets/my-preset.preset", "thumbUri": "{{base_url}}/ly.img.captionPresets/thumbnails/my-preset.png", "mimeType": "application/ubq-blocks-string" }, "payload": { "properties": [] } } ]} ``` Each asset entry requires a unique ID with namespace, localized label, meta with URIs and mime type, and optional payload properties for customization. ## Creating Custom Caption Presets[#](#creating-custom-caption-presets) ### Designing a Caption Style[#](#designing-a-caption-style) We create a styled text block as the basis for our preset. Text blocks support all the styling properties needed for captions including colors, fonts, backgrounds, shadows, and effects. ``` // Create a text block to use as the preset base// Text blocks support all the styling properties needed for captionsconst textBlock = engine.block.create('text'); // Set sample caption textengine.block.setString(textBlock, 'text/text', 'NEON GLOW'); // Position and size the text blockengine.block.setPositionX(textBlock, 50);engine.block.setPositionY(textBlock, 200);engine.block.setWidth(textBlock, 600);engine.block.setHeightMode(textBlock, 'Auto'); ``` We position and size the text block, then set sample caption text. The text block serves as our canvas for applying the styling that will define the preset’s appearance. ### Styling with Colors and Fonts[#](#styling-with-colors-and-fonts) We style the text with colors and configure font properties. The fill color becomes the `fill/solid/color` property in the preset: ``` // Style the text with a bright neon cyan color// This will be the fill/solid/color property in the presetengine.block.setColor(textBlock, 'fill/solid/color', { r: 0.0, g: 1.0, b: 1.0, a: 1.0}); ``` We also configure font size and load a typeface. When users apply this preset, their captions will inherit these font settings: ``` // Set font properties for the caption styleengine.block.setFloat(textBlock, 'text/fontSize', 48); // Use a bold font for better visibility// Load and set a typefaceconst typefaceResult = await engine.asset.findAssets('ly.img.typeface', { query: 'Roboto', page: 0, perPage: 10}); if (typefaceResult.assets.length > 0) { const typefaceAsset = typefaceResult.assets[0]; const typeface = typefaceAsset.payload?.typeface; if (typeface && typeface.fonts?.[0]?.uri) { engine.block.setFont(textBlock, typeface.fonts[0].uri, typeface); }} ``` ### Adding Visual Effects[#](#adding-visual-effects) We add a glowing drop shadow effect for the neon look. Drop shadow creates the characteristic glow effect that makes caption presets visually distinctive: ``` // Add a glowing drop shadow effect for the neon look// This creates the characteristic neon glow effectengine.block.setDropShadowEnabled(textBlock, true); // Set glow color (bright cyan to match text)engine.block.setColor(textBlock, 'dropShadow/color', { r: 0.0, g: 1.0, b: 1.0, a: 0.8}); // Configure shadow properties for a soft glowengine.block.setFloat(textBlock, 'dropShadow/blurRadius/x', 20);engine.block.setFloat(textBlock, 'dropShadow/blurRadius/y', 20);engine.block.setFloat(textBlock, 'dropShadow/offset/x', 0);engine.block.setFloat(textBlock, 'dropShadow/offset/y', 0); ``` Optionally, we add a semi-transparent background to help the caption stand out against video content: ``` // Optionally add a semi-transparent dark background// This helps the caption stand out against video contentengine.block.setBackgroundColorEnabled(textBlock, true);engine.block.setColor(textBlock, 'backgroundColor/color', { r: 0.0, g: 0.0, b: 0.1, a: 0.7}); ``` ### Serializing the Preset[#](#serializing-the-preset) We serialize the styled text block using `block.saveToString()`. This creates a serialized string that can be saved as a `.preset` or `.blocks` file: ``` // Serialize the styled text block to create a preset file// This serialized string can be saved as a .blocks or .preset file// Include 'bundle' scheme to allow serialization of blocks with bundled fontsconst serializedPreset = await engine.block.saveToString( [textBlock], ['buffer', 'http', 'https', 'bundle']); console.log('=== Serialized Preset ===');console.log('Save this as a .preset file (e.g., neon-glow.preset):');console.log(serializedPreset); ``` The serialized string contains all block properties and styling. Save this output as a file (e.g., `neon-glow.preset`) and create a thumbnail image showing the preset appearance. ## Defining Customizable Properties[#](#defining-customizable-properties) ### Color Properties[#](#color-properties) We define which properties users can customize without changing the entire preset. Color properties allow users to modify specific color aspects of a preset: ``` // Example content.json entry for the custom preset// This shows the structure needed to add the preset to content.jsonconst contentJsonEntry = { id: '//ly.img.captionPresets/neon-glow', label: { en: 'Neon Glow' }, meta: { uri: '{{base_url}}/ly.img.captionPresets/presets/neon-glow.preset', thumbUri: '{{base_url}}/ly.img.captionPresets/thumbnails/neon-glow.png', mimeType: 'application/ubq-blocks-string' }, payload: { properties: [ { type: 'Color', property: 'fill/solid/color', value: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }, defaultValue: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 } }, { type: 'Color', property: 'dropShadow/color', value: { r: 0.0, g: 1.0, b: 1.0, a: 0.8 }, defaultValue: { r: 0.0, g: 1.0, b: 1.0, a: 0.8 } }, { type: 'Color', property: 'backgroundColor/color', value: { r: 0.0, g: 0.0, b: 0.1, a: 0.7 }, defaultValue: { r: 0.0, g: 0.0, b: 0.1, a: 0.7 } } ] }}; console.log('\n=== content.json Entry ===');console.log('Add this entry to your content.json assets array:');console.log(JSON.stringify(contentJsonEntry, null, 2)); ``` Each property in the `payload.properties` array needs: * `type`: Must be `"Color"` for color properties * `property`: Property path (e.g., `"fill/solid/color"`, `"backgroundColor/color"`, `"dropShadow/color"`) * `value`: Current RGBA color object with `r`, `g`, `b`, `a` values (0-1 range) * `defaultValue`: Initial RGBA color object ### Supported Property Paths[#](#supported-property-paths) Available property paths for caption customization: * `fill/solid/color`: Text fill color * `backgroundColor/color`: Background color behind text * `dropShadow/color`: Drop shadow color * `stroke/color`: Stroke/outline color ## Updating the content.json File[#](#updating-the-contentjson-file) ### Adding a New Preset Entry[#](#adding-a-new-preset-entry) Add a new object to the `assets` array with all required fields. The complete structure for a preset entry: ``` { "id": "//ly.img.captionPresets/neon-glow", "label": { "en": "Neon Glow" }, "meta": { "uri": "{{base_url}}/ly.img.captionPresets/presets/neon-glow.preset", "thumbUri": "{{base_url}}/ly.img.captionPresets/thumbnails/neon-glow.png", "mimeType": "application/ubq-blocks-string" }, "payload": { "properties": [ { "type": "Color", "property": "fill/solid/color", "value": { "r": 0.0, "g": 1.0, "b": 1.0, "a": 1.0 }, "defaultValue": { "r": 0.0, "g": 1.0, "b": 1.0, "a": 1.0 } }, { "type": "Color", "property": "dropShadow/color", "value": { "r": 0.0, "g": 1.0, "b": 1.0, "a": 0.8 }, "defaultValue": { "r": 0.0, "g": 1.0, "b": 1.0, "a": 0.8 } } ] }} ``` Ensure the `mimeType` is set to `"application/ubq-blocks-string"` and use the `{{base_url}}` placeholder for dynamic path resolution. ### Complete content.json Example[#](#complete-contentjson-example) The complete content.json file structure wraps preset entries in the assets array: ``` // Example of a complete content.json file structureconst completeContentJson = { version: '3.0.0', id: 'ly.img.captionPresets', assets: [contentJsonEntry]}; console.log('\n=== Complete content.json Example ===');console.log(JSON.stringify(completeContentJson, null, 2)); ``` ## Hosting and Serving Custom Presets[#](#hosting-and-serving-custom-presets) ### Server Setup[#](#server-setup) Prepare the folder structure and upload files to your server: 1. Create folder structure matching `assets/v4/ly.img.captionPresets/` 2. Upload `content.json` to the root folder 3. Upload `.preset` files to `presets/` subfolder 4. Upload thumbnail images to `thumbnails/` subfolder 5. Ensure files are accessible via HTTP/HTTPS 6. Configure CORS headers if serving cross-origin ### Verifying File Access[#](#verifying-file-access) Test that all files are accessible before configuring CE.SDK: * Access `content.json` directly in browser * Access preset files and thumbnails via their URLs * Check browser console for CORS errors ## Loading Custom Presets into CE.SDK[#](#loading-custom-presets-into-cesdk) ### Base URL Configuration[#](#base-url-configuration) Set the base URL to point to your asset hosting location. CE.SDK automatically looks for `ly.img.captionPresets/content.json` relative to the base URL: ``` const config = { baseURL: 'https://your-server.com/assets/'}; CreativeEditorSDK.create('#cesdk_container', config).then(async (cesdk) => { // Caption presets load automatically from baseURL + 'ly.img.captionPresets/content.json'}); ``` Your custom presets will seamlessly integrate with any built-in presets and automatically appear in the caption presets panel in the UI. No additional source registration is needed when replacing the default presets. ## Troubleshooting[#](#troubleshooting) ### Preset Not Loading[#](#preset-not-loading) * Verify `content.json` is accessible at expected URL * Check browser console for 404 errors on preset files * Ensure `mimeType` is set to `"application/ubq-blocks-string"` * Verify `{{base_url}}` placeholder is used correctly ### Preset Styles Not Applying[#](#preset-styles-not-applying) * Ensure preset was serialized from a text block (not other block types) * Verify the serialized block contains styling properties * Check that property paths in `payload.properties` are correct ### Thumbnail Not Displaying[#](#thumbnail-not-displaying) * Verify thumbnail file exists at the `thumbUri` path * Check image format is PNG * Ensure CORS headers allow image loading ### Custom Colors Not Working[#](#custom-colors-not-working) * Verify `properties` array structure in content.json * Check property `type` is `"Color"` * Ensure `value` and `defaultValue` have correct RGBA format (0-1 range) ## API Reference[#](#api-reference) | Method | Category | Purpose | | --- | --- | --- | | `engine.block.create('text')` | Block | Create text block for preset styling | | `engine.block.saveToString(blocks)` | Block | Serialize styled block to preset format | | `engine.block.setColor(id, property, color)` | Block | Set color property (fill, background, etc.) | | `engine.block.setBackgroundColorEnabled()` | Block | Enable background color | | `engine.block.setDropShadowEnabled()` | Block | Enable drop shadow | | `engine.block.setFloat(id, property, value)` | Block | Set numeric properties (font size, etc.) | | `engine.block.setString(id, property, val)` | Block | Set string properties (text, font URI) | | `engine.asset.findAssets(sourceId, query)` | Asset | Find assets like typefaces | | `CreativeEngine.init(config)` | Engine | Initialize engine with base URL config | --- [Source](https:/img.ly/docs/cesdk/vue/create-video/timeline-editor-912252) --- # Video and Audio Timeline Web Editor The CreativeEditor SDK (CE.SDK) offers features for editing the video timeline, the horizontal layout where you arrange video, audio, and effects in the sequence they play. This tutorial will help you create advanced video editing tools for high-quality video exports. This tutorial focuses on the user interface components. For programmatic timeline manipulation, refer to the [Video Overview](vue/create-video/overview-b06512/) guide. ## When to Use[#](#when-to-use) Use the CE.SDK timeline features in web applications that incorporate the following tools: * Video montages * Marketing video editing * Social media content creation ## What You’ll Learn[#](#what-youll-learn) This tutorial will show you how to: * The video timeline works. * To create video scenes. * To manage video layers (tracks). * To edit a clip’s duration and offset. * To manage video playback. * To generate and display video thumbnails. ## How the Timeline Works[#](#how-the-timeline-works) This tutorial refers to the timeline, which is the horizontal area below the video editing canvas. The video timeline displays: * All clips on parallel tracks * A playhead for navigation * Controls for playback and editing ![Default CE.SDK Editor on Video Mode](/docs/cesdk/_astro/browser-cesdk-video-mode.CH_BlHSH_Z1Stxl4.png) Use the visual timeline for editing actions like: * Arranging clips along a time axis to control when each element **appears**. * Trimming clips to change their duration. * Layer content visually. ### Activate CE.SDK Video Mode[#](#activate-cesdk-video-mode) To work with the CE.SDK Editor in video mode, specify the scene’s design as follows: ``` cesdk.addDefaultAssetSources(),cesdk.addDemoAssetSources({ sceneMode: 'Video' }),await cesdk.createVideoScene(); ``` This tells the CreativeEditor: * To use the Video editing mode (rather than the Design one). * To load demo video assets to test the editor. * To create a video scene, ready for editing. ### Open/Close the Timeline Editing area[#](#openclose-the-timeline-editing-area) The default CreativeEditor settings display the timeline when launching the UI. Close it to increase canvas space when the scene doesn’t need timeline editing. To close it, click the **Timeline** toggle: ![CE.SDK timeline UI with collapse toggle highlighted](/docs/cesdk/_astro/browser-collapse-timeline.DEja8UsZ_Z1SUePY.png) To open it and access the visual timeline editing tools, click the same toggle: ![CE.SDK timeline UI with expanded toggle highlighted](/docs/cesdk/_astro/browser-expand-timeline.C8cUt1iq_Z10IQko.png) ### Timeline Structure[#](#timeline-structure) The timeline’s structure in the CE.SDK is the following: 1. Scene (root block) 2. Pages (as video segments) 3. Tracks (as parallel layers) 4. Clips (as graphic blocks) ### Track Structure[#](#track-structure) The track is a container that handles the content layer. The timeline organizes content into three track types: * Clip tracks (for video content) * Overlay track (for text and graphics over the video) * Audio tracks ![Two clips layered over audio and text](/docs/cesdk/_astro/browser-tracks.DX8QdV-Q_pOhht.png) The order in which track appears determines its position in the scene: * Moving a track to the top brings it to the front of the scene. * Moving a track to the bottom of the timeline sends the content the back of the scene. ![Clip 3 on top of clip 1 and clip 2 and behind the title](/docs/cesdk/_astro/browser-layering.DF1AxLA8_1OVs34.png) ### Control the Content Position[#](#control-the-content-position) You can adjust the position of the content using the vertical playhead line, which indicates the current playhead time. The playhead moves along the timeline, displaying the time code. Change the playhead position (when the clip starts playing) by either: * Clicking on any area in the time ruler. * Dragging the playhead with your cursor. ![Current play time, dragged playhead and dragging zone](/docs/cesdk/_astro/browser-timeline-drag.rjT8vWhl_Z1SQsMz.png) ### Scroll and Zoom on the Timeline[#](#scroll-and-zoom-on-the-timeline) Adjust the time scale to increase the level of details per frame: 1. Click anywhere in the timeline area. 2. Zoom into the timeline using: * Keyboard shortcuts * A mouse * A trackpad When zoomed in, the clip stretches visually—each pixel now represents fewer milliseconds—so you can fine-tune edits frame by frame. ![Unzoomed timeline VS zoomed timeline](/docs/cesdk/_astro/browser-zoom.DH0XgN7J_16HwOu.png) ## Control How the Clips Play[#](#control-how-the-clips-play) ### Play/Pause the Scene[#](#playpause-the-scene) The playback bar contains: * The **play/pause button** that plays the clip from the current playhead position. * A **loop toggle** that repeats the video when activated. * The **current timestamp** ![CE.SDK playback bar](/docs/cesdk/_astro/browser-play-bar.C73i1VUz_ZQmi6v.png) The scene plays while synchronizing: * All videos * Overlays * Audio ### Preview Frames[#](#preview-frames) Drag and drop (scrub) the playhead back and forth to preview frames without playing the clip in real time. This allows you to quickly find the exact moment you want to edit. ![Frame preview at 0:4:8](/docs/cesdk/_astro/browser-timeline-scrub.66cQ7f97_2qgKIJ.png) The timestamp is the current playhead time in HH:MM:SS:FF format. ![Frame preview at 0:4:8](/docs/cesdk/_astro/browser-timeline-scrub-timestamp.CP8roXss_ZEmUyb.png) ## Edit Video Clips[#](#edit-video-clips) The CreativeEditor’s timeline allows users to visually edit clips and scenes in the browser. This section lists all timeline editing features. ### Select Clips[#](#select-clips) Before editing any clip, you need to select it to apply modifications to it. To **select a clip**, click it either: * From the page * From the timeline ![Clip selected in CE.SDK UI](/docs/cesdk/_astro/browser-clip-select.kNn7LUDr_2c5A5x.png) Selecting multiple clips at once is available either: * With cursor clicks on each clip while holding modifier keys. * By drawing a frame in the scene with the cursor. Selecting a clip reveals editing handles to: * Crop the clip. * Flip the clip. * Trim the clip. ![2 clips selected at once](/docs/cesdk/_astro/browser-multiselect.VlsjTgoc_Z1F7I7g.png) ### Edit Clip’s Duration[#](#edit-clips-duration) In the timeline, selected clips show drag handles at their beginning and end. Use these handles based on your goals: * Trim the clip from the beginning: use the left handle (at the start of the clip). * Trim the clip from the end: use the right handle (at the end of the clip). This operation is called **“trimming” a clip**. As you move the clip, its position on the timeline automatically updates. Trimming shortens the clip, and the cut portions: * Don’t play anymore. * Disappear from the timeline. ### Split Clips[#](#split-clips) The Split Control splits the selected clip into two separate clips at the playhead. ![Clip split in 2 separate clips](/docs/cesdk/_astro/browser-timeline-split.ciC14j-1_1do0N0.png) Each resulting clip is then independently editable. **Splitting doesn’t remove any part of the original content.** ## Add Content to the Scene[#](#add-content-to-the-scene) Once you’ve edited the clips, you can add more elements to the scene to customize it further, such as: * Additional clips * Audio tracks * Graphics and text ### Add Another Clip[#](#add-another-clip) The web version of the CreativeEditor allows you to add more clips to the scene in two ways: * By clicking the “Add Clip” button: 1. Opens the video assets gallery. 2. Automatically adds any selected clips from the gallery to the timeline. * By dragging and dropping from your computer to either: 1. The page 2. The timeline Dragging the clip highlights the zone before dropping it. ![Clip being dragged to the page and clip being dragged to the timeline in the CE.SDK Editor UI](/docs/cesdk/_astro/browser-drag-n-drop.CiKsL8r1_ZMsdtz.png) The choice of the zone makes no difference: the clip’s position is automatically set at the beginning of the timeline. You can then reposition and edit it as needed. ### Add Audio and Overlays[#](#add-audio-and-overlays) Furthermore, there are two types of content that you can incorporate into the setting: * Overlays (text, images, stickers) * Audio **Add the new content** to the scene by either: * Clicking the kind of content to add from the **lateral menu**. * Using the **drag and drop** feature from your computer or another source. ![Lateral menu highlighting Uploads, Images, Audio, Text and Stickers](/docs/cesdk/_astro/browser-add-content.Du0Z9An7_Z2rMo7E.png) Each type of content appears automatically on its own track, at the start of the timeline. ## Arrange Clips[#](#arrange-clips) Use the CreativeEditor UI’s timeline to edit: * The order in which clips appear. * Each clip starting point. * Using advanced settings. ### Reorder Clips[#](#reorder-clips) Drag clips along the horizontal axis to move them earlier or later in the timeline. This makes a clip’s order relative to the other clip change. The CreativeEditor enhances visual clip arrangement in the timeline by: * Auto-adjusting positions to prevent overlapping clips on the same track. * Showing the clip’s predicted position using drop indicators. ![Timeline containing 2 clips, the second one being dragged along the horizontal axis](/docs/cesdk/_astro/browser-clips-arrange.BIEi6mag_20EOUB.png) ### Edit the Clip’s Starting Point[#](#edit-the-clips-starting-point) Each clip’s position along the horizontal axis indicates its start time in the composition. To change the start time of a clip, drag it either: * Left to make it start **earlier**. * Right to make it start **later**. Gaps between clips display as empty frames showing the background color when the scene plays. ![CE.SDK timeline showing an orange background color within a gap between 2 clips](/docs/cesdk/_astro/browser-clips-gap.CyaeN2c3_Z110uJu.png) ### Change the Background color[#](#change-the-background-color) To change the background color, click the **Background** button on the left side of the play bar. ![CE.SDK timeline with background color button highlighted](/docs/cesdk/_astro/browser-background-color.DA2Gegxw_ZVl7oN.png) This action opens a full color editing menu to customize: * Color settings (RGB, CMYK, Hex, Hue) * Transparency * Solid or gradients * Color picking from the scene This menu provides an option to **deactivate the background** as well. Deactivating the background makes the scene play on a transparent background. ## Configure the Timeline[#](#configure-the-timeline) ### Activate/Deactivate the Timeline[#](#activatedeactivate-the-timeline) The CreativeEditor SDK ships a UI with the timeline editor activated. To change the settings and deactivate the timeline , use the `ly.img.video.timeline` feature flag: [ Enable the timeline ](#tab-panel-365)[ Deactivate the timeline ](#tab-panel-366)[ Conditionally show the timeline ](#tab-panel-367) ``` // Enable the timeline (default for video scenes)cesdk.feature.enable('ly.img.video.timeline'); ``` ``` // Disable the timeline for a simplified interfacecesdk.feature.disable('ly.img.video.timeline'); ``` Pass the option to selectively hide the timeline unless the scene is in **Video Mode**: ``` // Only show timeline when in Video mode (this is the default behavior)cesdk.feature.set('ly.img.video.timeline', ({ engine }) => { return engine.scene.getMode() === 'Video';}); cesdk.feature.set('ly.img.video.controls.split', ({ engine }) => { const selected = engine.block.findAllSelected(); return selected.length === 1;}); ``` ### Hide or Show Tracks[#](#hide-or-show-tracks) Simplify the CreativeEditor interface by leveraging the track visibility settings. Hide or display tracks based on specific use cases. [ Video tracks ](#tab-panel-368)[ Overlays tracks ](#tab-panel-369)[ Audio tracks ](#tab-panel-370)[ All ](#tab-panel-371) **Enable** video features with: ``` cesdk.feature.enable('ly.img.video.clips'); ``` **Hide** video feature with: ``` cesdk.feature.disable('ly.img.video.clips'); ``` This will hide **Display** overlays tracks with: ``` cesdk.feature.enable('ly.img.video.overlays'); ``` **Hide** overlay tracks with: ``` cesdk.feature.disable('ly.img.video.overlays'); ``` **Display** audio tracks with: ``` cesdk.feature.enable('ly.img.video.audio'); ``` **Hide** audio tracks with: ``` cesdk.feature.disable('ly.img.video.audio'); ``` **Display** all tracks with: ``` cesdk.feature.enable([ 'ly.img.video.clips', 'ly.img.video.overlays', 'ly.img.video.audio']); ``` **Hide** all tracks with: ``` cesdk.feature.disable([ 'ly.img.video.clips', 'ly.img.video.overlays', 'ly.img.video.audio']); ``` ### Configure the Play Bar[#](#configure-the-play-bar) Simplify the play bar by hiding or displaying controls in the UI: [ Play bar ](#tab-panel-362)[ Loop control ](#tab-panel-363)[ Zoom ](#tab-panel-364) **Display** the play bar with: ``` cesdk.feature.enable('ly.img.video.controls.playback'); ``` **Hide** the play bar with: ``` cesdk.feature.disable('ly.img.video.controls.playback'); ``` **Display** the loop control with: ``` cesdk.feature.enable('ly.img.video.controls.loop'); ``` **Hide** the loop control with: ``` cesdk.feature.disable('ly.img.video.controls.loop'); ``` **Display** the zoom on the timeline with: ``` cesdk.feature.enable('ly.img.video.controls.timelineZoom'); ``` **Hide** the zoom into the timeline with: ``` cesdk.feature.disable('ly.img.video.controls.timelineZoom'); ``` ### Fine-tune Editing Actions[#](#fine-tune-editing-actions) Restrict or allow editing actions by hiding or displaying the editing controls: [ Split ](#tab-panel-372)[ Add Clip button ](#tab-panel-373)[ Background button ](#tab-panel-374)[ Global Settings ](#tab-panel-375) **Hide** the split button with: ``` cesdk.feature.disable('ly.img.video.controls.split'); ``` **Hide** the **Add Clip** button with: ``` cesdk.feature.disable('ly.img.video.addClip'); ``` **Hide** the background button with: ``` cesdk.feature.disable('ly.img.video.controls.background'); ``` **Activate** all video features at once using global patterns with `/*`: ``` // Enable all video featurescesdk.feature.enable('ly.img.video.*'); ``` Or **deactivate** video features at once: ``` // Disable all video control featurescesdk.feature.disable('ly.img.video.*'); ``` ### Activate Features Dynamically[#](#activate-features-dynamically) You can activate or deactivate timeline features dynamically, instead of hard-coding their **on/off** state. The following example detects the scene state: * When a clip is selected, it activates the Split button. * When nothing is selected, it hides the Split button. ``` // Disable split control when nothing is selectedcesdk.feature.set('ly.img.video.controls.split', ({ engine }) => { const selected = engine.block.findAllSelected(); return selected.length === 1;}); ``` ### Feature Reference[#](#feature-reference) | Feature ID | Description | | --- | --- | | `ly.img.video.timeline` | Show or hide the entire timeline panel | | `ly.img.video.clips` | Show or hide the video clips track | | `ly.img.video.overlays` | Show or hide the overlay track | | `ly.img.video.audio` | Show or hide the audio track | | `ly.img.video.addClip` | Enable or disable adding new clips | | `ly.img.video.controls` | Base feature for all video controls | | `ly.img.video.controls.toggle` | Show or hide timeline collapse/expand button | | `ly.img.video.controls.playback` | Show or hide play/pause and timestamp | | `ly.img.video.controls.loop` | Show or hide loop toggle | | `ly.img.video.controls.split` | Show or hide split clip control | | `ly.img.video.controls.background` | Show or hide background color controls | | `ly.img.video.controls.timelineZoom` | Show or hide zoom controls | ## Troubleshooting[#](#troubleshooting) | Issue | Solution | | --- | --- | | Timeline not displaying | ・ Verify the scene is in video mode. ・ Check that `ly.img.video.timeline` feature is enabled. | | Trim handles not displaying | ・ Click the clip first to reveal handles. ・ Check if the clip contains video/audio content. | | Play not starting | ・ Ensure the video has loaded. ・ Check the browser console for codec errors. ・ Check that the playhead falls within the page duration. | | Split not working | ・ Check that you’ve selected the clip to split. ・ Check that the playhead is within the selected clip’s duration . Make sure you’ve enabled `ly.img.video.controls.split`. | ## Next Steps[#](#next-steps) * [Trim Video Clips](vue/edit-video/trim-4f688b/) — Detailed trimming techniques, including frame-accurate editing. * [Control Audio and Video](vue/create-video/control-daba54/) — Master volume, playback speed, and timing. * [Activate or Deactivate Features](vue/user-interface/customization/disable-or-enable-f058e2/) \- Full feature flag reference. * [Compress and Export the Video](vue/export-save-publish/export/compress-29105e/) . --- [Source](https:/img.ly/docs/cesdk/vue/create-video/overview-b06512) --- # Create Videos Overview In addition to static designs, CE.SDK also allows you to create and edit videos. Working with videos introduces the concept of time into the scene, which requires you to switch the scene into the `"Video"` mode. In this mode, each page in the scene has its own separate timeline within which its children can be placed. The `"playback/time"` property of each page controls the progress of time through the page. In order to add videos to your pages, you can add a block with a `"//ly.img.ubq/fill/video"` fill. As the playback time of the page progresses, the corresponding point in time of the video fill is rendered by the block. You can also customize the video fill’s trim in order to control the portion of the video that should be looped while the block is visible. `//ly.img.ubq/audio` blocks can be added to the page in order to play an audio file during playback. The `playback/timeOffset` property controls after how many seconds the audio should begin to play, while the duration property defines how long the audio should play. The same APIs can be used for other design blocks as well, such as text or graphic blocks. Finally, the whole page can be exported as a video file using the `block.exportVideo` function. ## A Note on Browser Support[#](#a-note-on-browser-support) Video mode heavily relies on modern features like web codecs. A detailed list of supported browser versions can be found in our [Supported Browsers](vue/browser-support-28c1b0/) . Please also take note of [possible restrictions based on the host platform](vue/file-format-support-3c4b2a/) browsers are running on. ## Creating a Video Scene[#](#creating-a-video-scene) First, we create a scene that is set up for video editing by calling the `scene.createVideo()` API. Then we create a page, add it to the scene and define its dimensions. This page will hold our composition. ``` const scene = engine.scene.createVideo(); const page = engine.block.create('page');engine.block.appendChild(scene, page); engine.block.setWidth(page, 1280);engine.block.setHeight(page, 720); ``` ## Setting Page Durations[#](#setting-page-durations) Next, we define the duration of the page using the `setDuration(block: number, duration: number): void` API to be 20 seconds long. This will be the total duration of our exported video in the end. ``` engine.block.setDuration(page, 20); ``` ## Adding Videos[#](#adding-videos) In this example, we want to show two videos, one after the other. For this, we first create two graphic blocks and assign two `'video'` fills to them. ``` const video1 = engine.block.create('graphic');engine.block.setShape(video1, engine.block.createShape('rect'));const videoFill = engine.block.createFill('video');engine.block.setString( videoFill, 'fill/video/fileURI', 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4');engine.block.setFill(video1, videoFill); const video2 = engine.block.create('graphic');engine.block.setShape(video2, engine.block.createShape('rect'));const videoFill2 = engine.block.createFill('video');engine.block.setString( videoFill2, 'fill/video/fileURI', 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-kampus-production-8154913.mp4');engine.block.setFill(video2, videoFill2); ``` ## Creating a Track[#](#creating-a-track) While we could add the two blocks directly to the page and and manually set their sizes and time offsets, we can alternatively also use the `track` block to simplify this work. A `track` automatically adjusts the time offsets of its children to make sure that they play one after another without any gaps, based on each child’s duration. Tracks themselves cannot be selected directly by clicking on the canvas, nor do they have any visual representation. We create a `track` block, add it to the page and add both videos in the order in which they should play as the track’s children. Next, we use the `fillParent` API, which will resize all children of the track to the same dimensions as the page. The dimensions of a `track` are always derived from the dimensions of its children, so you should not call the `setWidth` or `setHeight` APIs on a track, but on its children instead if you can’t use the `fillParent` API. ``` const track = engine.block.create('track');engine.block.appendChild(page, track);engine.block.appendChild(track, video1);engine.block.appendChild(track, video2);engine.block.fillParent(track); ``` By default, each block has a duration of 5 seconds after it is created. If we want to show it on the page for a different amount of time, we can use the `setDuration` API. Note that we can just increase the duration of the first video block to 15 seconds without having to adjust anything about the second video. The `track` takes care of that for us automatically so that the second video starts playing after 15 seconds. ``` engine.block.setDuration(video1, 15); ``` If the video is longer than the duration of the graphic block that it’s attached to, it will cut off once the duration of the graphic is reached. If it is too short, the video will automatically loop for as long as its graphic block is visible. We can also manually define the portion of our video that should loop within the graphic using the `setTrimOffset(block: number, offset: number): void` and `setTrimLength(block: number, length: number): void` APIs. We use the trim offset to cut away the first second of the video and the trim length to only play 10 seconds of the video. Since our graphic is 15 seconds long, the trimmed video will be played fully once and then start looping for the remaining 5 seconds. ``` /* Make sure that the video is loaded before calling the trim APIs. */await engine.block.forceLoadAVResource(videoFill);engine.block.setTrimOffset(videoFill, 1);engine.block.setTrimLength(videoFill, 10); ``` We can control if a video will loop back to its beginning by calling `setLooping(block: number, looping: boolean): void`. Otherwise, the video will simply hold its last frame instead and audio will stop playing. Looping behavior is activated for all blocks by default. ``` engine.block.setLooping(videoFill, true); ``` ## Audio[#](#audio) If the video of a video fill contains an audio track, that audio will play automatically by default when the video is playing. We can mute it by calling `setMuted(block: number, muted: boolean): void`. ``` engine.block.setMuted(videoFill, true); ``` We can also add audio-only files to play together with the contents of the page by adding an `'audio'` block to the page and assigning it the URL of the audio file. ``` const audio = engine.block.create('audio');engine.block.appendChild(page, audio);engine.block.setString( audio, 'audio/fileURI', 'https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a'); ``` We can adjust the volume level of any audio block or video fill by calling `setVolume(block: number, volume: number): void`. The volume is given as a fraction in the range of 0 to 1. ``` /* Set the volume level to 70%. */engine.block.setVolume(audio, 0.7); ``` By default, our audio block will start playing at the very beginning of the page. We can change this by specifying how many seconds into the scene it should begin to play using the `setTimeOffset(block: number, offset: number): void` API. ``` /* Start the audio after two seconds of playback. */engine.block.setTimeOffset(audio, 2); ``` By default, our audio block will have a duration of 5 seconds. We can change this by specifying its duration in seconds by using the `setDuration(block: number, duration: number): void` API. ``` /* Give the Audio block a duration of 7 seconds. */engine.block.setDuration(audio, 7); ``` ## Exporting Video[#](#exporting-video) You can start exporting the entire page as a video file by calling `exportVideo()`. The encoding process will run in the background. You can get notified about the progress of the encoding process by the `progressCallback`. It will be called whenever another frame has been encoded. Since the encoding process runs in the background the engine will stay interactive. So, you can continue to use the engine to manipulate the scene. Please note that these changes won’t be visible in the exported video file because the scene’s state has been frozen at the start of the export. ``` /* Export page as mp4 video. */const videoBlob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', onProgress: (renderedFrames, encodedFrames, totalFrames) => { console.log( 'Rendered', renderedFrames, 'frames and encoded', encodedFrames, 'frames out of', totalFrames ); }}); /* Download video blob. */let anchor = document.createElement('a');anchor.href = URL.createObjectURL(videoBlob);anchor.download = 'exported-video.mp4';anchor.click(); ``` ## Exporting Audio[#](#exporting-audio) You can export just the audio from your video scene by calling `exportAudio()`. This allows you to extract the audio track separately, whether from an entire page, a single audio block, a video block with audio, or a track containing multiple audio sources. The audio export process runs in a background worker, similar to video export, keeping the main engine responsive. You can monitor the progress through the `onProgress` callback. ``` /* Export page audio as an WAV audio. */const audioBlob = await engine.block.exportAudio(page, { mimeType: 'audio/wav', onProgress: (renderedFrames, encodedFrames, totalFrames) => { console.log( 'Rendered', renderedFrames, 'frames and encoded', encodedFrames, 'frames out of', totalFrames ); }}); /* Download audio blob. */anchor = document.createElement('a');anchor.href = URL.createObjectURL(audioBlob);anchor.download = 'exported-audio.wav';anchor.click(); ``` --- [Source](https:/img.ly/docs/cesdk/vue/create-video/limitations-6a740d) --- # Video Limitations CE.SDK performs video processing client-side, providing privacy and responsiveness while introducing hardware-dependent constraints. This reference covers resolution limits, codec support, and platform-specific restrictions to help you plan video workflows within platform capabilities. ![Video Limitations example showing the CE.SDK video editor](/docs/cesdk/_astro/browser.hero.DO0JD4b6_c9yve.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-limitations-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-limitations-browser) Client-side video processing provides significant advantages for privacy and user experience, but it operates within the constraints of the user’s device. Understanding these limitations helps you build applications that work reliably across different hardware configurations and browsers. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Video Limitations Guide * * Demonstrates how to query video processing limitations in CE.SDK: * - Querying maximum export size * - Monitoring memory usage and availability * - Understanding resolution and duration constraints */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 in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine; // Query the maximum export dimensions supported by this device const maxExportSize = engine.editor.getMaxExportSize(); console.log('Maximum export size:', maxExportSize, 'pixels'); // The maximum export size depends on the GPU texture size limit // Typical values: 4096, 8192, or 16384 pixels // Query current memory consumption const usedMemory = engine.editor.getUsedMemory(); const usedMemoryMB = (usedMemory / (1024 * 1024)).toFixed(2); console.log('Memory used:', usedMemoryMB, 'MB'); // Query available memory for video processing const availableMemory = engine.editor.getAvailableMemory(); const availableMemoryMB = (availableMemory / (1024 * 1024)).toFixed(2); console.log('Memory available:', availableMemoryMB, 'MB'); // Browser tabs typically cap around 2GB due to WebAssembly's 32-bit address space // Calculate memory utilization percentage const totalMemory = usedMemory + availableMemory; const memoryUtilization = ((usedMemory / totalMemory) * 100).toFixed(1); console.log('Memory utilization:', memoryUtilization, '%'); // Check if a specific export size is feasible const desiredWidth = 3840; // 4K UHD const desiredHeight = 2160; const canExport4K = desiredWidth <= maxExportSize && desiredHeight <= maxExportSize; console.log( 'Can export at 4K UHD (3840x2160):', canExport4K ? 'Yes' : 'No' ); // Add a sample video to demonstrate the editor with video content const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : engine.scene.get(); const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create a video block that fills the page const videoBlock = await engine.block.addVideo( videoUri, pageWidth, pageHeight ); // Position the video at the center of the page engine.block.setPositionX(videoBlock, 0); engine.block.setPositionY(videoBlock, 0); // Select the video block engine.block.setSelected(videoBlock, true); // Re-check memory after loading video content const usedAfterLoad = engine.editor.getUsedMemory(); const availableAfterLoad = engine.editor.getAvailableMemory(); const usedAfterLoadMB = (usedAfterLoad / (1024 * 1024)).toFixed(2); const availableAfterLoadMB = (availableAfterLoad / (1024 * 1024)).toFixed( 2 ); console.log('After loading video:'); console.log(' Memory used:', usedAfterLoadMB, 'MB'); console.log(' Memory available:', availableAfterLoadMB, 'MB'); // Log summary of device capabilities console.log('--- Device Capabilities Summary ---'); console.log('Max export dimension:', maxExportSize, 'px'); console.log('4K UHD support:', canExport4K ? 'Supported' : 'Not supported'); console.log( 'Initial memory:', usedMemoryMB, 'MB used /', availableMemoryMB, 'MB available' ); console.log( 'Open the browser console to view detailed limitation information.' ); }} export default Example; ``` ## Resolution Limits[#](#resolution-limits) Video resolution capabilities depend on hardware resources and WebAssembly memory constraints. CE.SDK supports up to 4K UHD for playback and export on capable hardware. Import resolution is bounded by WebAssembly’s 32-bit address space and browser tab memory limits, which typically cap around 2GB. This means very high resolution video files may exceed available memory during processing. Playback and export at 4K depends on available GPU resources, and higher resolutions require proportionally more memory and processing power. Query the maximum export size before initiating exports to avoid failures: ``` // Query the maximum export dimensions supported by this deviceconst maxExportSize = engine.editor.getMaxExportSize();console.log('Maximum export size:', maxExportSize, 'pixels');// The maximum export size depends on the GPU texture size limit// Typical values: 4096, 8192, or 16384 pixels ``` The maximum export size varies by device GPU capabilities. Typical values range from 4096 to 16384 pixels depending on the graphics hardware. Before exporting at high resolutions, verify the target dimensions don’t exceed this limit: ``` // Check if a specific export size is feasibleconst desiredWidth = 3840; // 4K UHDconst desiredHeight = 2160;const canExport4K = desiredWidth <= maxExportSize && desiredHeight <= maxExportSize;console.log( 'Can export at 4K UHD (3840x2160):', canExport4K ? 'Yes' : 'No'); ``` ## Duration Limits[#](#duration-limits) Video duration affects editing responsiveness and export time. CE.SDK optimizes for short-form content while supporting longer videos with performance trade-offs. Stories and reels up to 2 minutes are fully supported with smooth editing performance. Videos up to 10 minutes work well on modern hardware, with export times typically around 1 minute for this length. Longer videos are technically possible but may impact editing responsiveness on less capable devices. For long-form content, consider these approaches: * Split longer videos into shorter segments for editing * Use lower resolution previews during editing, then export at full quality * Test on target devices to establish acceptable duration limits for your use case ## Frame Rate Support[#](#frame-rate-support) Frame rate affects both playback smoothness and export performance. Hardware acceleration significantly impacts high frame rate capabilities. 30 FPS at 1080p is broadly supported across devices and provides smooth playback on most hardware. 60 FPS and high-resolution combinations benefit from hardware acceleration. When hardware acceleration is unavailable, high frame rate video may drop frames during preview playback, though exports will maintain the correct timing. Variable frame rate sources may have timing precision limitations. For best results with variable frame rate content, consider transcoding to constant frame rate before importing into CE.SDK. ## Supported Codecs[#](#supported-codecs) CE.SDK supports widely-adopted video and audio codecs, with some platform-specific variations in availability. ### Video Codecs[#](#video-codecs) H.264/AVC in `.mp4` containers has universal support across all browsers and platforms. This is the most reliable codec choice for broad compatibility. H.265/HEVC in `.mp4` containers has platform-dependent support. Safari on macOS and iOS supports HEVC natively, while Chrome and Firefox support varies by operating system and codec availability. ### Audio Codecs[#](#audio-codecs) MP3 works in `.mp3` files or within `.mp4` containers, with universal browser support. AAC in `.m4a`, `.mp4`, or `.mov` containers is widely supported, though some browsers may require system codecs for encoding during export. ## Browser and Platform Restrictions[#](#browser-and-platform-restrictions) Browser capabilities depend on the host operating system, introducing platform-specific limitations that affect video processing. ### Windows Limitations[#](#windows-limitations) H.265 transparency is not supported on Windows hosts. If your workflow requires alpha channel video with HEVC, consider processing on macOS or using H.264 which supports alpha on all platforms. ### Linux Limitations[#](#linux-limitations) Chrome on Linux typically lacks encoder support for H.264 and AAC due to licensing restrictions in the Chrome Linux build. This means video imports and playback may work correctly, but exports may fail. If you’re targeting Linux users, consider: * Recommending Firefox, which may have different codec support * Providing fallback export options * Using server-side export processing for Linux users ### Chromium Limitations[#](#chromium-limitations) Chromium-based browsers (without proprietary codecs) don’t include video codecs due to licensing. They may fall back to system libraries on some platforms, such as macOS, but support is not guaranteed. Video editing functionality may not work reliably in pure Chromium builds. ### Mobile Browser Limitations[#](#mobile-browser-limitations) Video editing is not supported on mobile browsers on any platform due to technical limitations causing performance issues. For mobile video editing capabilities, use the native mobile SDKs for iOS and Android, which provide full video support. ## Hardware Requirements[#](#hardware-requirements) Device capabilities directly affect video processing performance. CE.SDK scales with available hardware resources. ### Recommended Hardware[#](#recommended-hardware) | Platform | Minimum Hardware | | --- | --- | | Desktop | Notebook or desktop released in the last 7 years with at least 4GB memory | | Mobile (Apple) | iPhone 8, iPad (6th gen) or newer | | Mobile (Android) | Phones and tablets released in the last 4 years | ### GPU Considerations[#](#gpu-considerations) Hardware acceleration improves encoding and decoding performance significantly. High-resolution and high-frame-rate exports benefit most from GPU support. The maximum export size depends on the maximum texture size the device’s GPU can allocate. Integrated graphics can handle most common video editing tasks. Discrete GPUs provide better performance for 4K content and complex compositions with multiple video layers. ## Memory Constraints[#](#memory-constraints) Client-side video processing operates within browser memory limits. Use the memory APIs to monitor consumption and make informed decisions about resource loading. Query current memory usage to understand how much has been consumed: ``` // Query current memory consumptionconst usedMemory = engine.editor.getUsedMemory();const usedMemoryMB = (usedMemory / (1024 * 1024)).toFixed(2);console.log('Memory used:', usedMemoryMB, 'MB'); ``` Check how much memory remains available for additional resources: ``` // Query available memory for video processingconst availableMemory = engine.editor.getAvailableMemory();const availableMemoryMB = (availableMemory / (1024 * 1024)).toFixed(2);console.log('Memory available:', availableMemoryMB, 'MB');// Browser tabs typically cap around 2GB due to WebAssembly's 32-bit address space ``` WebAssembly uses a 32-bit address space, limiting the maximum addressable memory. Browser tabs typically cap around 2GB of memory, though this varies by browser and system configuration. Multiple video tracks and effects increase memory usage proportionally. Query memory APIs before loading additional video files to avoid out-of-memory conditions: ``` // Re-check memory after loading video contentconst usedAfterLoad = engine.editor.getUsedMemory();const availableAfterLoad = engine.editor.getAvailableMemory();const usedAfterLoadMB = (usedAfterLoad / (1024 * 1024)).toFixed(2);const availableAfterLoadMB = (availableAfterLoad / (1024 * 1024)).toFixed( 2);console.log('After loading video:');console.log(' Memory used:', usedAfterLoadMB, 'MB');console.log(' Memory available:', availableAfterLoadMB, 'MB'); ``` ## Export Size Limitations[#](#export-size-limitations) Export dimensions are bounded by GPU texture size limits. Always query `getMaxExportSize()` before initiating exports to ensure the requested dimensions are supported. The maximum export size varies by device GPU capabilities. Common limits include: * **4096 pixels**: Older integrated graphics * **8192 pixels**: Most modern integrated and discrete GPUs * **16384 pixels**: High-end discrete GPUs Consider target platform requirements when planning export dimensions. Mobile devices and web playback rarely benefit from resolutions above 1080p or 4K, so exporting at extreme resolutions may not provide practical value. ## Troubleshooting[#](#troubleshooting) Common issues developers encounter related to video limitations: | Issue | Cause | Solution | | --- | --- | --- | | Video export fails on Linux | Chrome lacks H.264/AAC encoder support | Use Firefox or implement server-side export | | Slow playback at high resolution | Hardware cannot keep up with decoding | Reduce preview resolution or use proxy editing | | Export fails with large video | Memory limits exceeded | Reduce resolution or split into shorter segments | | H.265 transparency not working | Windows platform limitation | Use H.264 or process on macOS | | Mobile browser video not working | Mobile browsers don’t support video editing | Use native mobile SDK instead | | Export size rejected | Exceeds device GPU texture limits | Query `getMaxExportSize()` and reduce dimensions | ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `engine.editor.getMaxExportSize()` | Query the maximum export dimensions supported by the device | | `engine.editor.getAvailableMemory()` | Get available memory in bytes for video processing | | `engine.editor.getUsedMemory()` | Get current memory usage in bytes | --- [Source](https:/img.ly/docs/cesdk/vue/create-video/control-daba54) --- # Control Audio and Video Play, pause, seek, and preview audio and video content programmatically using CE.SDK’s playback control APIs. ![Control Audio and Video example showing video playback controls](/docs/cesdk/_astro/browser.hero.C8vI--wL_Z8VMo3.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-control-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); // Create a video scene await cesdk.createVideoScene(); const engine = cesdk.engine; // Get the page and set to 16:9 landscape for video const page = engine.block.findByType('page')[0]!; engine.block.setWidth(page, 1920); engine.block.setHeight(page, 1080); // 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[#](#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. ``` await engine.block.forceLoadAVResource(videoFill); ``` Without loading the resource first, accessing properties like duration or dimensions throws an error. ## Getting Video Metadata[#](#getting-video-metadata) Once the resource is loaded, query the video dimensions and total duration. ``` 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[#](#playing-and-pausing) Check if the block supports playback control using `supportsPlaybackControl`, then start or stop playback with `setPlaying`. ``` 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[#](#seeking) To jump to a specific position in the timeline, use `setPlaybackTime`. First, check if the block supports playback time with `supportsPlaybackTime`. ``` 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[#](#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. ``` console.log( `Visible at current time: ${engine.block.isVisibleAtCurrentPlaybackTime(videoBlock)}`); ``` ## Solo Playback[#](#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`. ``` 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[#](#troubleshooting) ### Properties Unavailable Before Resource Load[#](#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[#](#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[#](#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[#](#next-steps) * [Trim Video and Audio](vue/edit-video/trim-4f688b/) \- Control which portion of source media plays * [Loop Audio](vue/create-audio/audio/loop-937be7/) \- Enable repeating playback for audio blocks * [Adjust Volume](vue/create-video/audio/adjust-volume-7ecc4a/) \- Control audio volume and muting * [Adjust Speed](vue/create-video/audio/adjust-speed-908d57/) \- Change playback speed for audio * [Video Timeline Overview](vue/create-video/timeline-editor-912252/) \- Timeline editing system --- [Source](https:/img.ly/docs/cesdk/vue/create-templates/overview-4ebe30) --- # Overview In CE.SDK, a _template_ is a reusable, structured design that defines editable areas and constraints for end users. Templates can be based on static visuals or video compositions and are used to guide content creation, enable mass personalization, and enforce design consistency. Unlike a regular editable design, a template introduces structure through placeholders and constraints, allowing you to define which elements users can change and how. Templates support both static output formats (like PNG, PDF) and videos (like MP4), and can be created or applied using either the CE.SDK UI or API. Templates are a core part of enabling design automation, personalization, and streamlined workflows in any app that includes creative functionality. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## Why Use Templates?[#](#why-use-templates) Templates are a powerful tool for: * Maintaining **brand consistency** across all user-generated designs. * **Scaling** asset creation for campaigns, catalogs, or print products. * Providing a **guided experience** where users adapt content without starting from scratch. They are ideal for use cases like: * Personalized marketing campaigns * Dynamic social media ads * Product catalogs and e-commerce visuals * Custom print materials and photo books ## Ways to Create Templates[#](#ways-to-create-templates) You can create templates from scratch or by importing an existing template. **From Scratch:** Start a new project and design a scene with the intent of turning it into a template. You can define variables, placeholders, and constraints directly in the editor. **Import Existing Designs:** If you already have assets created in other tools, you can import them as templates. | Format | Description | | --- | --- | | `.idml` | InDesign | | `.psd` | Photoshop | | `.scene` | CE.SDK Native | Need to import a format not listed here? CE.SDK allows you to create custom importers for any file type by using our Scene and Block APIs to generate scenes programmatically. These imported designs can then be adapted into editable, structured templates inside CE.SDK. ## Dynamic Content in Templates[#](#dynamic-content-in-templates) Templates support dynamic content to enable data-driven generation of assets. CE.SDK provides several mechanisms: * **Text Variables**: Bind text elements to dynamic values (e.g., user names, product SKUs). * **Image Placeholders**: Reserve space for images to be inserted later. * **Video Placeholders**: Reserve space for videos, enabling dynamic insertion of video clips in a templated layout. This makes it easy to generate hundreds or thousands of personalized variations from a single design. ## Controlling Template Editing[#](#controlling-template-editing) Templates in CE.SDK offer powerful tools for controlling the editing experience: * **Editing Constraints**: Lock specific properties such as position, style, or content of elements. * **Locked Templates**: Prevent any edits outside allowed fields to protect design integrity. * **Fully Editable Templates**: Allow unrestricted editing for power users or advanced workflows. * **Form-Based Editing**: Build a custom editing interface for users to fill in variables and placeholders via input forms (a ready-made UI is not currently provided, but can be built using our APIs). These options let you strike a balance between creative freedom and design control. ## Working with Templates Programmatically and Through the UI[#](#working-with-templates-programmatically-and-through-the-ui) You can manage templates using both the UI and API: * **UI Integration**: Users can select, apply, and edit templates directly inside the CE.SDK interface. * **Programmatic Access**: Use the SDK’s APIs to create, apply, or modify templates as part of an automated workflow. * **Asset Library Integration**: Templates can appear in the asset library, allowing users to browse and pick templates visually. * The Asset Library’s appearance and behavior can be fully customized to fit your app’s needs. ## Managing Templates[#](#managing-templates) Templates are saved and reused just like any other CE.SDK asset: * **Save Templates** to a _Template Library_. * **Edit or Remove** existing templates from your asset library. * Templates are saved as Scene (`.scene`) or Archive (`.zip`) files and can be loaded across all platforms supported by CE.SDK (Web, Mobile, Server, Desktop) ## Default and Premium Templates[#](#default-and-premium-templates) * **Default Templates**: CE.SDK may include a small number of starter templates depending on your configuration. * **Premium Templates**: IMG.LY offers a growing collection of professionally designed templates available for licensing. * Templates can be imported, customized, and used directly within your app. Check your license or speak with our team for details on accessing premium templates. ## Templates as Assets[#](#templates-as-assets) Templates are treated as **assets** in CE.SDK. That means: * They can be included in local or remote asset libraries. * They can be shared, versioned, and indexed using the same systems as images or videos. * You can apply your own metadata, tags, and search capabilities to them. --- [Source](https:/img.ly/docs/cesdk/vue/create-templates/lock-131489) --- # Lock the Template Set up a two-surface integration where template creators have full editing access while template adopters can only modify designated areas. ![Lock the Template](/docs/cesdk/_astro/browser.hero.B1rc32Rd_2leXOy.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-lock-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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](vue/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; // Get the page and set dimensions const page = engine.block.findByType('page')[0]; engine.block.setWidth(page, 800); engine.block.setHeight(page, 500); // 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.setNavigationBarOrder([ '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[#](#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[#](#setting-up-the-creator-surface) The Creator surface is where templates are built. We use `engine.editor.setRole('Creator')` to give designers unrestricted access. ``` // The Creator role ignores all scope restrictionsengine.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[#](#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[#](#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[#](#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. ``` // Configure which elements Adopters can edit// Enable selection and text editing on the headlineengine.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](vue/rules/lock-content-9fa727/) . ## Configuring Scopes in the Editor UI[#](#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[#](#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. ``` // 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.setNavigationBarOrder([ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', 'ly.img.roleToggle.navigationBar', 'ly.img.spacer']); ``` ## Troubleshooting[#](#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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/create-templates/import-e50084) --- # Import Templates Load design templates into CE.SDK from archive URLs, scene URLs, and serialized strings. ![Import Templates](/docs/cesdk/_astro/browser.hero.D5xXl9Ck_ZnAA.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-import-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; // Import scene file as string for loadFromString demonstrationimport businessCardSceneString from './assets/business-card.scene?raw'; // Template sourcesconst fashionAdArchiveUrl = 'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip'; const postcardSceneUrl = 'https://cdn.img.ly/assets/demo/v1/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'); } 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(); console.log(`Template loaded with ${pages.length} page(s)`); } // Configure navigation bar with template loading buttons cesdk.ui.setNavigationBarOrder([ '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-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. ``` // Load template from archive URL (bundled assets)await engine.scene.loadFromArchiveURL(fashionAdArchiveUrl); ``` ## Load from URL[#](#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. ``` // Load template from a scene file URLawait engine.scene.loadFromURL(postcardSceneUrl); ``` ## Load from String[#](#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()`. ``` // Load template from serialized stringawait engine.scene.loadFromString(businessCardSceneString); ``` ## Working with the Loaded Scene[#](#working-with-the-loaded-scene) After loading a template, you can verify its contents and adjust the viewport. ### Verify the Scene[#](#verify-the-scene) Use `engine.scene.get()` to retrieve the scene block and `engine.scene.getPages()` to inspect its pages. ``` // Verify the loaded sceneconst loadedScene = engine.scene.get();if (loadedScene != null) { const pages = engine.scene.getPages(); console.log(`Template loaded with ${pages.length} page(s)`);} ``` ### Zoom to Content[#](#zoom-to-content) Fit the loaded template in the viewport using `zoomToBlock()` with optional padding. ``` // Zoom viewport to fit the loaded sceneconst scene = engine.scene.get();if (scene != null) { await engine.scene.zoomToBlock(scene, { padding: 40 });} ``` --- [Source](https:/img.ly/docs/cesdk/vue/create-templates/from-scratch-663cda) --- # Create From Scratch Build reusable design templates entirely through code using CE.SDK’s programmatic APIs for automation, batch generation, and custom template creation tools. ![Create Templates From Scratch](/docs/cesdk/_astro/browser.hero.Bcp3DQ32_1iwmgK.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-from-scratch-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); 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.insertNavigationBarOrderComponent('last', 'role.toggle'); cesdk.ui.insertNavigationBarOrderComponent('last', 'save.string'); cesdk.ui.insertNavigationBarOrderComponent('last', '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[#](#initialize-cesdk) We start by initializing CE.SDK and loading the asset sources. The `cesdk.addDefaultAssetSources()` and `cesdk.addDemoAssetSources()` methods provide access to fonts, images, and other assets. ``` // Initialize CE.SDK with Design mode and load asset sourcesawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true}); const engine = cesdk.engine; ``` ## Create a Blank Scene[#](#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. ``` // Template layout constants for a promotional cardconst CANVAS_WIDTH = 800;const CANVAS_HEIGHT = 1000;const PADDING = 40;const CONTENT_WIDTH = CANVAS_WIDTH - PADDING * 2; // Create a blank scene with custom dimensionsengine.scene.create('Free', { page: { size: { width: CANVAS_WIDTH, height: CANVAS_HEIGHT } }}); // Set design unit to Pixel for precise coordinate mappingengine.scene.setDesignUnit('Pixel'); ``` The scene creation method accepts a layout mode (`'Free'` for design mode) and optional page configuration. When options are provided, the scene automatically includes a page with the specified dimensions. ## Set Page Background[#](#set-page-background) We set a light background color to give the template a consistent base appearance. ``` // Set a gradient background for the templateconst 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[#](#add-text-blocks) Text blocks allow you to add styled text content. We create a headline that includes a variable token for dynamic content. ``` // Create headline text block with {{title}} variableconst headline = engine.block.create('text');engine.block.replaceText(headline, '{{title}}'); // Set font with proper typeface for consistent renderingengine.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 headlineengine.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[#](#add-text-variables) Text variables enable data-driven personalization. By using `{{variableName}}` tokens in text blocks, you can populate content programmatically. ``` // Set default value for the title variableengine.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[#](#add-graphic-blocks) Graphic blocks serve as containers for images. We create an image block that will become a placeholder for swappable media. ``` // Create image placeholder in the center of the cardconst 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[#](#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. ``` // Enable placeholder behavior on the image fillconst 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 placeholderengine.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[#](#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. ``` // Set global scope to 'Defer' for per-block controlengine.editor.setGlobalScope('layer/move', 'Defer');engine.editor.setGlobalScope('layer/resize', 'Defer'); // Lock all text block positions but allow text editingconst 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 replacementengine.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[#](#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. ``` // Register button component for saving template as stringcesdk.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 archivecesdk.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 barcesdk.ui.insertNavigationBarOrderComponent('last', 'role.toggle');cesdk.ui.insertNavigationBarOrderComponent('last', 'save.string');cesdk.ui.insertNavigationBarOrderComponent('last', '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[#](#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[#](#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[#](#next-steps) * [Placeholders](vue/create-templates/add-dynamic-content/placeholders-d9ba8a/) \- Configure placeholder behavior and visual controls in depth * [Text Variables](vue/create-templates/add-dynamic-content/text-variables-7ecb50/) \- Implement dynamic text personalization with variables * [Set Editing Constraints](vue/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) \- Lock layout properties to protect design integrity * [Add to Template Library](vue/create-templates/add-to-template-library-8bfbc7/) \- Register templates in the asset library for users to discover --- [Source](https:/img.ly/docs/cesdk/vue/create-templates/edit-or-remove-38a8be) --- # Edit or Remove Templates Modify existing templates and manage template lifecycle in your asset library using CE.SDK. ![Edit or Remove Templates example showing template management](/docs/cesdk/_astro/browser.hero.9eQfyFMc_PB6G3.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-edit-or-remove-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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 labelfunction generateThumbnail(label: string): string { const svg = ` ${label} `; 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'); } // Initialize CE.SDK with Design mode and create a scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); 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.setDockOrder([ ...cesdk.ui.getDockOrder(), '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') } }); 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 ); 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'); 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 ); console.log('Template updated in asset source'); // Apply the original template to show the starting point await engine.scene.loadFromString(originalContent); 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[#](#adding-templates) First, create a local asset source to store your templates: ``` // Create a local asset source for managing templatesengine.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: ``` // Create the template with text blocksconst 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: ``` // Save template content and add to asset sourceconst 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[#](#editing-templates) Modify template content using block APIs. You can update text, change images, adjust positions, and reconfigure any block properties. ``` // Edit the template content and save as a new versionengine.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[#](#removing-templates) Remove templates from asset sources using `removeAssetFromSource()`. This permanently deletes the template entry from the source. ``` // Add a temporary template to demonstrate removalengine.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 sourceengine.asset.removeAssetFromSource('my-templates', 'template-temporary'); ``` 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[#](#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. ``` // Update an existing template by removing and re-adding with same IDengine.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 changedengine.asset.assetSourceContentsChanged('my-templates'); ``` After updating templates, call `assetSourceContentsChanged()` to notify the UI that the asset source contents have changed. ## Best Practices[#](#best-practices) ### Versioning Strategies[#](#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[#](#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[#](#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[#](#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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/create-templates/add-to-template-library-8bfbc7) --- # Add to Template Library Create a template library where users can browse, preview, and apply templates from a custom asset source. ![Add to Template Library](/docs/cesdk/_astro/browser.hero.IGiPTVAA_Z2aUye2.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-add-to-template-library-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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; // Add default and demo asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // 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/v1/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/v1/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/v1/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'], sceneMode: 'Design', previewLength: 3, previewBackgroundType: 'cover', gridBackgroundType: 'cover', gridColumns: 2, cardLabelPosition: () => 'below' }); // Add template library to the dock cesdk.ui.setDockOrder([ ...cesdk.ui.getDockOrder(), '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/v1/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.insertNavigationBarOrderComponent('last', { 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[#](#saving-templates) Scenes can be exported in two formats for use as templates. ### String Format[#](#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. ``` // 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[#](#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. ``` // 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[#](#creating-a-template-asset-source) Register a local asset source using `engine.asset.addLocalSource()` with an ID and `applyAsset` callback. ``` // Create a local asset source for templatesengine.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[#](#adding-templates-to-the-source) Register templates using `engine.asset.addAssetToSource()` with an asset definition that includes metadata for display and loading. ``` // Add a template to the source with metadataengine.asset.addAssetToSource('my-templates', { id: 'template-postcard', label: { en: 'Postcard' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v1/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[#](#managing-templates) After the initial setup, you can manage templates programmatically. ``` // List all registered asset sourcesconst sources = engine.asset.findAllSources();console.log('Registered sources:', sources); // Notify UI when source contents changeengine.asset.assetSourceContentsChanged('my-templates'); // Query templates from the sourceconst queryResult = await engine.asset.findAssets('my-templates', { page: 0, perPage: 10});console.log('Templates in library:', queryResult.total); // Remove a template from the sourceengine.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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/create-templates/add-dynamic-content-53fad7) --- # Dynamic Content 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. ![Dynamic Content example showing variables and placeholders](/docs/cesdk/_astro/browser.hero.B38TgXsT_Z1AA3Nx.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-add-dynamic-content-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-add-dynamic-content-browser) ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and set role to Adopter await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; // Set editor role to Adopter for template usage engine.editor.setRole('Adopter'); const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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) Text variables enable data-driven text personalization. Define variables using `engine.variable.setString()`, then reference them in text blocks with `{{variableName}}` tokens. ``` engine.variable.setString('firstName', 'Jane');engine.variable.setString('lastName', 'Doe');engine.variable.setString('companyName', 'IMG.LY'); // Create heading with company variableconst 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. Variable keys are case-sensitive. `{{Name}}` and `{{name}}` are different variables. ## Placeholders[#](#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. ``` // Enable placeholder behavior for the hero imageif (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) Editing constraints protect design integrity by limiting what users can modify. Use scope-based APIs to lock specific properties while keeping others editable. ``` // Lock the logo: prevent moving, resizing, and selectionengine.block.setScopeEnabled(logo, 'layer/move', false);engine.block.setScopeEnabled(logo, 'layer/resize', false);engine.block.setScopeEnabled(logo, 'editor/select', false); // Verify constraints are appliedconst 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[#](#choosing-the-right-capability) | Need | Capability | | --- | --- | | Dynamic text content | Text Variables | | Swappable images/videos | Placeholders | | Lock specific properties | Editing Constraints | ## API Reference[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/programmatic-a688bf) --- # Programmatic Creation Build compositions entirely through code using CE.SDK’s APIs for automation, batch processing, and custom interfaces. ![Programmatic Creation](/docs/cesdk/_astro/browser.hero.Bv_VUfSd_5eHec.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-programmatic-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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 stylingconst 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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); const engine = cesdk.engine; // Create a new scene with social media dimensions (1080x1080) await cesdk.createDesignScene(); const scene = engine.scene.get()!; engine.block.setFloat(scene, 'scene/dpi', 300); const page = engine.block.findByType('page')[0]; engine.block.setWidth(page, 1080); engine.block.setHeight(page, 1080); // 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[#](#initialize-cesdk) We start by initializing CE.SDK and loading the asset sources. The `cesdk.addDefaultAssetSources()` and `cesdk.addDemoAssetSources()` methods provide access to fonts, images, and other assets. ``` // Initialize CE.SDK with Design mode and load asset sourcesawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true}); const engine = cesdk.engine; ``` ## Create Scene Structure[#](#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. ``` // Create a new scene with social media dimensions (1080x1080)await cesdk.createDesignScene();const scene = engine.scene.get()!; ``` The `cesdk.createDesignScene()` method creates a new design scene with a page. We set the page dimensions using `setWidth()` and `setHeight()`. ## Set Page Background[#](#set-page-background) We set the page background using a color fill. This demonstrates how to create and assign fills to blocks. ``` // Set page background to light lavender colorconst 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[#](#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-text-and-set-content) Create a text block and set its content with `replaceText()`: ``` // Add main headline text with bold Roboto fontconst 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[#](#style-entire-text-block) Apply styling to the entire text block using `toggleBoldFont()` and `setTextColor()`: ``` // Make headline boldif (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[#](#enable-automatic-font-sizing) Configure the text block to automatically scale its font size to fit within fixed dimensions: ``` // Set fixed container size and enable automatic font sizingengine.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[#](#range-based-text-styling) Apply different styles to specific character ranges within a single text block: ``` // 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[#](#fixed-font-size) Set an explicit font size instead of using auto-sizing: ``` // Add CTA text "Start a Free Trial" with bold fontconst 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[#](#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-shape-block) Create a graphic block and assign a shape to it: ``` // Add horizontal divider lineconst dividerLine = engine.block.create('graphic');const lineShape = engine.block.createShape('line');engine.block.setShape(dividerLine, lineShape); ``` ### Apply Fill to Shape[#](#apply-fill-to-shape) Create a color fill and apply it to the shape: ``` 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[#](#add-images) We add images using graphic blocks with image fills. ### Create an Image Block[#](#create-an-image-block) Create a graphic block with a rect shape and an image fill: ``` // Add IMG.LY logo imageconst 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[#](#position-and-size-blocks) All blocks use the same positioning and sizing APIs: ``` 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[#](#export-the-composition) CE.SDK provides two approaches for exporting compositions in browser environments. ### Export Using the Engine API[#](#export-using-the-engine-api) The `engine.block.export()` method exports a block as a blob that you can use programmatically: ``` // Export the composition to PNGconst blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1080, targetHeight: 1080}); // In browser, create a download linkconst 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[#](#export-using-built-in-actions) Alternatively, use the built-in export panel or actions for a complete export dialog: ``` 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[#](#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[#](#next-steps) * [Layer Management](vue/create-composition/layer-management-18f07a/) \- Control block stacking and organization * [Positioning and Alignment](vue/insert-media/position-and-align-cc6b6a/) \- Precise block placement * [Group and Ungroup](vue/create-composition/group-and-ungroup-62565a/) \- Group blocks for unified transforms * [Blend Modes](vue/create-composition/blend-modes-ad3519/) \- Control how blocks interact visually * [Export](vue/export-save-publish/export-82f968/) \- Export options and formats --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/overview-5b19c5) --- # Overview In CreativeEditor SDK (CE.SDK), a _composition_ is an arrangement of multiple design elements—such as images, text, shapes, graphics, and effects—combined into a single, cohesive visual layout. Unlike working with isolated elements, compositions allow you to design complex, multi-element visuals that tell a richer story or support more advanced use cases. All composition processing is handled entirely on the client side, ensuring fast, secure, and efficient editing without requiring server infrastructure. You can use compositions to create a wide variety of projects, including social media posts, marketing materials, collages, and multi-page exports like PDFs. Whether you are building layouts manually through the UI or generating them dynamically with code, compositions give you the flexibility and control to design at scale. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## Working with Multiple Pages and Artboards[#](#working-with-multiple-pages-and-artboards) CE.SDK supports working with multiple artboards or canvases within a single document, enabling you to design multi-page layouts or create several design variations within the same project. Typical multi-page use cases include: * Designing multi-page marketing brochures. * Exporting designs as multi-page PDFs. * Building multiple versions of a design for different audiences or platforms. ## Working with Elements[#](#working-with-elements) You can easily arrange and manage elements within a composition: * **Positioning and Aligning:** Move elements precisely and align them to each other or to the canvas. * **Guides and Snapping:** Use visual guides and automatic snapping to align objects accurately. * **Grouping:** Group elements for easier collective movement and editing. * **Layer Management:** Control the stacking order and organize elements in layers. * **Locking:** Lock elements to prevent accidental changes during editing. ## UI vs. Programmatic Creation[#](#ui-vs-programmatic-creation) ### Using the UI[#](#using-the-ui) The CE.SDK UI provides drag-and-drop editing, alignment tools, a layer panel, and snapping guides, making it easy to visually build complex compositions. You can group, align, lock, and arrange elements directly through intuitive controls. ### Programmatic Creation[#](#programmatic-creation) You can also build compositions programmatically by using CE.SDK’s APIs. This is especially useful for: * Automatically generating large volumes of designs. * Creating data-driven layouts. * Integrating CE.SDK into a larger automated workflow. ## Exporting Compositions[#](#exporting-compositions) CE.SDK compositions can be exported in several formats: | Category | Supported Formats | | --- | --- | | **Images** | `.png` (with transparency), `.jpeg`, `.webp`, `.tga` | | **Video** | `.mp4` (H.264 or H.265 on supported platforms with limited transparency support) | | **Print** | `.pdf` (supports underlayer printing and spot colors) | | **Scene** | `.scene` (description of the scene without any assets) | | **Archive** | `.zip` (fully self-contained archive that bundles the `.scene` file with all assets) | Our custom cross-platform C++ based rendering and layout engine ensures consistent output quality across devices. --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/multi-page-4d2b50) --- # Multi-Page Layouts Create multi-page designs in CE.SDK for brochures, presentations, catalogs, and other documents requiring multiple pages within a single scene. ![Multi-Page Layouts](/docs/cesdk/_astro/browser.hero.BhbDrQSV_Z1OgkLd.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-multi-page-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); 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[#](#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[#](#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[#](#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. ``` // Create a scene with HorizontalStack layoutengine.scene.create('HorizontalStack'); // Get the stack containerconst [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 pageconst 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[#](#configuring-page-spacing) We can add spacing between pages in a stack layout using the `stack/spacing` property. This creates visual separation between pages. ``` // 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[#](#adding-more-pages) To add additional pages, we create new page blocks, set their dimensions, and append them to the stack container. ``` // Create a second page with different contentconst 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 pageconst 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[#](#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[#](#setting-the-zoom-level) We can control the viewport zoom level using `engine.scene.setZoomLevel()`. A value of 1.0 represents 100% zoom. ``` engine.block.select(firstPage);engine.scene.enableZoomAutoFit(firstPage, 'Both'); ``` ## Troubleshooting[#](#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). --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/lock-design-0a81de) --- # Lock Design Protect design elements from unwanted modifications using CE.SDK’s scope-based permission system. ![Lock Design Hero](/docs/cesdk/_astro/browser.hero.Da13-an__Z1z36lz.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-lock-design-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext, Scope} from '@cesdk/cesdk-js';import packageJson from './package.json'; 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'); } // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]!; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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[#](#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. ``` // Lock the entire design by setting all scopes to Denyconst 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[#](#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. ``` // Enable selection for specific blocksengine.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[#](#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[#](#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`. ``` // Enable text editing on the text blockengine.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[#](#image-replacement) To allow users to swap images while protecting layout and position, enable the `fill/change` scope on placeholder blocks. ``` // Enable image replacement on the placeholder blockengine.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[#](#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. ``` // Check if operations are permitted on blocksconst 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); // trueconsole.log('- Can move locked image:', canMoveImage); // falseconsole.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[#](#discovering-available-scopes) To work with scopes programmatically, you can discover all available scope names and check their current settings. ``` // Discover all available scopesconst allScopes: Scope[] = engine.editor.findAllScopes();console.log('Available scopes:', allScopes); // Check global scope settingsconst 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 settingsconst textEditEnabled = engine.block.isScopeEnabled(textBlock, 'text/edit');console.log('Text block text/edit enabled:', textEditEnabled); // true ``` ## Available Scopes Reference[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/layout-b66311) --- # Design a Layout Create structured compositions using stack layouts that automatically arrange pages vertically or horizontally with consistent spacing. ![Design a Layout](/docs/cesdk/_astro/browser.hero.ChC-Lf1i_Z2wX3RL.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-layout-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); 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[#](#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. ``` // Create a scene with vertical stack layout// Pages arrange top-to-bottom automaticallyengine.scene.create('VerticalStack'); // Get the stack container created with the sceneconst [stack] = engine.block.findByType('stack'); // Create two pages that will stack verticallyconst 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 pagesengine.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[#](#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. ``` // Add image content to page 1const 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 2const 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[#](#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. ``` // Switch to horizontal stack layout// Pages now arrange left-to-rightengine.scene.setLayout('HorizontalStack'); // Verify the layout typeconst 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-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. ``` // Add a new page to the existing stack// The page automatically appears at the endconst 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 pageconst 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[#](#reorder-pages) Change page order using `insertChild()` to place a page at a specific index within the stack. ``` // Reorder pages using insertChild// Move page3 to the first positionengine.block.insertChild(stack, page3, 0); // Verify the new orderconst 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[#](#change-stack-spacing) Adjust spacing between pages using the `stack/spacing` property on the stack block. ``` // Update spacing between stacked pagesengine.block.setFloat(stack, 'stack/spacing', 40); // Verify the spacing valueconst 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[#](#switch-to-free-layout) For manual positioning, switch to `Free` layout. Pages keep their positions but stop auto-arranging. ``` // Check current layout typeconst layout = engine.scene.getLayout(); // Convert to free layout for manual positioningengine.scene.setLayout('Free'); // Now position pages manuallyconst [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[#](#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[#](#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[#](#next-steps) * [Auto-resize](vue/automation/auto-resize-4c2d58/) — Make blocks fit parent containers * [Manual Positioning](vue/edit-image/transform/move-818dd9/) — Position blocks in free layouts * [Layer Hierarchies](vue/create-composition/layer-management-18f07a/) — Organize blocks in hierarchical structures * [Create a Collage](vue/create-composition/collage-f7d28d/) — Build photo collages with templates --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/layer-management-18f07a) --- # Layer Management Organize design elements in CE.SDK using a hierarchical layer stack to control stacking order, visibility, and element relationships. ![Layer Management Hero](/docs/cesdk/_astro/browser.hero.BMzQlIVg_Z2cBFQ9.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-layer-management-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // Create a design scene with a page await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]!; engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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. ``` // Create a colored rectangleconst 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[#](#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[#](#getting-a-blocks-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). ``` // Get the parent of a blockconst 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[#](#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. ``` // Get all children of the pageconst 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[#](#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[#](#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. ``` // Add blocks to the page - last appended is on topengine.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[#](#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. ``` // 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[#](#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[#](#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[#](#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. ``` // Bring the red rectangle to the frontengine.block.bringToFront(redRect);console.log('Red rectangle brought to front'); ``` ### Send to Back[#](#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. ``` // Send the blue rectangle to the backengine.block.sendToBack(blueRect);console.log('Blue rectangle sent to back'); ``` ### Move Forward One Layer[#](#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. ``` // Move the green rectangle forward one layerengine.block.bringForward(greenRect);console.log('Green rectangle moved forward'); ``` ### Move Backward One Layer[#](#move-backward-one-layer) We move an element one position backward using `engine.block.sendBackward()`. This swaps the block with its immediate sibling behind. ``` // Move the yellow rectangle backward one layerengine.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[#](#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[#](#checking-and-toggling-visibility) We query the current visibility state using `engine.block.isVisible()` and change it using `engine.block.setVisible()`. ``` // Check and toggle visibilityconst isVisible = engine.block.isVisible(blueRect);console.log('Blue rectangle visible:', isVisible); // Hide the blue rectangle temporarilyengine.block.setVisible(blueRect, false);console.log('Blue rectangle hidden'); // Show it again for the final compositionengine.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[#](#managing-block-lifecycle) CE.SDK provides methods for duplicating blocks to create copies and destroying blocks to remove them permanently. ### Duplicating Blocks[#](#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. ``` // Duplicate a blockconst duplicateGreen = engine.block.duplicate(greenRect);engine.block.setPositionX(duplicateGreen, 400);engine.block.setPositionY(duplicateGreen, 300);// Change the duplicate's color to purpleconst 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[#](#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. ``` // Check if a block is valid before operationsconst isValidBefore = engine.block.isValid(yellowRect);console.log('Yellow rectangle valid before destroy:', isValidBefore); ``` ### Removing Blocks[#](#removing-blocks) We permanently remove a block and all its children from the scene using `engine.block.destroy()`. This operation cannot be undone programmatically. ``` // Remove a block from the sceneengine.block.destroy(yellowRect);console.log('Yellow rectangle destroyed'); // Check validity after destructionconst 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[#](#framing-the-result) After making layer changes, we zoom to fit the page in the viewport so the composition is clearly visible. ``` engine.scene.zoomToBlock(page, 40, 40, 40, 40); ``` ## Troubleshooting[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/group-and-ungroup-62565a) --- # Group and Ungroup Objects Group multiple blocks to move, scale, and transform them as a single unit; ungroup to edit them individually. ![Group and Ungroup Objects example showing grouped rectangles in CE.SDK](/docs/cesdk/_astro/browser.hero.BVZn_DGh_WiXiM.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-grouping-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. Groups are not currently available when editing videos. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } const engine = cesdk.engine; // Create a design scene and get the page await cesdk.createDesignScene(); const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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. **What cannot be grouped** * Scene blocks cannot be grouped * Blocks already part of a group cannot be grouped again until ungrouped ## Create the Blocks[#](#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. ``` // Create a graphic block with a colored rectangle shapeconst 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[#](#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. ``` // Check if the blocks can be grouped togetherconst canGroup = engine.block.isGroupable([block1, block2, block3]);console.log('Blocks can be grouped:', canGroup); ``` ## Create a Group[#](#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. ``` // Group the blocks togetherif (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[#](#navigate-group-selection) CE.SDK provides methods to navigate into and out of groups while editing. ### Enter a Group[#](#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. ``` // Enter the group to select individual membersengine.block.enterGroup(groupId); // Select a specific member within the groupengine.block.setSelected(block2, true);console.log('Selected member inside group'); ``` ### Exit a 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. ``` // Exit the group to return selection to the parent groupengine.block.exitGroup(block2);console.log('Exited group, group is now selected'); ``` ## Find and Inspect Groups[#](#find-and-inspect-groups) Discover groups in a scene and inspect their contents using `engine.block.findByType()`, `engine.block.getType()`, and `engine.block.getChildren()`. ``` // Find all groups in the sceneconst allGroups = engine.block.findByType('group');console.log('Number of groups in scene:', allGroups.length); // Check the type of the group blockconst groupType = engine.block.getType(groupId);console.log('Group block type:', groupType); // Get the members of the groupconst 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[#](#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. ``` // Ungroup the blocks to make them independent againengine.block.ungroup(groupId);console.log('Ungrouped blocks'); // Verify blocks are no longer in a groupconst groupsAfterUngroup = engine.block.findByType('group');console.log('Groups after ungrouping:', groupsAfterUngroup.length); ``` ## Troubleshooting[#](#troubleshooting) ### Blocks Cannot Be Grouped[#](#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[#](#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[#](#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[#](#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[#](#next-steps) Explore related topics: * [Layer Management](vue/create-composition/layer-management-18f07a/) \- Control z-order and visibility of blocks * [Position and Align](vue/insert-media/position-and-align-cc6b6a/) \- Arrange blocks precisely on the canvas * [Lock Design](vue/create-composition/lock-design-0a81de/) \- Prevent modifications to specific elements --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/collage-f7d28d) --- # Create a Collage 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. ![Example of a layout change in the demo app](/docs/cesdk/_astro/browser-collage.DQNEtX9t_1SILxi.png) 15 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/main/showcase-layouts?title=IMG.LY+CE.SDK%3A+Layouts&file=src%2Fcomponents%2Fcase%2FCaseComponent.jsx)[ GitHub](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/CaseComponent.jsx) **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[#](#what-youll-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[#](#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[#](#difference-between-layouts-and-templates) Layouts are [custom assets](vue/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[#](#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[#](#add-a-layouts-panel-to-the-cesdk-ui) The CE.SDK allows you to add [custom panels to the UI](vue/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/main/showcase-layouts/public/cases/layouts)). ### 1\. Add a Layouts Option to the CE.SDK Menu[#](#1-add-a-layouts-option-to-the-cesdk-menu) To **customize the CE.SDK UI** and create a **Layouts** panel, use the CE.SDK UserInterfaceAPI. Add a Layout button with `ui.setDockOrder()`, using the following properties: * `id` * `key` * `label` * `icon` * `entries` For example: ``` cesdk.ui.setDockOrder([ { id: 'ly.img.assetLibrary.dock', key: 'layouts-dock-entry', label: 'Layouts', icon: '@imgly/Layout', entries: ['layouts'], },]); ``` ![Default dock vs. custom dock with layout option.](/docs/cesdk/_astro/browser-add-layout.Dk46Per3_1JaxQd.png) ### 2\. Customize the Layouts Panel Appearance[#](#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: CustomCase.jsx ``` instance.ui.addAssetLibraryEntry({id: 'ly.img.layouts', // Referenced in the dock entry in Step 1sourceIds: ['ly.img.layouts'], // Points to our custom layout asset sourcepreviewLength: 2, // Number of preview items int the compact panelgridColumns: 2, // Organize tiles in 2 columnsgridItemHeight: 'square', // Square tilespreviewBackgroundType: 'contain', // Fit compact panel backgroundgridBackgroundType: 'contain' // Fit panel background}); ``` ![Default dock vs. custom dock with layout option.](/docs/cesdk/_astro/browser-panel-layout.DCB5qRZd_1viEcL.png) To learn more about panel customization, check the [Panel Customization guide](vue/user-interface/customization/panel-7ce1ee/) . ### 3\. Load Custom Assets[#](#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}}`. 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[#](#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[#](#1-prepare-and-allow-changes) * Allow block deletion with `editor.setGlobalScope('lifecycle/destroy', 'Allow')`. * Clear the selected blocks with `block.setSelected(block, false)`. 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[#](#2-load-the-new-layout) * Load the new layout with `block.loadFromString()`. * Return the first page from the loaded blocks as the layout page. 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[#](#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. 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[#](#4-transfer-content) Copy user text and images onto the new layout: createApplyLayoutAsset.js ``` copyAssets(engine, oldPage, page); ``` ### 5\. Sort Blocks Visually and Pair Content[#](#5-sort-blocks-visually-and-pair-content) Grab text and image blocks from both pages in visual order (top to bottom, left to right): 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: [ Step 1: Copy text content, font, color ](#tab-panel-299)[ Step 2: Copy images and placeholder behavior ](#tab-panel-300) 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); ``` 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[#](#6-cleanup-and-restore-state) Cleanup temporary blocks with `block.destroy()` and restore global scope: 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[#](#advanced-collage-techniques) [ Keep Placeholder Behavior ](#tab-panel-301)[ Distribute Content Evenly ](#tab-panel-302)[ Animate Layout Transitions ](#tab-panel-303) The CE.SDK BlockAPI eases the transfer of [placeholder behavior](vue/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()` 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: createApplyLayoutAsset.js ``` for ( let index = 0; index < imagesOnToPage.length && index < imagesOnFromPage.length; index++) { ... } ``` 2. Fill empty slots with defaults or placeholders: createApplyLayoutAsset.js ``` // loop condition ends when sources run out, leaving remaining target slots unchangedindex < imagesOnToPage.length && index < imagesOnFromPage.length; ``` 3. List content by importance or metadata: 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: LoadingSpinner.tsx ``` const LoadingSpinner = () => { return
;}; ``` You can code more customizations to incorporate these effects into collage transitions: * Fading * Sliding * Morphing ## Optimize the Layout Workflow[#](#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[#](#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[#](#next-steps) Now that know how to create collages with layouts, explore these related guides to expand your CE.SDK knowledge: * [Apply Templates](vue/create-templates/overview-4ebe30/) \- Work with templates instead of layouts * [Create Custom Asset Sources](vue/import-media/asset-panel/basics-f29078/) \- Import custom assets * [Customize UI Panels](vue/user-interface/customization/panel-7ce1ee/) \- Advanced UI customization * [Work with Images](vue/insert-media/images-63848a/) \- Manage image blocks and fills * [Scene Management](vue/open-the-editor/load-scene-478833/) \- Load and save scenes --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/blend-modes-ad3519) --- # Blend Modes Control how design blocks visually blend with underlying layers using CE.SDK’s blend mode system for professional layered compositions. ![Blend Modes example showing layered images with blend effects applied](/docs/cesdk/_astro/browser.hero.yB8b1WqE_1yPznY.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-blend-modes-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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; // Load assets and create a design scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); 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); 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); 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[#](#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. ``` // Check if the block supports blend modes before applyingif (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[#](#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. ``` // Apply a different blend mode to each overlayconst blendMode = blendModes[index];engine.block.setBlendMode(overlayBlock, blendMode); ``` After setting a blend mode, you can confirm the change by retrieving the current value: ``` // Retrieve and log the current blend modeconst currentMode = engine.block.getBlendMode(overlayBlock);console.log(`Cell ${index + 1} blend mode:`, currentMode); ``` ## Available Blend Modes[#](#available-blend-modes) CE.SDK provides 27 blend modes organized into categories, each producing different visual results: ### Normal Modes[#](#normal-modes) * **`PassThrough`** - Allows children of a group to blend with layers below the group * **`Normal`** - Default mode with no blending effect ### Darken Modes[#](#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[#](#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[#](#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[#](#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[#](#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[#](#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. ``` // Check if the block supports opacityif (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: ``` // Retrieve and log the opacity valueconst opacity = engine.block.getOpacity(overlayBlock);console.log(`Cell ${index + 1} opacity:`, opacity); ``` 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[#](#troubleshooting) ### Blend Mode Has No Visible Effect[#](#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[#](#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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/create-composition/add-background-375a47) --- # Add a Background Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks. ![Add a Background example showing gradient page fill, text with background color, and image shape](/docs/cesdk/_astro/browser.hero.CJFB9Ps3_XUAH7.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-add-background-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } const engine = cesdk.engine; // Create a design scene and get the page await cesdk.createDesignScene(); const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#setup) Create a design scene and get a reference to the page where we’ll apply backgrounds. ``` // Create a design scene and get the pageawait cesdk.createDesignScene();const pages = engine.block.findByType('page');const page = pages[0];if (!page) { throw new Error('No page found');} ``` ## Fills[#](#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[#](#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. ``` // Check feature support on different blocksconst 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[#](#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. ``` // Check if the page supports fill, then apply a pastel gradientif (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[#](#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. ``` // Check if the block supports fill, then apply an image fillif (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) 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[#](#check-background-color-support) Use `supportsBackgroundColor()` to verify a block supports this feature. Currently, only text blocks support background colors. ``` // Check feature support on different blocksconst 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[#](#apply-background-color) Enable the background color with `setBackgroundColorEnabled()`, then configure its appearance using property paths for color, padding, and corner radius. ``` // Add white background color to the featured text blockif (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[#](#troubleshooting) ### Fill Not Visible[#](#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[#](#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[#](#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[#](#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[#](#next-steps) Explore related topics: * [Apply Colors](vue/colors/apply-2211e3/) \- Work with RGB, CMYK, and spot colors * [Fills Overview](vue/fills/overview-3895ee/) \- Learn about all fill types in depth --- [Source](https:/img.ly/docs/cesdk/vue/create-audio/audio-2f700b) --- # Audio 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-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[#](#how-audio-works-in-the-cesdk) The CE.SDK represents audio as audio blocks. Each block has: * Source (file or extracted track) * Playback properties (time, speed, volume, mute) * Timeline properties (offset, duration, trim length) * Optional waveform thumbnails for UI visualization ### What Are the Timeline Properties[#](#what-are-the-timeline-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 timeline. * **Trim length**: cuts the audio to keep only a specific part of it. * **Duration**: defines how long the audio plays. ### What Are Waveforms[#](#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[#](#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[#](#cesdk-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`. | | **Timeline Management** | Offset | `setTimeOffset` | To move the playback starting point in the scene timeline. | | | 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[#](#examples) Find in the following list of examples different API calls listed in the preceding table. ### Create Audio[#](#create-audio) [ Create Audio ](#tab-panel-209)[ Extract Audio from a Video ](#tab-panel-210)[ Add Audio Sources ](#tab-panel-211)[ Export ](#tab-panel-212) To create an empty audio block, use: ``` 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. `videoBlockId` and `pageId` must refer to existing blocks. ``` const audioBlockId = engine.block.createAudioFromVideo(videoBlockId, 0);// Attach to the page so it’s part of the sceneengine.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. `pageId` must refer to an existing page. ``` // 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/v1/ly.img.audio/audios/far_from_home.m4a' ); ``` For details on loading sources, check [the dedicated guide](vue/import-media/from-remote-source/unsplash-8f31f0/) . This example exports the audio block in **mp4** format: `blockId` must refer to an existing audio block. ``` engine.block.exportAudio(blockId, { mymeType: 'audio/mp4' }); ``` This example exports the audio in **wav** format: ``` const audioData = await engine.block.exportAudio(blockId, { mimeType: 'audio/wav' }); ``` ### Control Audio Playback[#](#control-audio-playback) [ Basic Playback Control ](#tab-panel-205)[ Volume ](#tab-panel-206)[ Mute ](#tab-panel-207)[ Speed ](#tab-panel-208) Use `engine.block.setPlaybackTime(blockId, time: number)`. This example sets the current playback at 3 seconds: ``` engine.block.setPlaybackTime(blockId, 3) ``` Use `engine.block.setVolume(blockId, volume: number);`. This example sets the volume at 1 (max value): ``` engine.block.setVolume(blockId, 1); ``` Use `engine.block.setMuted(blockId, muted: boolean);`. This example mutes the audio of the block: ``` engine.block.setMuted(blockId, true); ``` Use `engine.block.setPlaybackSpeed(blockId, speed: number);`. This example multiplies the speed by 0.25: ``` engine.block.setPlaybackSpeed(blockId, 0.25); ``` ### Manage Audio Timeline[#](#manage-audio-timeline) [ Offset ](#tab-panel-213)[ Duration ](#tab-panel-214)[ Trimming ](#tab-panel-215)[ Generate Audio Thumbnails ](#tab-panel-216) 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 on the timeline: ``` engine.block.setTimeOffset(blockId, 2); ``` Use `engine.block.setDuration(blockId, duration: number)`. This example sets the audio duration for 300 seconds: ``` 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. ``` engine.block.setTrimOffset(blockId, 2)engine.block.setTrimLength(blockId, 10); ``` Use: ``` 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) `audioBlockId` must refer to an existing block. ``` 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[#](#next-steps) For each feature’s detailed instructions and options: * Explore the [CE.SDK API options](vue/api-reference/overview-8f24e1/) . * Check the dedicated guides in the audio section. --- [Source](https:/img.ly/docs/cesdk/vue/conversion/to-png-f1660c) --- # To PNG Export designs to PNG format with lossless quality and optional transparency support. ![Export to PNG example showing CE.SDK with PNG export buttons](/docs/cesdk/_astro/browser.hero.BDk8dEHt_IQeIB.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-png-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v1/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.insertNavigationBarOrderComponent('last', { 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[#](#export-to-png) Use `engine.block.export()` to export a design block to PNG. The method returns a Blob containing the image data. ``` // Export programmatically using the engine APIconst exportProgrammatically = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png' }); await cesdk.utils.downloadFile(blob, 'image/png');}; ``` ## Compression Level[#](#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. ``` // Export with compression level (0-9)// Higher values produce smaller files but take longerconst 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[#](#target-dimensions) Resize the output by setting `targetWidth` and `targetHeight`. The block scales to fill the target dimensions while maintaining its aspect ratio. ``` // Export with target dimensions// The block scales to fill the target while maintaining aspect ratioconst 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[#](#trigger-the-export-action) The built-in `exportDesign` action triggers the default export workflow with a loading dialog and automatically downloads the file. ``` // Trigger the built-in export actionconst triggerExportAction = async () => { await cesdk.actions.run('exportDesign', { mimeType: 'image/png' });}; ``` ## Override the Export Action[#](#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. ``` // Override the default export action to customize behaviorcesdk.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-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[#](#next-steps) * [Conversion Overview](vue/conversion/overview-44dc58/) \- Learn about other export formats * [Export Overview](vue/export-save-publish/export/overview-9ed3a8/) \- Understand the full export workflow * [To PDF](vue/conversion/to-pdf-eb937f/) \- Export designs to PDF format --- [Source](https:/img.ly/docs/cesdk/vue/conversion/to-pdf-eb937f) --- # To PDF 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. ![To PDF](/docs/cesdk/_astro/browser.hero.ScBvUdXV_ZGsisU.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-pdf-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); const engine = cesdk.engine; // Load a template scene await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v1/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.insertNavigationBarOrderComponent('last', { 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[#](#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[#](#convert-a-single-image-to-pdf) The example below loads an image, applies transformations, and exports it as a PDF. ``` // Prepare an image URLconst imageURL = 'https://img.ly/static/ubq_samples/sample_4.jpg'; // Create a new scene by loading the image immediatelyawait cesdk.createFromImage(imageURL); // Find the automatically added graphic block with an image fillconst block = engine.block.findByType('graphic')[0]; // Apply crop with a scale ratio of 2.0engine.block.setCropScaleRatio(block, 2.0); // Export as PDF Blobconst 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[#](#combine-multiple-images-into-a-single-pdf) The example below demonstrates how to merge multiple images into a single PDF document. ``` // Prepare image URLsconst 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' layoutconst scene = engine.scene.create('VerticalStack');const [stack] = engine.block.findByType('stack'); // Load all images as pagesfor (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 blobconst 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[#](#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. ``` // 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[#](#download-the-pdf) Use `cesdk.utils.downloadFile()` to save the exported PDF to the user’s device. ``` // Download using CE.SDK utilsawait 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[#](#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](vue/insert-media/images-63848a/) and perform any edits or adjustments. ### Add a PDF Export Button[#](#add-a-pdf-export-button) Integrate PDF export into the CE.SDK interface by adding a custom button to the navigation bar. ``` // Add PDF export buttons to the navigation barcesdk.ui.insertNavigationBarOrderComponent('last', { 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[#](#alternative-register-a-custom-component) You can also use `ui.registerComponent` to create a more customized button with full control over styling and behavior. ``` // Register a custom button componentcesdk.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 barcesdk.ui.setNavigationBarOrder([ ...cesdk.ui.getNavigationBarOrder(), 'convert.nav',]); ``` For more details on customizing the UI, see the [User Interface Configuration Guide](vue/user-interface/customization-72b2f8/) . ## Configure PDF Output Settings[#](#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[#](#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[#](#adjust-dpi-for-print-quality) Control the output resolution by setting the scene’s DPI property before export. ``` // Adjust the scene DPI for print-ready output// Higher DPI = better quality but larger file sizeengine.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) Enable high compatibility mode for consistent rendering across different PDF viewers. ``` // Enable high compatibility mode for consistent rendering across PDF viewers// This rasterizes complex elements like gradients with transparency at scene DPIconst 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[#](#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 ``` const scene = engine.scene.get()!; // For maximum performance (test with your print workflow first)engine.block.setFloat(scene, 'scene/dpi', 150); // Reduce from default 300const 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-specialty-printing) Add an underlayer for printing on non-white or transparent media like fabric or glass. ``` // Define the underlayer spot color before export// RGB values (0.8, 0.8, 0.8) provide a preview representationengine.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. ``` // Export with underlayer for special media printingconst 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[#](#customizing-pdf-output) You can configure all PDF settings together when exporting. ``` const scene = engine.scene.get()!; // Adjust the DPI to 72engine.block.setFloat(scene, 'scene/dpi', 72); // Set spot color to be used as underlayerengine.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[#](#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[#](#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[#](#next-steps) * [Export Options](vue/export-save-publish/export/overview-9ed3a8/) — Explore all available export formats and configuration * [Size Limits](vue/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints * [Export for Printing](vue/export-save-publish/for-printing-bca896/) — Learn more about print-specific export settings --- [Source](https:/img.ly/docs/cesdk/vue/conversion/to-base64-39ff25) --- # To Base64 Convert CE.SDK exports to Base64-encoded strings for embedding in HTML, storing in databases, or transmitting via JSON APIs. ![To Base64](/docs/cesdk/_astro/browser.hero.C5VdtRLz_Z2dL4W9.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-base64-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); const engine = cesdk.engine; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage()!; await engine.scene.zoomToBlock(page); cesdk.ui.insertNavigationBarOrderComponent('last', { 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[#](#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. ``` 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-blob-to-base64) Convert the exported Blob into a Base64 data URI using the browser’s `FileReader` API. ``` 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[#](#customize-the-built-in-export-action) Override the default `exportDesign` action to integrate Base64 conversion into CE.SDK’s built-in export flow. ``` 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[#](#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[#](#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 Base64 increases file size by approximately 33%. For images larger than 100KB, consider binary storage or direct URL references instead. ## Troubleshooting[#](#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[#](#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[#](#next-steps) * [Export Options](vue/export-save-publish/export/overview-9ed3a8/) — Explore all available export formats and configuration * [Export to PDF](vue/export-save-publish/export/to-pdf-95e04b/) — Generate PDFs for print and document workflows * [Partial Export](vue/export-save-publish/export/partial-export-89aaf6/) — Export specific regions or individual elements * [Size Limits](vue/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints --- [Source](https:/img.ly/docs/cesdk/vue/conversion/overview-44dc58) --- # Overview CreativeEditor SDK (CE.SDK) allows you to export designs into a variety of formats, making it easy to prepare assets for web publishing, printing, storage, and other workflows. You can trigger conversions either programmatically through the SDK’s API or manually using the built-in export options available in the UI. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## Supported Input and Output Formats[#](#supported-input-and-output-formats) CE.SDK accepts a range of input formats when working with designs, including: | Category | Supported Formats | | --- | --- | | **Images** | `.png`, `.jpeg`, `.jpg`, `.gif`, `.webp`, `.svg`, `.bmp` | | **Video** | `.mp4` (H.264/AVC, H.265/HEVC), `.mov` (H.264/AVC, H.265/HEVC), `.webm` (VP8, VP9, AV1) | | **Audio** | `.mp3`, `.m4a`, `.mp4` (AAC or MP3), `.mov` (AAC or MP3) | | **Animation** | `.json` (Lottie) | Need to import a format not listed here? CE.SDK allows you to create custom importers for any file type by using our Scene and Block APIs programmatically. When it comes to exporting or converting designs, the SDK supports the following output formats: | Category | Supported Formats | | --- | --- | | **Images** | `.png` (with transparency), `.jpeg`, `.webp`, `.tga` | | **Video** | `.mp4` (H.264 or H.265 on supported platforms with limited transparency support) | | **Print** | `.pdf` (supports underlayer printing and spot colors) | | **Scene** | `.scene` (description of the scene without any assets) | | **Archive** | `.zip` (fully self-contained archive that bundles the `.scene` file with all assets) | Our custom cross-platform C++ based rendering and layout engine ensures consistent output quality across devices. Each format serves different use cases, giving you the flexibility to adapt designs for your application’s needs. ## Conversion Methods[#](#conversion-methods) There are two main ways to trigger a conversion: * **Programmatically:** Use CE.SDK’s API methods to perform conversions directly from your code. This gives you full control over the export process, allowing you to customize settings, automate workflows, and integrate with other systems. * **Through the UI:** End users can trigger exports manually through CE.SDK’s built-in export options. The UI provides an intuitive way to export designs without writing code, ideal for non-technical users. Both methods provide access to core conversion features, ensuring you can choose the workflow that fits your project. ## Customization Options[#](#customization-options) When exporting designs, CE.SDK offers several customization options to meet specific output requirements: * **Resolution and DPI Settings:** Adjust the resolution for raster exports like PNG to optimize for screen or print. * **Output Dimensions:** Define custom width and height settings for the exported file, independent of the original design size. * **File Quality:** For formats that support compression (such as PNG or PDF), you can control the quality level to balance file size and visual fidelity. * **Background Transparency:** Choose whether to preserve transparent backgrounds or export with a solid background color. * **Page Selection:** When exporting multi-page documents (e.g., PDFs), you can select specific pages or export all pages at once. * **Video Frame Selection:** When exporting from a video, you can select a specific frame to export as an image, allowing for thumbnail generation or frame captures. These options help ensure that your exported content is optimized for its intended platform, whether it’s a website, a mobile app, or a print-ready document. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/undo-and-history-99479d) --- # Undo and History Implement undo/redo functionality and manage multiple history stacks to track editing operations. ![Undo and History example showing the CE.SDK editor with undo/redo controls](/docs/cesdk/_astro/browser.hero.f2TWgT48_Ztwbgd.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-undo-and-history-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // Create a design scene await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]!; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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. ``` // Load assets and create sceneawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true}); // Create a design sceneawait cesdk.createDesignScene(); const engine = cesdk.engine;const page = engine.block.findByType('page')[0]!; // Set page dimensionsengine.block.setWidth(page, 800);engine.block.setHeight(page, 600); ``` ## Using the Built-in Undo/Redo UI[#](#using-the-built-in-undoredo-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[#](#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. ``` // Create a triangle shape and add an undo step to record it in historyconst 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 undoneengine.editor.addUndoStep(); ``` After creating the shape, `canUndo()` returns `true` since the operation has been recorded as an undoable step. ## Performing Undo and Redo Operations[#](#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. ``` // Undo the block creationif (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. ``` // Redo to restore the blockif (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[#](#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. ``` // Subscribe to history updates to track state changesconst 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[#](#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. ``` // Manually add an undo step after custom operationsengine.block.setPositionX(block, 190);engine.editor.addUndoStep();console.log('Manual undo step added. canUndo:', engine.editor.canUndo()); // Remove the most recent undo step if neededif (engine.editor.canUndo()) { engine.editor.removeUndoStep(); console.log('Most recent undo step removed');}// Reset block position to its original locationengine.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[#](#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[#](#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. ``` // Create a second history stack for isolated operationsconst secondaryHistory = engine.editor.createHistory();const primaryHistory = engine.editor.getActiveHistory();console.log( 'Created secondary history. Primary:', primaryHistory, 'Secondary:', secondaryHistory); // Switch to the secondary historyengine.editor.setActiveHistory(secondaryHistory);console.log( 'Switched to secondary history. Active:', engine.editor.getActiveHistory()); // Operations in secondary history are isolated from the primary historyconst 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 historyengine.editor.addUndoStep();console.log( 'Block added in secondary history. canUndo:', engine.editor.canUndo()); // Switch back to primary historyengine.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[#](#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. ``` // Clean up the secondary history when no longer neededengine.editor.destroyHistory(secondaryHistory);console.log('Secondary history destroyed'); ``` ## Troubleshooting[#](#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. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/terminology-99e82d) --- # Terminology 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[#](#core-architecture) ### Engine[#](#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[#](#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_ operate in one of two modes: * **Design Mode**: Static designs like social posts, print materials, and graphics * **Video Mode**: Timeline-based content with duration, playback, and animation See [Scenes](vue/concepts/scenes-e8596d/) for details. ### Page[#](#page) _Pages_ are containers within a _Scene_ that hold content _Blocks_ (see below) and define working area dimensions. In _Design Mode_, pages are individual artboards. In _Video Mode_, pages are timeline compositions where _Blocks_ are arranged across time. See [Pages](vue/concepts/pages-7b6bae/) for details. ### Block[#](#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](vue/concepts/blocks-90241e/) for details. ## Block Anatomy[#](#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[#](#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](vue/fills/color-7129cd/) , [Gradient Fills](vue/filters-and-effects/gradients-0ff079/) , [Image Fills](vue/fills/image-e9cb5c/) , and [Video Fills](vue/fills/video-ec7f9f/) guides. ### Shape[#](#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](vue/shapes-9f1b2c/) for details. ### Effect[#](#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](vue/filters-and-effects-6f88ac/) for details. ### Blur[#](#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 **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](vue/filters-and-effects/blur-71d642/) for details. ### Drop Shadow[#](#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. Unlike effects, drop shadow is configured directly on the block rather than created and attached separately. ## Block Handling[#](#block-handling) These terms describe how _Blocks_ are categorized and identified. ### Type[#](#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[#](#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[#](#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](vue/concepts/blocks-90241e/) for details. ## Assets and Resources[#](#assets-and-resources) ### Asset[#](#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[#](#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[#](#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](vue/concepts/resources-a58d71/) for details. ### Buffer[#](#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](vue/concepts/buffers-9c565b/) for details. ## Templating and Automation[#](#templating-and-automation) These terms describe dynamic content and reusable designs. ### Template[#](#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[#](#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](vue/create-templates/add-dynamic-content/placeholders-d9ba8a/) for details. ### Variable[#](#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](vue/create-templates/add-dynamic-content/text-variables-7ecb50/) for details. ## Permissions and Scopes[#](#permissions-and-scopes) These terms relate to controlling what operations are allowed. ### Scope[#](#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](vue/create-templates/lock-131489/) for details. ### Role[#](#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[#](#layout-and-units) These terms relate to positioning and measurement. ### Design Unit[#](#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](vue/concepts/design-units-cc6597/) for details. ### DPI (Dots Per Inch)[#](#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[#](#operating-modes) These terms describe how CE.SDK runs. ### Scene Mode[#](#scene-mode) The operational mode of a _Scene_ determining available features: * **Design Mode**: Static designs. No timeline, no playback. Content arranged spatially on pages. * **Video Mode**: Time-based content. Includes timeline, playback controls, duration properties, and animations. Choose the mode when creating a scene—it affects which properties and operations are available. See [Scenes](vue/concepts/scenes-e8596d/) for details. ### Headless Mode[#](#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](vue/concepts/headless-mode/browser-24ab98/) for setup. ## Events and State[#](#events-and-state) These terms relate to monitoring changes. ### Event / Subscription[#](#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](vue/concepts/events-353f97/) for details. ### Block State[#](#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_. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/scenes-e8596d) --- # Scenes 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. ![Scenes example showing a two-page design with different shapes on each page](/docs/cesdk/_astro/browser.hero.Bxu2OFuX_Z1p4s6c.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-scenes-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and default assets await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); 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(); console.log('Scene design unit:', currentUnit); // Get the scene layout const layout = engine.scene.getLayout(); console.log('Scene layout:', layout); // Check scene mode (Design or Video) const mode = engine.scene.getMode(); console.log('Scene mode:', mode); // Access pages within the scene const pages = engine.scene.getPages(); console.log('Number of pages:', pages.length); // Get the current page (nearest to viewport center) const currentPage = engine.scene.getCurrentPage(); 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(); console.log('Current zoom level:', zoomLevel); // Save the scene to a string for persistence const sceneString = await engine.scene.saveToString(); 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); 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 }); } 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[#](#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-scenes) ### Creating an Empty Scene[#](#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. ``` // Create a new design scene with VerticalStack layout// The layout controls how pages are arranged in the canvasengine.scene.create('VerticalStack'); // Get the stack container and add spacing between pagesconst 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 ### Creating for Video Editing[#](#creating-for-video-editing) For video projects, use `engine.scene.createVideo()` which configures the scene for timeline-based editing: ``` const videoScene = engine.scene.createVideo({ page: { size: { width: 1920, height: 1080 } }}); ``` ### Adding Pages[#](#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. ``` // Create the first pageconst page1 = engine.block.create('page');engine.block.setWidth(page1, 800);engine.block.setHeight(page1, 600);engine.block.appendChild(stack, page1); // Create a second pageconst page2 = engine.block.create('page');engine.block.setWidth(page2, 800);engine.block.setHeight(page2, 600);engine.block.appendChild(stack, page2); ``` ### Adding Blocks[#](#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. ``` // Add a shape to the first pageconst 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 pageconst 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[#](#scene-properties) ### Design Units[#](#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. ``` // Query scene propertiesconst currentUnit = engine.scene.getDesignUnit();console.log('Scene design unit:', currentUnit); // Get the scene layoutconst layout = engine.scene.getLayout();console.log('Scene layout:', layout); // Check scene mode (Design or Video)const mode = engine.scene.getMode();console.log('Scene mode:', mode); ``` Supported units are `'Pixel'`, `'Millimeter'`, and `'Inch'`. For more details, see the [Design Units](vue/concepts/design-units-cc6597/) guide. ### Scene Mode[#](#scene-mode) Scenes operate in either Design mode or Video mode, determined at creation time. Use `engine.scene.getMode()` to check which mode is active: * **Design** — For static designs like posters, social media graphics, and print materials * **Video** — For timeline-based editing with animations and video clips ### Scene Layout[#](#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[#](#page-navigation) Access pages within your scene using these methods: ``` // Access pages within the sceneconst pages = engine.scene.getPages();console.log('Number of pages:', pages.length); // Get the current page (nearest to viewport center)const currentPage = engine.scene.getCurrentPage();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[#](#camera-and-zoom) ### Zoom to Block[#](#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: ``` // Zoom to show all pages in the sceneconst scene = engine.scene.get();if (scene) { await engine.scene.zoomToBlock(scene, { padding: 50 });} // Get the current zoom levelconst zoomLevel = engine.scene.getZoomLevel();console.log('Current zoom level:', zoomLevel); ``` ### Zoom Level[#](#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[#](#auto-fit-zoom) For continuous auto-framing, use `engine.scene.enableZoomAutoFit()` to automatically keep a block centered as the viewport resizes. ## Saving Scenes[#](#saving-scenes) ### Saving to String[#](#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. ``` // Save the scene to a string for persistenceconst sceneString = await engine.scene.saveToString();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-scenes) ### Loading from String[#](#loading-from-string) Use `engine.scene.loadFromString()` to restore a scene from a saved string: ``` // Demonstrate loading the scene from the saved string// This replaces the current scene with the saved versionawait engine.scene.loadFromString(sceneString);console.log('Scene loaded from saved string'); // Zoom to show all loaded pagesconst 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[#](#loading-from-url) Use `engine.scene.loadFromURL()` to load a scene directly from a remote location: ``` await engine.scene.loadFromURL('https://example.com/design.scene'); ``` ## Troubleshooting[#](#troubleshooting) ### Blocks Not Visible[#](#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[#](#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[#](#zoom-not-working) Verify the scene has a valid camera. Some UI configurations may override programmatic zoom controls. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/templating-f94385) --- # Templating Templates transform static designs into dynamic, data-driven content. They combine reusable layouts with variable text and placeholder media, enabling personalization at scale. ![Templating example showing a personalized postcard design](/docs/cesdk/_astro/browser.hero.DKLcd69q_2gb5Wr.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-templating-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK and add demo asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); 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/v1/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(); 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!'); 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(); console.log('Template placeholders:', placeholders.length); 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[#](#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) Variables enable dynamic text without modifying the design structure. Text blocks contain `{{variableName}}` tokens that CE.SDK resolves at render time. ``` // Set variable values to personalize the template// These values replace {{variableName}} tokens in text blocksengine.variable.setString('Name', 'Jane');engine.variable.setString('Greeting', 'Wish you were here!');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 →](vue/create-templates/add-dynamic-content/text-variables-7ecb50/) ## Placeholders[#](#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 →](vue/create-templates/add-dynamic-content/placeholders-d9ba8a/) ## Template Workflows[#](#template-workflows) Templates support several common workflows: ### Form-Based Customization[#](#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[#](#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[#](#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[#](#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: ``` // Load a postcard template from URL// Templates are scenes containing variable tokens and placeholder blocksconst templateUrl = 'https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene';await engine.scene.loadFromURL(templateUrl); // Zoom to show the full page in the viewportconst 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 →](vue/create-templates/import-e50084/) ## Creating Templates[#](#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 →](vue/create-templates/from-scratch-663cda/) --- [Source](https:/img.ly/docs/cesdk/vue/concepts/resources-a58d71) --- # Resources Manage external media files—images, videos, audio, and fonts—that blocks reference via URIs in CE.SDK. ![Resources example showing a scene with image and video blocks](/docs/cesdk/_astro/browser.hero.Wvxhme2z_Z1AOS0d.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-resources-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Video mode (required for video resources) cesdk.feature.enable('ly.img.video'); await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); 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[#](#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. ``` // Get the current scene and pageconst 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 marginsconst margin = 30;const gap = 20;const blockWidth = 300;const blockHeight = 200; // Set page dimensions to hug the blocksconst 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 blockconst 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 renderedconst 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[#](#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. ``` // Preload all resources in the scene before rendering// This ensures resources are cached and ready for displayconsole.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[#](#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. ``` // Create a second graphic block for videoconst 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 fillconst 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 propertiesconsole.log('Preloading video resource...');await engine.block.forceLoadAVResource(videoFill);console.log('Video resource preloaded'); // Now we can query video propertiesconst 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[#](#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. ``` // Find all transient resources that need persistence before export// Transient resources include buffers and blobs that won't survive serializationconst 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[#](#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. ``` // Get all media URIs referenced in the scene// Useful for pre-fetching or validating resource availabilityconst 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[#](#detecting-mime-types) Determine a resource’s content type with `getMimeType()`. The engine downloads the resource if it’s not already cached. ``` // Detect the MIME type of a resource// This downloads the resource if not already cachedconst 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[#](#relocating-resources) Update URL mappings when resources move with `relocateResource()`. This modifies the internal cache without changing scene data. ``` // Relocate a resource when its URL changes// This updates the internal cache mapping without modifying scene dataconst 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[#](#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. ``` // 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[#](#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. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/pages-7b6bae) --- # Pages 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. ![CE.SDK Pages Hero Image](/docs/cesdk/_astro/browser.hero.CFgjZ28G_1cgdMO.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) 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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); 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); // Check the scene mode (Design vs Video) const sceneMode = engine.scene.getMode(); console.log('Scene mode:', sceneMode); // 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[#](#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. ``` // Create a scene with VerticalStack layout for multi-page designsengine.scene.create('VerticalStack'); // Get the stack container to configure spacingconst [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()`. ``` // Create the first page and set its dimensionsconst 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 dimensionsconst 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. ``` // Add an image block to the first pageconst imageBlock = engine.block.create('graphic');engine.block.appendChild(firstPage, imageBlock); // Create a rect shape for the graphic blockconst rectShape = engine.block.createShape('rect');engine.block.setShape(imageBlock, rectShape); // Configure size and position after appending to the pageengine.block.setWidth(imageBlock, 400);engine.block.setHeight(imageBlock, 300);engine.block.setPositionX(imageBlock, 200);engine.block.setPositionY(imageBlock, 150); // Create and configure the image fillconst 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 pageconst textBlock = engine.block.create('text');engine.block.appendChild(secondPage, textBlock); // Configure text properties after appending to the pageengine.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 pageconst 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[#](#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. ``` // Get the scene to set page dimensionsconst 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[#](#finding-and-navigating-pages) CE.SDK provides several methods to locate and navigate between pages in your scene. ``` // Demonstrate finding pagesconst 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 APIconst 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[#](#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[#](#margins) Page margins define bleed areas useful for print designs. Enable margins and configure each side individually: ``` // Configure page properties on the first page// Enable and set margins for print bleedengine.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 pageengine.block.setString(firstPage, 'page/titleTemplate', 'Cover'); // Set a custom title template for the second pageengine.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[#](#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[#](#fill-and-background) Pages support fills for background colors or images using the standard fill system. ``` // Set a background fill on the second pageconst 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[#](#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 in video mode * **Free**: Pages can be positioned freely without automatic arrangement ## Pages in Design Mode vs. Video Mode[#](#pages-in-design-mode-vs-video-mode) Page behavior varies depending on the scene mode. Query the current mode with `engine.scene.getMode()`. ``` // Check the scene mode (Design vs Video)const sceneMode = engine.scene.getMode();console.log('Scene mode:', sceneMode); ``` ### Design Mode[#](#design-mode) In Design mode, 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 Mode[#](#video-mode) In Video mode, pages represent timeline 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[#](#troubleshooting) ### Content Not Visible[#](#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[#](#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[#](#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. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/events-353f97) --- # Events Monitor and react to block changes in real time by subscribing to creation, update, and destruction events in your CE.SDK scene. ![Events example showing a scene with event subscriptions](/docs/cesdk/_astro/browser.hero.B3rziJJT_Z1yCwsg.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-events-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); 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]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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[#](#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: ``` // Subscribe to events from all blocks in the scene// Pass an empty array to receive events from every blockconst 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[#](#subscribing-to-specific-blocks) For better performance when you only care about certain blocks, pass an array of block IDs to filter events: ``` // Subscribe to events for specific blocks only// This is more efficient when you only care about certain blocksconst 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[#](#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: ``` // Create a graphic block - this triggers a Created eventconst graphic = engine.block.create('graphic'); // Set up the graphic with a shape and fillconst rectShape = engine.block.createShape('rect');engine.block.setShape(graphic, rectShape); // Position and size the graphicengine.block.setPositionX(graphic, 200);engine.block.setPositionY(graphic, 150);engine.block.setWidth(graphic, 400);engine.block.setHeight(graphic, 300); // Add an image fillconst 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 visibleengine.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[#](#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: ``` // Modify the block - this triggers Updated events// Due to deduplication, multiple rapid changes result in one Updated eventengine.block.setRotation(graphic, 0.1); // Rotate slightlyengine.block.setFloat(graphic, 'opacity', 0.9); // Adjust opacityconsole.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[#](#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: ``` // Process events by checking the type propertyconst 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[#](#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: ``` // When handling Destroyed events, always check block validity// The block ID is no longer valid after destructionconst 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: ``` // Destroy the text block - this triggers a Destroyed eventengine.block.destroy(textBlock);console.log('Destroyed text block'); // After destruction, the block ID is no longer validconst 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[#](#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: ``` // Clean up subscriptions when no longer needed// This prevents memory leaks and reduces engine overheadunsubscribeAll();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[#](#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[#](#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[#](#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. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/editing-workflow-032d27) --- # Editing Workflow CE.SDK controls editing access through roles and scopes, enabling template workflows where designers create locked layouts and end-users customize only permitted elements. ![Editing workflow with role-based permissions in CE.SDK](/docs/cesdk/_astro/browser.hero.DnWcdO0m_Z227Hen.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) 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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design' }); await cesdk.createDesignScene(); 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) 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. ``` // Roles define user types: 'Creator', 'Adopter', 'Viewer', 'Presenter'const role = engine.editor.getRole();console.log('Current role:', role); // 'Creator' ``` ## Scopes[#](#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-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'`. ``` // 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: ``` // Lock the block - Adopters cannot select, edit, or move itengine.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[#](#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: ``` // 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[#](#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[#](#implementation-guides) For detailed implementation, see these guides: [Lock Design Elements](vue/create-templates/lock-131489/) — Step-by-step instructions for locking specific elements in templates [Set Editing Constraints](vue/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) — Configure which properties users can modify --- [Source](https:/img.ly/docs/cesdk/vue/concepts/edit-modes-1f5b6c) --- # Editor State 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. ![CE.SDK Editor State Hero Image](/docs/cesdk/_astro/browser.hero.Dc8L4H_C_xOACx.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) 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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); const engine = cesdk.engine; // Create a design scene with a page await cesdk.createDesignScene(); const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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 five built-in edit modes (Transform, Crop, Text, Trim, Playback) * 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[#](#edit-modes) CE.SDK supports five built-in edit modes, each designed for a specific type of interaction with canvas content. ### Transform Mode[#](#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. ``` // 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[#](#switching-edit-modes) Use `engine.editor.setEditMode()` to change the current editing mode. The mode determines what interactions are available on selected blocks. ``` // Select the image block and switch to Crop modeengine.block.select(imageBlock);engine.editor.setEditMode('Crop');console.log('Switched to Crop mode on image block'); // After a moment, switch to Transform modeengine.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) ### Custom Edit Modes[#](#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. ``` // Create a custom edit mode that inherits from Crop behaviorengine.editor.setEditMode('MyCustomCropMode', 'Crop');console.log( 'Created custom mode based on Crop:', engine.editor.getEditMode()); // Switch back to Transform for the demoengine.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[#](#subscribing-to-state-changes) The engine notifies subscribers whenever the editor state changes, including mode switches and cursor updates. ### Using onStateChanged[#](#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. ``` // Subscribe to state changes to track mode transitions// The returned function can be called to unsubscribe when no longer neededconst 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[#](#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[#](#reading-cursor-type) Use `engine.editor.getCursorType()` to get the cursor type to display. ``` // Get the cursor type to display the appropriate mouse cursorconst 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 ### Reading Cursor Rotation[#](#reading-cursor-rotation) For directional cursors like resize handles, use `engine.editor.getCursorRotation()` to get the rotation angle in radians. ``` // Get cursor rotation for directional cursors like resize handlesconst 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[#](#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[#](#screen-space-coordinates) Use `engine.editor.getTextCursorPositionInScreenSpaceX()` and `engine.editor.getTextCursorPositionInScreenSpaceY()` to get the cursor position in screen pixels. ``` // Select the text block and switch to Text mode to get cursor positionengine.block.select(textBlock);engine.editor.setEditMode('Text'); // Get text cursor position in screen spaceconst 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[#](#detecting-active-interactions) Determine whether the user is currently in the middle of an interaction like dragging or resizing. ### Using unstable\_isInteractionHappening[#](#using-unstable_isinteractionhappening) Call `engine.editor.unstable_isInteractionHappening()` to check if a user interaction is in progress. ``` // Check if a user interaction is currently in progressconst isInteracting = engine.editor.unstable_isInteractionHappening();console.log('Is interaction happening:', isInteracting);// Use this to defer expensive operations during drag/resize operationsif (!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[#](#troubleshooting) ### Mode Doesn’t Change Visually[#](#mode-doesnt-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[#](#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[#](#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[#](#text-cursor-position-is-00) Confirm the editor is in Text mode with an active text selection. The text cursor position is only valid when actively editing text content. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/design-units-cc6597) --- # Design Units Control measurement systems for precise physical dimensions—create print-ready documents with millimeter or inch units and configurable DPI for export quality. ![Design Units example showing an A4 document configured with millimeter units](/docs/cesdk/_astro/browser.hero.B3pz4OOC_2tgWgc.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-design-units-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); 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(); 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(); 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'); 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]; // Set page to A4 size in millimeters engine.block.setWidth(page, 210); engine.block.setHeight(page, 297); // Verify dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); 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 console.log('Unit comparison at 300 DPI:'); console.log( '- A4 width (210mm) will export as', 210 * (300 / 25.4), 'pixels' ); console.log( '- A4 height (297mm) will export as', 297 * (300 / 25.4), 'pixels' ); 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[#](#understanding-design-units) ### Supported Unit Types[#](#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[#](#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[#](#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'`. ``` // Get the current sceneconst scene = engine.scene.get();if (scene === null) { throw new Error('No scene available');} // Get the current design unitconst currentUnit = engine.scene.getDesignUnit();console.log('Current design unit:', currentUnit); // 'Pixel' by default ``` ## Setting the Design Unit[#](#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. ``` // Set design unit to Millimeter for print workflowengine.scene.setDesignUnit('Millimeter'); // Verify the changeconst newUnit = engine.scene.getDesignUnit();console.log('Design unit changed to:', newUnit); // 'Millimeter' ``` ## Configuring DPI[#](#configuring-dpi) Access DPI through the scene’s `scene/dpi` property. For print workflows, 300 DPI is the standard for high-quality output. ``` // Set DPI to 300 for print-quality exports// Higher DPI produces higher resolution outputengine.block.setFloat(scene, 'scene/dpi', 300); // Verify the DPI settingconst dpi = engine.block.getFloat(scene, 'scene/dpi');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[#](#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: ``` // Get the page and set A4 dimensions (210 x 297 mm)const page = engine.block.findByType('page')[0]; // Set page to A4 size in millimetersengine.block.setWidth(page, 210);engine.block.setHeight(page, 297); // Verify dimensionsconst pageWidth = engine.block.getWidth(page);const pageHeight = engine.block.getHeight(page);console.log(`Page dimensions: ${pageWidth}mm x ${pageHeight}mm`); ``` ## Font Sizes and Design Units[#](#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. ``` // Create a text block with millimeter dimensionsconst textBlock = engine.block.create('text');engine.block.appendChild(page, textBlock); // Position text at 20mm from left, 30mm from topengine.block.setPositionX(textBlock, 20);engine.block.setPositionY(textBlock, 30); // Set text block size to 170mm x 50mmengine.block.setWidth(textBlock, 170);engine.block.setHeight(textBlock, 50); // Add content to the text blockengine.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[#](#understanding-export-resolution) The relationship between design units and export resolution is important for print workflows: ``` // Demonstrate unit comparison// At 300 DPI: 1 inch = 300 pixels, 1 mm = ~11.81 pixelsconsole.log('Unit comparison at 300 DPI:');console.log( '- A4 width (210mm) will export as', 210 * (300 / 25.4), 'pixels');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[#](#troubleshooting) ### Exported Dimensions Don’t Match Expected Size[#](#exported-dimensions-dont-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[#](#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[#](#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. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/buffers-9c565b) --- # Buffers Store and manage temporary binary data directly in memory using CE.SDK’s buffer API for dynamically generated content. ![Buffers example showing audio waveform generated from buffer data](/docs/cesdk/_astro/browser.hero.DfcuPkem_Z2s6nvg.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-buffers-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; // Helper function to create a WAV file from audio samplesfunction 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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); // Create a video scene - required for audio blocks await cesdk.createVideoScene(); 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 a Video Scene[#](#setting-up-a-video-scene) Since this example uses audio blocks, we first create a video scene. Audio blocks require a timeline-based scene context. ``` // Create a video scene - required for audio blocksawait cesdk.createVideoScene(); ``` ## Creating and Managing Buffers[#](#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. ``` // Create a buffer and get its URIconst 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[#](#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. ``` // Generate sine wave audio samplesconst sampleRate = 44100;const duration = 2; // 2 secondsconst frequency = 440; // A4 noteconst 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 wavefor (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. ``` // Convert samples to WAV format and write to bufferconst wavData = createWavFile(samples, sampleRate, numChannels);engine.editor.setBufferData(bufferUri, 0, wavData); // Verify the buffer lengthconst bufferLength = engine.editor.getBufferLength(bufferUri);console.log('Buffer length:', bufferLength, 'bytes'); ``` ## Reading Data from Buffers[#](#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. ``` // Demonstrate reading buffer data backconst 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[#](#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. ``` // Demonstrate resizing a buffer with a separate demo bufferconst 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[#](#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. ``` // Create an audio blockconst audioBlock = engine.block.create('audio'); // Assign the buffer URI to the audio blockengine.block.setString(audioBlock, 'audio/fileURI', bufferUri); // Set audio duration to match the generated samplesengine.block.setDuration(audioBlock, duration); // Append the audio block to the pageengine.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[#](#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. ``` // 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`);} ``` **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[#](#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. ``` // Demonstrate persisting buffer data using a Blob URL// In production, you would upload to CDN/cloud storage insteadconst 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 URLengine.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[#](#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. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/blocks-90241e) --- # Blocks Work with blocks—the fundamental building units for all visual elements in CE.SDK designs. ![Blocks example showing a scene with graphic and text blocks](/docs/cesdk/_astro/browser.hero.BFgg33_i_Z2us7gc.webp) 15 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-blocks-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin,EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); 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]; // Set page dimensions to accommodate our blocks engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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/v1/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[#](#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()`: ``` // Get the current scene and pageconst scene = engine.scene.get();if (scene === null) { throw new Error('No scene available');} // Find the page block - pages contain all design elementsconst pages = engine.block.findByType('page');const page = pages[0]; // Set page dimensions to accommodate our blocksengine.block.setWidth(page, 800);engine.block.setHeight(page, 600); // Query the block type - returns the full type pathconst 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-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. ``` // Type is immutable, determined at creation// Kind is a custom label you can set and changeengine.block.setKind(page, 'main-canvas');const pageKind = engine.block.getKind(page);console.log('Page kind:', pageKind); // 'main-canvas' // Find blocks by kindconst 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[#](#block-hierarchy) Blocks form a tree structure where scenes contain pages, and pages contain design elements. ``` // Blocks form a tree: scene > page > elements// Append the graphic to the page to make it visibleengine.block.appendChild(page, graphic); // Query parent-child relationshipsconst 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[#](#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. ``` // Create a graphic block for an imageconst graphic = engine.block.create('graphic'); // Duplicate creates a copy with a new UUIDconst graphicCopy = engine.block.duplicate(graphic); // Destroy removes a block - the duplicate is no longer neededengine.block.destroy(graphicCopy); // Check if a block ID is still valid after operationsconst isOriginalValid = engine.block.isValid(graphic);const isCopyValid = engine.block.isValid(graphicCopy);console.log('Original valid:', isOriginalValid); // trueconsole.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[#](#working-with-fills) Graphic blocks display content through fills. We create a fill, attach it to a block, and configure its source. ``` // Create a rect shape to define the graphic's boundsconst 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 graphicconst 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 boundsengine.block.setEnum(graphic, 'contentFill/mode', 'Cover'); ``` CE.SDK supports several fill types including image, video, color, and gradient fills. See the [Fills guide](vue/filters-and-effects/gradients-0ff079/) for details on available fill types. ## Creating Text Blocks[#](#creating-text-blocks) Text blocks display formatted text content. We create a text block, position it, and set its content. ``` // Create a text block with contentconst 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 contentengine.block.setString( textBlock, 'text/text', 'Blocks are the building units of CE.SDK designs'); // Set font size to 72ptengine.block.setFloat(textBlock, 'text/fontSize', 72); // Center-align the textengine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); // Check the text block typeconst 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](vue/text-8a993a/) . ## Block Properties[#](#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`. ``` // Use reflection to discover available propertiesconst graphicProperties = engine.block.findAllProperties(graphic);console.log('Graphic block has', graphicProperties.length, 'properties'); // Get property type informationconst opacityType = engine.block.getPropertyType('opacity');console.log('Opacity property type:', opacityType); // 'Float' // Check if properties are readable/writableconst 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[#](#property-accessors) Use type-specific getters and setters matching the property type: ``` // Use type-specific getters and setters// Float propertiesengine.block.setFloat(graphic, 'opacity', 0.9);const opacity = engine.block.getFloat(graphic, 'opacity');console.log('Graphic opacity:', opacity); // Bool propertiesengine.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 firstconst 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[#](#uuid-names-and-identity) Each block has a UUID that remains stable across save and load operations. Block names are mutable labels for organization. ``` // Each block has a stable UUID across save/load cyclesconst graphicUUID = engine.block.getUUID(graphic);console.log('Graphic UUID:', graphicUUID); // Block names are mutable labels for organizationengine.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[#](#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. ``` // Select a block programmaticallyengine.block.select(graphic); // Selects graphic, deselects others // Check selection stateconst isGraphicSelected = engine.block.isSelected(graphic);console.log('Graphic is selected:', isGraphicSelected); // true // Add to selection without deselecting othersengine.block.setSelected(textBlock, true); // Get all selected blocksconst selectedBlocks = engine.block.findAllSelected();console.log('Selected blocks count:', selectedBlocks.length); // 2 // Subscribe to selection changesconst 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[#](#visibility) Control whether blocks appear on the canvas and are included in exports. ``` // Control block visibilityengine.block.setVisible(graphic, true);const isVisible = engine.block.isVisible(graphic);console.log('Graphic is visible:', isVisible); // Control export inclusionengine.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) 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. ``` // Control clipping behaviorengine.block.setClipped(graphic, false);const isClipped = engine.block.isClipped(graphic);console.log('Graphic is clipped:', isClipped); ``` ## Block State[#](#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`) ``` // Query block state - indicates loading statusconst 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[#](#serialization) Save blocks to strings for persistence and restore them later. ``` // Save blocks to a string for persistence// Include 'bundle' scheme to allow serialization of blocks with bundled fontsconst 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/v1/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 duplicatesfor (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[#](#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. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/assets-a84fdd) --- # Assets Understand the asset system—how external media and resources like images, stickers, or videos are handled in CE.SDK. ![Assets example showing asset source and applied content](/docs/cesdk/_astro/browser.hero.DOkCxaud_Z1AdUcc.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-assets-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext, AssetSource, AssetQueryData, AssetsQueryResult, AssetResult} from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * 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.addDefaultAssetSources(); await cesdk.createDesignScene(); 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](vue/insert-media/images-63848a/) , [Videos](vue/insert-media/videos-a5fa03/) , and [Shapes & Stickers](vue/insert-media/shapes-or-stickers-20ac68/) guides. ## Assets vs Blocks[#](#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[#](#the-asset-data-model) An asset describes content that can be added to designs. Each asset has an `id` and optional properties: ``` // An asset is a content definition with metadata// It describes content that can be added to designsconst 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` See the [Content JSON Schema](vue/import-media/content-json-schema-a7b3d2/) guide for the complete property reference. ## Asset Sources[#](#asset-sources) Asset sources provide assets to the editor. Each source has an `id` and implements a `findAssets()` method that returns paginated results. ``` // Asset sources provide assets to the editor// Each source has an id and a findAssets() methodconst 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[#](#querying-assets) Search and filter assets from registered sources using `findAssets()`: ``` // Query assets from a sourceconst 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[#](#applying-assets) Use `apply()` to create a new block from an asset: ``` // Apply an asset to create a block in the sceneif (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-asset-sources) Local sources store assets in memory and support dynamic add/remove operations. Use these for user uploads or runtime-generated content: ``` // Local sources support dynamic add/remove operationsengine.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[#](#source-events) Subscribe to asset source lifecycle events for reactive UIs: ``` // Subscribe to asset source lifecycle eventsconst unsubscribe = engine.asset.onAssetSourceUpdated((sourceId) => { console.log('Source updated:', sourceId);}); // Notify that source contents changedengine.asset.assetSourceContentsChanged('uploads'); unsubscribe(); ``` Call `assetSourceContentsChanged()` after modifying a source to notify subscribers. --- [Source](https:/img.ly/docs/cesdk/vue/concepts/architecture-6ea9b2) --- # Architecture 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-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[#](#content-hierarchy) CE.SDK organizes content in a tree: _Scene_ → _Pages_ → _Blocks_. * **Scene**: The root container. One scene per engine instance. Operates in either _Design Mode_ (static) or _Video Mode_ (timeline-based). * **Pages**: Containers within a scene. Artboards in Design Mode, timeline compositions in Video Mode. * **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](vue/concepts/scenes-e8596d/) , [Pages](vue/concepts/pages-7b6bae/) , and [Blocks](vue/concepts/blocks-90241e/) for details. ## The Six APIs[#](#the-six-apis) The engine exposes six API namespaces. Here’s how they interconnect: ### Scene API (`engine.scene`)[#](#scene-api-enginescene) 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`)[#](#block-api-engineblock) 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`)[#](#asset-api-engineasset) 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`)[#](#variable-api-enginevariable) 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`)[#](#editor-api-engineeditor) 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`)[#](#event-api-engineevent) 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[#](#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 Modes[#](#scene-modes) The scene mode affects which features are available: * **Design Mode**: Static designs—social posts, print materials, graphics. Blocks positioned spatially. No timeline. * **Video Mode**: Time-based content with duration, playback, and animation. Blocks arranged across time. Choose the mode when creating a scene. It determines which _Block API_ properties and _Editor API_ capabilities are available. See [Scenes](vue/concepts/scenes-e8596d/) for details. ## Integration Patterns[#](#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](vue/concepts/headless-mode/browser-24ab98/) . Both contexts use the same six APIs—only rendering differs. --- [Source](https:/img.ly/docs/cesdk/vue/colors/replace-48cd71) --- # Replace Individual Colors Selectively replace specific colors in images using CE.SDK’s Recolor and Green Screen effects. ![Replace Colors Hero](/docs/cesdk/_astro/browser.hero.DhCjeM-J_AFarF.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-replace-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); 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); 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]); 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[#](#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: ``` // Enable effects in the inspector panel using the Feature APIcesdk.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[#](#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[#](#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()`. ``` // Create a Recolor effect to swap red colors to blueconst 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 colorengine.block.setColor(recolorEffect, 'effect/recolor/toColor', { r: 0.0, g: 0.5, b: 1.0, a: 1.0}); // Blue target colorengine.block.appendEffect(block1, recolorEffect); // Select this block to show the effects panelengine.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[#](#configuring-color-matching-precision) Fine-tune which pixels are affected by adjusting the tolerance parameters with `engine.block.setFloat()`: ``` // Configure color matching precision for Recolor effectconst 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 sourceengine.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 toleranceengine.block.setFloat( recolorEffect2, 'effect/recolor/brightnessMatch', 0.2);// Adjust edge smoothnessengine.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[#](#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. ``` // Create a Green Screen effect to remove green backgroundsconst 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[#](#configuring-green-screen-parameters) Control the precision of color removal using these parameters: ``` // Fine-tune Green Screen removal parametersconst 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 toleranceengine.block.setFloat( greenScreenEffect2, 'effect/green_screen/colorMatch', 0.4);// Adjust edge smoothness for cleaner removalengine.block.setFloat( greenScreenEffect2, 'effect/green_screen/smoothness', 0.2);// Reduce color spill from green backgroundengine.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[#](#managing-multiple-effects) A single block can have multiple effects applied. Use the effect management APIs to list, toggle, and remove effects. ``` // Demonstrate managing multiple effects on a blockconst block5 = await engine.block.addImage(imageUri, { size: blockSize });engine.block.appendChild(page, block5); // Add multiple effects to the same blockconst 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 blockconst effects = engine.block.getEffects(block5);console.log('Number of effects:', effects.length); // 2 // Disable the first effect without removing itengine.block.setEffectEnabled(effects[0], false); // Check if effect is enabledconst isEnabled = engine.block.isEffectEnabled(effects[0]);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[#](#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()`. --- [Source](https:/img.ly/docs/cesdk/vue/colors/overview-16a177) --- # Overview 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 ](vue/get-started/overview-e18f40/) ## For Print and Screen[#](#for-print-and-screen) Designing for screen and designing for print involve different color requirements, and CreativeEditor SDK (CE.SDK) is built to support both. * **Screen workflows** use RGB-based color spaces like sRGB and Display P3. These spaces are optimized for digital displays, ensuring vibrant, consistent colors across different devices. * **Print workflows** typically rely on CMYK and Spot Colors. These spaces reflect how physical inks combine on paper and require more precise color control to match print output. CE.SDK makes it easy to design for both by: * Supporting inputs in multiple color spaces, including sRGB, CMYK, and Spot Colors. * Automatically managing color conversions between spaces. * Providing tooling to ensure that exported files are optimized for either screen (e.g., PNG, WebP) or print (e.g., PDF with Spot Color support). **Note:** CE.SDK allows you to specify colors using CMYK values, but these are always converted to RGB internally. You cannot produce a true CMYK PDF with CE.SDK, and we do not support print-specific PDF variants like PDF/X. However, Spot Colors are fully supported and properly embedded in exported PDFs. ## Color Management[#](#color-management) Color management is the process of controlling how colors are represented across different devices and outputs—such as screens, printers, and files—to ensure that the colors you see during design match the final result. In CreativeEditor SDK (CE.SDK), color management includes: * Supporting multiple input color spaces (e.g., sRGB, Display P3 for screens, CMYK, Spot Colors for print). * Handling accurate conversions between color spaces (e.g., RGB ↔ CMYK, approximating Spot Colors). * Allowing custom color palettes and color libraries to maintain consistent color choices across a project or brand. * Providing tools to adjust color properties like brightness, contrast, and temperature. * Ensuring consistent color appearance across different export formats and output mediums. Color management ensures that your designs maintain color accuracy and visual integrity whether they are displayed on a screen or printed professionally. ## Color Libraries and Custom Palettes[#](#color-libraries-and-custom-palettes) Color libraries in CE.SDK are collections of predefined colors that appear in the color picker, helping users maintain brand or project consistency. Libraries are implemented as asset sources, where each color is represented as an individual asset. Each color library is identified by a unique source ID, allowing you to create, configure, and manage custom palettes tailored to your design needs. This setup makes it easy to offer curated color options to end users, improving both usability and design consistency. ## Supported Color Spaces[#](#supported-color-spaces) CreativeEditor SDK supports a wide range of color spaces to meet different design requirements: * **sRGB:** The standard color space for most digital screens. * **Display P3:** A wider gamut color space often used on high-end displays. * **CMYK:** A subtractive color model used primarily for print workflows. * **Spot Colors:** Special premixed inks for print-specific designs requiring precise color matching. Each color space serves different use cases, ensuring that your designs remain accurate whether viewed on a screen or produced in print. ## Applying and Modifying Colors[#](#applying-and-modifying-colors) CE.SDK allows you to apply and modify colors both through the UI and programmatically via the API. You can set colors for fills, strokes, outlines, and other properties dynamically, enabling a wide range of creative control. Whether you are adjusting a single design element or programmatically updating entire templates, the SDK provides the flexibility you need. ## Color Adjustments and Properties[#](#color-adjustments-and-properties) CreativeEditor SDK provides a range of color adjustments to fine-tune your designs. Commonly used adjustments include: * **Brightness:** Adjust the overall lightness or darkness. * **Contrast:** Adjust the difference between light and dark areas. * **Saturation:** Increase or decrease the intensity of colors. * **Exposure:** Modify the overall exposure level. * **Temperature:** Adjust the color balance between warm and cool tones. * **Sharpness:** Enhance the crispness and clarity of edges. In addition to these, CE.SDK supports many more detailed adjustments. You can also modify specific color-related properties like fill color, stroke color, and opacity. All adjustments and property changes can be applied both through the UI and programmatically. ## Color Conversion[#](#color-conversion) CreativeEditor SDK automatically manages color conversions to help ensure consistent visual output across different mediums. The following conversion behaviors are supported: * **RGB and CMYK colors** can be converted between each other using a mathematical approximation. * **Spot colors** can be represented in RGB and CMYK by using a corresponding color approximation. * **RGB and CMYK colors cannot be converted to spot colors.** These automatic conversions help ensure that designs maintain visual consistency whether they are viewed on a digital screen or prepared for professional printing. --- [Source](https:/img.ly/docs/cesdk/vue/colors/for-screen-1911f8) --- # For Screen --- [Source](https:/img.ly/docs/cesdk/vue/colors/for-print-59bc05) --- # For Print --- [Source](https:/img.ly/docs/cesdk/vue/colors/create-color-palette-7012e0) --- # Create a Color Palette Build custom color palettes that appear in the CE.SDK color picker using sRGB, CMYK, and Spot colors. ![Create a Color Palette example showing custom color library in the color picker](/docs/cesdk/_astro/browser.hero._4mdc9OK_2wTnOb.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-create-color-palette-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { AssetDefinition, EditorPlugin, EditorPluginContext} from '@cesdk/cesdk-js';import packageJson from './package.json'; // Define color assets for each color space typeconst 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; // Set labels for the color library using i18n cesdk.i18n.setTranslations({ en: { 'libraries.my-brand-colors.label': 'Brand Colors' } }); await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // Create a local asset source for the color library engine.asset.addLocalSource('my-brand-colors'); // Add all color assets to the source for (const color of colors) { engine.asset.addAssetToSource('my-brand-colors', color); } // Configure the color picker to display the custom library cesdk.ui.updateAssetLibraryEntry('ly.img.colors', { sourceIds: ['ly.img.colors.defaultPalette', 'my-brand-colors'] }); await cesdk.createDesignScene(); // Set up the page with dimensions const page = engine.block.findByType('page')[0]; engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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) 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. ``` // Define color assets for each color space typeconst 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) 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) 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[#](#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()`. ``` // Create a local asset source for the color libraryengine.asset.addLocalSource('my-brand-colors'); // Add all color assets to the sourcefor (const color of colors) { engine.asset.addAssetToSource('my-brand-colors', 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[#](#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. ``` // Set labels for the color library using i18ncesdk.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[#](#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. ``` // Configure the color picker to display the custom librarycesdk.ui.updateAssetLibraryEntry('ly.img.colors', { sourceIds: ['ly.img.colors.defaultPalette', 'my-brand-colors']}); ``` The special source ID `'ly.img.colors.defaultPalette'` 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[#](#removing-colors) You can remove individual colors from a library using `engine.asset.removeAssetFromSource()` with the source ID and the color’s asset ID. ``` 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[#](#troubleshooting) ### Colors Not Appearing in Picker[#](#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[#](#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[#](#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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/colors/conversion-bcd82b) --- # Color Conversion Convert colors between sRGB, CMYK, and spot color spaces programmatically in CE.SDK. ![Color Conversion example showing color blocks with different color spaces](/docs/cesdk/_astro/browser.hero.CWTH754F_ZTE1aX.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-conversion-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; // Type guard helpers for identifying color typesfunction 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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); 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[#](#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[#](#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: ``` // Define a spot color with RGB approximation for screen previewengine.editor.setSpotColorRGB('Brand Red', 0.95, 0.25, 0.21); ``` Create an sRGB color block for screen display: ``` // 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: ``` // 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: ``` // 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[#](#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. ``` // Convert colors to sRGB for screen displayconst 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 sRGBconst 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[#](#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. ``` // Convert colors to CMYK for print workflowsconst 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 colorengine.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. 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[#](#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. ``` // Use type guards to identify color space before conversionif (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[#](#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[#](#practical-use-cases) ### Building a Color Picker[#](#building-a-color-picker) When displaying a color value from a block in a custom color picker, convert to sRGB to show RGB values: ``` 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[#](#export-preparation) Before PDF export for print, verify colors are in CMYK format: ``` 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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/colors/basics-307115) --- # Color Basics Understand the three color spaces in CE.SDK and when to use each for screen or print workflows. ![Color Basics example showing three colored blocks representing sRGB, CMYK, and Spot color spaces](/docs/cesdk/_astro/browser.hero.C5GeumZp_AUXeI.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-basics-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); 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[#](#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-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. ``` // 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-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. ``` // 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. 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) 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[#](#defining-spot-colors) Use `engine.editor.setSpotColorRGB()` or `engine.editor.setSpotColorCMYK()` to register a spot color with its screen preview approximation. ``` // Define a spot color with RGB approximation for screen previewengine.editor.setSpotColorRGB('MyBrand Red', 0.95, 0.25, 0.21); ``` ### Applying Spot Colors[#](#applying-spot-colors) Reference a defined spot color using a `SpotColor` object with the `name`, `tint`, and `externalReference` properties. ``` // 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. If a block references an undefined spot color, CE.SDK displays magenta (RGB: 1, 0, 1) as a fallback. ## Applying Stroke Colors[#](#applying-stroke-colors) Strokes support all three color spaces. Enable the stroke, set its width, then apply a color using the `'stroke/color'` property. ``` // Add strokes to demonstrate stroke color propertyengine.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[#](#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). ``` // 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[#](#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[#](#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[#](#next-steps) * [Apply Colors](vue/colors/apply-2211e3/) \- Apply colors to design elements programmatically * [CMYK Colors](vue/colors/for-print/cmyk-8a1334/) \- Work with CMYK for print workflows * [Spot Colors](vue/colors/for-print/spot-c3a150/) \- Define and manage spot colors for specialized printing --- [Source](https:/img.ly/docs/cesdk/vue/colors/apply-2211e3) --- # Apply Colors 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. ![Apply Colors example showing a block with fill, stroke, and shadow colors applied](/docs/cesdk/_astro/browser.hero.o8Aq5994_Z1PclGc.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-apply-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext, RGBAColor } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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. ``` // 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 referenceconst 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[#](#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. ``` // Define spot colors with screen preview approximationsengine.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[#](#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. ``` // Apply RGB color to fillconst fill = engine.block.getFill(block);engine.block.setColor(fill, 'fill/color/value', rgbaBlue); // Read the current fill colorconst 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[#](#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()`. ``` // Enable and apply stroke colorengine.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[#](#apply-shadow-colors) Drop shadow colors use the `'dropShadow/color'` property on the design block. Enable shadows first using `engine.block.setDropShadowEnabled()`. ``` // Enable and apply drop shadow colorengine.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[#](#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. ``` // Convert colors between color spacesconst 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[#](#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. ``` // List all defined spot colorsconst 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[#](#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. ``` // Update a spot color definitionengine.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-spot-color-definitions) Remove a spot color definition using `engine.editor.removeSpotColor()`. Blocks still referencing that color fall back to the default magenta approximation. ``` // 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[#](#troubleshooting) ### Spot Color Appears Magenta[#](#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[#](#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-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[#](#cant-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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/colors/adjust-590d1e) --- # Adjust Colors Fine-tune images and design elements using CE.SDK’s color adjustments system to control brightness, contrast, saturation, and other visual properties. ![Adjust Colors example showing images with various color adjustments applied](/docs/cesdk/_astro/browser.hero.CjzCBChu_Z1B3CxH.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-adjust-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); // Enable adjustments in the inspector panel cesdk.feature.enable('ly.img.adjustment'); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#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[#](#enable-adjustments-features) To give users access to adjustments in the inspector panel, we enable the adjustments feature using CE.SDK’s Feature API. ``` // Enable adjustments in the inspector panelcesdk.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[#](#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[#](#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. ``` // Check if a block supports effects before applying adjustmentsconst 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[#](#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()`. ``` // Create an adjustments effectconst adjustmentsEffect = engine.block.createEffect('adjustments'); // Attach the adjustments effect to the image blockengine.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[#](#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. ``` // Set brightness - positive values lighten, negative values darkenengine.block.setFloat( adjustmentsEffect, 'effect/adjustments/brightness', 0.4); // Set contrast - increases or decreases tonal rangeengine.block.setFloat( adjustmentsEffect, 'effect/adjustments/contrast', 0.35); // Set saturation - increases or decreases color intensityengine.block.setFloat( adjustmentsEffect, 'effect/adjustments/saturation', 0.5); // Set temperature - positive for warmer, negative for cooler tonesengine.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[#](#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. ``` // Read current adjustment valuesconst brightness = engine.block.getFloat( adjustmentsEffect, 'effect/adjustments/brightness');console.log('Current brightness:', brightness); // Discover all available adjustment propertiesconst 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[#](#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. ``` // Disable adjustments temporarily (effect remains attached)engine.block.setEffectEnabled(adjustmentsEffect, false);console.log( 'Adjustments enabled:', engine.block.isEffectEnabled(adjustmentsEffect)); // Re-enable adjustmentsengine.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[#](#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. ``` // Create a second image to demonstrate a different adjustment styleconst 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 blockconst 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[#](#refinement-adjustments) Beyond basic color corrections, CE.SDK provides refinement adjustments for fine-tuning image detail and tonal balance. ``` // Add refinement adjustments to demonstrate subtle enhancement propertiesconst refinementEffect = engine.block.createEffect('adjustments');engine.block.appendEffect(tempBlock, refinementEffect); // Sharpness - enhances edge definitionengine.block.setFloat( refinementEffect, 'effect/adjustments/sharpness', 0.4); // Clarity - increases mid-tone contrast for more detailengine.block.setFloat(refinementEffect, 'effect/adjustments/clarity', 0.35); // Highlights - adjusts bright areasengine.block.setFloat( refinementEffect, 'effect/adjustments/highlights', -0.2); // Shadows - adjusts dark areasengine.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[#](#managing-adjustments) ### Remove 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. ``` // Demonstrate removing an effectconst 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 indexconst tempEffects = engine.block.getEffects(tempBlock);const effectIndex = tempEffects.indexOf(tempEffect);if (effectIndex !== -1) { engine.block.removeEffect(tempBlock, effectIndex);} // Destroy the removed effect to free memoryengine.block.destroy(tempEffect); ``` The `removeEffect()` method takes an index position. After removal, destroy the effect instance to ensure proper cleanup. ### Reset Adjustments[#](#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[#](#troubleshooting) ### Adjustments Not Visible[#](#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[#](#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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/automation/overview-34d971) --- # Overview Workflow automation with CreativeEditor SDK (CE.SDK) enables you to programmatically generate, manipulate, and export creative assets—at scale. Whether you’re creating thousands of localized ads, preparing platform-specific variants of a campaign, or populating print-ready templates with dynamic data, CE.SDK provides a flexible foundation for automation. You can run automation entirely on the client, integrate it with your backend, or build hybrid “human-in-the-loop” workflows where users interact with partially automated scenes before export. The automation engine supports static pipelines, making it suitable for a wide range of publishing, e-commerce, and marketing applications. Video support will follow soon. [Launch Web Demo](https://img.ly/showcases/cesdk)[ Get Started ](vue/get-started/overview-e18f40/) ## What Can Be Automated with CE.SDK[#](#what-can-be-automated-with-cesdk) CE.SDK supports a wide variety of automation use cases, including: * **Design generation at scale**: Create thousands of variants from a single template, such as product cards or regionalized campaigns. * **Data-driven customization**: Merge external data (e.g., CSV, JSON, APIs) into templates to personalize text, images, or layout. * **Responsive output creation**: Automatically resize designs and export assets in different aspect ratios or dimensions for various platforms. * **Pre-export validation**: Detect issues like empty placeholders or low-resolution images before generating final output. * **Multimodal exporting**: Automate delivery to multiple formats including JPG, PNG, PDF, and MP4. ## Automation Contexts[#](#automation-contexts) ### Headless / Server-Side Automation[#](#headless--server-side-automation) Server-side automation provides complete control over content generation without rendering a UI. This is ideal for background processing, such as creating assets in response to API requests or batch-generating print files for a mail campaign. ### UI-Integrated Automation (Human-in-the-Loop)[#](#ui-integrated-automation-human-in-the-loop) For workflows that require user input or final approval, you can embed automation into the CE.SDK UI. Users can review, customize, or finalize designs that were pre-filled with dynamic data—ideal for marketing teams, e-commerce admins, or print professionals. ### Client-Side vs. Backend-Supported Workflows[#](#client-side-vs-backend-supported-workflows) Many automation workflows can run fully in the browser thanks to CE.SDK’s client-side architecture. However, a backend may be required for use cases involving: * Secure access to private assets * Large dataset lookups * Server-side template rendering * Scheduled or event-based triggers ## Customization Capabilities[#](#customization-capabilities) CE.SDK gives you deep control over how your automation pipeline behaves: ### Data Sources[#](#data-sources) Connect to a variety of inputs: * Local or remote JSON * CSV files * REST APIs * CMS or PIM systems ### Template Customization[#](#template-customization) * Define dynamic variables and conditional placeholders * Use reusable templates or generate them on-the-fly * Lock or constrain specific fields to preserve brand integrity ### Design Rules[#](#design-rules) Enforce visual and content constraints: * Brand-compliant colors and fonts * Overflow handling and text auto-resizing * Show/hide conditions and fallback logic ### Output Formats[#](#output-formats) | Category | Supported Formats | | --- | --- | | **Images** | `.png` (with transparency), `.jpeg`, `.webp`, `.tga` | | **Video** | `.mp4` (H.264 or H.265 on supported platforms with limited transparency support) | | **Print** | `.pdf` (supports underlayer printing and spot colors) | | **Scene** | `.scene` (description of the scene without any assets) | | **Archive** | `.zip` (fully self-contained archive that bundles the `.scene` file with all assets) | Our custom cross-platform C++ based rendering and layout engine ensures consistent output quality across devices. ## UI Customization for Automation[#](#ui-customization-for-automation) You can extend the CE.SDK UI to trigger and manage automation tasks directly in the interface: * Add buttons or panels to trigger workflows * Dynamically update the scene based on user input or external data * Customize visibility of UI components depending on the stage (e.g., pre-fill vs. review) This makes it easy to integrate human-in-the-loop flows while preserving a tailored editing experience. --- [Source](https:/img.ly/docs/cesdk/vue/automation/design-generation-98a99e) --- # Automate Design Generation 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[#](#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](vue/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](vue/use-templates/overview-ae74e1/) . Below is a diagram illustrating how data is merged into a template to produce a final design: ![Template data merge process diagram showing how variables and assets flow into the final output](/docs/cesdk/_astro/schema.excalidraw.CEnF4vNU_GsqTX.svg) ## Example Workflow[#](#example-workflow) ### 1\. Prepare the Template[#](#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: ![Example postcard template with highlighted variable placeholders for name and address](/docs/cesdk/_astro/scene-example-backside.DrVrBaSF_ZWnyVB.webp) ### 2\. Load the Template into the Editor[#](#2-load-the-template-into-the-editor) Initialize the CE.SDK and load your prepared template: ``` // Load a template from your server or a CDNconst sceneUrl = 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_2.scene';await engine.scene.loadFromURL(sceneUrl); ``` ### 3\. Provide Data to Populate the Template[#](#3-provide-data-to-populate-the-template) Populate your template with data from your chosen source: ``` // Option 1: Prepare your data as a javascript objectconst 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[#](#4-export-the-final-design) Once the template is populated, export the final design in your preferred format: ``` 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: ![Exported postcard design showing populated name and address fields](/docs/cesdk/_astro/scene-example-backside-export.CaGuyU7v_1hqBcg.webp) Need help with exports? Check out the [Export Guide](vue/export-save-publish/export-82f968/) for detailed instructions and options. ## Troubleshooting[#](#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 --- [Source](https:/img.ly/docs/cesdk/vue/automation/data-merge-ae087c) --- # Data Merge Generate personalized designs from a single template by merging external data into CE.SDK templates using text variables and placeholder blocks. ![Data Merge example showing personalized business card design](/docs/cesdk/_astro/browser.hero.Bn2APMuE_Z1a0OMs.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-automation-data-merge-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions for a business card layout engine.block.setWidth(page, 800); engine.block.setHeight(page, 400); // 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[#](#initialize-the-editor) We start by initializing CE.SDK with a Design scene and setting up the page dimensions for our template. ``` // Initialize CE.SDK with Design mode and load asset sourcesawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true});await cesdk.createDesignScene(); const engine = cesdk.engine;const page = engine.block.findByType('page')[0]; // Set page dimensions for a business card layoutengine.block.setWidth(page, 800);engine.block.setHeight(page, 400); ``` ## Prepare Sample Data[#](#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. ``` // Sample data to merge into the templateconst 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[#](#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. ``` // Create a profile photo block with a semantic nameconst 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[#](#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. ``` // 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[#](#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. ``` // Set the variable values from dataengine.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[#](#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. ``` // Discover all variables in the sceneconst variables = engine.variable.findAll();console.log('Variables in scene:', variables); // Check if the text block references any variablesconst 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[#](#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. ``` // Find blocks by their semantic nameconst [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[#](#export-the-design) After merging data into the template, export the personalized design using `engine.block.export()`. ``` // Export the personalized designconst blob = await engine.block.export(page, { mimeType: 'image/png' });console.log('Exported PNG blob:', blob.size, 'bytes'); // Create a download link for the exported imageconst 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[#](#troubleshooting) ### Variables Not Rendering[#](#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[#](#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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/automation/batch-processing-ab2d18) --- # Batch Processing 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[#](#what-youll-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[#](#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: [ Sequential (single core) ](#tab-panel-131)[ Parallel (2 cores) ](#tab-panel-132) ``` // ... downloadBlob logic//Start the engine and download the sceneconst 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. ``` 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[#](#how-to-batch-template-population) For this operation, you generate personalized outputs at scale by combining: * Templates * Structured data ### Set the Data Sources[#](#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: [ JSON file ](#tab-panel-128)[ API ](#tab-panel-129)[ Inline JavaScript object ](#tab-panel-130) ``` await fetch('path/to/dataset.json').then((r) => r.json()); ``` ``` await fetch('https://api.example.com/dataset').then((r) => r.json()); ``` ``` // Define key variableslet textVariables = { first_name: '', last_name: '', address: '', city: '',}; ``` ### Update the Template[#](#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](vue/create-templates/add-dynamic-content/text-variables-7ecb50/) | | Update image fills | `engine.block.setString(block, 'fill/image/imageFileURI', url)` | [Insert Images](vue/insert-media/images-63848a/) | | Edit block properties | `engine.block.setFloat(block, key, value)` / `engine.block.setColor(block, key, color)` | [Apply Effects](vue/filters-and-effects/apply-2764e4/) | ### Batch Export the Design[#](#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](vue/export-save-publish/export/to-png-f87eaf/) | | JPEG | `engine.block.export(block, 'image/jpeg', 0.95)` | [JPEG](vue/export-save-publish/export/to-jpeg-6f88e9/) | | PDF | `engine.block.export(block, 'application/pdf')` | [PDF](vue/export-save-publish/export/to-pdf-95e04b/) | | MP4 | `engine.block.exportVideo(block, MimeType.Mp4)` | [MP4](vue/export-save-publish/export/to-mp4-c998a8/) | Check all the export options in the [Export section](vue/export-save-publish/export/overview-9ed3a8/) . ### Batch Thumbnail Generation from Static Scenes[#](#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: ``` // Example: Real-time thumbnail generationconst 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](vue/engine-interface-6fb7cf/) . The CE.SDK also provides over 20 pre-designed text layouts to apply on thumbnails. Check the [relevant guide](vue/text/font-combinations-a1b2c3/) to use them. ### Batch Thumbnail Generation from Video Scenes[#](#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](vue/create-video/control-daba54/) | | Seek to timestamp | `engine.block.setPlaybackTime()` | [Control Audio and Video](vue/create-video/control-daba54/) | | Export single frame | `engine.block.export(block, options)` | [To PNG](vue/export-save-publish/export/to-png-f87eaf/) [Font Combinations](vue/text/font-combinations-a1b2c3/) | | Generate sequence thumbnails | `engine.block.generateVideoThumbnailSequence()` | [Trim Video Clips](vue/edit-video/trim-4f688b/) | | Size thumbnails consistently | `targetWidth / targetHeight` export options | [To PNG](vue/export-save-publish/export/to-png-f87eaf/) | The following code shows how to **generate thumbnails from a video**: ``` 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[#](#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. To **handle large batches**, consider the following workflows: * Split into smaller chunks. * Log progress. * Monitor status. ## Apply Error Handling[#](#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: ``` 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[#](#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[#](#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. ``` 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[#](#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[#](#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. ``` 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[#](#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[#](#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[#](#next-steps) * [Headless Mode](vue/concepts/headless-mode/browser-24ab98/) \- Learn headless engine operation basics * [Design Generation](vue/automation/design-generation-98a99e/) \- Automate single design generation workflows * [Export Designs](vue/export-save-publish/export/overview-9ed3a8/) \- Deep dive into export options and formats * [Text Variables](vue/create-templates/add-dynamic-content/text-variables-7ecb50/) \- Work with dynamic text content in templates * [Source Sets](vue/import-media/source-sets-5679c8/) \- Specify assets sources for each block. --- [Source](https:/img.ly/docs/cesdk/vue/automation/auto-resize-4c2d58) --- # Auto-Resize 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. ![Auto-Resize example showing text blocks with automatic sizing](/docs/cesdk/_astro/browser.hero.BtY1M0TW_Te1po.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-automation-auto-resize-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Initialize CE.SDK with Design mode and load asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // 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[#](#initialize-the-editor) We start by initializing CE.SDK with a Design scene and setting up the page dimensions for our layout. ``` // Initialize CE.SDK with Design mode and load asset sourcesawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true});await cesdk.createDesignScene(); const engine = cesdk.engine;const page = engine.block.findByType('page')[0]; // Set page dimensionsengine.block.setWidth(page, 800);engine.block.setHeight(page, 600); ``` ## Size Modes[#](#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[#](#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-for-text) Auto mode makes text blocks expand to fit their content: ``` // Create a text block with Auto sizing mode// Auto mode makes the block expand to fit its contentconst 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 contentengine.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-for-responsive-layouts) Percent mode sizes blocks relative to their parent: ``` // Create a block using Percent mode for responsive sizing// Percent mode sizes the block relative to its parentconst 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 widthengine.block.setHeight(backgroundBlock, 0.3); // 30% of parent height // Center the background blockengine.block.setPositionX(backgroundBlock, pageWidth * 0.1); // 10% marginengine.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[#](#reading-frame-dimensions) After layout, use `getFrameWidth()` and `getFrameHeight()` to read the computed dimensions: ``` // Read computed frame dimensions after layout// getFrameWidth/getFrameHeight return the actual rendered sizeconst 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[#](#centering-blocks) Combine Auto mode with frame dimensions to center blocks based on their actual size: ``` // Calculate centered position using frame dimensionsconst 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 centerengine.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[#](#additional-auto-sized-content) You can create multiple auto-sized blocks and position them relative to each other: ``` // Create a subtitle with Auto modeconst 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 centerconst 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[#](#verifying-size-modes) Query the current size modes to verify your configuration: ``` // Verify sizing modesconst 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[#](#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[#](#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 | --- [Source](https:/img.ly/docs/cesdk/vue/api-reference/overview-8f24e1) --- # API Reference For Vue, the following packages are available: [ @cesdk/cesdk-js Our flagship package that includes our full-featured Editor UI for Design and Video editing ](vue/api/cesdk-js/)[ @cesdk/engine For headless applications and for building your own UI ](vue/api/engine/) --- [Source](https:/img.ly/docs/cesdk/vue/api/engine) --- # Package: documentation ## Classes[#](#classes) | Class | Description | | --- | --- | | [AssetAPI](https://img.ly/docs/cesdk/vue/api/engine/classes/assetapi/) | Manage asset sources and apply assets to scenes. | | [BlockAPI](https://img.ly/docs/cesdk/vue/api/engine/classes/blockapi/) | Create, manipulate, and query the building blocks of your design. | | [CreativeEngine](https://img.ly/docs/cesdk/vue/api/engine/classes/creativeengine/) | The CreativeEngine is the core processing unit of CE.SDK and handles state management, rendering, input handling, and much more. It provides APIs to directly interact with assets, blocks, scenes, and variables. These APIs can be used in a headless environment to build and manipulate designs programmatically, or in a browser to create interactive applications. | | [EditorAPI](https://img.ly/docs/cesdk/vue/api/engine/classes/editorapi/) | Control the design editor’s behavior and settings. | | [EventAPI](https://img.ly/docs/cesdk/vue/api/engine/classes/eventapi/) | Subscribe to block lifecycle events in the design engine. | | [SceneAPI](https://img.ly/docs/cesdk/vue/api/engine/classes/sceneapi/) | Create, load, save, and manipulate scenes. | | [VariableAPI](https://img.ly/docs/cesdk/vue/api/engine/classes/variableapi/) | Manage text variables within design templates. | ## Functions[#](#functions) | Function | Description | | --- | --- | | [checkVideoExportSupport](https://img.ly/docs/cesdk/vue/api/engine/functions/checkvideoexportsupport/) | Throws an error if the current browser does not support video exporting. | | [checkVideoSupport](https://img.ly/docs/cesdk/vue/api/engine/functions/checkvideosupport/) | Throws an error if the current browser does not support video editing. | | [\_combineProperties](https://img.ly/docs/cesdk/vue/api/engine/functions/combineproperties/) | Combines multiple reactive properties into a single reactive property. | | [\_createDerivedProperty](https://img.ly/docs/cesdk/vue/api/engine/functions/createderivedproperty/) | Creates a derived reactive property from one or more sources. | | [\_createReactiveProperty](https://img.ly/docs/cesdk/vue/api/engine/functions/createreactiveproperty/) | Creates a reactive property with subscribe, value, and update methods. | | [\_createTrackedProperty](https://img.ly/docs/cesdk/vue/api/engine/functions/createtrackedproperty/) | Creates a reactive property that tracks a source and updates based on a getter/setter. | | [defaultLogger](https://img.ly/docs/cesdk/vue/api/engine/functions/defaultlogger/) | \- | | [isCMYKColor](https://img.ly/docs/cesdk/vue/api/engine/functions/iscmykcolor/) | Type guard for [CMYKColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/cmykcolor/). | | [isRGBAColor](https://img.ly/docs/cesdk/vue/api/engine/functions/isrgbacolor/) | Type guard for [RGBAColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/rgbacolor/). | | [isSpotColor](https://img.ly/docs/cesdk/vue/api/engine/functions/isspotcolor/) | Type guard for [SpotColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/spotcolor/). | | [\_makeSource](https://img.ly/docs/cesdk/vue/api/engine/functions/makesource/) | Creates a simple event source that can emit values to subscribed listeners. | | [\_mergeSources](https://img.ly/docs/cesdk/vue/api/engine/functions/mergesources/) | Merges multiple event sources into a single source that emits when any source emits. | | [supportsBrowser](https://img.ly/docs/cesdk/vue/api/engine/functions/supportsbrowser/) | Checks if the current browser supports necessary technologies to match our supported browsers | | [supportsVideo](https://img.ly/docs/cesdk/vue/api/engine/functions/supportsvideo/) | Checks if the current browser supports video editing. | | [supportsVideoExport](https://img.ly/docs/cesdk/vue/api/engine/functions/supportsvideoexport/) | Checks if the current browser supports video exporting. | | [supportsWasm](https://img.ly/docs/cesdk/vue/api/engine/functions/supportswasm/) | Checks if the current browser supports web assembly | ## Type Aliases[#](#type-aliases) | Type Alias | Description | | --- | --- | | [AddImageOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/addimageoptions/) | Options for adding images to the scene. | | [AnimationBaselineDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationbaselinedirection/) | \- | | [AnimationBlockSwipeTextDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationblockswipetextdirection/) | \- | | [AnimationEasing](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationeasing/) | \- | | [AnimationEntry](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationentry/) | Configuration options for animations. | | [AnimationGrowDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationgrowdirection/) | \- | | [AnimationJumpLoopDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationjumploopdirection/) | \- | | [AnimationKenBurnsDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationkenburnsdirection/) | \- | | [AnimationMergeTextDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationmergetextdirection/) | \- | | [AnimationOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationoptions/) | Options for configuring animations (in, loop, out animations). | | [AnimationSpinDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationspindirection/) | \- | | [AnimationSpinLoopDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationspinloopdirection/) | \- | | [AnimationType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationtype/) | The block type IDs for the animation blocks. These are the IDs used to create new animations using `cesdk.engine.block.createAnimation(id)`. Refer to [AnimationTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationtypeshorthand/) and [AnimationTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationtypelonghand/) for more details. | | [AnimationTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationtypelonghand/) | The longhand block type IDs for the animation blocks. These are the IDs used to create new animations using `cesdk.engine.block.createAnimation(id)`. | | [AnimationTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationtypeshorthand/) | \- | | [AnimationTypewriterTextWritingStyle](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationtypewritertextwritingstyle/) | \- | | [AnimationWipeDirection](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/animationwipedirection/) | \- | | [ApplicationMimeType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/applicationmimetype/) | Represents the application MIME types used in the editor. | | [AssetColor](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/assetcolor/) | Asset Color payload | | [AssetGroups](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/assetgroups/) | An asset can be member of multiple groups. Groups have a semantic meaning used to build and group UIs exploring the assets, e.g.sections in the content library, or for things like topics in Unsplash for instance. | | [AssetMetaData](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/assetmetadata/) | Generic asset information | | [AssetProperty](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/assetproperty/) | Asset property for payload | | [AssetTransformPreset](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/assettransformpreset/) | Transform preset payload | | [AudioExportOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/audioexportoptions/) | Represents the options for exporting audio. | | [AudioFromVideoOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/audiofromvideooptions/) | Options for configuring audio extraction from video operations. | | [AudioMimeType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/audiomimetype/) | Represents the audio MIME types used in the editor. | | [BlendMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/blendmode/) | \- | | [BlockEnumType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/blockenumtype/) | \- | | [BlockState](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/blockstate/) | Represents the state of a design block. | | [BlurType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/blurtype/) | The block type IDs for the blur blocks. These are the IDs used to create new blurs using `cesdk.engine.block.createBlur(id)`. Refer to [BlurTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/blurtypeshorthand/) and [BlurTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/blurtypelonghand/) for more details. | | [BlurTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/blurtypelonghand/) | The longhand block type IDs for the blur blocks. These are the IDs used to create new blurs using `cesdk.engine.block.createBlur(id)`. | | [BlurTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/blurtypeshorthand/) | \- | | [BooleanOperation](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/booleanoperation/) | Represents the names of boolean operations. | | [BoolPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/boolpropertyname/) | \- | | [CameraClampingOvershootMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/cameraclampingovershootmode/) | \- | | [Canvas](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/canvas/) | An HTML Canvas or an Offscreen Canvas | | [CaptionHorizontalAlignment](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/captionhorizontalalignment/) | \- | | [CaptionVerticalAlignment](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/captionverticalalignment/) | \- | | [CMYK](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/cmyk/) | Represents a color in the CMYK color space. | | [Color](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/color/) | Represents all color types supported by the engine. | | [ColorPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/colorpropertyname/) | \- | | [ColorSpace](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/colorspace/) | Represents the color space used in the editor. | | [ContentFillMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/contentfillmode/) | \- | | [CreateSceneOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/createsceneoptions/) | Options for creating a video scene. | | [CutoutOperation](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/cutoutoperation/) | Represents the type of a cutout. | | [CutoutType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/cutouttype/) | \- | | [DefaultAssetSourceId](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/defaultassetsourceid/) | Represents the default asset source IDs used in the editor. | | [DemoAssetSourceId](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/demoassetsourceid/) | Represents the default demo asset source IDs used in the editor. | | [DesignBlockId](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/designblockid/) | A numerical identifier for a design block | | [DesignBlockType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/designblocktype/) | The block type IDs for the top-level design blocks. These are the IDs used to create new blocks using `cesdk.engine.block.create(id)`. Refer to [DesignBlockTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/designblocktypeshorthand/) and [DesignBlockTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/designblocktypelonghand/) for more details. | | [DesignBlockTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/designblocktypelonghand/) | The longhand block type IDs for the top-level design blocks. These are the IDs used to create new blocks using `cesdk.engine.block.create(id)`. | | [DesignBlockTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/designblocktypeshorthand/) | \- | | [DoubleClickSelectionMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/doubleclickselectionmode/) | \- | | [DoublePropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/doublepropertyname/) | \- | | [DropShadowOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/dropshadowoptions/) | Options for configuring drop shadow effects on blocks. | | [EditMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/editmode/) | Represents the current edit mode of the editor. | | [EffectType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/effecttype/) | The block type IDs for the effect blocks. These are the IDs used to create new effects using `cesdk.engine.block.createEffect(id)`. Refer to [EffectTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/effecttypeshorthand/) and [EffectTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/effecttypelonghand/) for more details. | | [EffectTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/effecttypelonghand/) | The longhand block type IDs for the effect blocks. These are the IDs used to create new effects using `cesdk.engine.block.createEffect(id)`. | | [EffectTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/effecttypeshorthand/) | \- | | [EnginePluginContext](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/engineplugincontext/) | Represents the context for an engine plugin. | | [EnumPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/enumpropertyname/) | \- | | [EnumValues](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/enumvalues/) | \- | | [\_EqualsFn](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/equalsfn/) | A function that compares two values for equality | | [ExportOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/exportoptions/) | Represents the options for exporting a design block. | | [FillPixelStreamOrientation](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/fillpixelstreamorientation/) | \- | | [FillType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/filltype/) | The block type IDs for the fill blocks. These are the IDs used to create new fills using `cesdk.engine.block.createFill(id)`. Refer to [FillTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/filltypeshorthand/) and [FillTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/filltypelonghand/) for more details. | | [FillTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/filltypelonghand/) | The longhand block type IDs for the fill blocks. These are the IDs used to create new fills using `cesdk.engine.block.createFill(id)`. | | [FillTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/filltypeshorthand/) | \- | | [FloatPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/floatpropertyname/) | \- | | [FontSizeUnit](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/fontsizeunit/) | Extended design unit type that includes Point for font size operations. Maintains consistency with SceneDesignUnit’s capitalized naming convention. | | [FontStyle](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/fontstyle/) | Represents the style of a font. | | [FontWeight](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/fontweight/) | Represents the weight of a font. | | [GradientstopRGBA](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/gradientstoprgba/) | Represents a gradient stop in the RGBA color space. | | [HeightMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/heightmode/) | \- | | [HexColorString](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/hexcolorstring/) | Represents a hexadecimal color value (RGB or RGBA) that starts with a ’#’. | | [HistoryId](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/historyid/) | A numerical identifier for a history stack | | [HorizontalBlockAlignment](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/horizontalblockalignment/) | \- | | [ImageMimeType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/imagemimetype/) | Represents the image MIME types used in the editor. | | [IntPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/intpropertyname/) | \- | | [\_LegacySource](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/legacysource/) | A simplified source type for legacy API streams | | [\_Listener](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/listener/) | A listener function that receives value updates | | [Locale](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/locale/) | e.g. `en`, `de`, etc. | | [LogLevel](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/loglevel/) | Provides logging functionality for the Creative Editor SDK. | | [MimeType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/mimetype/) | Represents the MIME types used in the editor. | | [ObjectType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/objecttype/) | The block type IDs for all blocks types in the Creative Engine. Those are the types that can be passed to `cesdk.engine.block.findByType(type)` for example. Refer to [ObjectTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/objecttypeshorthand/) and [ObjectTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/objecttypelonghand/) for more details. | | [ObjectTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/objecttypelonghand/) | The longhand block type IDs for all blocks types in the Creative Engine. Those are the Types returned by the engine when calling `cesdk.engine.block.getType(blockId)` for example. | | [ObjectTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/objecttypeshorthand/) | The shorthand block type IDs for all blocks types in the Creative Engine. Those are the types that can be passed to `cesdk.engine.block.findByType(type)` for example. | | [OffscreenCanvas](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/offscreencanvas/) | A simplified placeholder type for `OffscreenCanvas`, to avoid a dependency on `@types/offscreencanvas` | | [OptionalPrefix](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/optionalprefix/) | \- | | [PaletteColor](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/palettecolor/) | Represents a color definition for the custom color palette. | | [PositionMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/positionmode/) | \- | | [PositionXMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/positionxmode/) | \- | | [PositionYMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/positionymode/) | \- | | [PropertyType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/propertytype/) | Represents the various types of properties that can be associated with design blocks. Each type corresponds to a different kind of data that can be used to define the properties of a design block within the system. | | [RGBA](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/rgba/) | Represents a color in the RGBA color space. | | [RoleString](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/rolestring/) | Represents a role string. | | [DesignUnit](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/designunit/) | \- | | [SceneLayout](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/scenelayout/) | \- | | [SceneMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/scenemode/) | \- | | [Scope](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/scope/) | Represents the various scopes that define the capabilities and permissions within the Creative Editor SDK. Each scope corresponds to a specific functionality or action that can be performed within the editor. | | [SettingBoolPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingboolpropertyname/) | \- | | [SettingColorPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingcolorpropertyname/) | \- | | [SettingEnumPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingenumpropertyname/) | \- | | [SettingEnumType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingenumtype/) | \- | | [SettingEnumValues](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingenumvalues/) | \- | | [SettingFloatPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingfloatpropertyname/) | \- | | [SettingIntPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingintpropertyname/) | \- | | [SettingKey](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingkey/) | Union type of all valid setting keys. | | [SettingsBool](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingsbool/) | \- | | [SettingsColor](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingscolor/) | Represents the color settings available in the editor. | | [~SettingsColorRGBA~](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingscolorrgba/) | Represents the color settings available in the editor. | | [SettingsEnum](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingsenum/) | \- | | [SettingsFloat](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingsfloat/) | \- | | [SettingsInt](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingsint/) | \- | | [SettingsString](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingsstring/) | \- | | [SettingStringPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingstringpropertyname/) | \- | | [SettingType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingtype/) | Represents the type of a setting. | | [SettingValueType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/settingvaluetype/) | Gets the value type for a specific setting key. | | [ShapeType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/shapetype/) | The block type IDs for the shape blocks. These are the IDs used to create new shapes using `cesdk.engine.block.createShape(id)`. Refer to [ShapeTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/shapetypeshorthand/) and [ShapeTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/shapetypelonghand/) for more details. | | [ShapeTypeLonghand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/shapetypelonghand/) | The longhand block type IDs for the blocks. These are the IDs used to create new shapes using `cesdk.engine.block.createShape(id)`. | | [ShapeTypeShorthand](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/shapetypeshorthand/) | \- | | [SizeMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/sizemode/) | \- | | [SortingOrder](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/sortingorder/) | The order to sort by if the asset source supports sorting. If set to None, the order is the same as the assets were added to the source. | | [SourceSetPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/sourcesetpropertyname/) | \- | | [SplitOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/splitoptions/) | Options for configuring block split operations. | | [StringPropertyName](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/stringpropertyname/) | \- | | [StrokeCornerGeometry](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/strokecornergeometry/) | \- | | [StrokePosition](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/strokeposition/) | \- | | [StrokeStyle](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/strokestyle/) | \- | | [\_Subscription](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/subscription/) | Represents a subscription to an event. | | [TextAnimationWritingStyle](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/textanimationwritingstyle/) | \- | | [TextCase](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/textcase/) | Represents the text case of a text block. | | [HorizontalTextAlignment](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/horizontaltextalignment/) | \- | | [TextVerticalAlignment](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/textverticalalignment/) | \- | | [TouchPinchAction](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/touchpinchaction/) | \- | | [TouchRotateAction](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/touchrotateaction/) | \- | | [~TypefaceDefinition~](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/typefacedefinition/) | Represents a typeface definition used in the editor. | | [\_Unsubscribe](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/unsubscribe/) | An unsubscribe function that removes a listener | | [VerticalBlockAlignment](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/verticalblockalignment/) | \- | | [VideoExportOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/videoexportoptions/) | Represents the options for exporting a video. | | [VideoMimeType](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/videomimetype/) | Represents the video MIME types used in the editor. | | [WidthMode](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/widthmode/) | \- | | [XYWH](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/xywh/) | Describes a rectangle on the screen. | | [ZoomAutoFitAxis](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/zoomautofitaxis/) | The axis(es) for which to auto-fit. | | [ZoomOptions](https://img.ly/docs/cesdk/vue/api/engine/type-aliases/zoomoptions/) | Options for zooming to a block with optional animation. | ## Interfaces[#](#interfaces) | Interface | Description | | --- | --- | | [AddVideoOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/addvideooptions/) | Options for adding videos to the scene. | | [ApplyAssetOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/applyassetoptions/) | Options for applying an asset to the scene. | | [Asset](https://img.ly/docs/cesdk/vue/api/engine/interfaces/asset/) | Generic asset information | | [AssetBooleanProperty](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetbooleanproperty/) | Asset boolean property definition | | [AssetCMYKColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetcmykcolor/) | Asset Color payload CMYK representation | | [AssetColorProperty](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetcolorproperty/) | Asset color property definition | | [AssetDefinition](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetdefinition/) | Definition of an asset used if an asset is added to an asset source. | | [AssetEnumProperty](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetenumproperty/) | Asset enum property definition | | [AssetFixedAspectRatio](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetfixedaspectratio/) | Asset transform preset payload fixed aspect ratio | | [AssetFixedSize](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetfixedsize/) | Asset transform preset payload fixed size | | [AssetFreeAspectRatio](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetfreeaspectratio/) | Asset transform preset payload free aspect ratio | | [AssetNumberProperty](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetnumberproperty/) | Asset number property definition | | [AssetPayload](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetpayload/) | Asset payload | | [AssetQueryData](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetquerydata/) | Defines a request for querying assets | | [AssetResult](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetresult/) | Single asset result of a query from the engine. | | [\_AssetResultCredits](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetresultcredits/) | Represents the credits for an asset result. | | [\_AssetResultLicense](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetresultlicense/) | Represents the license for an asset result. | | [AssetRGBColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetrgbcolor/) | Asset Color payload RGB representation | | [AssetSource](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetsource/) | A source of assets | | [AssetSpotColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetspotcolor/) | Asset Color payload SpotColor representation | | [AssetsQueryResult](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetsqueryresult/) | Return type of a `findAssets` query. | | [AssetStringProperty](https://img.ly/docs/cesdk/vue/api/engine/interfaces/assetstringproperty/) | Asset string property definition | | [AudioTrackInfo](https://img.ly/docs/cesdk/vue/api/engine/interfaces/audiotrackinfo/) | Information about a single audio track from a video. This interface provides comprehensive metadata about audio tracks, including codec information, technical specifications, and track details. | | [BlockEvent](https://img.ly/docs/cesdk/vue/api/engine/interfaces/blockevent/) | Represents an event related to a design block. | | [BlockStateError](https://img.ly/docs/cesdk/vue/api/engine/interfaces/blockstateerror/) | Represents an error state for a design block. | | [BlockStatePending](https://img.ly/docs/cesdk/vue/api/engine/interfaces/blockstatepending/) | Represents a pending state for a design block. | | [BlockStateReady](https://img.ly/docs/cesdk/vue/api/engine/interfaces/blockstateready/) | Represents a ready state for a design block. | | [BlurEvent](https://img.ly/docs/cesdk/vue/api/engine/interfaces/blurevent/) | Dispatched on the engine canvas when the text input has been blurred. Call `preventDefault()` to disallow this and refocus the engine text input. | | [Buffer](https://img.ly/docs/cesdk/vue/api/engine/interfaces/buffer/) | Represents a buffer of data. | | [CMYKColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/cmykcolor/) | Represents a CMYK color value. | | [CompleteAssetResult](https://img.ly/docs/cesdk/vue/api/engine/interfaces/completeassetresult/) | Asset results that are returned from the engine. | | [Configuration](https://img.ly/docs/cesdk/vue/api/engine/interfaces/configuration/) | Specifies the configuration for the Creative Editor SDK. | | [CursorEvent](https://img.ly/docs/cesdk/vue/api/engine/interfaces/cursorevent/) | Dispatched on the engine canvas when the text input has been blurred. Call `preventDefault()` to disallow this and refocus the engine text input. | | [EnginePlugin](https://img.ly/docs/cesdk/vue/api/engine/interfaces/engineplugin/) | Represents an engine plugin. | | [\_FindAssetsQuery](https://img.ly/docs/cesdk/vue/api/engine/interfaces/findassetsquery/) | Represents a query for finding assets. | | [\_Flip](https://img.ly/docs/cesdk/vue/api/engine/interfaces/flip/) | Specifies the horizontal and vertical flip states of a design block. | | [Font](https://img.ly/docs/cesdk/vue/api/engine/interfaces/font/) | Represents a font. | | [GradientColorStop](https://img.ly/docs/cesdk/vue/api/engine/interfaces/gradientcolorstop/) | Represents a gradient color stop. | | [HTMLCreativeEngineCanvasElement](https://img.ly/docs/cesdk/vue/api/engine/interfaces/htmlcreativeenginecanvaselement/) | A wrapper around a plain canvas | | [Logger](https://img.ly/docs/cesdk/vue/api/engine/interfaces/logger/) | Represents a logger function. | | [PageDuration](https://img.ly/docs/cesdk/vue/api/engine/interfaces/pageduration/) | \- | | [Range](https://img.ly/docs/cesdk/vue/api/engine/interfaces/range/) | An open range. | | [Reaction](https://img.ly/docs/cesdk/vue/api/engine/interfaces/reaction/) | Reactions track read calls and provide a way to react if they change. | | [\_ReactiveProperty](https://img.ly/docs/cesdk/vue/api/engine/interfaces/reactiveproperty/) | A reactive property with subscribe, value, and update methods | | [\_ReactivePropertyOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/reactivepropertyoptions/) | Options for creating a reactive property | | [Reactor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/reactor/) | The reactor coordinates the update of registered _Reactions_. | | [\_ReadonlyReactiveProperty](https://img.ly/docs/cesdk/vue/api/engine/interfaces/readonlyreactiveproperty/) | A read-only reactive property with subscribe and value methods | | [RefocusEvent](https://img.ly/docs/cesdk/vue/api/engine/interfaces/refocusevent/) | Dispatched on the engine canvas right before the engine will refocus its text input after a blur. Call `preventDefault()` to prevent the refocusing. | | [RGBAColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/rgbacolor/) | Represents an RGBA color value. | | [RGBColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/rgbcolor/) | Represents an RGB color value. | | [Settings](https://img.ly/docs/cesdk/vue/api/engine/interfaces/settings/) | Map of all available settings with their types. This provides type-safe access to all editor settings. | | [Size2](https://img.ly/docs/cesdk/vue/api/engine/interfaces/size2/) | \- | | [Source](https://img.ly/docs/cesdk/vue/api/engine/interfaces/source/) | A single source width an intrinsic width & height. | | [\_Source](https://img.ly/docs/cesdk/vue/api/engine/interfaces/source-1/) | A source that can emit values to subscribed listeners | | [SpotColor](https://img.ly/docs/cesdk/vue/api/engine/interfaces/spotcolor/) | Represents a spot color value. | | [TextFontSizeOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/textfontsizeoptions/) | Options for text font size operations with unit support. | | [TransientResource](https://img.ly/docs/cesdk/vue/api/engine/interfaces/transientresource/) | Represents a transient resource. | | [Typeface](https://img.ly/docs/cesdk/vue/api/engine/interfaces/typeface/) | Represents a typeface. | | [\_UBQAudioFromVideoOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/ubqaudiofromvideooptions/) | Specifies options for configuring audio extraction from video operations. | | [\_UBQExportAudioOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/ubqexportaudiooptions/) | Specifies options for exporting audio design blocks to various formats. | | [\_UBQExportOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/ubqexportoptions/) | Specifies options for exporting design blocks to various formats. | | [\_UBQExportVideoOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/ubqexportvideooptions/) | Specifies options for exporting video design blocks to various formats. | | [\_UBQSplitOptions](https://img.ly/docs/cesdk/vue/api/engine/interfaces/ubqsplitoptions/) | Specifies options for configuring block split operations. | | [Vec2](https://img.ly/docs/cesdk/vue/api/engine/interfaces/vec2/) | \- | | [Vec3](https://img.ly/docs/cesdk/vue/api/engine/interfaces/vec3/) | \- | ## Variables[#](#variables) | Variable | Description | | --- | --- | | [ANIMATION\_TYPES](https://img.ly/docs/cesdk/vue/api/engine/variables/animation_types/) | The shorthand block type IDs for the animation blocks. These are the IDs used to create new animations using `cesdk.engine.block.createAnimation(id)`. | | [AnimationBaselineDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationbaselinedirectionvalues/) | \- | | [AnimationBlockSwipeTextDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationblockswipetextdirectionvalues/) | \- | | [AnimationEasingValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationeasingvalues/) | \- | | [AnimationGrowDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationgrowdirectionvalues/) | \- | | [AnimationJumpLoopDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationjumploopdirectionvalues/) | \- | | [AnimationKenBurnsDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationkenburnsdirectionvalues/) | \- | | [AnimationMergeTextDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationmergetextdirectionvalues/) | \- | | [AnimationSpinDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationspindirectionvalues/) | \- | | [AnimationSpinLoopDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationspinloopdirectionvalues/) | \- | | [AnimationTypewriterTextWritingStyleValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationtypewritertextwritingstylevalues/) | \- | | [AnimationWipeDirectionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/animationwipedirectionvalues/) | \- | | [BlendModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/blendmodevalues/) | \- | | [BLUR\_TYPES](https://img.ly/docs/cesdk/vue/api/engine/variables/blur_types/) | The shorthand block type IDs for the blur blocks. These are the IDs used to create new blurs using `cesdk.engine.block.createBlur(id)`. | | [CameraClampingOvershootModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/cameraclampingovershootmodevalues/) | \- | | [CaptionHorizontalAlignmentValues](https://img.ly/docs/cesdk/vue/api/engine/variables/captionhorizontalalignmentvalues/) | \- | | [CaptionVerticalAlignmentValues](https://img.ly/docs/cesdk/vue/api/engine/variables/captionverticalalignmentvalues/) | \- | | [ContentFillModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/contentfillmodevalues/) | \- | | [CutoutTypeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/cutouttypevalues/) | \- | | [DESIGN\_BLOCK\_TYPES](https://img.ly/docs/cesdk/vue/api/engine/variables/design_block_types/) | The shorthand block type IDs for the top-level design blocks. These are the IDs used to create new blocks using `cesdk.engine.block.create(id)`. | | [DoubleClickSelectionModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/doubleclickselectionmodevalues/) | \- | | [EFFECT\_TYPES](https://img.ly/docs/cesdk/vue/api/engine/variables/effect_types/) | The shorthand block type IDs for the effect blocks. These are the IDs used to create new effects using `cesdk.engine.block.createEffect(id)`. | | [FILL\_TYPES](https://img.ly/docs/cesdk/vue/api/engine/variables/fill_types/) | The shorthand block type IDs for the fill blocks. These are the IDs used to create new fills using `cesdk.engine.block.createFill(id)`. | | [FillPixelStreamOrientationValues](https://img.ly/docs/cesdk/vue/api/engine/variables/fillpixelstreamorientationvalues/) | \- | | [HeightModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/heightmodevalues/) | \- | | [~LogLevel~](https://img.ly/docs/cesdk/vue/api/engine/variables/loglevel/) | Provides a set of predefined log levels for the Creative Editor SDK. | | [~MimeType~](https://img.ly/docs/cesdk/vue/api/engine/variables/mimetype/) | Represents the MIME types used in the editor. | | [PositionXModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/positionxmodevalues/) | \- | | [PositionYModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/positionymodevalues/) | \- | | [SceneDesignUnitValues](https://img.ly/docs/cesdk/vue/api/engine/variables/scenedesignunitvalues/) | \- | | [SceneLayoutValues](https://img.ly/docs/cesdk/vue/api/engine/variables/scenelayoutvalues/) | \- | | [SceneModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/scenemodevalues/) | \- | | [SHAPE\_TYPES](https://img.ly/docs/cesdk/vue/api/engine/variables/shape_types/) | The shorthand block type IDs for the shape blocks. These are the IDs used to create new shapes using `cesdk.engine.block.createShape(id)`. | | [StrokeCornerGeometryValues](https://img.ly/docs/cesdk/vue/api/engine/variables/strokecornergeometryvalues/) | \- | | [StrokePositionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/strokepositionvalues/) | \- | | [StrokeStyleValues](https://img.ly/docs/cesdk/vue/api/engine/variables/strokestylevalues/) | \- | | [TextAnimationWritingStyleValues](https://img.ly/docs/cesdk/vue/api/engine/variables/textanimationwritingstylevalues/) | \- | | [TextHorizontalAlignmentValues](https://img.ly/docs/cesdk/vue/api/engine/variables/texthorizontalalignmentvalues/) | \- | | [TextVerticalAlignmentValues](https://img.ly/docs/cesdk/vue/api/engine/variables/textverticalalignmentvalues/) | \- | | [TouchPinchActionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/touchpinchactionvalues/) | \- | | [TouchRotateActionValues](https://img.ly/docs/cesdk/vue/api/engine/variables/touchrotateactionvalues/) | \- | | [WidthModeValues](https://img.ly/docs/cesdk/vue/api/engine/variables/widthmodevalues/) | \- | --- [Source](https:/img.ly/docs/cesdk/vue/api/cesdk-js) --- # Package: documentation ## Classes[#](#classes) | Class | Description | | --- | --- | | [AssetAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/assetapi/) | Manage asset sources and apply assets to scenes. | | [BlockAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/blockapi/) | Create, manipulate, and query the building blocks of your design. | | [CreativeEngine](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/creativeengine/) | The CreativeEngine is the core processing unit of CE.SDK and handles state management, rendering, input handling, and much more. It provides APIs to directly interact with assets, blocks, scenes, and variables. These APIs can be used in a headless environment to build and manipulate designs programmatically, or in a browser to create interactive applications. | | [EditorAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/editorapi/) | Control the design editor’s behavior and settings. | | [EventAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/eventapi/) | Subscribe to block lifecycle events in the design engine. | | [SceneAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/sceneapi/) | Create, load, save, and manipulate scenes. | | [VariableAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/variableapi/) | Manage text variables within design templates. | | [ActionsAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/actionsapi/) | ActionsAPI provides a centralized way to manage and customize actions for various user interactions in the Creative Engine SDK. | | [CreativeEditorSDK](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/creativeeditorsdk/) | The main entry point for the Creative Editor SDK. | | [FeatureAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/featureapi/) | Controls the availability of features within the Creative Editor SDK. | | [InternationalizationAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/internationalizationapi/) | Manages localization and internationalization settings for the Creative Editor SDK. | | [UserInterfaceAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/userinterfaceapi/) | Control the user interface and behavior of the Creative Editor SDK. | | [UtilsAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/classes/utilsapi/) | UtilsAPI provides utility functions for common operations in the Creative Engine SDK. | ## Functions[#](#functions) | Function | Description | | --- | --- | | [supportsBrowser](https://img.ly/docs/cesdk/vue/api/cesdk-js/functions/supportsbrowser/) | Checks if the current browser supports necessary technologies to match our supported browsers | | [checkVideoSupport](https://img.ly/docs/cesdk/vue/api/cesdk-js/functions/checkvideosupport/) | Throws an error if the current browser does not support video editing. | | [supportsVideo](https://img.ly/docs/cesdk/vue/api/cesdk-js/functions/supportsvideo/) | Checks if the current browser supports video editing. | | [checkVideoExportSupport](https://img.ly/docs/cesdk/vue/api/cesdk-js/functions/checkvideoexportsupport/) | Throws an error if the current browser does not support video exporting. | | [supportsVideoExport](https://img.ly/docs/cesdk/vue/api/cesdk-js/functions/supportsvideoexport/) | Checks if the current browser supports video exporting. | | [supportsWasm](https://img.ly/docs/cesdk/vue/api/cesdk-js/functions/supportswasm/) | Checks if the current browser supports web assembly | ## Type Aliases[#](#type-aliases) | Type Alias | Description | | --- | --- | | [SizeMode](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/sizemode/) | \- | | [PositionMode](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/positionmode/) | \- | | [VerticalBlockAlignment](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/verticalblockalignment/) | \- | | [HorizontalBlockAlignment](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/horizontalblockalignment/) | \- | | [PropertyType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/propertytype/) | Represents the various types of properties that can be associated with design blocks. Each type corresponds to a different kind of data that can be used to define the properties of a design block within the system. | | [TextCase](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/textcase/) | Represents the text case of a text block. | | [BooleanOperation](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/booleanoperation/) | Represents the names of boolean operations. | | [EngineExportOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/engineexportoptions/) | Represents the options for exporting a design block. | | [VideoExportOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/videoexportoptions/) | Represents the options for exporting a video. | | [AudioExportOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/audioexportoptions/) | Represents the options for exporting audio. | | [SplitOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/splitoptions/) | Options for configuring block split operations. | | [DropShadowOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/dropshadowoptions/) | Options for configuring drop shadow effects on blocks. | | [AnimationEntry](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/animationentry/) | Configuration options for animations. | | [AnimationOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/animationoptions/) | Options for configuring animations (in, loop, out animations). | | [AddImageOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/addimageoptions/) | Options for adding images to the scene. | | [SettingType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/settingtype/) | Represents the type of a setting. | | [SettingsBool](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/settingsbool/) | \- | | [SettingsString](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/settingsstring/) | \- | | [SettingsFloat](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/settingsfloat/) | \- | | [SettingsColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/settingscolor/) | Represents the color settings available in the editor. | | [~SettingsColorRGBA~](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/settingscolorrgba/) | Represents the color settings available in the editor. | | [SettingsEnum](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/settingsenum/) | \- | | [ZoomOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/zoomoptions/) | Options for zooming to a block with optional animation. | | [CreateSceneOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/createsceneoptions/) | Options for creating a video scene. | | [DefaultAssetSourceId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/defaultassetsourceid/) | Represents the default asset source IDs used in the editor. | | [DemoAssetSourceId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/demoassetsourceid/) | Represents the default demo asset source IDs used in the editor. | | [~TypefaceDefinition~](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/typefacedefinition/) | Represents a typeface definition used in the editor. | | [EnginePluginContext](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/engineplugincontext/) | Represents the context for an engine plugin. | | [LogLevel](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/loglevel/) | Provides logging functionality for the Creative Editor SDK. | | [MimeType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/mimetype/) | Represents the MIME types used in the editor. | | [ImageMimeType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/imagemimetype/) | Represents the image MIME types used in the editor. | | [AudioMimeType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/audiomimetype/) | Represents the audio MIME types used in the editor. | | [VideoMimeType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/videomimetype/) | Represents the video MIME types used in the editor. | | [ApplicationMimeType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/applicationmimetype/) | Represents the application MIME types used in the editor. | | [AssetGroups](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/assetgroups/) | An asset can be member of multiple groups. Groups have a semantic meaning used to build and group UIs exploring the assets, e.g.sections in the content library, or for things like topics in Unsplash for instance. | | [SortingOrder](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/sortingorder/) | The order to sort by if the asset source supports sorting. If set to None, the order is the same as the assets were added to the source. | | [AssetMetaData](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/assetmetadata/) | Generic asset information | | [AssetColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/assetcolor/) | Asset Color payload | | [AssetTransformPreset](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/assettransformpreset/) | Transform preset payload | | [AssetProperty](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/assetproperty/) | Asset property for payload | | [DesignBlockTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/designblocktypeshorthand/) | \- | | [DesignBlockTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/designblocktypelonghand/) | The longhand block type IDs for the top-level design blocks. These are the IDs used to create new blocks using `cesdk.engine.block.create(id)`. | | [DesignBlockType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/designblocktype/) | The block type IDs for the top-level design blocks. These are the IDs used to create new blocks using `cesdk.engine.block.create(id)`. Refer to [DesignBlockTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/designblocktypeshorthand/) and [DesignBlockTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/designblocktypelonghand/) for more details. | | [ShapeTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/shapetypeshorthand/) | \- | | [ShapeTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/shapetypelonghand/) | The longhand block type IDs for the blocks. These are the IDs used to create new shapes using `cesdk.engine.block.createShape(id)`. | | [ShapeType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/shapetype/) | The block type IDs for the shape blocks. These are the IDs used to create new shapes using `cesdk.engine.block.createShape(id)`. Refer to [ShapeTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/shapetypeshorthand/) and [ShapeTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/shapetypelonghand/) for more details. | | [FillTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/filltypeshorthand/) | \- | | [FillTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/filltypelonghand/) | The longhand block type IDs for the fill blocks. These are the IDs used to create new fills using `cesdk.engine.block.createFill(id)`. | | [FillType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/filltype/) | The block type IDs for the fill blocks. These are the IDs used to create new fills using `cesdk.engine.block.createFill(id)`. Refer to [FillTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/filltypeshorthand/) and [FillTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/filltypelonghand/) for more details. | | [EffectTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/effecttypeshorthand/) | \- | | [EffectTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/effecttypelonghand/) | The longhand block type IDs for the effect blocks. These are the IDs used to create new effects using `cesdk.engine.block.createEffect(id)`. | | [EffectType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/effecttype/) | The block type IDs for the effect blocks. These are the IDs used to create new effects using `cesdk.engine.block.createEffect(id)`. Refer to [EffectTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/effecttypeshorthand/) and [EffectTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/effecttypelonghand/) for more details. | | [BlurTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/blurtypeshorthand/) | \- | | [BlurTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/blurtypelonghand/) | The longhand block type IDs for the blur blocks. These are the IDs used to create new blurs using `cesdk.engine.block.createBlur(id)`. | | [BlurType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/blurtype/) | The block type IDs for the blur blocks. These are the IDs used to create new blurs using `cesdk.engine.block.createBlur(id)`. Refer to [BlurTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/blurtypeshorthand/) and [BlurTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/blurtypelonghand/) for more details. | | [AnimationTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/animationtypeshorthand/) | \- | | [AnimationTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/animationtypelonghand/) | The longhand block type IDs for the animation blocks. These are the IDs used to create new animations using `cesdk.engine.block.createAnimation(id)`. | | [AnimationType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/animationtype/) | The block type IDs for the animation blocks. These are the IDs used to create new animations using `cesdk.engine.block.createAnimation(id)`. Refer to [AnimationTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/animationtypeshorthand/) and [AnimationTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/animationtypelonghand/) for more details. | | [ObjectTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/objecttypeshorthand/) | The shorthand block type IDs for all blocks types in the Creative Engine. Those are the types that can be passed to `cesdk.engine.block.findByType(type)` for example. | | [ObjectTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/objecttypelonghand/) | The longhand block type IDs for all blocks types in the Creative Engine. Those are the Types returned by the engine when calling `cesdk.engine.block.getType(blockId)` for example. | | [ObjectType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/objecttype/) | The block type IDs for all blocks types in the Creative Engine. Those are the types that can be passed to `cesdk.engine.block.findByType(type)` for example. Refer to [ObjectTypeShorthand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/objecttypeshorthand/) and [ObjectTypeLonghand](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/objecttypelonghand/) for more details. | | [OffscreenCanvas](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/offscreencanvas/) | A simplified placeholder type for `OffscreenCanvas`, to avoid a dependency on `@types/offscreencanvas` | | [Canvas](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/canvas/) | An HTML Canvas or an Offscreen Canvas | | [HexColorString](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/hexcolorstring/) | Represents a hexadecimal color value (RGB or RGBA) that starts with a ’#’. | | [PaletteColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/palettecolor/) | Represents a color definition for the custom color palette. | | [ColorSpace](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/colorspace/) | Represents the color space used in the editor. | | [CutoutOperation](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/cutoutoperation/) | Represents the type of a cutout. | | [DesignBlockId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/designblockid/) | A numerical identifier for a design block | | [HistoryId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/historyid/) | A numerical identifier for a history stack | | [ZoomAutoFitAxis](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/zoomautofitaxis/) | The axis(es) for which to auto-fit. | | [EditMode](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/editmode/) | Represents the current edit mode of the editor. | | [FontWeight](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/fontweight/) | Represents the weight of a font. | | [FontStyle](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/fontstyle/) | Represents the style of a font. | | [BlendMode](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/blendmode/) | \- | | [ContentFillMode](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/contentfillmode/) | \- | | [DesignUnit](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/designunit/) | \- | | [SceneLayout](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/scenelayout/) | \- | | [SceneMode](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/scenemode/) | \- | | [StrokeCornerGeometry](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/strokecornergeometry/) | \- | | [StrokePosition](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/strokeposition/) | \- | | [StrokeStyle](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/strokestyle/) | \- | | [CutoutType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/cutouttype/) | \- | | [AnimationEasing](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/animationeasing/) | \- | | [RoleString](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/rolestring/) | Represents a role string. | | [Scope](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/scope/) | Represents the various scopes that define the capabilities and permissions within the Creative Editor SDK. Each scope corresponds to a specific functionality or action that can be performed within the editor. | | [RGBA](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/rgba/) | Represents a color in the RGBA color space. | | [CMYK](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/cmyk/) | Represents a color in the CMYK color space. | | [XYWH](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/xywh/) | Describes a rectangle on the screen. | | [GradientstopRGBA](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/gradientstoprgba/) | Represents a gradient stop in the RGBA color space. | | [BlockState](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/blockstate/) | Represents the state of a design block. | | [ActionFunction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/actionfunction/) | Type helper for retrieving the correct action function type based on the action ID. Returns the strongly-typed action for known actions, or a custom action type for unknown IDs. | | [ActionId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/actionid/) | Available action event types that can be registered with the ActionsAPI. These correspond to different UI actions that can be customized. Supports both predefined action types from the Actions interface and custom string identifiers. | | [AssetEntryId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/assetentryid/) | Asset library entry IDs that can be used with asset library APIs. Includes built-in entry IDs registered by the SDK, and allows custom entry IDs. | | [AssetLibraryDockComponent](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/assetlibrarydockcomponent/) | Represents an asset library dock component. | | [AssetLibraryPanelPayload](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/assetlibrarypanelpayload/) | Represents the payload for the asset library panel in the Creative Editor SDK. This interface defines the title, entries, and placement options for the asset library panel. | | [BuilderRenderFunction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/builderrenderfunction/) | Function that defines a component with the help of the passed builder object. | | [CanvasBarComponentId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/canvasbarcomponentid/) | Represents the ID of a canvas bar component. | | [CanvasMenuComponentId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/canvasmenucomponentid/) | A list of the component IDs that can be used in the canvas menu. | | [CanvasMenuComponents](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/canvasmenucomponents/) | \- | | [CanvasMenuOrderComponent](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/canvasmenuordercomponent/) | \- | | [ChildrenOrder](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/childrenorder/) | Represents the order of children components in a dropdown. | | [ComponentId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/componentid/) | Represents the ID of a component. | | [Configuration](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/configuration/) | Represents the user-provided configuration for the Creative Editor SDK. This type allows for partial configuration settings, making all properties optional. | | [CopyAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/copyaction/) | Action function for copying selected blocks to the clipboard | | [CustomActionFunction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/customactionfunction/) | A generic action function type for custom actions. Supports both synchronous and asynchronous implementations with flexible parameters. | | [CustomPanelMountFunction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/custompanelmountfunction/) | Represents a function that mounts a custom panel. | | [DialogAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/dialogaction/) | Represents an action in the dialog. | | [DialogContent](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/dialogcontent/) | Represents the content of the dialog. | | [DialogProgress](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/dialogprogress/) | Represents the progress of the dialog. | | [DialogSize](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/dialogsize/) | Represents the size of the dialog. | | [DialogType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/dialogtype/) | Represents the type of dialog. | | [DockOrderComponent](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/dockordercomponent/) | Represents a dock order component. | | [DockOrderComponentId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/dockordercomponentid/) | Represents the ID of a dock order component. | | [EditorPluginContext](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/editorplugincontext/) | Represents the context for an editor plugin. This type extends the `EnginePluginContext` with an optional `cesdk` property. | | [ExportAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/exportaction/) | Action function for handling export operations. Can be called with or without options to customize the export behavior. Supports both standard and video export workflows through a generic type parameter. The return type is automatically inferred based on the input options type. | | [ExportSceneAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/exportsceneaction/) | Action function for handling scene export operations. | | [FeatureId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/featureid/) | All built-in CE.SDK Feature Ids. | | [FeaturePredicate](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/featurepredicate/) | The feature predicate is used to enable or disable a feature based on the boolean or the return value of the function. | | [FeaturePredicateContext](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/featurepredicatecontext/) | Represents the context for enabling a feature. This type extends `IsEnabledFeatureContext` and includes a function to check the previous enable state and a function to get the default predicate. | | [FileMimeType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/filemimetype/) | Represents the MIME types for files supported by the file operations in UtilsAPI. | | [ImportSceneAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/importsceneaction/) | Action function for handling scene import operations. | | [InsertOrderComponentLocation](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/insertordercomponentlocation/) | Represents the location for inserting an order component. | | [InspectorBarComponentId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/inspectorbarcomponentid/) | Represents the ID of an inspector bar component. | | [IsEnabledFeatureContext](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/isenabledfeaturecontext/) | Represents the context for determining if a feature is enabled. This type includes the `CreativeEngine` instance. | | [LocaleKey](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/localekey/) | Represents the supported locale keys for the Creative Editor SDK. | | [NavigationBarComponentId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/navigationbarcomponentid/) | A list of the component IDs that can be used in the navigation bar. | | [NavigationBarComponents](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/navigationbarcomponents/) | \- | | [NavigationBarOrderComponent](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/navigationbarordercomponent/) | \- | | [NotificationDuration](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/notificationduration/) | Represents the duration of the notification. | | [NotificationType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/notificationtype/) | Represents the type of notification. | | [OnExportOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/onexportoptions/) | This interface extends the base ExportOptions with additional information about the export, including which design blocks were exported and the mimeType. | | [OnExportVideoOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/onexportvideooptions/) | This interface extends the base VideoExportOptions with additional information about the export, including which design blocks were exported and the mimeType. | | [OnUnsupportedBrowserAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/onunsupportedbrowseraction/) | Action function that is invoked when an unsupported browser is detected. This allows custom handling of unsupported browser scenarios. | | [Optional](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/optional/) | Turn value at K of T into a Partial | | [OrderComponentMatcher](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/ordercomponentmatcher/) | Represents a matcher for order components. | | [PageFormatDefinition](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/pageformatdefinition/) | Represents the definition of a page format in the Creative Editor SDK. This interface defines the width, height, unit, and optional fixed orientation for a page format. | | [PanelDisposer](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/paneldisposer/) | Represents a function that disposes of a panel. | | [PanelId](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/panelid/) | Represents a unique identifier for a panel in the Creative Editor SDK. This type defines specific panel IDs and allows for custom panel IDs. | | [PanelOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/paneloptions/) | Represents the options for a panel in the Creative Editor SDK. This interface defines the options for a panel, including whether it is closable by the user, its position, whether it is floating, and its payload. | | [PanelPayload](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/panelpayload/) | Represents the payload for a panel in the Creative Editor SDK. This type defines the payload based on the panel ID. | | [PasteAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/pasteaction/) | Action function for pasting blocks from the clipboard | | [PreviewType](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/previewtype/) | Represents a preview, which can be either an image or a color. | | [PreviewTypeColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/previewtypecolor/) | Represents a color preview. | | [PreviewTypeImage](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/previewtypeimage/) | Represents an image preview. | | [SaveSceneAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/savesceneaction/) | Action function for handling scene saving operations. | | [ScrollToBlockAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/scrolltoblockaction/) | Action function for scrolling to a specific block | | [ScrollToPageAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/scrolltopageaction/) | Action function for scrolling to a specific page | | [ShareSceneAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/sharesceneaction/) | Action function for handling scene sharing operations. | | [Suffix](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/suffix/) | Represents additional options for a button, which can be used as a suffix. | | [TimelineCollapseAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/timelinecollapseaction/) | Action function for collapsing the video timeline | | [TimelineExpandAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/timelineexpandaction/) | Action function for expanding the video timeline | | [TimelineZoomInAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/timelinezoominaction/) | Action function for zooming in the video timeline by one step | | [TimelineZoomOutAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/timelinezoomoutaction/) | Action function for zooming out the video timeline by one step | | [TimelineZoomResetAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/timelinezoomresetaction/) | Action function for resetting the video timeline zoom to default level (1.0) | | [TimelineZoomToFitAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/timelinezoomtofitaction/) | Action function for fitting the video timeline to show all content | | [TimelineZoomToLevelAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/timelinezoomtolevelaction/) | Action function for setting the video timeline zoom to a specific level | | [UnknownPanelPayload](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/unknownpanelpayload/) | Represents an unknown payload for a panel in the Creative Editor SDK. This type defines a generic payload with unknown keys and values. | | [UnknownTranslations](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/unknowntranslations/) | Allows for custom translation keys beyond the built-in ones. | | [UploadAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/uploadaction/) | Action function for uploading files to asset sources. | | [ViewStyle](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/viewstyle/) | Represents the view style options in the Creative Editor SDK. This type defines the possible view styles, which are ‘advanced’ and ‘default’. | | [ZoomInAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/zoominaction/) | Action function for zooming in by one step | | [ZoomOutAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/zoomoutaction/) | Action function for zooming out by one step | | [ZoomToBlockAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/zoomtoblockaction/) | Action function for zooming to a specific block | | [ZoomToLevelAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/zoomtolevelaction/) | Action function for setting zoom to a specific level | | [ZoomToPageAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/zoomtopageaction/) | Action function for zooming to a page with optional padding | | [ZoomToSelectionAction](https://img.ly/docs/cesdk/vue/api/cesdk-js/type-aliases/zoomtoselectionaction/) | Action function for zooming to the current selection | ## Enumerations[#](#enumerations) | Enumeration | Description | | --- | --- | | [PanelPosition](https://img.ly/docs/cesdk/vue/api/cesdk-js/enumerations/panelposition/) | This enum is used to specify the position of various panels within the user interface, such as the inspector, settings, and asset library panels. | ## Interfaces[#](#interfaces) | Interface | Description | | --- | --- | | [ApplyAssetOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/applyassetoptions/) | Options for applying an asset to the scene. | | [AddVideoOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/addvideooptions/) | Options for adding videos to the scene. | | [HTMLCreativeEngineCanvasElement](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/htmlcreativeenginecanvaselement/) | A wrapper around a plain canvas | | [\_EngineConfiguration](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/engineconfiguration/) | Specifies the configuration for the Creative Editor SDK. | | [EnginePlugin](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/engineplugin/) | Represents an engine plugin. | | [Logger](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/logger/) | Represents a logger function. | | [Source](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/source/) | A single source width an intrinsic width & height. | | [AssetRGBColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetrgbcolor/) | Asset Color payload RGB representation | | [AssetCMYKColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetcmykcolor/) | Asset Color payload CMYK representation | | [AssetSpotColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetspotcolor/) | Asset Color payload SpotColor representation | | [AssetFixedAspectRatio](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetfixedaspectratio/) | Asset transform preset payload fixed aspect ratio | | [AssetFreeAspectRatio](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetfreeaspectratio/) | Asset transform preset payload free aspect ratio | | [AssetFixedSize](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetfixedsize/) | Asset transform preset payload fixed size | | [AssetStringProperty](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetstringproperty/) | Asset string property definition | | [AssetNumberProperty](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetnumberproperty/) | Asset number property definition | | [AssetBooleanProperty](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetbooleanproperty/) | Asset boolean property definition | | [AssetEnumProperty](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetenumproperty/) | Asset enum property definition | | [AssetColorProperty](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetcolorproperty/) | Asset color property definition | | [AssetPayload](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetpayload/) | Asset payload | | [Asset](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/asset/) | Generic asset information | | [AssetDefinition](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetdefinition/) | Definition of an asset used if an asset is added to an asset source. | | [AssetResult](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetresult/) | Single asset result of a query from the engine. | | [CompleteAssetResult](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/completeassetresult/) | Asset results that are returned from the engine. | | [AssetQueryData](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetquerydata/) | Defines a request for querying assets | | [AssetsQueryResult](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetsqueryresult/) | Return type of a `findAssets` query. | | [AssetSource](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetsource/) | A source of assets | | [RGBColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/rgbcolor/) | Represents an RGB color value. | | [RGBAColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/rgbacolor/) | Represents an RGBA color value. | | [CMYKColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/cmykcolor/) | Represents a CMYK color value. | | [SpotColor](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/spotcolor/) | Represents a spot color value. | | [GradientColorStop](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/gradientcolorstop/) | Represents a gradient color stop. | | [Range](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/range/) | An open range. | | [Font](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/font/) | Represents a font. | | [Typeface](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/typeface/) | Represents a typeface. | | [Buffer](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/buffer/) | Represents a buffer of data. | | [TransientResource](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/transientresource/) | Represents a transient resource. | | [BlockEvent](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/blockevent/) | Represents an event related to a design block. | | [AssetLibraryEntry](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/assetlibraryentry/) | Represents an entry in the asset library, combining data and view configurations. | | [Builder](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/builder/) | Interface for all available builder. Depending on the context different implementation might be used. A “Button” in the canvas menu might render different component than a button in the topbar or a panel. | | [BuilderRenderFunctionContext](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/builderrenderfunctioncontext/) | Represents the context for rendering a builder function. | | [BuiltinTranslations](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/builtintranslations/) | Built-in translation keys provided by the Creative Editor SDK. | | [ButtonGroupOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/buttongroupoptions/) | Represents options for a button group. | | [ButtonOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/buttonoptions/) | Represents options for a button. | | [CanvasMenuActionButton](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/canvasmenuactionbutton/) | Base interface for action buttons in the canvas menu. Contains common properties shared across all canvas menu button types. | | [CanvasMenuCustomActionButton](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/canvasmenucustomactionbutton/) | Interface representing a custom canvas menu action button. Note: This component requires a key and has a required label, unlike other action buttons. | | [CanvasMenuOptionsComponent](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/canvasmenuoptionscomponent/) | Interface representing the canvas menu options dropdown component. This component can contain children components that are rendered in a dropdown menu. | | [CESDKConfiguration](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/cesdkconfiguration/) | Represents the configuration settings for the Creative Editor SDK. This interface defines various settings such as locale, theme, development mode, user interface, internationalization, accessibility, callbacks, feature flags, and logger. | | [CheckboxOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/checkboxoptions/) | Represents options for a checkbox. | | [ColorInputOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/colorinputoptions/) | Represents options for a color input. | | [ComponentOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/componentoptions/) | Represents options for a component. | | [ComponentPayload](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/componentpayload/) | Represents the payload of a component. | | [CustomDockComponent](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/customdockcomponent/) | Represents a custom dock component. | | [Dialog](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/dialog/) | Represents a dialog configuration. | | [DropdownChildrenContext](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/dropdownchildrencontext/) | Represents the context for the children of a dropdown. | | [DropdownOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/dropdownoptions/) | Represents options for a dropdown. | | [EditorPlugin](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/editorplugin/) | Represents an editor plugin. This interface defines the structure of an editor plugin, including its name, version, and initialization function. | | [ExportOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/exportoptions/) | Specifies options for exporting design blocks to various formats. | | [HeadingOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/headingoptions/) | Represents options for a heading. | | [InputOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/inputoptions/) | Represents options for an input. | | [LibraryOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/libraryoptions/) | Represents options for a library. | | [MediaPreviewOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/mediapreviewoptions/) | Represents options for a media preview. | | [NavigationBarActionButton](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/navigationbaractionbutton/) | Base interface for action buttons in the navigation bar. Contains common properties shared across all action button types. | | [NavigationBarCustomActionButton](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/navigationbarcustomactionbutton/) | Interface representing a generic Action Button in the navigation bar component. Note: This component requires a key and has a required label, unlike other action buttons. | | [Notification](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/notification/) | Represents a notification configuration. | | [NumberInputOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/numberinputoptions/) | Represents options for a number input. | | [OrderComponent](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/ordercomponent/) | Represents an order component. | | [OrderComponentWithChildren](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/ordercomponentwithchildren/) | Represents a custom dock component. | | [OrderContext](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/ordercontext/) | Interface representing the context for ordering components. | | [RegisteredActions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/registeredactions/) | Represents a collection of action functions used throughout the application. Each property corresponds to a specific UI action or event that can be customized. | | [ReplaceAssetLibraryEntriesContext](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/replaceassetlibraryentriescontext/) | Provides context for replacing asset library entries, including selected blocks and default entry IDs. | | [SectionOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/sectionoptions/) | Represents options for a section. | | [SelectOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/selectoptions/) | Represents options for a select input. | | [SelectValue](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/selectvalue/) | Represents a value for a select input. | | [SliderOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/slideroptions/) | Represents options for a slider. | | [TextAreaOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/textareaoptions/) | Represents options for a text area. | | [TextInputOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/textinputoptions/) | Represents options for a text input. | | [TextOptions](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/textoptions/) | Represents options for text. | | [Translations](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/translations/) | Complete translation type that includes both built-in and custom translations. | | [UserInterface](https://img.ly/docs/cesdk/vue/api/cesdk-js/interfaces/userinterface/) | Specifies the configuration for the user interface of the Creative Editor SDK. | ## Namespaces[#](#namespaces) | Namespace | Description | | --- | --- | | [CESDKConfiguration](https://img.ly/docs/cesdk/vue/api/cesdk-js/documentation/namespaces/cesdkconfiguration/) | Namespace for `CESDKConfiguration` to include deprecated keys. This namespace includes deprecated keys that are part of the public API via the `CombinedConfiguration` type. These keys are used in the ConfigMigrations but are not used internally. | | [ConfigTypes](https://img.ly/docs/cesdk/vue/api/cesdk-js/documentation/namespaces/configtypes/) | \- | | [ExperimentalBuilder](https://img.ly/docs/cesdk/vue/api/cesdk-js/documentation/namespaces/experimentalbuilder/) | Namespace containing experimental features for the builder. These features are subject to change and may not be stable for production use. | | [ExperimentalUserInterfaceAPI](https://img.ly/docs/cesdk/vue/api/cesdk-js/documentation/namespaces/experimentaluserinterfaceapi/) | Provides experimental methods for controlling the UI of the Creative Editor SDK. | | [UserInterfaceElements](https://img.ly/docs/cesdk/vue/api/cesdk-js/documentation/namespaces/userinterfaceelements/) | \- | ## Variables[#](#variables) | Variable | Description | | --- | --- | | [~LogLevel~](https://img.ly/docs/cesdk/vue/api/cesdk-js/variables/loglevel/) | Provides a set of predefined log levels for the Creative Editor SDK. | | [~MimeType~](https://img.ly/docs/cesdk/vue/api/cesdk-js/variables/mimetype/) | Represents the MIME types used in the editor. | --- [Source](https:/img.ly/docs/cesdk/vue/animation/types-4e5f41) --- # Supported Animation Types Apply entrance, exit, and loop animations to design blocks using the available animation types in CE.SDK. ![Animation types demonstrating slide, fade, zoom, and loop effects on image blocks](/docs/cesdk/_astro/browser.hero.Dr4F1NOC_Z15nJHj.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-animation-types-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Enable video features for animation playback cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene (required for animations) await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine; const scene = engine.scene.get()!; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Set page dimensions engine.block.setWidth(page, 1920); engine.block.setHeight(page, 1080); // 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); console.log('Slide animation properties:', properties); // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); 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) Entrance animations define how a block appears. We use `createAnimation()` with the animation type and attach it using `setInAnimation()`. ### Slide Animation[#](#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. ``` // Create a slide animation that enters from the leftconst 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=topengine.block.setFloat(slideAnimation, 'animation/slide/direction', Math.PI);engine.block.setEnum(slideAnimation, 'animationEasing', 'EaseOut'); ``` ### Fade Animation[#](#fade-animation) The fade animation transitions opacity from invisible to fully visible. Easing controls the animation curve. ``` // Create a fade entrance animationconst fadeAnimation = engine.block.createAnimation('fade');engine.block.setInAnimation(block2, fadeAnimation);engine.block.setDuration(fadeAnimation, 1.0);engine.block.setEnum(fadeAnimation, 'animationEasing', 'EaseInOut'); ``` ### Zoom Animation[#](#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. ``` // Create a zoom animation with fade effectconst 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) 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. ``` // Create entrance and exit animationsconst 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 disappearsconst 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) Loop animations run continuously while the block is visible. They can combine with entrance and exit animations. We use `setLoopAnimation()` to attach them. ``` // Create a breathing loop animationconst 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 scaleengine.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[#](#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. ``` // Apply entrance, exit, and loop animations togetherconst 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[#](#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. ``` // Discover available properties for any animationconst properties = engine.block.findAllProperties(slideAnimation);console.log('Slide animation properties:', properties); // Query available easing optionsconst easingOptions = engine.block.getEnumValues('animationEasing');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[#](#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[#](#next-steps) * [Base Animations](vue/animation/create/base-0fc5c4/) — Create and attach animations to blocks * [Text Animations](vue/animation/create/text-d6f4aa/) — Animate text with writing styles * [Animation Overview](vue/animation/overview-6a2ef2/) — Animation concepts and capabilities --- [Source](https:/img.ly/docs/cesdk/vue/animation/overview-6a2ef2) --- # Overview 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 ](vue/get-started/overview-e18f40/) ## Animation Capabilities in CE.SDK[#](#animation-capabilities-in-cesdk) CE.SDK enables animation across a variety of design elements, giving you the flexibility to animate: * **Images:** Animate image blocks with movements like fades, zooms, or rotations. * **Text:** Animate text layers to create effects such as typewriter reveals or slide-ins. * **Shapes and Graphics:** Add motion to vector shapes, icons, and graphic blocks to create visually rich layouts. You can animate key properties of these elements, including: * **Position:** Move elements across the canvas. * **Scale:** Zoom in or out dynamically. * **Rotation:** Spin or pivot elements over time. * **Opacity:** Fade elements in and out. ## Supported Animation Types[#](#supported-animation-types) CE.SDK provides a range of animation types designed for common motion effects. Core categories include: * **Fade:** Smooth transitions in or out using opacity changes. * **Slide:** Move elements into or out of the frame from any direction. * **Zoom:** Scale elements up or down to create dynamic emphasis. * **Rotate:** Apply rotational motion for spins or turns. These animations can be used as in, out or loop animations to create complex sequences. ## Timeline and Keyframes[#](#timeline-and-keyframes) Animations in CE.SDK are structured around a **timeline-based editing system**. Each scene has a timeline where elements are placed and animated relative to playback time. Animations can be created entirely through the UI’s timeline editor or managed programmatically by interacting with the CreativeEngine’s animation APIs, offering flexibility for different workflows. --- [Source](https:/img.ly/docs/cesdk/vue/animation/edit-32c12a) --- # Edit Animations Modify existing animations by reading properties, changing duration and easing, and replacing or removing animations from blocks. ![Edit animations demonstrating property modification, easing changes, and animation replacement on image blocks](/docs/cesdk/_astro/browser.hero.D5AlwXRE_Z1G4lQN.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-animation-edit-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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](vue/animation/create/base-0fc5c4/) guide. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Enable video features for animation playback cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene (required for animations) await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages[0]!; // Set page dimensions engine.block.setWidth(page, 1920); engine.block.setHeight(page, 1080); // 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[#](#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. ``` // Retrieve animations from a blockconst 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 existsif (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[#](#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. ``` // Read animation propertiesconst 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[#](#modifying-animation-duration) Change animation timing with `setDuration()`. The duration is specified in seconds. ``` // Modify animation durationconst 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[#](#changing-easing-functions) Easing controls animation acceleration. We use `setEnum()` with the `'animationEasing'` property to change it. ``` // Change animation easingconst 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[#](#adjusting-animation-specific-properties) Each animation type has unique configurable properties. For slide animations, we can change the entry direction. ``` // Adjust animation-specific propertiesconst 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[#](#replacing-animations) To swap an animation type, destroy the existing animation before setting a new one. This prevents memory leaks from orphaned animation objects. ``` // Replace an existing animationconst 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 animationconst 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[#](#removing-animations) Remove an animation by destroying it. After destruction, the getter returns `0`. ``` // Add a loop animation to demonstrate removalconst loopAnim = engine.block.createAnimation('breathing_loop');engine.block.setLoopAnimation(block6, loopAnim);engine.block.setDuration(loopAnim, 1.0); // Remove the loop animation by destroying itconst 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[#](#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[#](#next-steps) * [Base Animations](vue/animation/create/base-0fc5c4/) — Create entrance, exit, and loop animations * [Text Animations](vue/animation/create/text-d6f4aa/) — Animate text with writing styles and character control * [Animation Overview](vue/animation/overview-6a2ef2/) — Understand animation concepts and capabilities --- [Source](https:/img.ly/docs/cesdk/vue/animation/create-15cf50) --- # Create Animations Add motion to design elements by creating entrance, exit, and loop animations using CE.SDK’s animation system. ![Create Animations example showing animated blocks with various animation types](/docs/cesdk/_astro/browser.hero.cu8I5kIA_Z2fMEOx.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-animation-create-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/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. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Enable video features for animation playback cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Load assets and create a video scene (required for animations) await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Video', withUploadAssetSources: true }); await cesdk.createVideoScene(); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages[0]; // Set page dimensions and duration const pageWidth = 1920; const pageHeight = 1080; engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); 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[#](#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. ``` 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 applyingif (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) Entrance animations define how blocks appear on screen. We create the animation with `createAnimation()`, attach it with `setInAnimation()`, and configure the duration with `setDuration()`. ``` // Add fade entrance for the logoconst 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) 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. ``` 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 endengine.block.setTimeOffset(footerBlock, 0);engine.block.setDuration(footerBlock, 5.0); // Create exit animation that plays at the end of the block's durationconst 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 on the timeline. ## Loop Animations[#](#loop-animations) Loop animations run continuously while the block is visible. We use animation types ending in `_loop` and attach them with `setLoopAnimation()`. ``` 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 frameengine.block.setEnum(logoBlock, 'contentFill/mode', 'Contain'); // Create a pulsating loop animationconst 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[#](#animation-properties) Each animation type exposes configurable properties. We use `setFloat()` and `setEnum()` to adjust these properties, and `findAllProperties()` to discover available options. ``` // Create slide animation and configure direction for titleconst 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-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. ``` 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 revealconst 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[#](#managing-animation-lifecycle) We can retrieve current animations using `getInAnimation()`, `getOutAnimation()`, and `getLoopAnimation()`. A return value of 0 indicates no animation is attached. ``` // Example: Retrieve animations to verify they're attachedconst 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 optionsconst 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[#](#troubleshooting) ### Animation Not Playing[#](#animation-not-playing) Verify the block supports animations with `supportsAnimation()`. Check that the scene is in Video mode, as animations require timeline-based playback. ### Duration Issues[#](#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[#](#memory-leaks) When replacing an animation, destroy the old animation instance before creating a new one: ``` 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[#](#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[#](#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[#](#next-steps) * [Base Animations](vue/animation/create/base-0fc5c4/) — Detailed non-text block animations * [Text Animations](vue/animation/create/text-d6f4aa/) — Text-specific animation control * [Edit Animations](vue/animation/edit-32c12a/) — Modify existing animations * [Animation Overview](vue/animation/overview-6a2ef2/) — Animation concepts --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/ui-extensions/register-new-component-b04a04) --- # Register a New Component Register custom UI components using CE.SDK’s builder system and place them in different areas of the editor interface like the navigation bar, inspector bar, dock, and canvas menu. ![Register New Component](/docs/cesdk/_astro/browser.hero.B9VmwL_R_ZfhEap.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-register-new-component-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-register-new-component-browser) The builder system provides a declarative API for creating UI components that integrate with CE.SDK. Components registered via `cesdk.ui.registerComponent()` receive a render function that is automatically re-invoked when relevant engine state changes, enabling reactive UIs without manual subscription management. You can create buttons, dropdowns, inputs, and other UI elements that react to engine state changes. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; const registerNewComponentPlugin: EditorPlugin = { name: 'ly.img.registerNewComponentPlugin', version: '1.0.0', async initialize({ cesdk, engine }: EditorPluginContext) { if (cesdk == null) return; // Load a scene so the editor has content to display await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); // Register a custom button component that shows the selected block's type. // The render function is automatically re-invoked when engine state changes. cesdk.ui.registerComponent( 'com.example.blockTypeButton', ({ builder, engine: eng, cesdk: cesdkInstance }) => { // Engine API calls are tracked. When the selection changes, // the component re-renders automatically. const selectedBlocks = eng.block.findAllSelected(); const selectedBlock = selectedBlocks.length > 0 ? selectedBlocks[0] : null; const blockType = selectedBlock ? eng.block.getType(selectedBlock) : null; const label = blockType ? formatBlockType(blockType) : 'No Selection'; builder.Button('block-type-display', { label, icon: '@imgly/icons/Info', isDisabled: !selectedBlock, onClick: () => { const message = selectedBlock ? `Selected block type: ${blockType}` : 'No block selected'; cesdkInstance.ui.showNotification({ message, type: 'info' }); } }); } ); // Register a component with a dropdown menu containing buttons. // Dropdowns in the navigation bar support Button and Separator elements. cesdk.ui.registerComponent( 'com.example.actionsDropdown', ({ builder, engine: eng, cesdk: cesdkInstance }) => { const selectedBlocks = eng.block.findAllSelected(); const selectedBlock = selectedBlocks.length > 0 ? selectedBlocks[0] : null; builder.Dropdown('actions-dropdown', { label: 'Actions', icon: '@imgly/icons/Adjustments', children: () => { builder.Button('action-duplicate', { label: 'Duplicate', icon: '@imgly/icons/Duplicate', variant: 'plain', isDisabled: !selectedBlock, onClick: () => { if (selectedBlock) { eng.block.duplicate(selectedBlock); cesdkInstance.ui.showNotification({ message: 'Block duplicated', type: 'info' }); } } }); builder.Button('action-delete', { label: 'Delete', icon: '@imgly/icons/Trash', variant: 'plain', color: 'danger', isDisabled: !selectedBlock, onClick: () => { if (selectedBlock) { eng.block.destroy(selectedBlock); cesdkInstance.ui.showNotification({ message: 'Block deleted', type: 'info' }); } } }); builder.Separator('action-separator'); builder.Button('action-select-all', { label: 'Select All', icon: '@imgly/icons/SelectAll', variant: 'plain', onClick: () => { const page = eng.scene.getCurrentPage(); if (page) { const children = eng.block.getChildren(page); children.forEach((child) => eng.block.setSelected(child, true) ); } } }); } }); } ); // Place the custom components in the navigation bar. // Use setNavigationBarOrder to define the order of components. cesdk.ui.setNavigationBarOrder([ 'ly.img.save', 'com.example.blockTypeButton', 'com.example.actionsDropdown', 'ly.img.spacer', 'ly.img.undo', 'ly.img.redo', 'ly.img.zoom.navigationBar' ]); }}; // Helper function to format block type for displayfunction formatBlockType(blockType: string): string { // Extract the last part of the block type (e.g., '//ly.img.ubq/graphic' -> 'Graphic') const parts = blockType.split('/'); const typeName = parts[parts.length - 1]; return typeName.charAt(0).toUpperCase() + typeName.slice(1);} export default registerNewComponentPlugin; ``` This guide demonstrates registering custom components including a button, dropdown menu, checkbox, and select control, then placing them in the navigation bar and inspector bar. ## Registering a Component[#](#registering-a-component) Use `cesdk.ui.registerComponent()` to register a component with a unique ID and a render function. The render function receives `builder`, `engine`, `cesdk`, `state`, and `payload` parameters. ``` // Register a custom button component that shows the selected block's type.// The render function is automatically re-invoked when engine state changes.cesdk.ui.registerComponent( 'com.example.blockTypeButton', ({ builder, engine: eng, cesdk: cesdkInstance }) => { // Engine API calls are tracked. When the selection changes, // the component re-renders automatically. const selectedBlocks = eng.block.findAllSelected(); const selectedBlock = selectedBlocks.length > 0 ? selectedBlocks[0] : null; const blockType = selectedBlock ? eng.block.getType(selectedBlock) : null; const label = blockType ? formatBlockType(blockType) : 'No Selection'; builder.Button('block-type-display', { label, icon: '@imgly/icons/Info', isDisabled: !selectedBlock, onClick: () => { const message = selectedBlock ? `Selected block type: ${blockType}` : 'No block selected'; cesdkInstance.ui.showNotification({ message, type: 'info' }); } }); }); ``` ### Component ID Naming[#](#component-id-naming) Use reverse domain notation for component IDs (e.g., `'com.example.myButton'`). This ensures uniqueness and avoids conflicts with built-in components. ### Render Function Signature[#](#render-function-signature) The render function receives these parameters: * **builder**: Object providing methods to create UI elements * **engine**: The engine instance for accessing block and scene state * **cesdk**: The editor instance for UI operations like notifications * **state**: Function for managing local component state * **payload**: Optional data passed when adding the component to an order ## Engine Reactivity[#](#engine-reactivity) Components automatically re-render when engine APIs called within the render function detect state changes. Engine method calls like `engine.block.findAllSelected()` are tracked, and only components that use changed engine state re-render. In the example above, calling `eng.block.findAllSelected()` and `eng.block.getType()` inside the render function creates automatic subscriptions. When the selection changes, the component re-renders with updated values. This reactive approach eliminates manual subscription management. The builder system handles all subscriptions internally. ## Using Builder Elements[#](#using-builder-elements) The builder object provides methods to create UI elements within your component. ### Button[#](#button) The `builder.Button()` method creates an interactive button. It accepts a unique ID and configuration options including `label`, `icon`, `onClick`, and state flags like `isDisabled`. ### Dropdown with Nested Content[#](#dropdown-with-nested-content) Use `builder.Dropdown()` to create a dropdown menu with nested content. The `children` callback function lets you add buttons and separators inside the dropdown. ``` // Register a component with a dropdown menu containing buttons.// Dropdowns in the navigation bar support Button and Separator elements.cesdk.ui.registerComponent( 'com.example.actionsDropdown', ({ builder, engine: eng, cesdk: cesdkInstance }) => { const selectedBlocks = eng.block.findAllSelected(); const selectedBlock = selectedBlocks.length > 0 ? selectedBlocks[0] : null; builder.Dropdown('actions-dropdown', { label: 'Actions', icon: '@imgly/icons/Adjustments', children: () => { builder.Button('action-duplicate', { label: 'Duplicate', icon: '@imgly/icons/Duplicate', variant: 'plain', isDisabled: !selectedBlock, onClick: () => { if (selectedBlock) { eng.block.duplicate(selectedBlock); cesdkInstance.ui.showNotification({ message: 'Block duplicated', type: 'info' }); } } }); builder.Button('action-delete', { label: 'Delete', icon: '@imgly/icons/Trash', variant: 'plain', color: 'danger', isDisabled: !selectedBlock, onClick: () => { if (selectedBlock) { eng.block.destroy(selectedBlock); cesdkInstance.ui.showNotification({ message: 'Block deleted', type: 'info' }); } } }); builder.Separator('action-separator'); builder.Button('action-select-all', { label: 'Select All', icon: '@imgly/icons/SelectAll', variant: 'plain', onClick: () => { const page = eng.scene.getCurrentPage(); if (page) { const children = eng.block.getChildren(page); children.forEach((child) => eng.block.setSelected(child, true) ); } } }); } }); }); ``` The dropdown receives the same configuration as buttons, but uses `children` instead of `onClick` to define the dropdown content. ### Available Builder Components[#](#available-builder-components) Not every location supports every builder component yet. The following table shows the available builder components and their properties. | Builder Component | Description | Properties | | --- | --- | --- | | `builder.Button` | A simple button to react on a user click. | **label**: The button label (supports i18n keys). **onClick**: Click handler. **variant**: `regular` (default) or `plain`. **color**: `accent` or `danger`. **icon**: The button icon. **trailingIcon**: Trailing icon. **isActive**: Active state indicator. **isSelected**: Selected state indicator. **isDisabled**: Disabled state. **isLoading**: Loading state. **loadingProgress**: Progress value 0-1. **tooltip**: Hover tooltip (supports i18n keys). | | `builder.ButtonGroup` | Grouping of multiple buttons in a segmented control. | **children**: Function to render grouped buttons (only Button and Dropdown allowed). | | `builder.Dropdown` | A button that opens a dropdown with content. | Same as Button, but with **children** instead of onClick for dropdown content. | | `builder.Heading` | Renders text as a heading. | **content**: The heading text. | | `builder.Separator` | Adds visual separation between entries. | No properties. Follows special layout rules for consecutive separators. | | `builder.Component` | Renders another registered component. | **componentId**: The registered component ID. **payload**: Optional data passed to the component. | | `builder.Checkbox` | Toggle checkbox control. | **value**: Current value. **setValue**: Change handler. **inputLabel**: Checkbox label. | | `builder.Select` | Dropdown select with options. | **value**: Current selection object. **setValue**: Change handler. **values**: Array of option objects with `id`, `label`, and optional `icon`. | | `builder.TextInput` | Text input field. | **value**: Current value. **setValue**: Change handler. **placeholder**: Placeholder text. | | `builder.NumberInput` | Numeric input field. | **value**: Current value. **setValue**: Change handler. **min/max**: Range limits. | | `builder.Slider` | Numeric range slider. | **value**: Current value. **setValue**: Change handler. **min/max/step**: Range configuration. | | `builder.Section` | Container for grouping related controls. | **children**: Function to render section contents. | ## Managing Component State[#](#managing-component-state) The `state` function provides local state management within components, similar to React’s `useState`. ``` const { value, setValue } = state('unique-id', defaultValue); ``` State persists across re-renders with the same ID. Calling `setValue()` triggers a component re-render. Since the returned object matches input component expectations, you can spread it directly into components. ``` cesdk.ui.registerComponent('counter', ({ builder, state }) => { const { value, setValue } = state('counter', 0); builder.Button('counter-button', { label: `${value} clicks`, onClick: () => { setValue(value + 1); } });}); ``` ## Placing Components in the Navigation Bar[#](#placing-components-in-the-navigation-bar) Add components to the navigation bar using `cesdk.ui.setNavigationBarOrder()`. The navigation bar appears at the top of the editor. ``` // Place the custom components in the navigation bar.// Use setNavigationBarOrder to define the order of components.cesdk.ui.setNavigationBarOrder([ 'ly.img.save', 'com.example.blockTypeButton', 'com.example.actionsDropdown', 'ly.img.spacer', 'ly.img.undo', 'ly.img.redo', 'ly.img.zoom.navigationBar']); ``` Use `cesdk.ui.insertNavigationBarOrderComponent()` to position components relative to existing ones without replacing the entire order. ## Placing Components in the Inspector Bar[#](#placing-components-in-the-inspector-bar) The inspector bar appears at the bottom when a block is selected. Add components using `cesdk.ui.setInspectorBarOrder()` or position relative to existing components with `cesdk.ui.insertInspectorBarOrderComponent()`. ``` cesdk.ui.setInspectorBarOrder([ 'com.example.myInspectorButton', 'ly.img.fill', 'ly.img.stroke']); ``` ## Placing Components in the Dock[#](#placing-components-in-the-dock) The dock is the vertical sidebar for asset libraries and tools. Add components using `cesdk.ui.setDockOrder()` or `cesdk.ui.insertDockOrderComponent()`. ``` cesdk.ui.insertDockOrderComponent( { id: 'ly.img.assetLibrary.dock' }, 'com.example.myDockButton', 'after'); ``` ## Placing Components in the Canvas Menu[#](#placing-components-in-the-canvas-menu) The canvas menu appears as a floating menu near selected blocks. Add components using `cesdk.ui.setCanvasMenuOrder()` or `cesdk.ui.insertCanvasMenuOrderComponent()`. ``` cesdk.ui.setCanvasMenuOrder([ 'ly.img.delete', 'com.example.myCanvasMenuAction', 'ly.img.duplicate']); ``` ## Placing Components in the Canvas Bar[#](#placing-components-in-the-canvas-bar) Canvas bars appear above or below the canvas area. Specify `'top'` or `'bottom'` position using `cesdk.ui.setCanvasBarOrder()` or `cesdk.ui.insertCanvasBarOrderComponent()`. ``` cesdk.ui.setCanvasBarOrder(['com.example.myCanvasBarButton'], 'top'); ``` ## Passing Payload Data[#](#passing-payload-data) Components can receive contextual data through the `payload` parameter. Pass data when adding a component to an order using an object with `id` and additional properties. ``` cesdk.ui.registerComponent( 'myDockEntry.dock', ({ builder: { Button }, payload }) => { const { label } = payload; Button('entry-button', { label }); }); cesdk.ui.setDockOrder([ { id: 'myDockEntry.dock', label: 'Custom Label' }]); ``` Use TypeScript generics with `registerComponent()` to type the payload parameter. ## Troubleshooting[#](#troubleshooting) ### Component Not Rendering[#](#component-not-rendering) Verify the component ID matches between registration and placement in order arrays. Check that the registration happens before setting the order. Component IDs are case-sensitive. ### State Not Persisting[#](#state-not-persisting) Ensure unique state IDs within the component. Duplicate IDs cause conflicts and unexpected behavior. Each `state()` call should use a distinct identifier. ### Component Not Updating[#](#component-not-updating) Confirm engine API calls are made inside the render function, not outside. The reactor only tracks calls made during the render function execution. Moving engine calls outside breaks reactivity. ### Order Changes Not Applying[#](#order-changes-not-applying) Check that the order method matches the UI location. Use `setDockOrder()` for the dock, `setNavigationBarOrder()` for navigation, and so on. Mismatched methods silently fail. ## API Reference[#](#api-reference) | Method | Category | Purpose | | --- | --- | --- | | `cesdk.ui.registerComponent()` | Component Registration | Register a custom component with a unique ID and render function | | `cesdk.ui.setNavigationBarOrder()` | UI Layout | Set the order of components in the navigation bar | | `cesdk.ui.getNavigationBarOrder()` | UI Layout | Get the current navigation bar component order | | `cesdk.ui.insertNavigationBarOrderComponent()` | UI Layout | Insert a component relative to an existing navigation bar component | | `cesdk.ui.setInspectorBarOrder()` | UI Layout | Set the order of components in the inspector bar | | `cesdk.ui.getInspectorBarOrder()` | UI Layout | Get the current inspector bar component order | | `cesdk.ui.insertInspectorBarOrderComponent()` | UI Layout | Insert a component relative to an existing inspector bar component | | `cesdk.ui.setDockOrder()` | UI Layout | Set the order of components in the dock | | `cesdk.ui.getDockOrder()` | UI Layout | Get the current dock component order | | `cesdk.ui.insertDockOrderComponent()` | UI Layout | Insert a component relative to an existing dock component | | `cesdk.ui.setCanvasMenuOrder()` | UI Layout | Set the order of components in the canvas menu | | `cesdk.ui.getCanvasMenuOrder()` | UI Layout | Get the current canvas menu component order | | `cesdk.ui.insertCanvasMenuOrderComponent()` | UI Layout | Insert a component relative to an existing canvas menu component | | `cesdk.ui.setCanvasBarOrder()` | UI Layout | Set the order of components in the canvas bar (top or bottom) | | `cesdk.ui.getCanvasBarOrder()` | UI Layout | Get the current canvas bar component order | | `cesdk.ui.insertCanvasBarOrderComponent()` | UI Layout | Insert a component relative to an existing canvas bar component | | `builder.Button()` | Builder | Create an interactive button element | | `builder.Dropdown()` | Builder | Create a dropdown menu with nested children | | `builder.ButtonGroup()` | Builder | Create a group of related buttons | | `builder.Checkbox()` | Builder | Create a toggle checkbox control | | `builder.Select()` | Builder | Create a select dropdown with predefined options | | `builder.TextInput()` | Builder | Create a text input field | | `builder.NumberInput()` | Builder | Create a numeric input field | | `builder.Slider()` | Builder | Create a numeric slider control | | `builder.Section()` | Builder | Create a container for grouping controls | | `builder.Separator()` | Builder | Create a visual divider | | `builder.Heading()` | Builder | Create a heading text element | | `builder.Component()` | Builder | Render another registered component with optional payload | | `state()` | Component State | Access local state with get/set capabilities | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/ui-extensions/quick-actions-1e478d) --- # Quick Actions Extend CE.SDK with one-click editing actions using official plugins for background removal, vectorization, QR codes, and cutouts. ![Quick Actions example showing background removal, vectorize, and cutout buttons](/docs/cesdk/_astro/browser.hero.CbO9vkat_27SWe5.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-quick-actions-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-quick-actions-browser) Quick actions are single-click operations that appear in the canvas menu when users select a block. CE.SDK provides official plugins that add image processing capabilities like background removal, vectorization, and QR code generation. These plugins integrate directly with the editor UI and execute their operations immediately when clicked. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web';import VectorizerPlugin from '@imgly/plugin-vectorizer-web';import CutoutLibraryPlugin from '@imgly/plugin-cutout-library-web';import QRCodePlugin from '@imgly/plugin-qr-code-web'; export default class QuickActionsExample implements EditorPlugin { name = 'QuickActionsExample'; version = '1.0.0'; async initialize({ cesdk }: EditorPluginContext) { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // Add background removal plugin with canvas menu button await cesdk.addPlugin( BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu'] } }) ); // Add vectorizer plugin with canvas menu button await cesdk.addPlugin( VectorizerPlugin({ ui: { locations: 'canvasMenu' } }) ); // Add cutout library plugin for print workflows (dock only, no canvas menu) await cesdk.addPlugin(CutoutLibraryPlugin()); // Add cutout library to the dock for easy access const cutoutAssetEntry = cesdk.ui.getAssetLibraryEntry('ly.img.cutout.entry'); cesdk.ui.setDockOrder([ ...cesdk.ui.getDockOrder(), { id: 'ly.img.assetLibrary.dock', label: 'Cutout', key: 'ly.img.assetLibrary.dock', icon: cutoutAssetEntry?.icon, entries: ['ly.img.cutout.entry'] }, ]); // Add QR code plugin (adds canvas menu button automatically) await cesdk.addPlugin(QRCodePlugin()); // Add QR code generator to the dock cesdk.ui.setDockOrder([ ...cesdk.ui.getDockOrder(), 'ly.img.spacer', 'ly.img.generate-qr.dock' ]); // Create scene with gradient background and text await cesdk.createDesignScene(); const page = engine.block.findByType('page')[0]; const pageWidth = 800; const pageHeight = 600; engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); // Add gradient background to the page const pageFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(pageFill, 'fill/gradient/colors', [ { stop: 0, color: { r: 0.18, g: 0.1, b: 0.4, a: 1 } }, { stop: 1, color: { r: 0.55, g: 0.25, b: 0.6, a: 1 } } ]); engine.block.setFloat(pageFill, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(pageFill, 'fill/gradient/linear/startPointY', 0); engine.block.setFloat(pageFill, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(pageFill, 'fill/gradient/linear/endPointY', 1); engine.block.setFill(page, pageFill); // Add main title text with auto height const titleBlock = engine.block.create('text'); engine.block.setString(titleBlock, 'text/text', 'Explore Quick Actions'); engine.block.setFloat(titleBlock, 'text/fontSize', 100); engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(titleBlock, pageWidth); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); // Set title text color to white engine.block.setTextColor(titleBlock, { r: 1, g: 1, b: 1, a: 1 }); // Add subtitle text with auto height const subtitleBlock = engine.block.create('text'); engine.block.setString(subtitleBlock, 'text/text', 'IMG.LY'); engine.block.setFloat(subtitleBlock, 'text/fontSize', 64); engine.block.setEnum(subtitleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(subtitleBlock, pageWidth); engine.block.setHeightMode(subtitleBlock, 'Auto'); engine.block.appendChild(page, subtitleBlock); // Set subtitle text color to white engine.block.setTextColor(subtitleBlock, { r: 1, g: 1, b: 1, a: 1 }); // Add a sample image to demonstrate quick actions const imageBlock = engine.block.create('graphic'); // Set shape for the graphic block const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); // Set 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); const imageSize = 250; engine.block.setWidth(imageBlock, imageSize); engine.block.setHeight(imageBlock, imageSize); engine.block.appendChild(page, imageBlock); // Position all elements - text at top, image below const titleHeight = engine.block.getFrameHeight(titleBlock); const subtitleHeight = engine.block.getFrameHeight(subtitleBlock); const textSpacing = 10; const imageGap = 80; // Position content vertically centered with offset const totalHeight = titleHeight + textSpacing + subtitleHeight + imageGap + imageSize; const startY = (pageHeight - totalHeight) / 2 + 40; engine.block.setPositionX(titleBlock, 0); engine.block.setPositionY(titleBlock, startY); engine.block.setPositionX(subtitleBlock, 0); engine.block.setPositionY(subtitleBlock, startY + titleHeight + textSpacing); engine.block.setPositionX(imageBlock, (pageWidth - imageSize) / 2); engine.block.setPositionY( imageBlock, startY + titleHeight + textSpacing + subtitleHeight + imageGap ); // Select the image to show the canvas menu with quick actions engine.block.select(imageBlock); // Open the cutout library panel cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['ly.img.cutout.entry'], title: 'Cutout' } }); }} ``` This guide demonstrates how to install and configure quick action plugins, add asset libraries to the dock, and optimize plugin loading for production use. ## Plugin Overview[#](#plugin-overview) This guide covers four official plugins that extend CE.SDK with quick actions: | Plugin | Use Case | | --- | --- | | Background Removal | Remove backgrounds from product photos | | Vectorizer | Convert logos to scalable vectors | | QR Code | Generate trackable QR codes for marketing | | Cutout Library | Add die-cut shapes for print production | ## Adding Quick Action Plugins[#](#adding-quick-action-plugins) Each plugin adds a button to the canvas menu that appears when users select compatible blocks. Install the plugin package, then call `cesdk.addPlugin()` to register it with the editor. ### Installing the Plugins[#](#installing-the-plugins) The background removal plugin requires `onnxruntime-web` for its machine learning model. The vectorizer and QR code plugins have no additional dependencies. [ npm ](#tab-panel-649)[ yarn ](#tab-panel-650)[ pnpm ](#tab-panel-651) Terminal window ``` npm install @imgly/plugin-background-removal-web @imgly/plugin-vectorizer-web @imgly/plugin-qr-code-web onnxruntime-web@1.21.0 ``` Terminal window ``` yarn add @imgly/plugin-background-removal-web @imgly/plugin-vectorizer-web @imgly/plugin-qr-code-web onnxruntime-web@1.21.0 ``` Terminal window ``` pnpm add @imgly/plugin-background-removal-web @imgly/plugin-vectorizer-web @imgly/plugin-qr-code-web onnxruntime-web@1.21.0 ``` ### Background Removal[#](#background-removal) Removes backgrounds from images using AI-powered segmentation. Runs entirely in-browser via WebAssembly. ``` // Add background removal plugin with canvas menu buttonawait cesdk.addPlugin( BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu'] } })); ``` See the [Remove Background](vue/edit-image/remove-bg-9dfcf7/) guide for model selection and performance tuning. ### Vectorization[#](#vectorization) Converts raster images to scalable vector graphics. Useful for logos and illustrations that need to scale without quality loss. ``` // Add vectorizer plugin with canvas menu buttonawait cesdk.addPlugin( VectorizerPlugin({ ui: { locations: 'canvasMenu' } })); ``` See the [Vectorize](vue/edit-image/vectorize-2b4c7f/) guide for timeout and grouping threshold settings. ### QR Code Generation[#](#qr-code-generation) Generates QR codes with customizable content and styling. Terminal window ``` npm install @imgly/plugin-qr-code-web ``` Register the plugin: ``` // Add QR code plugin (adds canvas menu button automatically)await cesdk.addPlugin(QRCodePlugin()); ``` Add the generator panel to the dock for creating new codes: ``` // Add QR code generator to the dockcesdk.ui.setDockOrder([ ...cesdk.ui.getDockOrder(), 'ly.img.spacer', 'ly.img.generate-qr.dock']); ``` ## Adding Cutout Library to Dock[#](#adding-cutout-library-to-dock) Provides die-cut shapes for print production workflows like stickers, packaging, and labels. Terminal window ``` npm install @imgly/plugin-cutout-library-web ``` Register the plugin to load the cutout asset source: ``` // Add cutout library plugin for print workflows (dock only, no canvas menu)await cesdk.addPlugin(CutoutLibraryPlugin()); ``` Add the library to the dock using `setDockOrder()` with the entry’s icon from `getAssetLibraryEntry()`: ``` // Add cutout library to the dock for easy accessconst cutoutAssetEntry = cesdk.ui.getAssetLibraryEntry('ly.img.cutout.entry');cesdk.ui.setDockOrder([ ...cesdk.ui.getDockOrder(), { id: 'ly.img.assetLibrary.dock', label: 'Cutout', key: 'ly.img.assetLibrary.dock', icon: cutoutAssetEntry?.icon, entries: ['ly.img.cutout.entry'] },]); ``` Users can add rectangular or elliptical cutouts, or create custom shapes from paths. Cutout boundaries export as die-cut lines in PDF output. ## Performance Best Practices[#](#performance-best-practices) Plugins that use machine learning models download their model files on first use. Consider these optimizations when adding multiple plugins: * **Lazy load plugins** - Use dynamic `import()` to defer loading until the user needs the feature. This reduces initial bundle size and speeds up editor startup. * **Preload models during idle time** - Call `requestIdleCallback()` to initialize plugins after the editor renders. The models cache locally for subsequent operations. * **Register plugins in priority order** - The canvas menu displays buttons in registration order. Add frequently-used plugins first so their buttons appear in prominent positions. * **Track initialization state** - Maintain a boolean flag to prevent adding the same plugin multiple times if your initialization code can run more than once. ## Troubleshooting[#](#troubleshooting) **Canvas menu button missing** - Verify that `addPlugin()` completes before the scene loads. Plugins register their UI components during initialization. **Background removal slow on first use** - The plugin downloads approximately 30MB of model data on first use. Subsequent operations use the cached model. **Cutout shapes not appearing in export** - Cutout paths only render in PDF exports. Check that your export configuration includes the PDF format. **Dock entry not visible** - Ensure `setDockOrder()` runs after the plugin initializes. The asset library entry must exist before it can be added to the dock. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `cesdk.addPlugin(plugin)` | Registers a plugin and runs its initialization | | `cesdk.ui.setDockOrder(order)` | Sets which components appear in the dock sidebar | | `cesdk.ui.getDockOrder()` | Returns the current dock component configuration | | `cesdk.ui.getAssetLibraryEntry(id)` | Retrieves an asset library entry by its ID | | `cesdk.ui.setCanvasMenuOrder(order)` | Sets which components appear in the canvas menu | | `cesdk.ui.getCanvasMenuOrder()` | Returns the current canvas menu configuration | ## Next Steps[#](#next-steps) * [Remove Background](vue/edit-image/remove-bg-9dfcf7/) \- Configure background removal model and processing options * [Vectorize](vue/edit-image/vectorize-2b4c7f/) \- Adjust vectorization accuracy and performance settings * [Add a Custom Panel](vue/user-interface/ui-extensions/create-custom-panel-d87b83/) \- Build panels for operations that need configuration * [Register a New Component](vue/user-interface/ui-extensions/register-new-component-b04a04/) \- Create custom UI components for the canvas menu * [Add a Custom Feature](vue/user-interface/ui-extensions/add-custom-feature-2a26b6/) \- Package functionality into reusable plugins --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/ui-extensions/notifications-and-dialogs-fec36a) --- # Notifications and Dialogs Display notifications and dialogs to communicate with users during the editing experience using CE.SDK’s UI API. ![Notifications and Dialogs example showing the CE.SDK editor with notification and dialog UI](/docs/cesdk/_astro/browser.hero.B-4Uuw-f_ZEuLiT.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-notifications-and-dialogs-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-notifications-and-dialogs-browser) Notifications and dialogs are essential for communicating with users during the editing experience. Notifications appear as temporary, non-blocking messages at the edge of the editor for status updates and feedback. Dialogs are modal overlays that interrupt the workflow to present information or collect user decisions. Both are created and controlled through the `cesdk.ui` API. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // Use cesdk convenience method - automatically handles zoom await cesdk.createDesignScene(); // Register a notifications dropdown in the navigation bar cesdk.ui.registerComponent( 'ly.img.notifications.demo.navigationBar', ({ builder }) => { // Display a simple notification with a string message builder.Button('simple-notification', { label: 'Simple', onClick: () => { cesdk.ui.showNotification('Welcome to CE.SDK!'); } }); // Display notifications with different types builder.Button('info-notification', { label: 'Info', onClick: () => { cesdk.ui.showNotification({ message: 'This is an info notification', type: 'info' }); } }); builder.Button('success-notification', { label: 'Success', onClick: () => { cesdk.ui.showNotification({ message: 'Operation completed successfully', type: 'success' }); } }); builder.Button('warning-notification', { label: 'Warning', onClick: () => { cesdk.ui.showNotification({ message: 'Please check your input', type: 'warning' }); } }); builder.Button('error-notification', { label: 'Error', onClick: () => { cesdk.ui.showNotification({ message: 'Something went wrong', type: 'error' }); } }); // Add an action button to a notification builder.Button('action-notification', { label: 'With Action', onClick: () => { cesdk.ui.showNotification({ message: 'New template available', type: 'info', duration: 'long', action: { label: 'View', onClick: ({ id }) => { console.log('Action clicked on notification:', id); cesdk.ui.dismissNotification(id); } } }); } }); // Create a loading notification that updates to success builder.Button('loading-notification', { label: 'Loading → Success', onClick: () => { const loadingId = cesdk.ui.showNotification({ message: 'Processing your request...', type: 'loading', duration: 'infinite' }); // Simulate async operation completing setTimeout(() => { cesdk.ui.updateNotification(loadingId, { type: 'success', message: 'Processing complete!', duration: 'medium' }); }, 2000); } }); // Show a notification that can be dismissed builder.Button('dismiss-notification', { label: 'Auto-Dismiss', onClick: () => { const notificationId = cesdk.ui.showNotification({ message: 'This will be dismissed in 2 seconds', type: 'info', duration: 'infinite' }); setTimeout(() => { cesdk.ui.dismissNotification(notificationId); }, 2000); } }); // Handle notification dismiss events builder.Button('ondismiss-notification', { label: 'With Callback', onClick: () => { cesdk.ui.showNotification({ message: 'Dismiss me to see the callback', type: 'info', duration: 'long', onDismiss: () => { console.log('Notification was dismissed'); } }); } }); } ); // Register a dialogs dropdown in the navigation bar cesdk.ui.registerComponent( 'ly.img.dialogs.demo.navigationBar', ({ builder }) => { // Display a simple dialog with a string message builder.Button('simple-dialog', { label: 'Simple', onClick: () => { cesdk.ui.showDialog('This is a simple dialog message'); } }); // Show a dialog and close it programmatically builder.Button('close-dialog', { label: 'Auto-Close', onClick: () => { const dialogId = cesdk.ui.showDialog( 'This dialog will close in 2 seconds' ); setTimeout(() => { cesdk.ui.closeDialog(dialogId); }, 2000); } }); // Display a warning dialog with actions builder.Button('warning-dialog', { label: 'Warning', onClick: () => { cesdk.ui.showDialog({ type: 'warning', content: { title: 'Unsaved Changes', message: 'You have unsaved changes. Do you want to save before leaving?' }, actions: [ { label: 'Save', color: 'accent', onClick: ({ id }) => { console.log('Save clicked'); cesdk.ui.closeDialog(id); } }, { label: "Don't Save", variant: 'plain', onClick: ({ id }) => { console.log('Discard clicked'); cesdk.ui.closeDialog(id); } } ], cancel: { label: 'Cancel', onClick: ({ id }) => cesdk.ui.closeDialog(id) } }); } }); // Create a loading dialog with progress indicator builder.Button('progress-dialog', { label: 'Progress', onClick: () => { const progressDialogId = cesdk.ui.showDialog({ type: 'loading', content: { title: 'Exporting', message: 'Preparing your export...' }, progress: 'indeterminate', clickOutsideToClose: false }); // Simulate progress updates let progress = 0; const progressInterval = setInterval(() => { progress += 20; cesdk.ui.updateDialog(progressDialogId, { progress: { value: progress, max: 100 }, content: { title: 'Exporting', message: `Processing... ${progress}%` } }); if (progress >= 100) { clearInterval(progressInterval); cesdk.ui.updateDialog(progressDialogId, { type: 'success', content: { title: 'Export Complete', message: 'Your file has been exported successfully.' }, progress: undefined, actions: [ { label: 'Done', color: 'accent', onClick: ({ id }) => cesdk.ui.closeDialog(id) } ], clickOutsideToClose: true }); } }, 500); } }); // Dialog with multi-paragraph content and large size builder.Button('content-dialog', { label: 'Large Content', onClick: () => { cesdk.ui.showDialog({ type: 'info', size: 'large', content: { title: 'About This Feature', message: [ 'Notifications and dialogs help communicate with users during the editing workflow.', 'Use notifications for non-blocking feedback and dialogs for important decisions.' ] }, actions: [ { label: 'Got It', color: 'accent', onClick: ({ id }) => cesdk.ui.closeDialog(id) } ] }); } }); // Handle dialog close events builder.Button('onclose-dialog', { label: 'With Callback', onClick: () => { cesdk.ui.showDialog({ type: 'info', content: 'Close this dialog to see the callback', onClose: () => { console.log('Dialog was closed'); } }); } }); } ); // Add the demo dropdowns to the navigation bar cesdk.ui.setNavigationBarOrder([ 'ly.img.navigationBar.position.left', 'ly.img.navigationBar.position.center', 'ly.img.navigationBar.position.right', 'ly.img.notifications.demo.navigationBar', 'ly.img.dialogs.demo.navigationBar' ]); }} export default Example; ``` ## Displaying Notifications[#](#displaying-notifications) Notifications provide non-blocking feedback without interrupting the user’s workflow. They appear in the lower right corner of the editor and automatically dismiss after a set duration. ### Creating Notifications[#](#creating-notifications) Use `cesdk.ui.showNotification()` to display a notification. Pass a string for a simple message or a configuration object for full control. The method returns a unique ID for managing the notification. ``` cesdk.ui.showNotification('Welcome to CE.SDK!'); ``` ### Notification Types[#](#notification-types) Configure the visual appearance with the `type` property. Available types convey different levels of importance: * `info` — General information (default) * `success` — Positive confirmations * `warning` — Cautions requiring attention * `error` — Errors or failures * `loading` — Operations in progress with a spinner ``` cesdk.ui.showNotification({ message: 'This is an info notification', type: 'info'}); ``` ### Notification Duration[#](#notification-duration) Control how long notifications remain visible using the `duration` property: * `short` — Brief display for quick updates * `medium` — Standard duration (default) * `long` — Extended display for important messages * `infinite` — Persists until dismissed programmatically * A number for custom millisecond duration ### Adding Actions to Notifications[#](#adding-actions-to-notifications) Include an `action` object to add a clickable button. The action’s `onClick` callback receives an object with the notification ID, allowing self-dismissal or other operations. ``` cesdk.ui.showNotification({ message: 'New template available', type: 'info', duration: 'long', action: { label: 'View', onClick: ({ id }) => { console.log('Action clicked on notification:', id); cesdk.ui.dismissNotification(id); } }}); ``` ### Loading Notifications[#](#loading-notifications) For operations with unknown completion time, create a loading notification with `infinite` duration. Update or dismiss it when the operation completes. ``` const loadingId = cesdk.ui.showNotification({ message: 'Processing your request...', type: 'loading', duration: 'infinite'}); ``` ### Updating Notifications[#](#updating-notifications) Modify an existing notification with `cesdk.ui.updateNotification()`. Pass the ID and a partial notification object with updated properties. This is useful for changing a loading notification to a success message when an operation completes. ``` cesdk.ui.updateNotification(loadingId, { type: 'success', message: 'Processing complete!', duration: 'medium'}); ``` ### Dismissing Notifications[#](#dismissing-notifications) Use `cesdk.ui.dismissNotification()` with the notification ID to remove it programmatically. This is useful for canceling loading notifications when operations complete or when you need to clear notifications based on user actions. ``` const notificationId = cesdk.ui.showNotification({ message: 'This will be dismissed in 2 seconds', type: 'info', duration: 'infinite'}); setTimeout(() => { cesdk.ui.dismissNotification(notificationId);}, 2000); ``` ### Handling Dismiss Callbacks[#](#handling-dismiss-callbacks) Use the `onDismiss` callback to execute code when a notification is dismissed, whether by timeout, user action, or programmatic dismissal. ``` cesdk.ui.showNotification({ message: 'Dismiss me to see the callback', type: 'info', duration: 'long', onDismiss: () => { console.log('Notification was dismissed'); }}); ``` ## Displaying Dialogs[#](#displaying-dialogs) Dialogs present modal content requiring user attention or decisions. They overlay the editor and block interaction until dismissed. ### Creating Dialogs[#](#creating-dialogs) Use `cesdk.ui.showDialog()` to display a dialog. Pass a string for a simple message or a configuration object for full control. The method returns a unique ID for managing the dialog. ``` cesdk.ui.showDialog('This is a simple dialog message'); ``` ### Closing Dialogs[#](#closing-dialogs) Use `cesdk.ui.closeDialog()` with the dialog ID to close it programmatically. ``` const dialogId = cesdk.ui.showDialog( 'This dialog will close in 2 seconds');setTimeout(() => { cesdk.ui.closeDialog(dialogId);}, 2000); ``` ### Dialog Types and Actions[#](#dialog-types-and-actions) Configure the visual style with the `type` property. Available types are `regular` (default), `info`, `success`, `warning`, `error`, and `loading`. Add action buttons with the `actions` array and a cancel button with the `cancel` property. ``` cesdk.ui.showDialog({ type: 'warning', content: { title: 'Unsaved Changes', message: 'You have unsaved changes. Do you want to save before leaving?' }, actions: [ { label: 'Save', color: 'accent', onClick: ({ id }) => { console.log('Save clicked'); cesdk.ui.closeDialog(id); } }, { label: "Don't Save", variant: 'plain', onClick: ({ id }) => { console.log('Discard clicked'); cesdk.ui.closeDialog(id); } } ], cancel: { label: 'Cancel', onClick: ({ id }) => cesdk.ui.closeDialog(id) }}); ``` Each action object includes: * `label` — Button text * `variant` — `regular` (default) or `plain` for a text-only button * `color` — `accent` for primary actions or `danger` for destructive actions * `onClick` — Callback receiving the dialog ID for closing or other operations ### Progress Indicators[#](#progress-indicators) Display progress in loading dialogs with the `progress` property: * `indeterminate` — For unknown duration * A number (0-100) — For percentage progress * An object with `value` and `max` — For custom progress ranges ``` const progressDialogId = cesdk.ui.showDialog({ type: 'loading', content: { title: 'Exporting', message: 'Preparing your export...' }, progress: 'indeterminate', clickOutsideToClose: false}); ``` ### Updating Dialogs[#](#updating-dialogs) Modify an existing dialog with `cesdk.ui.updateDialog()`. Pass the ID and a partial dialog object. This is useful for updating progress indicators or changing dialog content during operations. ``` cesdk.ui.updateDialog(progressDialogId, { progress: { value: progress, max: 100 }, content: { title: 'Exporting', message: `Processing... ${progress}%` }}); ``` ### Dialog Content[#](#dialog-content) Set content as a string for simple messages or an object with `title` and `message` properties. The message can be a string or an array of strings for multiple paragraphs. Use the `size` property to control dialog dimensions (`regular` or `large`). ``` cesdk.ui.showDialog({ type: 'info', size: 'large', content: { title: 'About This Feature', message: [ 'Notifications and dialogs help communicate with users during the editing workflow.', 'Use notifications for non-blocking feedback and dialogs for important decisions.' ] }, actions: [ { label: 'Got It', color: 'accent', onClick: ({ id }) => cesdk.ui.closeDialog(id) } ]}); ``` ### Handling Close Callbacks[#](#handling-close-callbacks) Use the `onClose` callback to execute cleanup code when a dialog closes, whether by action, cancel, clicking outside, or programmatic closure. ``` cesdk.ui.showDialog({ type: 'info', content: 'Close this dialog to see the callback', onClose: () => { console.log('Dialog was closed'); }}); ``` ### Click Outside Behavior[#](#click-outside-behavior) Control whether clicking outside the dialog closes it with the `clickOutsideToClose` property. It defaults to `true`. Set to `false` for mandatory interactions like loading dialogs. ## Troubleshooting[#](#troubleshooting) ### Notification Not Visible[#](#notification-not-visible) Verify the notification was created with a valid message string or configuration object. Check that the duration hasn’t expired before you could observe it. Ensure the editor UI is properly initialized before calling notification methods. ### Dialog Not Closing[#](#dialog-not-closing) Confirm you’re calling `cesdk.ui.closeDialog()` with the correct dialog ID. Check that action callbacks properly close the dialog. If `clickOutsideToClose` is `false`, ensure an action or cancel button is provided. ### Callback Not Firing[#](#callback-not-firing) Ensure the callback function is properly defined in the notification or dialog configuration. Verify the notification or dialog hasn’t already been dismissed before the callback could fire. Check for JavaScript errors in callback code. ### Progress Not Updating[#](#progress-not-updating) Confirm you’re using `cesdk.ui.updateDialog()` with the correct dialog ID. Verify the progress value is in the expected format (number, `indeterminate`, or object with `value` and `max`). ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `cesdk.ui.showNotification()` | Display a non-blocking notification | | `cesdk.ui.dismissNotification()` | Remove a notification by ID | | `cesdk.ui.updateNotification()` | Update notification content or properties | | `cesdk.ui.showDialog()` | Display a modal dialog | | `cesdk.ui.closeDialog()` | Close a dialog by ID | | `cesdk.ui.updateDialog()` | Update dialog content or properties | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/ui-extensions/customize-behaviour-c09cb2) --- # Customize UI Behavior Control CE.SDK’s interface programmatically at runtime through event subscriptions, panel operations, notifications, and dynamic feature management. ![Customize UI Behavior](/docs/cesdk/_astro/browser.hero.BZbruW70_hFlwH.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-customize-behaviour-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-customize-behaviour-browser) UI behavior customization enables responsive, context-aware editing experiences. You can listen for user interactions, manipulate UI state dynamically based on application logic, integrate CE.SDK with external workflows, and build custom behavior that responds to editing events. ``` import type { CreativeEngine, EditorPlugin, EditorPluginContext} from '@cesdk/cesdk-js';import type { BlockEvent } from '@cesdk/cesdk-js'; export default class CustomizeBehaviorExample implements EditorPlugin { name = 'CustomizeBehaviorExample'; version = '1.0.0'; async initialize({ cesdk, engine }: EditorPluginContext) { // Load a simple scene for demonstration await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); // Show welcome dialog first, then run demonstrations after user confirms this.demonstrateDialogs(cesdk, engine); } private demonstrateEventSubscription( engine: CreativeEngine, cesdk: any ): void { // Subscribe to all block events const unsubscribe = engine.event.subscribe([], (events: BlockEvent[]) => { events.forEach((event) => { console.log(`Block event: ${event.type} on block ${event.block}`); // Show notification when blocks are created if (event.type === 'Created') { cesdk.ui.showNotification({ message: `Block created: ${event.block}`, type: 'info', duration: 'short' }); } }); }); // Store unsubscribe function for cleanup (window as any).unsubscribeEvents = unsubscribe; } private demonstratePanelManagement(cesdk: any): void { // Check if inspector panel is open const isInspectorOpen = cesdk.ui.isPanelOpen('//ly.img.panel/inspector'); console.log('Inspector panel open:', isInspectorOpen); // Open panel conditionally if (!isInspectorOpen) { cesdk.ui.openPanel('//ly.img.panel/inspector'); } // Close panel after delay (for demonstration) setTimeout(() => { cesdk.ui.closePanel('//ly.img.panel/inspector'); console.log('Inspector panel closed'); }, 2000); // Find all available panels const allPanels = cesdk.ui.findAllPanels(); console.log('Available panels:', allPanels); } private demonstrateNotifications(cesdk: any): void { // Show simple notification const notificationId = cesdk.ui.showNotification('Welcome to CE.SDK!'); console.log('Notification ID:', notificationId); // Show notification with configuration setTimeout(() => { cesdk.ui.showNotification({ message: 'This is a success message', type: 'success', duration: 'medium' }); }, 1000); // Show error notification setTimeout(() => { cesdk.ui.showNotification({ message: 'This is an error notification', type: 'error', duration: 'long' }); }, 2000); } private demonstrateDialogs(cesdk: any, engine: CreativeEngine): void { // Show welcome dialog immediately cesdk.ui.showDialog({ type: 'info', content: { title: 'Welcome to CE.SDK UI Behavior Customization', message: "This example demonstrates how to programmatically control CE.SDK's interface through event subscriptions, panel management, notifications, dialogs, custom actions, and theme controls. Click 'Confirm' to start the demonstration." }, actions: [ { label: 'Cancel', onClick: (context) => { console.log('User cancelled demonstration'); cesdk.ui.closeDialog(context.id); } }, { label: 'Confirm', onClick: (context) => { console.log('User confirmed - starting demonstrations'); cesdk.ui.closeDialog(context.id); // Run all demonstrations after user confirms this.demonstrateEventSubscription(engine, cesdk); this.demonstratePanelManagement(cesdk); this.demonstrateNotifications(cesdk); this.demonstrateCustomActions(cesdk); this.demonstrateThemeControl(cesdk); this.demonstrateFeatureManagement(cesdk); } } ] }); } private demonstrateCustomActions(cesdk: any): void { // Register a custom download action cesdk.actions.register( 'downloadFile', async (blob: Blob, mimeType: string) => { console.log('Custom download action called:', { blob, mimeType }); // Custom download logic const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `export.${mimeType.split('/')[1]}`; link.click(); URL.revokeObjectURL(url); cesdk.ui.showNotification({ message: 'File downloaded successfully!', type: 'success' }); } ); // List all registered actions const actions = cesdk.actions.list(); console.log('Registered actions:', actions); // Check if specific action exists const downloadAction = cesdk.actions.get('downloadFile'); console.log('Download action registered:', !!downloadAction); } private demonstrateThemeControl(cesdk: any): void { // Get current theme const currentTheme = cesdk.ui.getTheme(); console.log('Current theme:', currentTheme); // Toggle theme after delay setTimeout(() => { const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; cesdk.ui.setTheme(newTheme); console.log('Theme changed to:', newTheme); cesdk.ui.showNotification({ message: `Theme switched to ${newTheme} mode`, type: 'info' }); }, 5000); // Get current scale const currentScale = cesdk.ui.getScale(); console.log('Current scale:', currentScale); } private demonstrateFeatureManagement(cesdk: any): void { // Check if feature is enabled const isUndoEnabled = cesdk.feature.isEnabled('ly.img.undo'); console.log('Undo feature enabled:', isUndoEnabled); // Disable feature conditionally setTimeout(() => { // Example: Disable undo for demonstration // cesdk.feature.disable('ly.img.undo'); console.log('Feature management demonstrated (undo not disabled)'); }, 6000); // List features matching pattern const allFeatures = cesdk.feature.list({ matcher: 'ly.img.*' }); console.log('Available features:', allFeatures.slice(0, 10)); } async demonstrateExternalIntegration( engine: CreativeEngine, cesdk: any ): Promise { // Example: Integrate with external state management const externalState = { selectedBlockCount: 0, lastEventType: '' }; // Subscribe to events and update external state engine.event.subscribe([], (events: BlockEvent[]) => { events.forEach((event) => { externalState.lastEventType = event.type; console.log('External state updated:', externalState); }); }); // Listen for external changes and update UI // In a real app, this would connect to Redux, MobX, etc. setInterval(() => { const selectedBlocks = engine.block.findAllSelected(); if (selectedBlocks.length !== externalState.selectedBlockCount) { externalState.selectedBlockCount = selectedBlocks.length; console.log('Selection changed:', externalState.selectedBlockCount); // Show panel when blocks are selected if (selectedBlocks.length > 0) { cesdk.ui.openPanel('//ly.img.panel/inspector'); } } }, 1000); } private demonstrateContextAwareWorkflow( engine: CreativeEngine, cesdk: any ): void { // Create context-aware workflow engine.event.subscribe([], (events: BlockEvent[]) => { events.forEach((event) => { if (event.type === 'Created') { // Show notification for new blocks cesdk.ui.showNotification({ message: 'New block created - Opening inspector', type: 'info' }); // Open inspector panel cesdk.ui.openPanel('//ly.img.panel/inspector'); } if (event.type === 'Destroyed') { // Check if any blocks remain const allBlocks = engine.block.findAll(); if (allBlocks.length === 0) { cesdk.ui.showNotification({ message: 'All blocks removed', type: 'info' }); } } }); }); }} ``` This guide demonstrates the core behavior customization APIs: notifications and dialogs for user feedback, custom action registration, dynamic theme and scale control, feature management, and external state integration. For detailed event handling and panel management, see the dedicated guides linked in each section. ## Listening to Editing Events[#](#listening-to-editing-events) Subscribe to block events to react to changes in your scene. The event system delivers batched updates for Created, Updated, and Destroyed events. ``` const unsubscribe = engine.event.subscribe([], (events: BlockEvent[]) => { events.forEach((event) => { if (event.type === 'Created') { cesdk.ui.showNotification({ message: 'Block created', type: 'info' }); } });}); ``` For comprehensive event handling patterns including selection tracking and state synchronization, see the Event Subscriptions guide. ## Programmatic Panel Management[#](#programmatic-panel-management) Control panel visibility dynamically using the UI API. Open, close, and query panel state to adapt your interface to user actions. ``` if (!cesdk.ui.isPanelOpen('//ly.img.panel/inspector')) { cesdk.ui.openPanel('//ly.img.panel/inspector');} ``` For complete panel management including custom panel registration and advanced patterns, see the Custom Panels guide. ## Showing Notifications and Dialogs[#](#showing-notifications-and-dialogs) ### Displaying Notifications[#](#displaying-notifications) Show temporary feedback using `cesdk.ui.showNotification()`. Configure the notification’s message, type (`info`, `success`, `error`), and duration (`short`, `medium`, `long`). ``` cesdk.ui.showNotification({ message: 'Operation completed successfully', type: 'success', duration: 'medium'}); ``` ### Creating Confirmation Dialogs[#](#creating-confirmation-dialogs) Present user decisions with `cesdk.ui.showDialog()`. Define `onClick` handlers for action buttons that receive a `context` parameter containing the dialog ID. ``` cesdk.ui.showDialog({ type: 'info', content: { title: 'Confirm Action', message: 'Are you sure you want to proceed?' }, actions: [ { label: 'Cancel', onClick: (context) => cesdk.ui.closeDialog(context.id) }, { label: 'Confirm', onClick: (context) => { cesdk.ui.closeDialog(context.id); // Your action logic here } } ]}); ``` ## Registering Custom Actions[#](#registering-custom-actions) Register custom action handlers to intercept UI events like export, load, and download. The editor calls your action when users trigger the corresponding UI event. ``` cesdk.actions.register('downloadFile', async (blob: Blob, mimeType: string) => { const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `export.${mimeType.split('/')[1]}`; link.click(); URL.revokeObjectURL(url);}); ``` ## Dynamic Theme and Scale Control[#](#dynamic-theme-and-scale-control) Control the editor’s theme and UI scale at runtime. Set theme to `'light'`, `'dark'`, or `'system'`, and scale to `'normal'` or `'large'`. ``` // Toggle themeconst currentTheme = cesdk.ui.getTheme();cesdk.ui.setTheme(currentTheme === 'dark' ? 'light' : 'dark'); // Set scalecesdk.ui.setScale('large'); ``` ## Feature Management at Runtime[#](#feature-management-at-runtime) Control feature availability based on user permissions or application state. Enable, disable, and query features dynamically. ``` // Check and toggle featuresif (cesdk.feature.isEnabled('ly.img.undo')) { cesdk.feature.disable('ly.img.undo');} else { cesdk.feature.enable('ly.img.undo');} // List available featuresconst features = cesdk.feature.list({ matcher: 'ly.img.*' }); ``` ## Integrating with External State[#](#integrating-with-external-state) Connect CE.SDK events to Redux, MobX, or other state systems for bidirectional synchronization. ``` // Subscribe to CE.SDK events and update external stateengine.event.subscribe([], (events: BlockEvent[]) => { events.forEach((event) => { // Dispatch to your state management system dispatch({ type: 'CESDK_EVENT', payload: event }); });}); // Listen to external state changes and update CE.SDK UIstore.subscribe(() => { const state = store.getState(); if (state.shouldShowInspector) { cesdk.ui.openPanel('//ly.img.panel/inspector'); }}); ``` ## Troubleshooting[#](#troubleshooting) ### Event Subscriptions Not Firing[#](#event-subscriptions-not-firing) Verify the engine is initialized and blocks exist before subscribing. Event subscriptions only fire for changes after subscription - you won’t receive events for existing state. Check that your callback function executes without errors, as exceptions are logged but don’t prevent future events. ### Panel Operations Failing[#](#panel-operations-failing) Check panel ID spelling and registration status. Panel IDs are case-sensitive and must match exactly. Verify the panel is registered before attempting to open it. Custom panels must be registered through plugins before they become available for panel operations. ### Actions Not Executing[#](#actions-not-executing) Ensure the action is registered before calling `cesdk.actions.run()`. The method throws an error if the action doesn’t exist. Register actions during plugin initialization or before they’re needed. Check that action names match exactly when registering and executing. ### Memory Leaks from Event Subscriptions[#](#memory-leaks-from-event-subscriptions) Always call the unsubscribe cleanup function when you no longer need event listeners. Store the return value from `engine.event.subscribe()` and call it when your component unmounts or the feature is disabled. Failing to unsubscribe causes memory leaks and unnecessary callback executions. ### UI Not Updating After State Changes[#](#ui-not-updating-after-state-changes) Verify event handlers execute without errors. Check the browser console for exceptions thrown in callbacks. Ensure your UI update logic runs after async operations complete. Theme and feature changes apply immediately, so check that you’re querying the correct state after modifications. ### Theme or Scale Changes Not Applying[#](#theme-or-scale-changes-not-applying) Check if function-based configs are evaluated correctly. For theme functions, ensure they return valid values (`'light'` or `'dark'`). For scale functions, verify the container width calculation is correct. Function-based configs re-evaluate on each access, so ensure your logic accounts for this behavior. ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `engine.event.subscribe()` | Subscribe to block lifecycle events | | `cesdk.ui.openPanel()` | Open a specific panel programmatically | | `cesdk.ui.closePanel()` | Close a panel by ID or pattern | | `cesdk.ui.isPanelOpen()` | Check if panel is currently open | | `cesdk.ui.findAllPanels()` | Find all available panels with filtering | | `cesdk.ui.showNotification()` | Display temporary notification message | | `cesdk.ui.showDialog()` | Show confirmation or custom dialog | | `cesdk.ui.closeDialog()` | Close dialog by ID | | `cesdk.actions.register()` | Register custom action handler | | `cesdk.actions.get()` | Retrieve registered action function | | `cesdk.actions.run()` | Execute registered action | | `cesdk.actions.list()` | List all registered action IDs | | `cesdk.ui.setTheme()` | Change editor theme at runtime | | `cesdk.ui.getTheme()` | Get current resolved theme | | `cesdk.ui.setScale()` | Control UI density and sizing | | `cesdk.ui.getScale()` | Get current resolved scale | | `cesdk.feature.enable()` | Enable feature dynamically | | `cesdk.feature.disable()` | Disable feature dynamically | | `cesdk.feature.isEnabled()` | Check if feature is enabled | | `cesdk.feature.list()` | List features matching pattern | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/ui-extensions/create-custom-panel-d87b83) --- # Create a Custom Panel Create custom sidebar panels that integrate with CE.SDK’s user interface using the builder system and built-in components. ![Create Custom Panel](/docs/cesdk/_astro/browser.hero.BcYkmxGO_Z21ssvN.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-create-custom-panel-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-create-custom-panel-browser) Custom panels extend CE.SDK by adding sidebar interfaces that match the editor’s design language. The builder system provides pre-built components for forms, buttons, and media display, allowing you to create rich editing experiences without building UI from scratch. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; export default class CreateCustomPanelExample implements EditorPlugin { name = 'CreateCustomPanelExample'; version = '1.0.0'; async initialize(context: EditorPluginContext) { const { cesdk } = context; if (!cesdk) return; await cesdk.createDesignScene(); cesdk.i18n.setTranslations({ en: { 'panel.my-settings': 'My Settings Panel' } }); cesdk.ui.registerPanel('my-settings', ({ builder, engine, state }) => { const textState = state('text', 'Hello CE.SDK'); const opacityState = state('opacity', 100); builder.Section('settings', { title: 'Settings', children: () => { builder.TextInput('name', { inputLabel: 'Name', ...textState }); builder.Slider('opacity', { inputLabel: 'Opacity', min: 0, max: 100, ...opacityState }); builder.Checkbox('enabled', { inputLabel: 'Enable feature', value: true, setValue: () => {} }); builder.Button('apply', { label: 'Apply', onClick: () => { const page = engine.block.findByType('page')[0]; engine.block.setOpacity(page, opacityState.value / 100); } }); const selected = engine.block.findAllSelected(); if (selected.length > 0) { builder.Text('info', { content: `${selected.length} selected` }); } } }); }); cesdk.ui.registerComponent('settings-btn', ({ builder }) => { builder.Button('toggle', { label: 'Settings', icon: '@imgly/Settings', isActive: cesdk.ui.isPanelOpen('my-settings'), onClick: () => cesdk.ui.openPanel('my-settings') }); }); cesdk.ui.setDockOrder([...cesdk.ui.getDockOrder(), 'settings-btn']); cesdk.ui.openPanel('my-settings'); }} ``` This guide covers how to register panels, use builder components, manage local state, respond to engine changes, set panel titles, and integrate panels with the dock. ## Registering a Custom Panel[#](#registering-a-custom-panel) Register a panel using `cesdk.ui.registerPanel()` with a unique ID and a render function. The render function receives `builder`, `engine`, and `state` as arguments. ``` cesdk.ui.registerPanel('my-settings', ({ builder, engine, state }) => { ``` The panel renders whenever the function is called. CE.SDK tracks engine method calls within the render function and re-invokes it when relevant state changes. ## Managing Local State[#](#managing-local-state) Use the `state` function to create panel-local state that persists across re-renders. Call `state('key', defaultValue)` to get `value` and `setValue` properties. ``` const textState = state('text', 'Hello CE.SDK');const opacityState = state('opacity', 100); ``` State objects integrate directly with input components by spreading the object into the component props. ## Structuring with Sections[#](#structuring-with-sections) Group related components using `builder.Section()`. Sections accept a title and a children function containing nested components. ``` builder.Section('settings', { title: 'Settings', children: () => { ``` Sections provide visual organization and collapsible areas within your panel. ## Using Input Components[#](#using-input-components) ### Text Input[#](#text-input) Capture text with `builder.TextInput()`. Bind to state using the value and setValue properties. ``` builder.TextInput('name', { inputLabel: 'Name', ...textState}); ``` ### Slider[#](#slider) Handle numeric ranges with `builder.Slider()`. Configure min, max, and step values. ``` builder.Slider('opacity', { inputLabel: 'Opacity', min: 0, max: 100, ...opacityState}); ``` ### Checkbox[#](#checkbox) Toggle boolean values with `builder.Checkbox()`. ``` builder.Checkbox('enabled', { inputLabel: 'Enable feature', value: true, setValue: () => {}}); ``` ## Adding Buttons[#](#adding-buttons) Add interactive buttons with `builder.Button()`. Configure the label, icon, and onClick handler. ``` builder.Button('apply', { label: 'Apply', onClick: () => { ``` ## Accessing Engine State[#](#accessing-engine-state) Access engine state within the render function to create reactive panels. The panel re-renders when tracked engine state changes. ``` const selected = engine.block.findAllSelected();if (selected.length > 0) { builder.Text('info', { content: `${selected.length} selected` });} ``` ## Modifying Engine State[#](#modifying-engine-state) Use engine APIs within event handlers to modify the scene based on panel input. ``` const page = engine.block.findByType('page')[0];engine.block.setOpacity(page, opacityState.value / 100); ``` ## Setting the Panel Title[#](#setting-the-panel-title) Set panel titles through i18n translations. The translation key follows the pattern `panel.[panelId]`. ``` cesdk.i18n.setTranslations({ en: { 'panel.my-settings': 'My Settings Panel' }}); ``` ## Adding a Dock Button[#](#adding-a-dock-button) Register a component that renders a button to toggle your panel. Use `cesdk.ui.isPanelOpen()` to track state. ``` cesdk.ui.registerComponent('settings-btn', ({ builder }) => { builder.Button('toggle', { label: 'Settings', icon: '@imgly/Settings', isActive: cesdk.ui.isPanelOpen('my-settings'), onClick: () => cesdk.ui.openPanel('my-settings') });}); ``` Add the component to the dock order. ``` cesdk.ui.setDockOrder([...cesdk.ui.getDockOrder(), 'settings-btn']); ``` ## Opening the Panel[#](#opening-the-panel) Open your custom panel programmatically using `cesdk.ui.openPanel()`. ``` cesdk.ui.openPanel('my-settings'); ``` To learn how to manage panel lifecycle and positioning, see [Panel](vue/user-interface/customization/panel-7ce1ee/) . ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `cesdk.ui.registerPanel(id, renderFn)` | Registers a panel with a unique ID and render function. The render function receives `builder`, `engine`, and `state`. | | `cesdk.ui.openPanel(id)` | Opens a registered panel by ID. | | `cesdk.ui.isPanelOpen(id)` | Returns `true` if the panel is currently open. | | `state(key, defaultValue)` | Creates panel-local state. Returns `{ value, setValue }` for use with input components. | | `cesdk.i18n.setTranslations(translations)` | Sets translation strings. Use key `panel.[panelId]` for panel titles. | ## Next Steps[#](#next-steps) [Register New Component](vue/user-interface/ui-extensions/register-new-component-b04a04/) — Create reusable custom components for use across panels. [Customize UI Behavior](vue/user-interface/ui-extensions/customize-behaviour-c09cb2/) — Control UI programmatically with events and notifications. [Add New Button](vue/user-interface/ui-extensions/add-new-button-74884d/) — Add buttons to dock, inspector bar, and other UI areas. [Dock](vue/user-interface/customization/dock-cb916c/) — Configure the editor dock area and component ordering. --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/ui-extensions/add-new-button-74884d) --- # Add a New Button Add custom buttons to the CE.SDK editor to trigger actions, control panels, or implement custom workflows. ![Add a New Button example showing theme and favorite toggle buttons in navigation bar](/docs/cesdk/_astro/browser.hero.SYo7VW8-_Er46S.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-add-new-button-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-add-new-button-browser) CE.SDK provides a component registration system for adding custom UI elements. The builder API ensures buttons match the editor’s look and feel while giving full control over behavior. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; class Example implements EditorPlugin { name = 'guides-user-interface-ui-extensions-add-new-button-browser'; version = '1.0.0'; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); // Setup the page with a gradient background and centered text const page = engine.block.findByType('page')[0]; if (page) { // Add 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 text elements const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create title text "Create custom buttons" const titleText = engine.block.create('text'); engine.block.replaceText(titleText, 'Create custom buttons'); engine.block.setFloat(titleText, 'text/fontSize', 20); 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); // Create "IMG.LY" text const imglyText = engine.block.create('text'); engine.block.replaceText(imglyText, 'IMG.LY'); engine.block.setFloat(imglyText, 'text/fontSize', 14); engine.block.setTextColor(imglyText, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setWidthMode(imglyText, 'Auto'); engine.block.setHeightMode(imglyText, 'Auto'); engine.block.appendChild(page, imglyText); // Get dimensions and center both texts const titleWidth = engine.block.getFrameWidth(titleText); const titleHeight = engine.block.getFrameHeight(titleText); const imglyWidth = engine.block.getFrameWidth(imglyText); const imglyHeight = engine.block.getFrameHeight(imglyText); const spacing = 8; const totalHeight = titleHeight + spacing + imglyHeight; const startY = (pageHeight - totalHeight) / 2; // Position title text engine.block.setPositionX(titleText, (pageWidth - titleWidth) / 2); engine.block.setPositionY(titleText, startY); // Position IMG.LY text below title engine.block.setPositionX(imglyText, (pageWidth - imglyWidth) / 2); engine.block.setPositionY(imglyText, startY + titleHeight + spacing); } cesdk.ui.registerComponent('theme.toggle', ({ builder }) => { const isDark = cesdk.ui.getTheme() === 'dark'; builder.Button('toggle-theme', { label: 'Dark Mode', icon: isDark ? '@imgly/ToggleIconOn' : '@imgly/ToggleIconOff', variant: 'regular', isActive: isDark, onClick: () => { cesdk.ui.setTheme(isDark ? 'light' : 'dark'); } }); }); cesdk.ui.registerComponent('favorite.toggle', ({ builder, state }) => { const { value: isFavorite, setValue: setIsFavorite } = state( 'isFavorite', false ); builder.Button('toggle-favorite', { label: 'Favorite', icon: '@imgly/ShapeStar', variant: 'regular', isActive: isFavorite, onClick: () => { setIsFavorite(!isFavorite); } }); }); cesdk.ui.registerComponent('quick.export', ({ builder }) => { builder.Button('quick-export', { label: 'Export', icon: '@imgly/Download', variant: 'regular', onClick: () => { cesdk.actions.run('exportDesign'); } }); }); cesdk.ui.insertNavigationBarOrderComponent('last', 'favorite.toggle'); cesdk.ui.insertNavigationBarOrderComponent('last', 'quick.export'); cesdk.ui.insertNavigationBarOrderComponent('last', 'theme.toggle'); }} export default Example; ``` This guide demonstrates adding custom buttons to the navigation bar: a theme toggle, a favorite button with local state, and an export button that triggers a built-in action. ## Register a Button Component[#](#register-a-button-component) Use `cesdk.ui.registerComponent()` to create the theme toggle button. The render function receives a `builder` object for creating UI elements. Use `cesdk.ui.getTheme()` to read the current theme and `cesdk.ui.setTheme()` to change it. ``` cesdk.ui.registerComponent('theme.toggle', ({ builder }) => { const isDark = cesdk.ui.getTheme() === 'dark'; builder.Button('toggle-theme', { label: 'Dark Mode', icon: isDark ? '@imgly/ToggleIconOn' : '@imgly/ToggleIconOff', variant: 'regular', isActive: isDark, onClick: () => { cesdk.ui.setTheme(isDark ? 'light' : 'dark'); } });}); ``` The render function re-runs when relevant state changes. Calling `cesdk.ui.getTheme()` tracks the theme state, so the component automatically re-renders when `setTheme()` is called. ## Use Component State[#](#use-component-state) For buttons that need to maintain their own state, use the `state()` function provided in the render context. This creates reactive state that persists across re-renders and automatically triggers updates when changed. ``` cesdk.ui.registerComponent('favorite.toggle', ({ builder, state }) => { const { value: isFavorite, setValue: setIsFavorite } = state( 'isFavorite', false ); builder.Button('toggle-favorite', { label: 'Favorite', icon: '@imgly/ShapeStar', variant: 'regular', isActive: isFavorite, onClick: () => { setIsFavorite(!isFavorite); } });}); ``` The `state()` function returns an object with `value` (the current state) and `setValue` (a function to update it). When `setValue` is called, the component re-renders with the new value. The `isActive` property provides visual feedback when the button state is active. ## Trigger Built-in Actions[#](#trigger-built-in-actions) Combine custom buttons with built-in actions to create streamlined workflows. Use `cesdk.actions.run()` to execute registered actions like export, save, or zoom operations. ``` cesdk.ui.registerComponent('quick.export', ({ builder }) => { builder.Button('quick-export', { label: 'Export', icon: '@imgly/Download', variant: 'regular', onClick: () => { cesdk.actions.run('exportDesign'); } });}); ``` The [Actions API](vue/actions-6ch24x/) provides access to built-in operations (`exportDesign`, `saveScene`, `zoom.in`, etc.) and supports custom actions you register yourself. ## Add to the Navigation Bar[#](#add-to-the-navigation-bar) Use `insertNavigationBarOrderComponent()` to add buttons to the navigation bar. The first argument is a matcher (`'first'`, `'last'`, a number, component ID, or function), and the second is your component ID. ``` cesdk.ui.insertNavigationBarOrderComponent('last', 'theme.toggle'); ``` You can add buttons to other UI locations using similar APIs: | Location | Insert API | Set Order API | | --- | --- | --- | | Navigation Bar | `insertNavigationBarOrderComponent()` | `setNavigationBarOrder()` | | Dock | `insertDockOrderComponent()` | `setDockOrder()` | | Canvas Menu | `insertCanvasMenuOrderComponent()` | `setCanvasMenuOrder()` | | Inspector Bar | `insertInspectorBarOrderComponent()` | `setInspectorBarOrder()` | | Canvas Bar | `insertCanvasBarOrderComponent()` | `setCanvasBarOrder()` | ## Button Properties[#](#button-properties) The builder’s `Button` method accepts these configuration options: | Property | Type | Description | | --- | --- | --- | | `label` | `string | string[]` | Button text, supports i18n keys | | `icon` | `CustomIcon` | Icon from the icon set | | `onClick` | `() => void` | Click handler function | | `isSelected` | `boolean` | Highlight as selected | | `isActive` | `boolean` | Show active state styling | | `isDisabled` | `boolean` | Disable interaction | | `isLoading` | `boolean` | Show loading indicator | | `loadingProgress` | `number` | Loading progress (0-1) | | `color` | `'accent' | 'danger'` | Color variant | | `variant` | `'regular' | 'plain'` | Style variant | | `tooltip` | `string | string[]` | Hover tooltip text | | `shortcut` | `string` | Keyboard shortcut display | ## API Reference[#](#api-reference) | API | Description | | --- | --- | | `registerComponent()` | Register a custom component with a render function | | `state()` | Create reactive component state that persists across re-renders | | `insertNavigationBarOrderComponent()` | Insert a component into the navigation bar | | `getTheme()` | Get the current editor theme (`'light'` or `'dark'`) | | `setTheme()` | Set the editor theme | | `actions.run()` | Execute a registered action by ID | | `actions.list()` | List all available action IDs | ## Next Steps[#](#next-steps) * [Register a New Component](vue/user-interface/ui-extensions/register-new-component-b04a04/) — Learn the full component registration API for building complex UI elements * [Create a Custom Panel](vue/user-interface/ui-extensions/create-custom-panel-d87b83/) — Build panels with multiple interactive elements * [Configure the Panel](vue/user-interface/customization/panel-7ce1ee/) — Control panel visibility and behavior --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/ui-extensions/asset-library-f2c082) --- # Asset Library Extend the asset library by adding custom asset sources, enabling user uploads, and organizing content with groups and categories. ![Asset Library with custom sources](/docs/cesdk/_astro/browser.hero.CyCKFaHN_ZTCNSQ.webp) 15 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-asset-library-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-asset-library-browser) CE.SDK’s asset library lets users browse and add media to designs. You can extend this system by registering custom asset sources that fetch content from your own APIs, DAM systems, or third-party services like Unsplash. ``` import type { EditorPlugin, EditorPluginContext, AssetQueryData, AssetsQueryResult, AssetResult} from '@cesdk/cesdk-js';import { createApi, OrderBy } from 'unsplash-js';import packageJson from './package.json'; // Create Unsplash API client with proxy URL// For production, use your own Unsplash proxy with your API keyconst unsplashApi = createApi({ apiUrl: 'https://api.img.ly/unsplashProxy'}); // Transform Unsplash photo to CE.SDK AssetResult formatfunction transformToAssetResult(photo: { id: string; description: string | null; alt_description: string | null; width: number; height: number; urls: { regular: string; small: string }; user: { name: string; links: { html: string } };}): AssetResult { return { id: photo.id, label: photo.alt_description || photo.description || 'Unsplash Photo', tags: photo.alt_description?.split(' ').slice(0, 5), meta: { uri: photo.urls.regular, thumbUri: photo.urls.small, blockType: '//ly.img.ubq/graphic', fillType: '//ly.img.ubq/fill/image', shapeType: '//ly.img.ubq/shape/rect', kind: 'image', width: photo.width, height: photo.height }, credits: { name: photo.user.name, url: photo.user.links.html }, utm: { source: 'CE.SDK Demo', medium: 'referral' } };} // Fetch photos from Unsplash APIasync function fetchUnsplashPhotos( queryData: AssetQueryData): Promise<{ photos: AssetResult[]; total: number }> { // Unsplash uses 1-indexed pages, CE.SDK uses 0-indexed const page = queryData.page + 1; if (queryData.query) { // Search endpoint for query-based searches const response = await unsplashApi.search.getPhotos({ query: queryData.query, page, perPage: queryData.perPage }); if (response.type === 'error') { throw new Error('Failed to search Unsplash photos'); } const results = response.response.results; return { photos: results.map(transformToAssetResult), total: response.response.total }; } else { // List endpoint for browsing popular photos const response = await unsplashApi.photos.list({ orderBy: OrderBy.POPULAR, page, perPage: queryData.perPage }); if (response.type === 'error') { throw new Error('Failed to list Unsplash photos'); } const results = response.response.results; return { photos: results.map(transformToAssetResult), // For list endpoint, estimate total as large number since it's paginated total: 10000 }; }} 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 design scene to work with await cesdk.createDesignScene(); // Create an Unsplash asset source with remote API fetching engine.asset.addSource({ id: 'unsplash-images', async findAssets(queryData: AssetQueryData): Promise { try { const { photos, total } = await fetchUnsplashPhotos(queryData); // Calculate if there are more pages const startIndex = queryData.page * queryData.perPage; const hasNextPage = startIndex + photos.length < total; return { assets: photos, currentPage: queryData.page, nextPage: hasNextPage ? queryData.page + 1 : undefined, total }; } catch (error) { console.error('Failed to fetch Unsplash photos:', error); return { assets: [], currentPage: queryData.page, nextPage: undefined, total: 0 }; } }, // Provide available groups for filtering async getGroups(): Promise { return ['nature', 'architecture', 'people', 'animals']; }, // Unsplash credits and license credits: { name: 'Unsplash', url: 'https://unsplash.com' }, license: { name: 'Unsplash License', url: 'https://unsplash.com/license' } }); // Create a local asset source for user uploads engine.asset.addLocalSource('user-uploads', [ 'image/jpeg', 'image/png', 'image/webp' ]); // Add a custom asset library panel to display Unsplash images cesdk.ui.addAssetLibraryEntry({ id: 'unsplash-panel', sourceIds: ['unsplash-images'], title: 'Unsplash Images', icon: 'ly.img.image', gridColumns: 3, gridItemHeight: 'square', cardLabel: (asset: AssetResult) => asset.label || '', cardStyle: (asset: AssetResult) => ({ backgroundImage: `url(${asset.meta?.thumbUri})`, backgroundSize: 'cover', backgroundPosition: 'center' }) }); // Add an asset library panel for user uploads cesdk.ui.addAssetLibraryEntry({ id: 'uploads-panel', sourceIds: ['user-uploads'], title: 'My Uploads', icon: 'ly.img.upload', gridColumns: 3, gridItemHeight: 'square', canAdd: true, cardLabel: (asset: AssetResult) => asset.label || '', cardStyle: (asset: AssetResult) => ({ backgroundImage: `url(${asset.meta?.thumbUri})`, backgroundSize: 'cover', backgroundPosition: 'center' }) }); // Set dock to show both Unsplash and Uploads panels cesdk.ui.setDockOrder([ { id: 'ly.img.assetLibrary.dock', key: 'unsplash-images', icon: '@imgly/Image', label: 'Unsplash', entries: ['unsplash-panel'] }, { id: 'ly.img.assetLibrary.dock', key: 'user-uploads', icon: '@imgly/Upload', label: 'Uploads', entries: ['uploads-panel'] } ]); // Register middleware to handle asset application engine.asset.registerApplyMiddleware( async (sourceId, assetResult, apply, context) => { // Log asset application for debugging console.log(`Applying asset from source: ${sourceId}`); console.log('Asset:', assetResult.label); // For Unsplash, you would typically track the download here // using the download_location URL to comply with Unsplash guidelines // Call the original apply function to create the block const blockId = await apply(sourceId, assetResult); return blockId; } ); // Query available asset sources const allSources = engine.asset.findAllSources(); console.log('All asset sources:', allSources); // Get metadata from the Unsplash source const credits = engine.asset.getCredits('unsplash-images'); console.log('Source credits:', credits); const license = engine.asset.getLicense('unsplash-images'); console.log('Source license:', license); // Query and manage asset library entries const allEntries = cesdk.ui.findAllAssetLibraryEntries(); console.log('All asset library entries:', allEntries); // Get the Unsplash panel entry const unsplashEntry = cesdk.ui.getAssetLibraryEntry('unsplash-panel'); console.log('Unsplash entry:', unsplashEntry); // Update the panel configuration cesdk.ui.updateAssetLibraryEntry('unsplash-panel', { gridColumns: 4 }); // Open the Unsplash asset library panel on startup cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['unsplash-panel'], title: 'Unsplash' } }); }} export default Example; ``` This guide covers creating custom asset sources, enabling user uploads, organizing assets with groups, and displaying content in the asset library UI. ## Creating Custom Asset Sources[#](#creating-custom-asset-sources) We register a source with `engine.asset.addSource()`, providing a unique ID and a `findAssets` function. This function receives query parameters and returns paginated asset results with metadata. ``` // Create an Unsplash asset source with remote API fetchingengine.asset.addSource({ id: 'unsplash-images', async findAssets(queryData: AssetQueryData): Promise { try { const { photos, total } = await fetchUnsplashPhotos(queryData); // Calculate if there are more pages const startIndex = queryData.page * queryData.perPage; const hasNextPage = startIndex + photos.length < total; return { assets: photos, currentPage: queryData.page, nextPage: hasNextPage ? queryData.page + 1 : undefined, total }; } catch (error) { console.error('Failed to fetch Unsplash photos:', error); return { assets: [], currentPage: queryData.page, nextPage: undefined, total: 0 }; } }, // Provide available groups for filtering async getGroups(): Promise { return ['nature', 'architecture', 'people', 'animals']; }, // Unsplash credits and license credits: { name: 'Unsplash', url: 'https://unsplash.com' }, license: { name: 'Unsplash License', url: 'https://unsplash.com/license' }}); ``` The source configuration includes: * A unique `id` to identify the source * The `findAssets` function that returns assets matching the query * Optional `getGroups` function to provide filtering categories * Credits and license information for attribution The `findAssets` function receives query parameters including search text, groups, tags, pagination, and locale. Return matching results with proper pagination metadata. ## Enabling User Uploads[#](#enabling-user-uploads) Local sources support dynamic asset management at runtime. Create them with `engine.asset.addLocalSource()` and add or remove individual assets programmatically. ``` // Create a local asset source for user uploadsengine.asset.addLocalSource('user-uploads', [ 'image/jpeg', 'image/png', 'image/webp']); ``` When creating the source, specify supported MIME types to restrict which file types can be added. This is useful for user uploads or runtime-generated content. The `addAssetToSource` method adds new assets that become immediately available in query results. Each asset needs an ID, optional localized labels and tags, and metadata with URIs and block types. ## Organizing Assets with Groups[#](#organizing-assets-with-groups) Groups help users find assets through filtering. Return group arrays in asset results to organize content into categories. ``` // Provide available groups for filteringasync getGroups(): Promise { return ['nature', 'architecture', 'people', 'animals'];}, ``` Implement `getGroups()` to provide the complete list of available categories. This populates filter options in the asset library panel. Users can then filter results by selecting specific groups. ## Adding Asset Library Panels[#](#adding-asset-library-panels) Asset library entries define how a panel looks and what it displays. Each entry connects one or more asset sources to a visual presentation with grid layouts, card styling, and interaction behaviors. ``` // Add a custom asset library panel to display Unsplash imagescesdk.ui.addAssetLibraryEntry({ id: 'unsplash-panel', sourceIds: ['unsplash-images'], title: 'Unsplash Images', icon: 'ly.img.image', gridColumns: 3, gridItemHeight: 'square', cardLabel: (asset: AssetResult) => asset.label || '', cardStyle: (asset: AssetResult) => ({ backgroundImage: `url(${asset.meta?.thumbUri})`, backgroundSize: 'cover', backgroundPosition: 'center' })}); // Add an asset library panel for user uploadscesdk.ui.addAssetLibraryEntry({ id: 'uploads-panel', sourceIds: ['user-uploads'], title: 'My Uploads', icon: 'ly.img.upload', gridColumns: 3, gridItemHeight: 'square', canAdd: true, cardLabel: (asset: AssetResult) => asset.label || '', cardStyle: (asset: AssetResult) => ({ backgroundImage: `url(${asset.meta?.thumbUri})`, backgroundSize: 'cover', backgroundPosition: 'center' })}); // Set dock to show both Unsplash and Uploads panelscesdk.ui.setDockOrder([ { id: 'ly.img.assetLibrary.dock', key: 'unsplash-images', icon: '@imgly/Image', label: 'Unsplash', entries: ['unsplash-panel'] }, { id: 'ly.img.assetLibrary.dock', key: 'user-uploads', icon: '@imgly/Upload', label: 'Uploads', entries: ['uploads-panel'] }]); ``` The panel configuration includes: * `sourceIds` linking to your asset sources * Visual settings like grid columns and item height * `cardStyle` function to display thumbnails as background images * `cardLabel` function to show asset descriptions * `canAdd` to enable the upload button for local sources ## Creating an Uploads Panel[#](#creating-an-uploads-panel) For user uploads, create a dedicated panel that references the local source and enables the add functionality. ``` // Add an asset library panel for user uploadscesdk.ui.addAssetLibraryEntry({ id: 'uploads-panel', sourceIds: ['user-uploads'], title: 'My Uploads', icon: 'ly.img.upload', gridColumns: 3, gridItemHeight: 'square', canAdd: true, cardLabel: (asset: AssetResult) => asset.label || '', cardStyle: (asset: AssetResult) => ({ backgroundImage: `url(${asset.meta?.thumbUri})`, backgroundSize: 'cover', backgroundPosition: 'center' })}); ``` Setting `canAdd: true` displays an upload button in the panel. When users click it, they can add new assets to the local source. Use `setDockOrder` to control which panels appear in the dock and their order. ## Using Apply Middleware[#](#using-apply-middleware) Register middleware with `engine.asset.registerApplyMiddleware()` to intercept all asset applications regardless of source. Middleware can modify data, track usage, or implement custom placement logic. ``` // Register middleware to handle asset applicationengine.asset.registerApplyMiddleware( async (sourceId, assetResult, apply, context) => { // Log asset application for debugging console.log(`Applying asset from source: ${sourceId}`); console.log('Asset:', assetResult.label); // For Unsplash, you would typically track the download here // using the download_location URL to comply with Unsplash guidelines // Call the original apply function to create the block const blockId = await apply(sourceId, assetResult); return blockId; }); ``` The middleware receives the source ID, asset result, original apply function, and application context. Call the original apply function and optionally modify the created block afterward. ## Querying Asset Sources[#](#querying-asset-sources) Inspect registered sources and retrieve their metadata for debugging or displaying attribution. ``` // Query available asset sourcesconst allSources = engine.asset.findAllSources();console.log('All asset sources:', allSources); // Get metadata from the Unsplash sourceconst credits = engine.asset.getCredits('unsplash-images');console.log('Source credits:', credits); const license = engine.asset.getLicense('unsplash-images');console.log('Source license:', license); ``` The `getCredits` and `getLicense` methods return the attribution information provided when registering the source. ## Managing Asset Library Entries[#](#managing-asset-library-entries) List, retrieve, and update asset library entries to adjust the UI dynamically. ``` // Query and manage asset library entriesconst allEntries = cesdk.ui.findAllAssetLibraryEntries();console.log('All asset library entries:', allEntries); // Get the Unsplash panel entryconst unsplashEntry = cesdk.ui.getAssetLibraryEntry('unsplash-panel');console.log('Unsplash entry:', unsplashEntry); // Update the panel configurationcesdk.ui.updateAssetLibraryEntry('unsplash-panel', { gridColumns: 4}); ``` These methods let you modify panel configuration at runtime, such as changing the grid layout based on screen size or user preferences. ## Remote API Integration Example[#](#remote-api-integration-example) This example demonstrates fetching images from Unsplash using the `unsplash-js` library. The same pattern applies to any remote API or DAM system. ### Setting Up the API Client[#](#setting-up-the-api-client) ``` // Create Unsplash API client with proxy URL// For production, use your own Unsplash proxy with your API keyconst unsplashApi = createApi({ apiUrl: 'https://api.img.ly/unsplashProxy'}); ``` The `createApi` function accepts an `apiUrl` parameter pointing to a proxy server. The proxy handles authentication and avoids CORS issues. ### Transforming API Responses[#](#transforming-api-responses) Transform external API responses into the `AssetResult` structure that CE.SDK expects. ``` // Transform Unsplash photo to CE.SDK AssetResult formatfunction transformToAssetResult(photo: { id: string; description: string | null; alt_description: string | null; width: number; height: number; urls: { regular: string; small: string }; user: { name: string; links: { html: string } };}): AssetResult { return { id: photo.id, label: photo.alt_description || photo.description || 'Unsplash Photo', tags: photo.alt_description?.split(' ').slice(0, 5), meta: { uri: photo.urls.regular, thumbUri: photo.urls.small, blockType: '//ly.img.ubq/graphic', fillType: '//ly.img.ubq/fill/image', shapeType: '//ly.img.ubq/shape/rect', kind: 'image', width: photo.width, height: photo.height }, credits: { name: photo.user.name, url: photo.user.links.html }, utm: { source: 'CE.SDK Demo', medium: 'referral' } };} ``` Map the external fields to CE.SDK properties: * Image URLs for `uri` and `thumbUri` * Descriptions for labels and search tags * Author information for attribution credits ### Fetching from the Remote API[#](#fetching-from-the-remote-api) ``` // Fetch photos from Unsplash APIasync function fetchUnsplashPhotos( queryData: AssetQueryData): Promise<{ photos: AssetResult[]; total: number }> { // Unsplash uses 1-indexed pages, CE.SDK uses 0-indexed const page = queryData.page + 1; if (queryData.query) { // Search endpoint for query-based searches const response = await unsplashApi.search.getPhotos({ query: queryData.query, page, perPage: queryData.perPage }); if (response.type === 'error') { throw new Error('Failed to search Unsplash photos'); } const results = response.response.results; return { photos: results.map(transformToAssetResult), total: response.response.total }; } else { // List endpoint for browsing popular photos const response = await unsplashApi.photos.list({ orderBy: OrderBy.POPULAR, page, perPage: queryData.perPage }); if (response.type === 'error') { throw new Error('Failed to list Unsplash photos'); } const results = response.response.results; return { photos: results.map(transformToAssetResult), // For list endpoint, estimate total as large number since it's paginated total: 10000 }; }} ``` Handle pagination differences between your API and CE.SDK. Unsplash uses 1-indexed pages while CE.SDK uses 0-indexed. ## Troubleshooting[#](#troubleshooting) ### Assets Not Appearing in UI[#](#assets-not-appearing-in-ui) Verify the source ID appears in an asset library entry’s `sourceIds` array. Check that `findAssets` returns the correct structure with all required fields. Ensure the asset library entry is added to the dock order. ### Search Not Working[#](#search-not-working) Confirm your `findAssets` function handles the `query` parameter and filters results. Check that asset labels and tags are properly set for the requested locale. ### Upload Errors[#](#upload-errors) Validate MIME types against `getSupportedMimeTypes()` before attempting to add assets. Ensure local sources are created with `addLocalSource()` before calling `addAssetToSource()`. ### Remote API Issues[#](#remote-api-issues) Verify the proxy URL is correct and accessible. Check browser console for CORS errors or API response issues. Ensure proper error handling in the `findAssets` function. --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/ui-extensions/add-custom-feature-2a26b6) --- # Add a Custom Feature Bundle custom functionality into reusable plugins for CE.SDK. ![Add a Custom Feature example showing CE.SDK with a custom canvas menu button](/docs/cesdk/_astro/browser.hero.BAOqBpZ7_m1uCE.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-add-custom-feature-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ui-extensions-add-custom-feature-browser) Plugins provide a structured way to package UI components, event handlers, actions, and configuration into a single unit. While you can customize CE.SDK without plugins by calling APIs directly after initialization, plugins help organize code for sharing across projects or publishing for others to use. **Do I Need to Create a Plugin to Customize the Editor?** **Short answer: No.** Keep in mind that you neither have to create a plugin to customize the CE.SDK, nor do you have to publish it. Feel free to use all APIs directly after initializing the CE.SDK. This is completely fine. Even if you decide to bundle everything into a plugin, you can still keep it private and use it only for your own integration. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; interface CustomFeaturePluginConfig { ui?: { locations?: ('canvasMenu' | 'inspectorBar')[]; };} const DEFAULT_CONFIG: CustomFeaturePluginConfig = { ui: { locations: [] }}; const CustomFeaturePlugin = ( userConfig: CustomFeaturePluginConfig = {}): EditorPlugin => { // Merge user config with defaults const config: CustomFeaturePluginConfig = { ...DEFAULT_CONFIG, ui: { ...DEFAULT_CONFIG.ui, ...userConfig.ui } }; return { name: 'CustomFeaturePlugin', version: '1.0.0', async initialize({ cesdk, engine }: EditorPluginContext): Promise { if (!cesdk) { console.log('Plugin initialized in engine-only mode'); return; } console.log('CustomFeaturePlugin initialized'); // Load default assets and create a design scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const page = engine.block.findByType('page')[0]; if (page) { // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Create gradient background fill const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.1, g: 0.1, b: 0.2, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.2, b: 0.5, a: 1.0 }, stop: 0.5 }, { color: { r: 0.1, g: 0.3, b: 0.4, a: 1.0 }, stop: 1 } ]); 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); // Create centered "IMG.LY" text const textBlock = engine.block.create('text'); engine.block.replaceText(textBlock, 'IMG.LY'); engine.block.setTextFontSize(textBlock, 80); engine.block.setTextColor(textBlock, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.appendChild(page, textBlock); // Center the text on the page const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const textWidth = engine.block.getFrameWidth(textBlock); const textHeight = engine.block.getFrameHeight(textBlock); engine.block.setPositionX(textBlock, (pageWidth - textWidth) / 2); engine.block.setPositionY(textBlock, (pageHeight - textHeight) / 2); // Select the text block to show the canvas menu engine.block.select(textBlock); engine.block.findAllSelected().forEach(b => engine.block.setSelected(b, false)); engine.block.setSelected(engine.scene.getCurrentPage(), true); } // Register a custom button component cesdk.ui.registerComponent( 'customFeaturePlugin.action.canvasMenu', (context) => { context.builder.Button('custom-action', { label: 'Custom Action', icon: '@imgly/Apps', onClick: () => { cesdk.ui.showNotification({ message: 'Custom action triggered!', type: 'success', duration: 'short' }); console.log('Custom action executed'); } }); } ); // Only add to canvas menu if configured const locations = config.ui?.locations ?? []; if (locations.includes('canvasMenu')) { const currentOrder = cesdk.ui.getCanvasMenuOrder(); cesdk.ui.setCanvasMenuOrder([ 'customFeaturePlugin.action.canvasMenu', ...currentOrder ]); console.log('Custom action added to canvas menu'); } // Subscribe to block events for demonstration const unsubscribe = engine.event.subscribe([], (events) => { events.forEach((event) => { if (event.type === 'Created') { console.log(`Block created: ${event.block}`); } }); }); // Store unsubscribe for potential cleanup (window as any).unsubscribeCustomFeature = unsubscribe; } };}; export default CustomFeaturePlugin; ``` This guide covers creating plugins with the factory function pattern, registering custom components during initialization, and letting integrators control where your plugin’s UI elements appear. ## Plugin Types[#](#plugin-types) Two plugin interfaces exist: `EditorPlugin` for full editor integration and `EnginePlugin` for headless engine use. Editor plugins receive both `cesdk` and `engine` in their context; engine plugins receive only `engine`. Structure your plugins to handle both contexts for maximum flexibility. ## Creating a Plugin[#](#creating-a-plugin) ### The Plugin Interface[#](#the-plugin-interface) Plugins implement an interface with `name`, `version`, and `initialize` properties. The `initialize` method executes when you call `cesdk.addPlugin()` or `engine.addPlugin()`. Return a promise from `initialize` for async setup operations. ``` const CustomFeaturePlugin = ( userConfig: CustomFeaturePluginConfig = {}): EditorPlugin => { // Merge user config with defaults const config: CustomFeaturePluginConfig = { ...DEFAULT_CONFIG, ui: { ...DEFAULT_CONFIG.ui, ...userConfig.ui } }; return { name: 'CustomFeaturePlugin', version: '1.0.0', async initialize({ cesdk, engine }: EditorPluginContext): Promise { ``` ### Adding a Plugin to the Editor[#](#adding-a-plugin-to-the-editor) Call `cesdk.addPlugin()` after creating the editor instance. Pass a plugin object directly or call a factory function that returns one. ``` // Add the plugin with configurationawait cesdk.addPlugin( CustomFeaturePlugin({ ui: { locations: ['canvasMenu'] } })); ``` ## Making Plugins Configurable[#](#making-plugins-configurable) ### The Factory Function Pattern[#](#the-factory-function-pattern) Export a function that returns the plugin object rather than the object directly. This allows integrators to pass configuration options. Even without current configuration needs, this pattern prevents breaking changes when adding options later. ``` interface CustomFeaturePluginConfig { ui?: { locations?: ('canvasMenu' | 'inspectorBar')[]; };} const DEFAULT_CONFIG: CustomFeaturePluginConfig = { ui: { locations: [] }}; ``` The factory function merges user-provided configuration with sensible defaults. Document available options so integrators understand how to customize behavior. ## Plugin Initialization[#](#plugin-initialization) ### Registering Components[#](#registering-components) Use `cesdk.ui.registerComponent()` to add custom UI components. Components become available for placement in canvas menus, inspector bars, and other locations. The builder API creates buttons, inputs, and other controls. ``` // Register a custom button componentcesdk.ui.registerComponent( 'customFeaturePlugin.action.canvasMenu', (context) => { context.builder.Button('custom-action', { label: 'Custom Action', icon: '@imgly/Apps', onClick: () => { cesdk.ui.showNotification({ message: 'Custom action triggered!', type: 'success', duration: 'short' }); console.log('Custom action executed'); } }); }); ``` ### Subscribing to Events[#](#subscribing-to-events) Use `engine.event.subscribe()` to react to block changes. Store the unsubscribe function for cleanup when needed. Event subscriptions set up reactive behavior without executing immediate actions. ``` // Subscribe to block events for demonstrationconst unsubscribe = engine.event.subscribe([], (events) => { events.forEach((event) => { if (event.type === 'Created') { console.log(`Block created: ${event.block}`); } });}); // Store unsubscribe for potential cleanup(window as any).unsubscribeCustomFeature = unsubscribe; ``` ## Controlling Component Placement[#](#controlling-component-placement) Offer a configuration option for default locations. Check this option during initialization and only modify order when explicitly requested by the integrator. ``` // Only add to canvas menu if configuredconst locations = config.ui?.locations ?? [];if (locations.includes('canvasMenu')) { const currentOrder = cesdk.ui.getCanvasMenuOrder(); cesdk.ui.setCanvasMenuOrder([ 'customFeaturePlugin.action.canvasMenu', ...currentOrder ]); console.log('Custom action added to canvas menu');} ``` This approach lets integrators decide whether to use default placement or handle positioning themselves. ## Troubleshooting[#](#troubleshooting) ### Plugin Not Initializing[#](#plugin-not-initializing) Verify `addPlugin()` is called after the editor or engine is created. Check for errors thrown during `initialize` - exceptions prevent plugin setup. Ensure async `initialize` functions return promises properly. ### Components Not Appearing[#](#components-not-appearing) Confirm components are registered with unique IDs. Check that component IDs match exactly when setting order. Verify the location is configured in the plugin options if using optional placement. ### Context Arguments Missing[#](#context-arguments-missing) For editor plugins, `cesdk` is undefined when added via `engine.addPlugin()`. Always check if `cesdk` exists before calling editor-specific APIs. Structure code to work with engine-only contexts when needed. ### Configuration Not Applied[#](#configuration-not-applied) Verify the factory function pattern is used and called with parentheses: `MyPlugin()` not `MyPlugin`. Check that configuration is passed to the factory: `MyPlugin({ option: value })`. ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `cesdk.addPlugin()` | Add and initialize a plugin to the editor | | `engine.addPlugin()` | Add and initialize a plugin to the engine | | `cesdk.ui.registerComponent()` | Register a custom UI component | | `cesdk.ui.setCanvasMenuOrder()` | Set component order in canvas menu | | `cesdk.ui.getCanvasMenuOrder()` | Get current canvas menu component order | | `engine.event.subscribe()` | Subscribe to block lifecycle events | ## Next Steps[#](#next-steps) * [Register New Component](vue/user-interface/ui-extensions/register-new-component-b04a04/) — Learn component registration in detail * [Customize UI Behavior](vue/user-interface/ui-extensions/customize-behaviour-c09cb2/) — React to events and control UI programmatically * [Create Custom Panel](vue/user-interface/ui-extensions/create-custom-panel-d87b83/) — Build custom panels for your plugin * [Disable or Enable Features](vue/user-interface/customization/disable-or-enable-f058e2/) — Control feature availability --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/save-export-buttons-96018b) --- # Save and Export Buttons This guide shows you how to add, customize, and configure save and export buttons in the CE.SDK navigation bar. You’ll learn how to use the Navigation Bar Order API to display built-in action buttons and the Actions API to implement custom save and export workflows. ![Save and Export Buttons](/docs/cesdk/_astro/browser.hero.DFAdDEGz_Z6y62k.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * Save and Export Buttons Example * * Demonstrates adding save/export buttons to the navigation bar * and overriding their default behavior with custom handlers. */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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create gradient fill for page background (purple to violet to cyan diagonal) const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.388, g: 0.4, b: 0.945, a: 1 }, stop: 0 }, // #6366f1 indigo { color: { r: 0.545, g: 0.361, b: 0.965, a: 1 }, stop: 0.5 }, // #8b5cf6 violet { color: { r: 0.024, g: 0.714, b: 0.831, a: 1 }, stop: 1 } // #06b6d4 cyan ]); // 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); // Create centered text with title and subtitle const titleText = engine.block.create('text'); engine.block.appendChild(page, titleText); engine.block.replaceText(titleText, 'Save and Export Buttons\n\nimg.ly'); engine.block.setWidth(titleText, pageWidth); engine.block.setHeightMode(titleText, 'Auto'); engine.block.setPositionX(titleText, 0); engine.block.setPositionY(titleText, pageHeight * 0.35); engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center'); engine.block.setFloat(titleText, 'text/fontSize', 24); engine.block.setTextColor(titleText, { r: 1, g: 1, b: 1, a: 1 }); // Deselect all blocks for clean hero image engine.block.findAllSelected().forEach((block) => { engine.block.setSelected(block, false); }); // Select the page to show the page toolbar (looks better in hero) engine.block.select(page); // Override the saveScene action with custom logic cesdk.actions.register('saveScene', async () => { // Replace with your backend upload logic console.trace('saveScene action triggered'); const archive = await cesdk.engine.scene.saveToArchive(); cesdk.utils.downloadFile(archive, 'application/zip'); }); // Add all save and export buttons to the navigation bar cesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', 'ly.img.exportImage.navigationBar', { // Override the PDF export button's onClick callback id: 'ly.img.exportPDF.navigationBar', onClick: async () => { const { blobs } = await cesdk.utils.export({ mimeType: 'application/pdf' }); cesdk.utils.downloadFile(blobs[0], 'application/pdf'); cesdk.ui.showNotification({ message: 'PDF exported successfully!', type: 'success', duration: 'short' }); } }, 'ly.img.exportScene.navigationBar', 'ly.img.exportArchive.navigationBar' ] }); }} export default Example; ``` This guide demonstrates how to add save and export buttons to your CE.SDK editor’s navigation bar and customize their behavior with custom action handlers. Use the built-in actions to get started quickly, then override them to fully fit your workflows. ## Available Save and Export Buttons[#](#available-save-and-export-buttons) CE.SDK provides several built-in action buttons for the navigation bar. Each button triggers an action when clicked: | Button ID | Action | Default Behavior | | --- | --- | --- | | `ly.img.saveScene.navigationBar` | `saveScene` | Downloads scene as `.scene` file | | `ly.img.exportImage.navigationBar` | `exportDesign` with PNG | Downloads PNG image | | `ly.img.exportPDF.navigationBar` | `exportDesign` with PDF | Downloads PDF document | | `ly.img.exportVideo.navigationBar` | `exportDesign` with MP4 | Downloads MP4 video | | `ly.img.exportScene.navigationBar` | `exportScene` | Downloads scene file | | `ly.img.exportArchive.navigationBar` | `exportScene` (archive) | Downloads scene archive | ## Adding Buttons to the Navigation Bar[#](#adding-buttons-to-the-navigation-bar) Use `cesdk.ui.setNavigationBarOrder()` to configure the navigation bar with save and export buttons. The buttons must be wrapped in the `ly.img.actions.navigationBar` container: ``` // Add all save and export buttons to the navigation barcesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', 'ly.img.exportImage.navigationBar', ``` For complete control over the navigation bar layout, see the [Navigation Bar](vue/user-interface/customization/navigation-bar-4e5d39/) guide which covers `setNavigationBarOrder()` and other customization options. ## Overriding Actions[#](#overriding-actions) By default, each button triggers a built-in action. To implement custom logic like uploading to a backend, register a custom handler using `cesdk.actions.register()`: ``` // Override the saveScene action with custom logiccesdk.actions.register('saveScene', async () => { // Replace with your backend upload logic console.trace('saveScene action triggered'); const archive = await cesdk.engine.scene.saveToArchive(); cesdk.utils.downloadFile(archive, 'application/zip');}); ``` The `saveScene` action receives no arguments. Use `cesdk.engine.scene.saveToString()` to serialize the scene, or `cesdk.engine.scene.saveToArchive()` if you need to include referenced assets. ## Overriding Export Buttons[#](#overriding-export-buttons) Instead of overriding the global `exportDesign` action, you can provide a custom `onClick` callback directly on individual buttons. This approach gives you fine-grained control over each button’s behavior: ``` // Override the PDF export button's onClick callbackid: 'ly.img.exportPDF.navigationBar',onClick: async () => { const { blobs } = await cesdk.utils.export({ mimeType: 'application/pdf' }); cesdk.utils.downloadFile(blobs[0], 'application/pdf'); cesdk.ui.showNotification({ message: 'PDF exported successfully!', type: 'success', duration: 'short' });} ``` The `onClick` callback completely replaces the button’s default behavior. Use `cesdk.ui.showNotification()` to display feedback after the export completes. The notification accepts `type` values of `'success'`, `'error'`, `'info'`, `'warning'`, or `'loading'`. ## Troubleshooting[#](#troubleshooting) ### Button Not Appearing[#](#button-not-appearing) * Verify the button ID is spelled correctly * Ensure buttons are wrapped in the `ly.img.actions.navigationBar` container * Check that the scene mode supports the export type (video export requires Video mode) ### Export Produces Empty Result[#](#export-produces-empty-result) * Ensure a page exists in the scene * Check that the page has visible content * Verify export dimensions are valid ### Save Action Not Triggered[#](#save-action-not-triggered) * Confirm the `saveScene` action is registered before the button is clicked * Check the browser console for errors in your action handler ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `cesdk.actions.register(actionId, callback)` | Register a custom action handler | | `cesdk.actions.run(actionId, ...args)` | Programmatically trigger a registered action | | `cesdk.ui.setNavigationBarOrder(order)` | Set the navigation bar component order | | `cesdk.utils.export(options)` | Export with automatic loading dialog | | `cesdk.utils.downloadFile(blob, mimeType)` | Download a blob as a file | | `cesdk.ui.showNotification(notification)` | Display a notification message | | `cesdk.engine.scene.saveToString()` | Serialize scene to string | ## Next Steps[#](#next-steps) * [Navigation Bar](vue/user-interface/customization/navigation-bar-4e5d39/) \- Complete navigation bar customization * [Actions](vue/actions-6ch24x/) \- Register and customize action handlers * [Export Options](vue/export-save-publish/export/overview-9ed3a8/) \- Export configuration and format options --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/rearrange-buttons-97022a) --- # Rearrange Buttons Control the layout of CE.SDK’s UI areas by rearranging, inserting, and removing components using the Ordering APIs. You can customize the navigation bar, canvas menu, canvas bar, dock, and inspector bar to match your application’s workflow. ![Rearrange Buttons Example](/docs/cesdk/_astro/browser.hero.Cjb9JWPa_Z2dOGk2.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) CE.SDK provides Ordering APIs to control which components appear in each UI area and in what order. Each area has `get`, `set`, `insert`, `update`, and `remove` methods for flexible customization. Components use identifiers like `'ly.img.undoRedo.navigationBar'`, and you can add separators (`'ly.img.separator'`) and spacers (`'ly.img.spacer'`) to organize groups. This guide covers getting the current order, setting a complete new order, inserting components at specific positions, updating component properties, removing components, and applying context-specific orderings. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Get the current navigation bar order const currentOrder = cesdk.ui.getNavigationBarOrder(); console.log('Current navigation bar order:', currentOrder); // Set a custom navigation bar with fewer buttons cesdk.ui.setNavigationBarOrder([ 'ly.img.zoom.navigationBar', 'ly.img.actions.navigationBar', 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', 'ly.img.back.navigationBar' ]); // Remove flip options from the canvas menu cesdk.ui.removeCanvasMenuOrderComponent('ly.img.flipX.canvasMenu'); cesdk.ui.removeCanvasMenuOrderComponent('ly.img.flipY.canvasMenu'); // Configure a custom dock with asset library buttons cesdk.ui.setDockOrder([ { id: 'ly.img.assetLibrary.dock', key: 'shapes', label: 'Shapes', icon: '@imgly/Shapes', entries: ['ly.img.vectorpath'] }, 'ly.img.separator', { id: 'ly.img.assetLibrary.dock', key: 'images', label: 'Images', icon: '@imgly/Image', entries: ['ly.img.image'] }, { id: 'ly.img.assetLibrary.dock', key: 'text', label: 'Text', icon: '@imgly/Text', entries: ['ly.img.text'] } ]); // Insert a separator before the export actions button cesdk.ui.insertNavigationBarOrderComponent( 'ly.img.actions.navigationBar', 'ly.img.separator', 'before' ); // Update a component's properties without changing its position cesdk.ui.updateDockOrderComponent( { id: 'ly.img.assetLibrary.dock', key: 'images' }, { label: 'Photos' } ); // Set a different canvas menu order for Text edit mode cesdk.ui.setCanvasMenuOrder( [ 'ly.img.text.bold.canvasMenu', 'ly.img.text.italic.canvasMenu', 'ly.img.separator', 'ly.img.copy.canvasMenu', 'ly.img.paste.canvasMenu', 'ly.img.delete.canvasMenu' ], { editMode: 'Text' } ); // Load assets and create design scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); console.log('Rearrange buttons example loaded successfully!'); }} export default Example; ``` ## Understanding UI Areas[#](#understanding-ui-areas) CE.SDK has five main UI areas you can customize: * **Navigation Bar**: Top bar with back, undo/redo, zoom, preview, and export actions * **Canvas Menu**: Context menu when right-clicking or long-pressing the canvas * **Canvas Bar**: Quick actions at the top or bottom of the canvas * **Dock**: Bottom or side panel for asset libraries and tools * **Inspector Bar**: Editing controls for the selected block ## Getting the Current Order[#](#getting-the-current-order) Before rearranging, retrieve the current component arrangement to see what’s available. ``` // Get the current navigation bar orderconst currentOrder = cesdk.ui.getNavigationBarOrder();console.log('Current navigation bar order:', currentOrder); ``` The returned array contains component objects with `id` and optional properties. Common navigation bar components include: * `'ly.img.back.navigationBar'` - Back button * `'ly.img.undoRedo.navigationBar'` - Undo/redo buttons * `'ly.img.zoom.navigationBar'` - Zoom controls * `'ly.img.preview.navigationBar'` - Preview button * `'ly.img.actions.navigationBar'` - Export actions dropdown * `'ly.img.close.navigationBar'` - Close button * `'ly.img.title.navigationBar'` - Document title * `'ly.img.pageResize.navigationBar'` - Page resize button ## Setting a Complete Order[#](#setting-a-complete-order) Replace the entire component order by passing an array of component IDs. Components not included in the array will be hidden. ``` // Set a custom navigation bar with fewer buttonscesdk.ui.setNavigationBarOrder([ 'ly.img.zoom.navigationBar', 'ly.img.actions.navigationBar', 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', 'ly.img.back.navigationBar']); ``` This creates a streamlined navigation bar with zoom controls and export actions on the left, undo/redo in the middle, a flexible spacer, and the back button pushed to the right. ## Using Separators and Spacers[#](#using-separators-and-spacers) Use `'ly.img.separator'` to add visual dividers between groups of buttons. Use `'ly.img.spacer'` to create flexible space that pushes components apart. Multiple spacers distribute space evenly. ## Removing Components[#](#removing-components) Remove specific components without replacing the entire order. ``` // Remove flip options from the canvas menucesdk.ui.removeCanvasMenuOrderComponent('ly.img.flipX.canvasMenu');cesdk.ui.removeCanvasMenuOrderComponent('ly.img.flipY.canvasMenu'); ``` The `removeCanvasMenuOrderComponent` method returns an object with the `removed` count and updated `order` array. ## Configuring the Dock[#](#configuring-the-dock) The dock uses an object format for asset library buttons. Each button specifies its entries, label, and icon. ``` // Configure a custom dock with asset library buttonscesdk.ui.setDockOrder([ { id: 'ly.img.assetLibrary.dock', key: 'shapes', label: 'Shapes', icon: '@imgly/Shapes', entries: ['ly.img.vectorpath'] }, 'ly.img.separator', { id: 'ly.img.assetLibrary.dock', key: 'images', label: 'Images', icon: '@imgly/Image', entries: ['ly.img.image'] }, { id: 'ly.img.assetLibrary.dock', key: 'text', label: 'Text', icon: '@imgly/Text', entries: ['ly.img.text'] }]); ``` When adding multiple asset library buttons, include a unique `key` to distinguish them. The `entries` array specifies which asset sources appear when the button is clicked. ## Inserting at Specific Positions[#](#inserting-at-specific-positions) Insert components relative to existing ones using matchers and location parameters. ``` // Insert a separator before the export actions buttoncesdk.ui.insertNavigationBarOrderComponent( 'ly.img.actions.navigationBar', 'ly.img.separator', 'before'); ``` The third parameter specifies the location: `'before'`, `'after'`, or `'replace'`. ### Matcher Options[#](#matcher-options) The insert, update, and remove methods accept various matcher types: * **String ID**: `'ly.img.duplicate.canvasMenu'` - Matches by component ID * **Object**: `{ id: 'ly.img.zoom.navigationBar' }` - Matches by partial properties * **Position**: `'first'` or `'last'` - Matches first or last component * **Index**: `0`, `1`, `2`, etc. - Matches by position index * **Function**: `(component, index) => boolean` - Custom matching logic ## Updating Component Properties[#](#updating-component-properties) Modify properties of existing components without changing their position. ``` // Update a component's properties without changing its positioncesdk.ui.updateDockOrderComponent( { id: 'ly.img.assetLibrary.dock', key: 'images' }, { label: 'Photos' }); ``` The update can be a partial object with new properties or a function that returns updates based on current values. ## Context-Based Ordering[#](#context-based-ordering) Apply different orderings for specific edit modes by passing an `orderContext` parameter. ``` // Set a different canvas menu order for Text edit modecesdk.ui.setCanvasMenuOrder( [ 'ly.img.text.bold.canvasMenu', 'ly.img.text.italic.canvasMenu', 'ly.img.separator', 'ly.img.copy.canvasMenu', 'ly.img.paste.canvasMenu', 'ly.img.delete.canvasMenu' ], { editMode: 'Text' }); ``` Available edit modes include `'Transform'` (default), `'Text'`, `'Crop'`, and others. This lets you show different controls when editing text versus transforming shapes. ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `cesdk.ui.getNavigationBarOrder(orderContext?)` | Get current navigation bar component order | | `cesdk.ui.setNavigationBarOrder(order, orderContext?)` | Set navigation bar component order | | `cesdk.ui.insertNavigationBarOrderComponent(matcher, component, location?, orderContext?)` | Insert component into navigation bar | | `cesdk.ui.updateNavigationBarOrderComponent(matcher, update, orderContext?)` | Update component properties in navigation bar | | `cesdk.ui.removeNavigationBarOrderComponent(matcher, orderContext?)` | Remove component from navigation bar | | `cesdk.ui.getCanvasMenuOrder(orderContext?)` | Get current canvas menu component order | | `cesdk.ui.setCanvasMenuOrder(order, orderContext?)` | Set canvas menu component order | | `cesdk.ui.insertCanvasMenuOrderComponent(matcher, component, location?, orderContext?)` | Insert component into canvas menu | | `cesdk.ui.updateCanvasMenuOrderComponent(matcher, update, orderContext?)` | Update component properties in canvas menu | | `cesdk.ui.removeCanvasMenuOrderComponent(matcher, orderContext?)` | Remove component from canvas menu | | `cesdk.ui.getCanvasBarOrder(position, orderContext?)` | Get canvas bar order for position (‘top’ or ‘bottom’) | | `cesdk.ui.setCanvasBarOrder(order, position, orderContext?)` | Set canvas bar order for position | | `cesdk.ui.getDockOrder(orderContext?)` | Get current dock component order | | `cesdk.ui.setDockOrder(order, orderContext?)` | Set dock component order | | `cesdk.ui.insertDockOrderComponent(matcher, component, location?, orderContext?)` | Insert component into dock | | `cesdk.ui.updateDockOrderComponent(matcher, update, orderContext?)` | Update component properties in dock | | `cesdk.ui.removeDockOrderComponent(matcher, orderContext?)` | Remove component from dock | | `cesdk.ui.getInspectorBarOrder(orderContext?)` | Get current inspector bar component order | | `cesdk.ui.setInspectorBarOrder(order, orderContext?)` | Set inspector bar component order | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/panel-7ce1ee) --- # Panel Customization This guide shows you how to control CE.SDK’s UI panels programmatically, allowing you to show, hide, position, and configure panels like the inspector, asset library, and settings. You’ll learn how to use the Panel API to customize panel behavior for your specific user interface requirements. ![Panel Customization example showing CE.SDK with inspector and asset library panels](/docs/cesdk/_astro/browser.hero.CkqSD-Nf_ZABSWJ.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) ``` import type { EditorPlugin, EditorPluginContext, PanelPosition} from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * Panel Customization Example * * This example demonstrates how to use CE.SDK's Panel API to: * - Show and hide panels programmatically * - Position panels (left/right) * - Make panels float or dock * - Check panel state * - Find panels by criteria * - Configure panel payloads */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 panel features through Feature API cesdk.feature.enable('ly.img.inspector', () => true); cesdk.feature.enable('ly.img.library.panel', () => true); cesdk.feature.enable('ly.img.settings', () => true); // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Configure default panel positioning cesdk.ui.setPanelPosition( '//ly.img.panel/inspector', 'left' as PanelPosition ); cesdk.ui.setPanelFloating('//ly.img.panel/inspector', false); cesdk.ui.setPanelPosition( '//ly.img.panel/assetLibrary', 'left' as PanelPosition ); // Check if a panel is open before opening if (!cesdk.ui.isPanelOpen('//ly.img.panel/inspector')) { console.log('Inspector is not open yet'); } // Open inspector panel with default settings cesdk.ui.openPanel('//ly.img.panel/inspector'); // Add an image to demonstrate replace library functionality const image = await engine.asset.defaultApplyAsset({ id: 'ly.img.cesdk.images.samples/sample.1', meta: { uri: 'https://cdn.img.ly/assets/demo/v1/ly.img.image/images/sample_1.jpg', width: 2500, height: 1667 } }); if (image) { // Position the image in the center of the page const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const imageWidth = engine.block.getWidth(image); const imageHeight = engine.block.getHeight(image); engine.block.setPositionX(image, (pageWidth - imageWidth) / 2); engine.block.setPositionY(image, (pageHeight - imageHeight) / 2); // Select the image engine.block.setSelected(image, true); // Open replace library with custom options // This panel will float and be positioned on the right cesdk.ui.openPanel('//ly.img.panel/assetLibrary.replace', { position: 'right' as PanelPosition, floating: true, closableByUser: true }); // Find all currently open panels const openPanels = cesdk.ui.findAllPanels({ open: true }); console.log('Currently open panels:', openPanels); // Find all panels on the left const leftPanels = cesdk.ui.findAllPanels({ position: 'left' as PanelPosition }); console.log('Panels on the left:', leftPanels); // Get panel position and floating state const inspectorPosition = cesdk.ui.getPanelPosition( '//ly.img.panel/inspector' ); const inspectorFloating = cesdk.ui.getPanelFloating( '//ly.img.panel/inspector' ); console.log( `Inspector is on the ${inspectorPosition} side, floating: ${inspectorFloating}` ); // Demonstrate responsive panel behavior const updatePanelLayout = () => { const isNarrowViewport = window.innerWidth < 768; // Float panels on narrow viewports cesdk.ui.setPanelFloating('//ly.img.panel/inspector', isNarrowViewport); // Adjust positioning based on available space if (!isNarrowViewport && window.innerWidth > 1200) { cesdk.ui.setPanelPosition( '//ly.img.panel/inspector', 'right' as PanelPosition ); } else if (!isNarrowViewport) { cesdk.ui.setPanelPosition( '//ly.img.panel/inspector', 'left' as PanelPosition ); } }; // Apply responsive layout updatePanelLayout(); // Update on window resize window.addEventListener('resize', updatePanelLayout); if (cesdk.ui.isPanelOpen('//ly.img.panel/assetLibrary.replace')) { cesdk.ui.closePanel('//ly.img.panel/assetLibrary.replace'); } cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { title: 'Custom Media Library', entries: ['ly.img.image', 'ly.img.video', 'ly.img.upload'] } }); // Example: Close all ly.img panels using wildcard // Uncomment to test: // setTimeout(() => { // console.log('Closing all ly.img panels...'); // cesdk.ui.closePanel('//ly.img.*'); // }, 10000); } }} export default Example; ``` This guide demonstrates CE.SDK’s Panel API through a working example that shows how to control panels programmatically, including opening, closing, positioning, and configuring panel behavior. ## Understanding CE.SDK Panels[#](#understanding-cesdk-panels) Panels are reusable UI components in CE.SDK that provide different editing and configuration capabilities. Each panel has a unique ID and can be positioned on the left or right side of the canvas (or bottom on mobile viewports). The Panel API gives you programmatic control over when and how these panels appear. ### Available Default Panels[#](#available-default-panels) CE.SDK provides several built-in panels: * **`//ly.img.panel/inspector`** - Displays properties and editing controls for the currently selected block * **`//ly.img.panel/assetLibrary`** - Main asset library panel for inserting new content into your design * **`//ly.img.panel/assetLibrary.replace`** - Replacement library for swapping the content of the selected block * **`//ly.img.panel/settings`** - Settings panel for customizing the editor during runtime These panels must be enabled through the Feature API before they can be used: ``` // Enable panel features through Feature APIcesdk.feature.enable('ly.img.inspector', () => true);cesdk.feature.enable('ly.img.library.panel', () => true);cesdk.feature.enable('ly.img.settings', () => true); ``` ## Opening and Closing Panels[#](#opening-and-closing-panels) The Panel API provides methods to open and close panels programmatically, giving you control over the user’s interface workflow. ### Opening Panels[#](#opening-panels) Use `cesdk.ui.openPanel()` to display a panel. The panel will only open if it exists, is registered, and isn’t already open: ``` // Open inspector panel with default settingscesdk.ui.openPanel('//ly.img.panel/inspector'); ``` You can override the panel’s default position and floating behavior with options: ``` // Open replace library with custom options// This panel will float and be positioned on the rightcesdk.ui.openPanel('//ly.img.panel/assetLibrary.replace', { position: 'right' as PanelPosition, floating: true, closableByUser: true}); ``` The options parameter accepts: * **`position`**: `'left'` or `'right'` - Override the default panel position * **`floating`**: `boolean` - Override whether the panel floats over the canvas * **`closableByUser`**: `boolean` - Control if users can close the panel * **`payload`**: `object` - Pass data to the panel (see Panel Payloads section) ### Closing Panels[#](#closing-panels) Use `cesdk.ui.closePanel()` to hide panels. This method supports both exact panel IDs and wildcard patterns: ``` if (cesdk.ui.isPanelOpen('//ly.img.panel/assetLibrary.replace')) { cesdk.ui.closePanel('//ly.img.panel/assetLibrary.replace');} ``` You can also use wildcard patterns to close multiple panels at once: ``` // cesdk.ui.closePanel('//ly.img.*'); ``` Wildcard patterns are useful for cleaning up multiple panels at once or closing all panels from a specific namespace. ## Checking Panel State[#](#checking-panel-state) Before opening or manipulating panels, you can check their current state using `cesdk.ui.isPanelOpen()`: ``` // Check if a panel is open before openingif (!cesdk.ui.isPanelOpen('//ly.img.panel/inspector')) { console.log('Inspector is not open yet');} ``` ## Finding Panels[#](#finding-panels) To discover all available panels or filter panels by their state, use `cesdk.ui.findAllPanels()`: ``` // Find all currently open panelsconst openPanels = cesdk.ui.findAllPanels({ open: true });console.log('Currently open panels:', openPanels); // Find all panels on the leftconst leftPanels = cesdk.ui.findAllPanels({ position: 'left' as PanelPosition});console.log('Panels on the left:', leftPanels); ``` This method is particularly useful for debugging or building custom UI that needs to reflect the current panel state. ## Positioning Panels[#](#positioning-panels) Panels can be positioned on the left or right side of the canvas. Use `cesdk.ui.setPanelPosition()` to set the default position: ``` // Position inspector on the leftcesdk.ui.setPanelPosition('//ly.img.panel/inspector', 'left'); // Position asset library on the rightcesdk.ui.setPanelPosition('//ly.img.panel/assetLibrary', 'right'); ``` You can also use a function for dynamic positioning: ``` // Position based on viewport widthcesdk.ui.setPanelPosition('//ly.img.panel/inspector', () => { const viewportWidth = window.innerWidth; return viewportWidth > 1200 ? 'right' : 'left';}); ``` To get the current position and floating state of a panel: ``` // Get panel position and floating stateconst inspectorPosition = cesdk.ui.getPanelPosition( '//ly.img.panel/inspector');const inspectorFloating = cesdk.ui.getPanelFloating( '//ly.img.panel/inspector');console.log( `Inspector is on the ${inspectorPosition} side, floating: ${inspectorFloating}`); ``` Note that setting the position affects both the default behavior and currently open panels, unless the panel was opened with an explicit `position` option. ## Floating Panels[#](#floating-panels) Panels can either float over the canvas (potentially obscuring content) or dock beside it. Floating is useful for compact layouts or temporary panels. ### Making Panels Float[#](#making-panels-float) Use `cesdk.ui.setPanelFloating()` to control floating behavior: ``` // Make inspector float over the canvascesdk.ui.setPanelFloating('//ly.img.panel/inspector', true); // Dock asset library beside the canvascesdk.ui.setPanelFloating('//ly.img.panel/assetLibrary', false); ``` Like positioning, you can use a function for responsive floating: ``` // Float on narrow viewports, dock on widecesdk.ui.setPanelFloating('//ly.img.panel/inspector', () => { return window.innerWidth < 768;}); ``` ### Checking Floating State[#](#checking-floating-state) To check if a panel is currently floating: ``` const isFloating = cesdk.ui.getPanelFloating('//ly.img.panel/inspector');if (isFloating) { console.log('Inspector is floating over the canvas');} else { console.log('Inspector is docked beside the canvas');} ``` ## Panel Payloads[#](#panel-payloads) Some panels accept a payload object that determines their content and behavior. The asset library panel is the most common example: ``` cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { title: 'Custom Media Library', entries: ['ly.img.image', 'ly.img.video', 'ly.img.upload'] }}); ``` The asset library payload accepts: * **`title`**: `string | string[]` - Panel title, or breadcrumb navigation if an array * **`entries`**: `string[]` - Array of asset library entry IDs to display Custom panels registered through plugins can define their own payload types. ## Common Workflows[#](#common-workflows) Here are practical examples combining Panel API methods for common use cases. ### Conditional Panel Opening[#](#conditional-panel-opening) Check if a panel is open before opening it to avoid unnecessary operations: ``` // Only open inspector if it's not already visibleif (!cesdk.ui.isPanelOpen('//ly.img.panel/inspector')) { cesdk.ui.openPanel('//ly.img.panel/inspector', { position: 'left', floating: false });} ``` ### Asset Selection with Replace Library[#](#asset-selection-with-replace-library) Open the replace library for users to swap content of the selected block: ``` // Create scene and add an imageawait cesdk.createDesignScene();await cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design' }); const engine = cesdk.engine;const image = await engine.asset.defaultApplyAsset({ id: 'ly.img.cesdk.images.samples/sample.1', meta: { uri: 'https://cdn.img.ly/assets/demo/v1/ly.img.image/images/sample_1.jpg', width: 2500, height: 1667 }}); if (image) { // Select the image engine.block.setSelected(image, true); // Open replace library if not already open if (!cesdk.ui.isPanelOpen('//ly.img.panel/assetLibrary.replace')) { cesdk.ui.openPanel('//ly.img.panel/assetLibrary.replace', { position: 'right', floating: false }); }} ``` ## Feature API Integration[#](#feature-api-integration) The Panel API works with the Feature API to control panel availability and behavior. ### Enabling Panel Features[#](#enabling-panel-features) Before using panels, enable their features: ``` // Enable inspector featurecesdk.feature.enable('ly.img.inspector', () => true); // Enable asset library featurecesdk.feature.enable('ly.img.library.panel', () => true); // Enable settings featurecesdk.feature.enable('ly.img.settings', () => true); // Check if a feature is enabledconst isInspectorEnabled = cesdk.feature.isEnabled('ly.img.inspector', { engine: cesdk.engine}); ``` ## Troubleshooting[#](#troubleshooting) ### Panel Not Opening[#](#panel-not-opening) **Problem**: Calling `openPanel()` does nothing. **Solutions**: * Verify the panel ID is correct using `findAllPanels()` * Check that the panel’s feature is enabled via Feature API * Ensure the panel isn’t already open with `isPanelOpen()` * Confirm the panel is registered and exists in the system ``` // Debug panel availabilityconst allPanels = cesdk.ui.findAllPanels();console.log('Available panels:', allPanels); const isEnabled = cesdk.feature.isEnabled('ly.img.inspector', { engine: cesdk.engine});console.log('Inspector feature enabled:', isEnabled); ``` ### Position or Floating Settings Not Applied[#](#position-or-floating-settings-not-applied) **Problem**: Panel appears in unexpected position or floating state. **Solutions**: * Check if the panel was opened with explicit `position` or `floating` options that override defaults * Call `setPanelPosition()` or `setPanelFloating()` before opening the panel * Remember that session options in `openPanel()` take precedence over default settings ``` // Set defaults firstcesdk.ui.setPanelPosition('//ly.img.panel/inspector', 'left');cesdk.ui.setPanelFloating('//ly.img.panel/inspector', false); // Then open without overriding optionscesdk.ui.openPanel('//ly.img.panel/inspector'); ``` ### Replace Library Shows Nothing[#](#replace-library-shows-nothing) **Problem**: Opening `//ly.img.panel/assetLibrary.replace` displays an empty panel. **Solutions**: * Ensure a block is selected before opening the replace library * Verify the selected block has asset replacement configured * Check that relevant asset library entries are set up in your asset sources ``` // Ensure a block is selectedconst selectedBlocks = cesdk.engine.block.findAllSelected();if (selectedBlocks.length === 0) { console.warn('No block selected - replace library will be empty');} ``` ### Panels Overlap on Mobile[#](#panels-overlap-on-mobile) **Problem**: Multiple panels overlap on narrow viewports. **Solutions**: * Close unnecessary panels before opening new ones * Use `findAllPanels({ open: true })` to check currently open panels * Make panels floating on mobile to save space * Implement responsive panel management (see Responsive Panel Layout workflow) ``` // Close all panels before opening one on mobileif (window.innerWidth < 768) { cesdk.ui.closePanel('//ly.img.*');}cesdk.ui.openPanel('//ly.img.panel/inspector', { floating: true }); ``` ## API Reference[#](#api-reference) | Method | Parameters | Returns | Purpose | | --- | --- | --- | --- | | `cesdk.ui.openPanel()` | `panelId: string` `options?: { position?, floating?, closableByUser?, payload? }` | `void` | Opens a panel with optional configuration override | | `cesdk.ui.closePanel()` | `panelId: string` | `void` | Closes panels matching ID or wildcard pattern | | `cesdk.ui.isPanelOpen()` | `panelId: string` `options?: { position?, floating?, payload? }` | `boolean` | Checks if panel is open with optional criteria matching | | `cesdk.ui.findAllPanels()` | `options?: { open?, position?, floating?, payload? }` | `string[]` | Returns panel IDs matching specified criteria | | `cesdk.ui.setPanelPosition()` | `panelId: string` `position: 'left' | 'right' | (() => PanelPosition)` | `void` | Sets default panel position | | `cesdk.ui.getPanelPosition()` | `panelId: string` | `'left' | 'right'` | Returns current panel position | | `cesdk.ui.setPanelFloating()` | `panelId: string` `floating: boolean | (() => boolean)` | `void` | Sets whether panel floats over canvas | | `cesdk.ui.getPanelFloating()` | `panelId: string` | `boolean` | Returns whether panel is floating | ## Next Steps[#](#next-steps) * [Create Custom Panels](vue/user-interface/ui-extensions/create-custom-panel-d87b83/) \- Learn how to register your own custom panels * [Asset Library](vue/import-media/asset-library-65d6c4/) \- Configure which assets appear in library panels * [Inspector Bar](vue/user-interface/customization/inspector-bar-8ca1cd/) \- Customize the inspector bar for editing properties --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/page-format-496315) --- # Page Format CE.SDK includes a built-in page format selector in the resize panel that lets users choose from predefined page sizes. You can customize which formats appear by registering custom asset sources. This is useful when offering print-ready formats, specific social media dimensions, or limiting available sizes for brand consistency. ![Page Format example showing custom page format presets in the CE.SDK resize panel](/docs/cesdk/_astro/browser.hero.asAKdKFe_Z1INmCF.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Page Format Customization Guide * * This example demonstrates: * - Creating custom page format presets * - Configuring page dimensions with different design units * - Registering custom page formats with the UI * - Setting a default page format * - Controlling orientation behavior */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'); } // Load default CE.SDK asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // Create a local asset source for custom page formats cesdk.engine.asset.addLocalSource('my-custom-formats'); // Add custom page format presets with dimensions in millimeters cesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'din-a4-portrait', label: { en: 'DIN A4 Portrait' }, meta: { default: true }, payload: { transformPreset: { type: 'FixedSize', width: 210, height: 297, designUnit: 'Millimeter' } } }); cesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'din-a4-landscape', label: { en: 'DIN A4 Landscape' }, payload: { transformPreset: { type: 'FixedSize', width: 297, height: 210, designUnit: 'Millimeter' } } }); cesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'din-a3-portrait', label: { en: 'DIN A3 Portrait' }, payload: { transformPreset: { type: 'FixedSize', width: 297, height: 420, designUnit: 'Millimeter' } } }); // Add a page format using pixel dimensions cesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'social-instagram-square', label: { en: 'Instagram Square' }, meta: { fixedOrientation: true }, payload: { transformPreset: { type: 'FixedSize', width: 1080, height: 1080, designUnit: 'Pixel' } } }); // Add a page format using inch dimensions cesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'us-letter-portrait', label: { en: 'US Letter Portrait' }, payload: { transformPreset: { type: 'FixedSize', width: 8.5, height: 11, designUnit: 'Inch' } } }); // Register custom page format source with the UI // This replaces the default page formats with only the custom ones cesdk.ui.updateAssetLibraryEntry('ly.img.pagePresets', { sourceIds: ['my-custom-formats'] }); // Intercept format application to apply to existing pages instead of creating new ones cesdk.engine.asset.registerApplyMiddleware( async (sourceId, assetResult, apply) => { // Only intercept our custom page format source if (sourceId !== 'my-custom-formats') { return apply(sourceId, assetResult); } // Get the first page const pages = cesdk.engine.scene.getPages(); if (pages.length === 0) { return apply(sourceId, assetResult); } // Apply the format to the existing page const page = pages[0]; await cesdk.engine.asset.applyToBlock(sourceId, assetResult, page); // Zoom to show the updated page await cesdk.engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); return page; } ); // Create a design scene - the default format (DIN A4 Portrait) is applied await cesdk.createDesignScene(); // Zoom to fit the page in the viewport const engine = cesdk.engine; const pages = engine.block.findByType('page'); if (pages.length > 0) { await engine.scene.zoomToBlock(pages[0], { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); } // Open the page resize panel on startup cesdk.ui.openPanel('//ly.img.panel/inspector/pageResize'); }} export default Example; ``` This guide covers how to create custom page format presets, configure dimensions using different design units, and register formats with the built-in resize panel. ## Using the Built-in Page Format UI[#](#using-the-built-in-page-format-ui) The page format selector is part of the resize panel, which users with the Creator [role](vue/concepts/editing-workflow-032d27/) can access from the document inspector. When a user selects a format, the page dimensions change accordingly. The orientation toggle allows switching between portrait and landscape unless the format has a fixed orientation. To display custom formats in this panel, you register them with the `ly.img.pagePresets` asset library entry using `updateAssetLibraryEntry`. Your custom formats then appear alongside or replace the default options. ## Adding Default Asset Sources[#](#adding-default-asset-sources) Before adding custom formats, we load the default CE.SDK asset sources to ensure the editor has access to standard assets for design mode. ``` // Load default CE.SDK asset sourcesawait cesdk.addDefaultAssetSources();await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true}); ``` ## Creating a Custom Page Format Source[#](#creating-a-custom-page-format-source) We create a local asset source to hold our custom page formats. Each format is added as an asset with a `payload.transformPreset` property that defines its dimensions. ``` // Create a local asset source for custom page formatscesdk.engine.asset.addLocalSource('my-custom-formats'); ``` ## Adding Page Format Assets[#](#adding-page-format-assets) Each page format asset requires a `payload.transformPreset` configuration with the following properties: * **`type`**: Must be `'FixedSize'` for page format presets * **`width`**: Page width in the specified design unit * **`height`**: Page height in the specified design unit * **`designUnit`**: Unit for dimensions (`'Pixel'`, `'Millimeter'`, or `'Inch'`) You can also set optional properties in the `meta` object: * **`default`**: Boolean to mark as the default format applied when creating new scenes * **`fixedOrientation`**: Boolean to prevent orientation changes in the UI ### Using Millimeter Dimensions[#](#using-millimeter-dimensions) For print formats, we specify dimensions in millimeters. Setting `default: true` on a format makes it the initial page size when creating a new scene. ``` // Add custom page format presets with dimensions in millimeterscesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'din-a4-portrait', label: { en: 'DIN A4 Portrait' }, meta: { default: true }, payload: { transformPreset: { type: 'FixedSize', width: 210, height: 297, designUnit: 'Millimeter' } }}); cesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'din-a4-landscape', label: { en: 'DIN A4 Landscape' }, payload: { transformPreset: { type: 'FixedSize', width: 297, height: 210, designUnit: 'Millimeter' } }}); cesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'din-a3-portrait', label: { en: 'DIN A3 Portrait' }, payload: { transformPreset: { type: 'FixedSize', width: 297, height: 420, designUnit: 'Millimeter' } }}); ``` ### Using Pixel Dimensions[#](#using-pixel-dimensions) For digital formats like social media, we use pixel dimensions. Setting `fixedOrientation: true` disables the orientation toggle for formats where aspect ratio should not change. ``` // Add a page format using pixel dimensionscesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'social-instagram-square', label: { en: 'Instagram Square' }, meta: { fixedOrientation: true }, payload: { transformPreset: { type: 'FixedSize', width: 1080, height: 1080, designUnit: 'Pixel' } }}); ``` ### Using Inch Dimensions[#](#using-inch-dimensions) For formats common in regions using imperial measurements, we specify dimensions in inches. ``` // Add a page format using inch dimensionscesdk.engine.asset.addAssetToSource('my-custom-formats', { id: 'us-letter-portrait', label: { en: 'US Letter Portrait' }, payload: { transformPreset: { type: 'FixedSize', width: 8.5, height: 11, designUnit: 'Inch' } }}); ``` ## Registering Custom Sources with the UI[#](#registering-custom-sources-with-the-ui) We use `updateAssetLibraryEntry` to configure which sources appear in the page format selector. The `ly.img.pagePresets` entry ID controls the resize panel’s page format UI. ``` // Register custom page format source with the UI// This replaces the default page formats with only the custom onescesdk.ui.updateAssetLibraryEntry('ly.img.pagePresets', { sourceIds: ['my-custom-formats']}); ``` By specifying only our custom source ID, we replace the default formats entirely. To keep the default formats alongside custom ones, include `'ly.img.page.presets'` in the `sourceIds` array: ``` cesdk.ui.updateAssetLibraryEntry('ly.img.pagePresets', { sourceIds: ['ly.img.page.presets', 'my-custom-formats']}); ``` ## Applying Formats to Existing Pages[#](#applying-formats-to-existing-pages) By default, applying a page format from the resize panel creates a new page with that format. To apply formats to an existing page instead, we register an apply middleware that intercepts format application. ``` // Intercept format application to apply to existing pages instead of creating new onescesdk.engine.asset.registerApplyMiddleware( async (sourceId, assetResult, apply) => { // Only intercept our custom page format source if (sourceId !== 'my-custom-formats') { return apply(sourceId, assetResult); } // Get the first page const pages = cesdk.engine.scene.getPages(); if (pages.length === 0) { return apply(sourceId, assetResult); } // Apply the format to the existing page const page = pages[0]; await cesdk.engine.asset.applyToBlock(sourceId, assetResult, page); // Zoom to show the updated page await cesdk.engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); return page; }); ``` The middleware checks if the applied asset comes from your custom format source. If so, it applies the format to the first page using `applyToBlock` instead of creating a new page. After applying, it zooms to show the updated page dimensions. ## Page Orientation[#](#page-orientation) Orientation is determined by the width and height values in the format definition. When width is greater than height, the format defaults to landscape. When height is greater than width, it defaults to portrait. Users can toggle orientation in the resize panel unless `fixedOrientation` is set to `true` in the preset. This is useful for formats like Instagram Square where the 1:1 aspect ratio should remain fixed. ## Creating the Scene[#](#creating-the-scene) After configuring the page formats, we create a design scene. The format marked with `default: true` is automatically applied. ``` // Create a design scene - the default format (DIN A4 Portrait) is appliedawait cesdk.createDesignScene(); ``` ## Opening the Resize Panel on Startup[#](#opening-the-resize-panel-on-startup) To give users immediate access to page formats when the editor loads, we open the resize panel programmatically after creating the scene. ``` // Open the page resize panel on startupcesdk.ui.openPanel('//ly.img.panel/inspector/pageResize'); ``` ## Troubleshooting[#](#troubleshooting) * **Custom formats not appearing**: Verify the source is registered with `updateAssetLibraryEntry` before creating the scene * **Default format not applied**: Ensure the asset source with the default format is loaded before scene initialization * **Orientation toggle disabled**: Check if `fixedOrientation` is set to `true` in the preset ## API Reference[#](#api-reference) | Method | Category | Description | | --- | --- | --- | | `cesdk.addDefaultAssetSources()` | CESDK | Load default CE.SDK asset sources including page presets | | `cesdk.engine.asset.addLocalSource(sourceId)` | Asset | Create a new local asset source for page formats | | `cesdk.engine.asset.addAssetToSource(sourceId, asset)` | Asset | Add a page format asset to a local source | | `cesdk.engine.asset.registerApplyMiddleware(middleware)` | Asset | Register middleware to intercept asset application | | `cesdk.engine.asset.applyToBlock(sourceId, asset, block)` | Asset | Apply an asset to a specific block | | `cesdk.ui.updateAssetLibraryEntry(id, options)` | UI | Configure which sources appear in the page format selector | | `cesdk.ui.openPanel(panelId)` | UI | Open a panel programmatically | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/navigation-bar-4e5d39) --- # Navigation Bar This guide explains how to customize the navigation bar in CE.SDK. The navigation bar is the horizontal toolbar at the top of the editor containing buttons for back navigation, undo/redo, zoom controls, and export actions. You’ll learn how to reorder, remove, and add components using the Order API, and configure action button handlers for save and export operations. ![Navigation Bar Hero](/docs/cesdk/_astro/browser.hero.B8tlNHMZ_Z1tldAc.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) Actions that affect browser navigation (e.g. going back or closing the editor), have global effects on the scene (e.g. undo/redo and zoom), or process the scene in some way (e.g. saving and exporting) should be placed in the navigation bar. ## Show or Hide the Navigation[#](#show-or-hide-the-navigation) `show: boolean` is used to show or hide the complete navigation `position: string` is used to set the location of the navigation bar to either top or bottom. ``` import CreativeEditorSDK from 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/index.js'; const config = { // license: 'YOUR_CESDK_LICENSE_KEY', userId: 'guides-user', ui: { elements: { /* ... */ navigation: { show: true, // 'false' to hide the navigation completely position: 'top', // 'top' or 'bottom' /* ... */ }, panels: { /* ... */ }, dock: { /* ... */ }, libraries: { /* ... */ }, blocks: { /* ... */ }, }, },}; CreativeEditorSDK.create('#cesdk_container', config).then(async cesdk => { // Set the editor view mode cesdk.ui.setView('default'); // Do something with the instance of CreativeEditor SDK, for example: // Populate the asset library with default / demo asset sources. cesdk.addDefaultAssetSources(); cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true, }); await cesdk.createDesignScene();}); ``` ## Configure Navigation Buttons[#](#configure-navigation-buttons) Navigation bar components are configured dynamically using the Navigation Bar Order API. The `insertNavigationBarOrderComponent` method allows you to add or insert components into the navigation bar. ### Adding Action Buttons[#](#adding-action-buttons) Use the actions dropdown component to add action buttons to the navigation bar: ``` import CreativeEditorSDK from 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/index.js'; const config = { // license: 'YOUR_CESDK_LICENSE_KEY', userId: 'guides-user', ui: { elements: { /* ... */ navigation: { show: true, // 'false' to hide the navigation completely position: 'top', // 'top' or 'bottom' }, panels: { /* ... */ }, dock: { /* ... */ }, libraries: { /* ... */ }, blocks: { /* ... */ }, }, },}; CreativeEditorSDK.create('#cesdk_container', config).then(async cesdk => { // Configure navigation bar actions after initialization cesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', 'ly.img.importScene.navigationBar', 'ly.img.exportImage.navigationBar', 'ly.img.exportPDF.navigationBar', 'ly.img.exportScene.navigationBar', ], }); // Add back button at the beginning cesdk.ui.insertNavigationBarOrderComponent( 'first', { id: 'ly.img.back.navigationBar', onClick: () => { // Handle back action window.history.back(); }, }, 'before', ); // Add close button at the end cesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.close.navigationBar', onClick: () => { // Handle close action window.close(); }, }); // Populate the asset library with default / demo asset sources. cesdk.addDefaultAssetSources(); cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true, }); await cesdk.createDesignScene();}); ``` ## Custom Call-To-Action Buttons[#](#custom-call-to-action-buttons) Custom actions are added using the Navigation Bar Order API. Add custom buttons directly to the navigation bar or inside the actions dropdown with full control over their appearance and behavior. ### Button Properties[#](#button-properties) * `key` - unique identifier for custom actions (required for `ly.img.action.navigationBar`) * `label` - text label, can be an i18n key that will be looked up in the translation table * `icon` - icon id from our ‘Essentials’ set ( [see full list here](vue/user-interface/appearance/icons-679e32/) ), or a custom icon id added via `addIconSet` * `variant` - button style: `'regular'` (default) or `'plain'` (subtle/borderless) * `color` - button color: `'accent'` (primary/highlighted) or `'danger'` (red/warning) * `onClick` - callback function with signature `() => void | Promise`. If returning a promise, a spinner shows until it resolves * `isDisabled` - boolean to disable the button * `isLoading` - boolean to show loading state ### Button Styles[#](#button-styles) Custom buttons support different visual styles through the `variant` and `color` properties: ``` // Regular variant (default) - standard button appearance{ id: 'ly.img.action.navigationBar', key: 'share', label: 'Share', icon: '@imgly/Share', variant: 'regular', onClick: () => { /* ... */ }} // Accent color - highlighted/primary appearance{ id: 'ly.img.action.navigationBar', key: 'publish', label: 'Publish', icon: '@imgly/Upload', color: 'accent', onClick: () => { /* ... */ }} // Plain variant - subtle/borderless appearance{ id: 'ly.img.action.navigationBar', key: 'preview', label: 'Preview', icon: '@imgly/EyeOpen', variant: 'plain', onClick: () => { /* ... */ }} // Danger color - red/warning appearance for destructive actions{ id: 'ly.img.action.navigationBar', key: 'reset', label: 'Reset', icon: '@imgly/Reset', color: 'danger', onClick: () => { /* ... */ }} ``` ### Adding Multiple Custom Buttons[#](#adding-multiple-custom-buttons) You can add custom buttons both as standalone items in the navigation bar and inside the actions dropdown: ``` cesdk.ui.setNavigationBarOrder([ // Back button { id: 'ly.img.back.navigationBar', onClick: () => window.history.back(), }, 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', 'ly.img.title.navigationBar', 'ly.img.spacer', 'ly.img.zoom.navigationBar', // Standalone custom buttons with different styles { id: 'ly.img.action.navigationBar', key: 'share', label: 'Share', icon: '@imgly/Share', variant: 'regular', onClick: () => { cesdk.ui.showNotification({ message: 'Sharing...', type: 'info' }); }, }, { id: 'ly.img.action.navigationBar', key: 'publish', label: 'Publish', icon: '@imgly/Upload', color: 'accent', onClick: () => { cesdk.ui.showNotification({ message: 'Publishing...', type: 'success' }); }, }, // Actions dropdown with more options { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.saveScene.navigationBar', onClick: async () => { const scene = await cesdk.engine.scene.saveToString(); console.log('Scene saved:', scene.length, 'characters'); }, }, { id: 'ly.img.exportImage.navigationBar', onClick: async () => { const { blobs } = await cesdk.utils.export({ mimeType: 'image/png' }); cesdk.utils.downloadFile(blobs[0], 'image/png'); }, }, { id: 'ly.img.action.navigationBar', key: 'print', label: 'Print', icon: '@imgly/Print', onClick: () => window.print(), }, ], },]); ``` ## Rearrange Components[#](#rearrange-components) There are 6 APIs for getting, setting, updating, removing, and inserting components in the Navigation Bar. The content of the Navigation Bar changes based on the current [edit mode](vue/concepts/edit-modes-1f5b6c/) (`'Transform'` (the default), `'Text'`, `'Crop'`, `'Trim'`, or a custom value), so all APIs accept an `orderContext` argument to specify the mode. For example usage of similar APIs, see also [Moving Existing Buttons](vue/user-interface/customization/rearrange-buttons-97022a/) or [Adding New Buttons](vue/user-interface/ui-extensions/add-new-button-74884d/) in the Guides section. Note that the Navigation Bar is also configurable using our [UI configuration](vue/user-interface/customization-72b2f8/) . ### Get the Current Order[#](#get-the-current-order) ``` getNavigationBarOrder( orderContext: OrderContext = { editMode: 'Transform' }) ``` When omitting the `orderContext` parameter, the order for the `'Transform'` edit mode is returned, e.g. ``` cesdk.ui.getNavigationBarOrder();// => [// {id: 'ly.img.back.navigationBar'},// {id: 'ly.img.undoRedo.navigationBar'},// ...// ] ``` ### Set a new Order[#](#set-a-new-order) ``` setNavigationBarOrder( navigationBarOrder: (NavigationBarComponentId | OrderComponent)[], orderContext: OrderContext = { editMode: 'Transform' }) ``` When omitting the `orderContext` parameter, the order is set for the default edit mode (`'Transform'`), e.g.: ``` // Sets the order for transform mode by defaultcesdk.ui.setNavigationBarOrder(['my.component.for.transform.mode']); ``` ### Update Components[#](#update-components) ``` updateNavigationBarOrderComponent( matcher: OrderComponentMatcher>, update: NavigationBarComponentId | Partial> | ((component: OrderComponent) => Partial>), orderContext?: OrderContext) ``` Updates existing components in the navigation bar. The matcher can be: * `'first'` or `'last'` - matches the first or last component * A number - matches the component at that index * A component ID string * A partial object describing the component to match * A function that receives the component and index, returning true if it matches The update can be: * A new component ID string * A partial object with updated properties * A function that receives the current component and returns the updated one Returns an object with the number of updated components and the updated order array. ``` // Change the save button label and style for a specific contextcesdk.ui.updateNavigationBarOrderComponent( 'ly.img.saveScene.navigationBar', component => ({ ...component, label: 'Save Draft', color: 'accent', }),); // Disable export buttons when user doesn't have export permissionscesdk.ui.updateNavigationBarOrderComponent( { id: 'ly.img.exportImage.navigationBar' }, { isDisabled: true },); // Replace the default actions with a custom action buttoncesdk.ui.updateNavigationBarOrderComponent( component => component.id === 'ly.img.actions.navigationBar', { id: 'ly.img.customAction.navigationBar', label: 'Publish', icon: '@imgly/Share', },); ``` ### Remove Components[#](#remove-components) ``` removeNavigationBarOrderComponent( matcher: OrderComponentMatcher>, orderContext?: OrderContext) ``` Removes components from the navigation bar. The matcher can be: * `'first'` or `'last'` - matches the first or last component * A number - matches the component at that index * A component ID string * A partial object describing the component to match * A function that receives the component and index, returning true if it matches Returns an object with the number of removed components and the updated order array. ``` // Remove PDF export option for mobile userscesdk.ui.removeNavigationBarOrderComponent('ly.img.exportPDF.navigationBar'); // Remove all export buttons for users without export permissionscesdk.ui.removeNavigationBarOrderComponent(component => component.id.includes('export'),); // Remove the close button in embedded modecesdk.ui.removeNavigationBarOrderComponent({ id: 'ly.img.close.navigationBar',}); ``` ### Insert Components[#](#insert-components) ``` insertNavigationBarOrderComponent( matcher: OrderComponentMatcher>, component: NavigationBarComponentId | OrderComponent, location?: InsertOrderComponentLocation, orderContext?: OrderContext) ``` Inserts new components into the navigation bar. The matcher can be: * `'first'` or `'last'` - matches the first or last component * A number - matches the component at that index (e.g., `2`) * A component ID string (e.g., `'ly.img.saveScene.navigationBar'`) * A partial object describing the component to match (e.g., `{ id: 'ly.img.saveScene.navigationBar' }`) * A function that receives the component and index, returning true if it matches The location can be: * `'before'` - Insert before the matched component * `'after'` - Insert after the matched component (default) * `'replace'` - Replace the matched component Returns the updated navigation bar order array. ``` // Add actions dropdown at the end of the navigation barcesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', 'ly.img.exportImage.navigationBar', ],}); // Add a back button at the beginningcesdk.ui.insertNavigationBarOrderComponent( 'first', { id: 'ly.img.back.navigationBar', onClick: () => window.history.back(), }, 'before',); // Insert a custom button after the save buttoncesdk.ui.insertNavigationBarOrderComponent( { id: 'ly.img.saveScene.navigationBar' }, { id: 'ly.img.custom.navigationBar', label: 'Custom Action', onClick: () => console.log('Custom action'), }, 'after',); ``` ## Navigation Bar Components[#](#navigation-bar-components) The following lists the default Navigation Bar components available within CE.SDK. ### Available Action Components[#](#available-action-components) These components can be added to the `ly.img.actions.navigationBar` container: | Component ID | Description | | --- | --- | | `ly.img.saveScene.navigationBar` | Save scene action - saves the current scene | | `ly.img.importScene.navigationBar` | Load scene action - loads a scene file | | `ly.img.exportScene.navigationBar` | Download scene action - downloads the current scene | | `ly.img.exportImage.navigationBar` | Export as image - exports the design as an image file | | `ly.img.exportPDF.navigationBar` | Export as PDF - exports the design as a PDF file | | `ly.img.exportVideo.navigationBar` | Export as video - exports the design as a video file | | `ly.img.exportArchive.navigationBar` | Create archive - creates an archive of the scene | | `ly.img.importArchive.navigationBar` | Load archive - loads an archive file | | `ly.img.shareScene.navigationBar` | Share action - custom share functionality | | `ly.img.action.navigationBar` | Custom action button (requires `key` property) | ### Layout Helpers[#](#layout-helpers) | Component ID | Description | | --- | --- | | `ly.img.separator` | Adds a vertical separator (`
` element) in the Navigation Bar. Separators follow some special layouting rules: \- If 2 or more separators end up next to each other (e.g. due to other components not rendering), **only 1** separator will be rendered. \- Separators that end up being the first or last element in the Inspector Bar will **not** be rendered. \- Separators directly adjacent _to the left side_ of a spacer (see below) will **not** be rendered. | | `ly.img.spacer` | Adds horizontal spacing in the Navigation Bar. Spacers will try to fill all available whitespace, by distributing the available space between all spacers found in the Navigation Bar. | ### Common Controls[#](#common-controls) | Component ID | Description | | --- | --- | | `ly.img.title.navigationBar` | Title: A section displaying the title of the currently opened scene file. The title displayed on the UI is set by the `config.ui.elements.navigation.title` parameter. If this parameter is not specified, the system will instead check the component’s payload. To define a payload, rather than adding the ID directly to the order, insert an object structured like this: `{ id: 'ly.img.title.navigationBar', title: 'My Scene' }` | | `ly.img.back.navigationBar` | Back button: A button used to navigate to the previous page. Note that this button is hidden by default, and can be configured using the UI Elements configuration. | | `ly.img.close.navigationBar` | Close button: A button used to close the editor. Note that this button is hidden by default, and can be configured using the UI Elements configuration. | | `ly.img.undoRedo.navigationBar` | Undo/Redo controls: Two buttons for un-doing and re-doing recent changes. | | `ly.img.pageResize.navigationBar` | Page Resize button: A button used to control the page resize panel. | | `ly.img.zoom.navigationBar` | Zoom controls: Two buttons for zooming the view in and out, separated by a third button showing the current zoom level and opening a dropdown offering common zoom operations (Auto-Fit Page, Fit Page, Fit Selection, 200% Zoom, 100% Zoom, 50% Zoom, Zoom In, Zoom Out). | | `ly.img.preview.navigationBar` | Preview button: Toggles Preview mode, which allows viewing and editing the scene like an Adopter would (e.g. with all Placeholder constraints enforced). Changes made in Preview are not saved and will be discarded when leaving Preview. | | `ly.img.actions.navigationBar` | Call To Action buttons: A dropdown container for action buttons. You can customize its children to include any combination of save, export, load, and custom action buttons. The first child appears as a prominent button in the navigation bar, while additional children appear in the dropdown menu. | ## Default Order[#](#default-order) The default order of the Navigation Bar is the following: ``` [ 'ly.img.undoRedo.navigationBar', 'ly.img.pageResize.navigationBar', 'ly.img.spacer', 'ly.img.title.navigationBar', 'ly.img.spacer', 'ly.img.zoom.navigationBar', 'ly.img.preview.navigationBar',]; ``` ## Integration with Callbacks API[#](#integration-with-callbacks-api) Action buttons in the navigation bar automatically trigger the corresponding callbacks registered with the Callbacks API when used as string IDs. You can also provide custom onClick handlers to override the default behavior. ### Using Registered Callbacks[#](#using-registered-callbacks) ``` // Register callbacks with the Callbacks APIcesdk.actions.register('saveScene', async () => { const scene = await cesdk.engine.scene.saveToString(); console.log('Scene ready to save:', scene.length, 'characters'); // Production: // await fetch('/api/scenes', { // method: 'POST', // body: JSON.stringify({ scene }), // headers: { 'Content-Type': 'application/json' }, // }); cesdk.ui.showNotification('Scene saved successfully');}); cesdk.actions.register('exportDesign', async options => { const { blobs, options: exportOptions } = await cesdk.utils.export(options); console.log('Export ready:', blobs[0].size, 'bytes'); // Production: // await uploadToCDN(blobs[0]); cesdk.ui.showNotification('Export successful');}); // Add navigation buttons that will use registered callbackscesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', // Uses registered saveScene callback 'ly.img.exportImage.navigationBar', // Uses registered exportDesign callback ],}); ``` ### Custom onClick Override[#](#custom-onclick-override) ``` // Override default callback with custom onClick handlercesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.saveScene.navigationBar', onClick: async () => { // Custom logic that overrides the registered callback const scene = await cesdk.engine.scene.saveToString(); console.log('Scene ready for custom save:', scene.length, 'characters'); // Production: // await customSaveLogic(scene); }, }, ],}); ``` ## Default Behaviors[#](#default-behaviors) The following action components trigger their corresponding callbacks when used as string IDs: | Component ID | Callback Triggered | Default Behavior | | --- | --- | --- | | `ly.img.saveScene.navigationBar` | `saveScene` | Downloads the scene as a `.scene` file to the user’s device | | `ly.img.importScene.navigationBar` | `importScene` | Opens a file picker to load a `.scene` file from the user’s device | | `ly.img.exportScene.navigationBar` | `exportScene` | Downloads the current scene as a `.scene` file | | `ly.img.exportImage.navigationBar` | `exportDesign` | Exports and downloads the design as an image file (PNG/JPEG) | | `ly.img.exportPDF.navigationBar` | `exportDesign` | Exports and downloads the design as a PDF file | | `ly.img.exportVideo.navigationBar` | `exportDesign` | Exports and downloads the design as a video file (MP4) | | `ly.img.exportArchive.navigationBar` | `exportScene` | Creates and downloads an archive of the scene with all assets | | `ly.img.importArchive.navigationBar` | `importScene` | Opens a file picker to load an archive file | | `ly.img.shareScene.navigationBar` | `shareScene` | No default behavior - requires callback registration | ## Using Default vs Custom Behaviors[#](#using-default-vs-custom-behaviors) ### Example 1: Default Behavior[#](#example-1-default-behavior) When using just the string ID without registering a custom callback, the default behavior is triggered (downloads the file to the user’s device): ``` // Using default behavior - downloads scene file to user's devicecesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', // Uses default download behavior ],}); ``` ### Example 2: Custom Behavior via Registered Callback[#](#example-2-custom-behavior-via-registered-callback) Register a custom callback to override the default behavior for all instances of the button: ``` // Register custom saveScene callbackcesdk.actions.register('saveScene', async () => { const scene = await cesdk.engine.scene.saveToString(); console.log('Scene ready to save:', scene.length, 'characters'); // Production: // await fetch('/api/scenes', { // method: 'POST', // body: JSON.stringify({ scene }), // headers: { 'Content-Type': 'application/json' }, // }); cesdk.ui.showNotification('Scene saved to cloud');}); // String ID now uses the registered callback instead of defaultcesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', // Triggers registered saveScene callback ],}); ``` ### Example 3: Custom Behavior via onClick Handler[#](#example-3-custom-behavior-via-onclick-handler) Provide a custom onClick handler directly in the component configuration to override both default and registered callbacks: ``` // Override with custom onClick handlercesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.saveScene.navigationBar', onClick: async () => { // This custom onClick overrides any registered callback const scene = await cesdk.engine.scene.saveToString(); console.log( 'Scene ready for alternative save:', scene.length, 'characters', ); // Production: // await alternativeSaveLogic(scene); cesdk.ui.showNotification('Saved with alternative method'); }, }, ],}); ``` ## API Reference[#](#api-reference) | Method | Category | Purpose | | --- | --- | --- | | `cesdk.ui.getNavigationBarOrder()` | UI Layout | Get current navigation bar component order | | `cesdk.ui.setNavigationBarOrder()` | UI Layout | Set navigation bar component order and configuration | | `cesdk.ui.insertNavigationBarOrderComponent()` | UI Layout | Insert a component relative to an existing component | | `cesdk.ui.removeNavigationBarOrderComponent()` | UI Layout | Remove a component from the navigation bar | | `cesdk.ui.updateNavigationBarOrderComponent()` | UI Layout | Update properties of an existing component | | `cesdk.ui.registerComponent()` | Component Registration | Register a custom component for use in ordering APIs | | `cesdk.feature.enable()` | Feature | Enable a feature by ID | | `cesdk.feature.disable()` | Feature | Disable a feature by ID | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/inspector-bar-8ca1cd) --- # Inspector Bar This guide shows you how to customize the inspector bar in CE.SDK, the contextual toolbar that appears above the canvas when a block is selected. You’ll learn how to control which components appear, reorder them, add custom components, and configure layouts for different edit modes. ![Inspector Bar customization showing CE.SDK with customized inspector bar controls](/docs/cesdk/_astro/browser.hero.B0hLBlRX_Z2guIBe.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * Inspector Bar Customization Example * * This example demonstrates how to use CE.SDK's Inspector Bar Order API to: * - Get and set the inspector bar component order * - Insert custom components into the inspector bar * - Remove built-in components * - Configure different orders for different edit modes * - Register custom components for the inspector 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 for this plugin'); } // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Add an image to demonstrate the inspector bar const image = await engine.asset.defaultApplyAsset({ id: 'ly.img.cesdk.images.samples/sample.1', meta: { uri: 'https://cdn.img.ly/assets/demo/v1/ly.img.image/images/sample_1.jpg', width: 2500, height: 1667 } }); if (image) { // Position the image in the center of the page const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const imageWidth = engine.block.getWidth(image); const imageHeight = engine.block.getHeight(image); engine.block.setPositionX(image, (pageWidth - imageWidth) / 2); engine.block.setPositionY(image, (pageHeight - imageHeight) / 2); // Select the image to show the inspector bar engine.block.select(image); } // Get the current inspector bar order const currentOrder = cesdk.ui.getInspectorBarOrder(); console.log('Current inspector bar order:', currentOrder); // Get the order for a specific edit mode const cropOrder = cesdk.ui.getInspectorBarOrder({ editMode: 'Crop' }); console.log('Crop mode inspector bar order:', cropOrder); // Set a completely new inspector bar order // This replaces all existing components with a simplified set cesdk.ui.setInspectorBarOrder([ 'ly.img.fill.inspectorBar', 'ly.img.separator', 'ly.img.stroke.inspectorBar', 'ly.img.separator', 'ly.img.filter.inspectorBar', 'ly.img.effect.inspectorBar', 'ly.img.spacer', 'ly.img.crop.inspectorBar' ]); // Remove specific components from the inspector bar // Remove shadow controls for a simpler UI const removeResult = cesdk.ui.removeInspectorBarOrderComponent( 'ly.img.shadow.inspectorBar' ); console.log('Removed components:', removeResult.removed); // Remove multiple components using a matcher function cesdk.ui.removeInspectorBarOrderComponent((component) => component.id.includes('blur') ); // Register a custom component for the inspector bar cesdk.ui.registerComponent( 'my.custom.quickExport.inspectorBar', ({ builder }) => { builder.Button('quick-export', { label: 'Quick Export', icon: '@imgly/icons/Download', onClick: async () => { const pages = engine.block.findByType('page'); if (pages.length > 0) { const blob = await engine.block.export(pages[0], { mimeType: 'image/png' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'export.png'; link.click(); URL.revokeObjectURL(url); } } }); } ); // Insert the custom component after the crop controls cesdk.ui.insertInspectorBarOrderComponent( 'ly.img.crop.inspectorBar', 'my.custom.quickExport.inspectorBar', 'after' ); // Insert a component at the beginning of the inspector bar cesdk.ui.insertInspectorBarOrderComponent( 'first', 'ly.img.opacityOptions.inspectorBar', 'before' ); // Update an existing component's properties // Add a custom key to identify this specific instance cesdk.ui.updateInspectorBarOrderComponent('ly.img.fill.inspectorBar', { key: 'main-fill-control' }); // Configure different inspector bar orders for different edit modes // Set a minimal order for Crop mode cesdk.ui.setInspectorBarOrder(['ly.img.cropControls.inspectorBar'], { editMode: 'Crop' }); // Set a custom order for Text editing mode cesdk.ui.setInspectorBarOrder( [ 'ly.img.text.typeFace.inspectorBar', 'ly.img.text.fontSize.inspectorBar', 'ly.img.separator', 'ly.img.text.bold.inspectorBar', 'ly.img.text.italic.inspectorBar', 'ly.img.separator', 'ly.img.text.alignHorizontal.inspectorBar', 'ly.img.spacer', 'ly.img.fill.inspectorBar' ], { editMode: 'Text' } ); // Control inspector bar visibility with view modes // The inspector bar appears in 'default' view and is hidden in 'advanced' view const currentView = cesdk.ui.getView(); console.log('Current view mode:', currentView); // Toggle between default (inspector bar visible) and advanced (inspector panel visible) // Uncomment to switch views: // cesdk.ui.setView('advanced'); // Hides inspector bar, shows inspector panel // cesdk.ui.setView('default'); // Shows inspector bar // Log the final inspector bar order const finalOrder = cesdk.ui.getInspectorBarOrder(); console.log('Final inspector bar order:', finalOrder); }} export default Example; ``` This guide demonstrates CE.SDK’s Inspector Bar Order API through a working example that shows how to customize the inspector bar programmatically, including getting and setting the component order, inserting custom components, removing built-in components, and configuring different layouts for different edit modes. The main location for block-specific functionality is the inspector bar. Any action or setting available to the user for the currently selected block that does not appear in the canvas menu should be added here. ## Show or Hide the Inspector Bar[#](#show-or-hide-the-inspector-bar) Two different views of the editor are available: * The ‘advanced’ view always shows an inspector panel to the side of the canvas. * The ‘default’ view hides the inspector panel until it is needed, and uses a minimal inspector bar on top of the canvas instead. Use `cesdk.ui.setView()` to toggle between `'advanced'` and `'default'` view after initializing the editor. ``` // Control inspector bar visibility with view modes// The inspector bar appears in 'default' view and is hidden in 'advanced' viewconst currentView = cesdk.ui.getView();console.log('Current view mode:', currentView); // Toggle between default (inspector bar visible) and advanced (inspector panel visible)// Uncomment to switch views:// cesdk.ui.setView('advanced'); // Hides inspector bar, shows inspector panel// cesdk.ui.setView('default'); // Shows inspector bar ``` ## Show or Hide Sections[#](#show-or-hide-sections) * `opacity: boolean` shows or hides the `opacity` section in the inspector ui for every block. * `transform: boolean` shows or hides the `transform` section in the inspector ui for every block. * `adjustments: boolean` shows or hides the `adjustments` section in the inspector ui for the image block. * `filters: boolean` shows or hides the `filters` section in the inspector ui for the image block. * `effects: boolean` shows or hides the `effects` section in the inspector ui for the image block. * `blur: boolean` shows or hides the `blur` section in the inspector ui for the image block. * `crop: boolean` shows or hides the `crop` section in the inspector ui for the image block. ``` import CreativeEditorSDK from 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/index.js'; const config = { // license: 'YOUR_CESDK_LICENSE_KEY', userId: 'guides-user', ui: { elements: { /* ... */ navigation: { /* ... */ }, panels: { /* ... */ }, dock: { /* ... */ }, libraries: { /* ... */ }, blocks: { opacity: false, // true or false transform: false, // true or false '//ly.img.ubq/graphic': { adjustments: true, // true or false filters: false, // true or false effects: false, // true or false blur: true, // true or false crop: false, // true or false }, /* ... */ }, }, },}; CreativeEditorSDK.create('#cesdk_container', config).then(async instance => { // Set the editor view mode instance.ui.setView('default'); // Do something with the instance of CreativeEditor SDK, for example: // Populate the asset library with default / demo asset sources. cesdk.addDefaultAssetSources(); cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true, }); await cesdk.createDesignScene();}); ``` ## Pages[#](#pages) * `manage: boolean` if `false` removes all UI elements to add/duplicate/delete pages for every role. * `format: boolean` if `false` removes all UI elements to change the format of pages for every role. * `maxDuration: number` controls the maximum allowed duration of a page, if in video mode ``` import CreativeEditorSDK from 'https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/index.js'; const config = { // license: 'YOUR_CESDK_LICENSE_KEY', userId: 'guides-user', ui: { elements: { /* ... */ navigation: { /* ... */ }, panels: { /* ... */ }, dock: { /* ... */ }, libraries: { /* ... */ }, blocks: { /* ... */ '//ly.img.ubq/page': { manage: true, format: true, maxDuration: 30 * 60, }, }, }, },}; CreativeEditorSDK.create('#cesdk_container', config).then(async instance => { // Set the editor view mode instance.ui.setView('default'); // Do something with the instance of CreativeEditor SDK, for example: // Populate the asset library with default / demo asset sources. cesdk.addDefaultAssetSources(); cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true, }); await cesdk.createDesignScene();}); ``` ## Rearrange Components[#](#rearrange-components) There are 6 APIs for getting, setting, updating, removing, and inserting components in the Inspector Bar. The content of the Inspector Bar changes based on the current [edit mode](vue/concepts/edit-modes-1f5b6c/) (`'Transform'` (the default), `'Text'`, `'Crop'`, `'Trim'`, or a custom value), so all APIs accept an `orderContext` argument to specify the mode. For example usage of these APIs, see also [Moving Existing Buttons](vue/user-interface/customization/rearrange-buttons-97022a/) in the Guides section. ### Get the Current Order[#](#get-the-current-order) Retrieve the current order of components with `cesdk.ui.getInspectorBarOrder()`. This returns an array of `OrderComponent` objects, each with an `id` and optional `key` property. ``` // Get the current inspector bar orderconst currentOrder = cesdk.ui.getInspectorBarOrder();console.log('Current inspector bar order:', currentOrder); // Get the order for a specific edit modeconst cropOrder = cesdk.ui.getInspectorBarOrder({ editMode: 'Crop' });console.log('Crop mode inspector bar order:', cropOrder); ``` When omitting the `orderContext` parameter, the order for the `'Transform'` edit mode is returned. You can pass an `orderContext` object with an `editMode` property to get the order for a specific edit mode. ### Set a new Order[#](#set-a-new-order) Replace the entire inspector bar order with `cesdk.ui.setInspectorBarOrder()`. You can pass either component ID strings or `OrderComponent` objects. ``` // Set a completely new inspector bar order// This replaces all existing components with a simplified setcesdk.ui.setInspectorBarOrder([ 'ly.img.fill.inspectorBar', 'ly.img.separator', 'ly.img.stroke.inspectorBar', 'ly.img.separator', 'ly.img.filter.inspectorBar', 'ly.img.effect.inspectorBar', 'ly.img.spacer', 'ly.img.crop.inspectorBar']); ``` This completely replaces the existing order with the specified components. When omitting the `orderContext` parameter, the order is set for the default edit mode (`'Transform'`). ### Update Components[#](#update-components) Update properties of existing components with `cesdk.ui.updateInspectorBarOrderComponent()`. The `key` property can be used to identify specific instances when the same component ID appears multiple times. ``` // Update an existing component's properties// Add a custom key to identify this specific instancecesdk.ui.updateInspectorBarOrderComponent('ly.img.fill.inspectorBar', { key: 'main-fill-control'}); ``` The matcher can be: * `'first'` or `'last'` - matches the first or last component * A number - matches the component at that index * A component ID string * A partial object describing the component to match * A function that receives the component and index, returning true if it matches The update can be a partial object with updated properties or a function that receives the current component and returns the updated properties. ### Remove Components[#](#remove-components) Remove components from the inspector bar with `cesdk.ui.removeInspectorBarOrderComponent()`. This method accepts various matchers to identify which components to remove. ``` // Remove specific components from the inspector bar// Remove shadow controls for a simpler UIconst removeResult = cesdk.ui.removeInspectorBarOrderComponent( 'ly.img.shadow.inspectorBar');console.log('Removed components:', removeResult.removed); // Remove multiple components using a matcher functioncesdk.ui.removeInspectorBarOrderComponent((component) => component.id.includes('blur')); ``` The method returns an object with a `removed` count and the updated `order` array. The matcher can be: * `'first'` or `'last'` - matches the first or last component * A number - matches the component at that index * A component ID string * A partial object describing the component to match * A function that receives the component and index, returning true if it matches ### Insert Components[#](#insert-components) Insert components at specific positions with `cesdk.ui.insertInspectorBarOrderComponent()`. ``` // Insert the custom component after the crop controlscesdk.ui.insertInspectorBarOrderComponent( 'ly.img.crop.inspectorBar', 'my.custom.quickExport.inspectorBar', 'after'); // Insert a component at the beginning of the inspector barcesdk.ui.insertInspectorBarOrderComponent( 'first', 'ly.img.opacityOptions.inspectorBar', 'before'); ``` The third parameter specifies the insertion location relative to the matched component: * `'before'` - Insert before the matched component * `'after'` - Insert after the matched component (default) * `'replace'` - Replace the matched component You can use `'first'` or `'last'` as matchers to insert at the beginning or end of the order. ### Registering Custom Components[#](#registering-custom-components) Before adding a custom component to the inspector bar, you must register it with `cesdk.ui.registerComponent()`. ``` // Register a custom component for the inspector barcesdk.ui.registerComponent( 'my.custom.quickExport.inspectorBar', ({ builder }) => { builder.Button('quick-export', { label: 'Quick Export', icon: '@imgly/icons/Download', onClick: async () => { const pages = engine.block.findByType('page'); if (pages.length > 0) { const blob = await engine.block.export(pages[0], { mimeType: 'image/png' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'export.png'; link.click(); URL.revokeObjectURL(url); } } }); }); ``` The render function receives a builder object for creating UI elements like buttons, inputs, and other controls. ### Edit Mode-Specific Configuration[#](#edit-mode-specific-configuration) Configure different inspector bar layouts for different edit modes by passing an `orderContext` with the `editMode` property. ``` // Configure different inspector bar orders for different edit modes// Set a minimal order for Crop modecesdk.ui.setInspectorBarOrder(['ly.img.cropControls.inspectorBar'], { editMode: 'Crop'}); // Set a custom order for Text editing modecesdk.ui.setInspectorBarOrder( [ 'ly.img.text.typeFace.inspectorBar', 'ly.img.text.fontSize.inspectorBar', 'ly.img.separator', 'ly.img.text.bold.inspectorBar', 'ly.img.text.italic.inspectorBar', 'ly.img.separator', 'ly.img.text.alignHorizontal.inspectorBar', 'ly.img.spacer', 'ly.img.fill.inspectorBar' ], { editMode: 'Text' }); ``` Edit modes without specific configurations fall back to the Transform mode order. ## Inspector Bar Components[#](#inspector-bar-components) The following lists the default Inspector Bar components available within CE.SDK. Take special note of the “Feature ID” column. Most components can be hidden/disabled by disabling the corresponding feature using the Feature API. Also note that many components are only rendered for the block types listed in the “Renders for” column, because their associated controls (e.g. font size) are only meaningful for specific kinds of blocks (e.g. text). ### Layout Helpers[#](#layout-helpers) | Component ID | Description | | --- | --- | | `ly.img.separator` | Adds a vertical separator (`
` element) in the Inspector Bar. Separators follow some special layouting rules: \- If 2 or more separators end up next to each other (e.g. due to other components not rendering), **only 1** separator will be rendered. \- Separators that end up being the first or last element in the Inspector Bar will **not** be rendered. \- Separators directly adjacent _to the left side_ of a spacer (see below) will **not** be rendered. | | `ly.img.spacer` | Adds horizontal spacing in the Inspector Bar. Spacers will try to fill all available whitespace, by distributing the available space between all spacers found in the Inspector Bar. | ### Common Controls[#](#common-controls) These components are useful for editing various different block types. | Component ID | Description | Feature ID | Renders for | | --- | --- | --- | --- | | `ly.img.fill.inspectorBar` | Fill controls button: Opens the Fill panel, containing controls for selecting the fill type (Color, Image, Video, Audio) , and for editing the fill. For Color, this contains the Color Picker and the Color Library. For Images, this contains a Preview card, a “Replace” button to replace the media, and a “Crop” button for opening the Crop panel. For Video, this contains a Preview card, a “Replace” button to replace the media, a “Crop” button for opening the Crop panel, a “Trim” button for opening the Trim panel, and a Volume slider. | `ly.img.fill` | All blocks except: Stickers and Cutouts | | `ly.img.combine.inspectorBar` | Combine button: Opens a dropdown offering a choice of boolean operations (Union, Subtract, Intersect, Exclude). | `ly.img.combine` | Selections containing multiple Shapes or Cutouts | | `ly.img.crop.inspectorBar` | Crop button: Enters Crop mode when pressed. See the section on Crop Mode below for components used during that mode. | `ly.img.crop` | Image, Video, Shapes with Image Fill | | `ly.img.stroke.inspectorBar` | Stroke controls button: Renders a labeled color swatch button when stroke is inactive, which opens the Color Panel when pressed. When stroke is active, renders 2 additional controls: a “Width” button opening a dropdown containing a number input to control stroke width, and a “Style” button opening a dropdown containing a selection of stroke styles (Solid, Dashed, Dashed Round, Long Dashes, Long Dashed Round, Dotted). | `ly.img.stroke` | Image, Shape, Text, Video | | `ly.img.adjustment.inspectorBar` | Adjustment button: Opens the Adjustment panel containing sliders to influence numerous image properties (Brightness, Saturation, Contrast, Gamma, Clarity, Exposure, Shadows, Highlights, Blacks, Whites, Temperature, Sharpness). | `ly.img.adjustment` | Image, Shape, Text, Video | | `ly.img.filter.inspectorBar` | Filter button: Opens the Filter panel containing a large selection of image filters, and an “Intensity” slider for the currently selected filter. | `ly.img.filter` | Image, Shape, Text, Video | | `ly.img.effect.inspectorBar` | Effect button: Opens the Effect panel containing a large selection of image effects, and various sliders controlling the properties of the selected effect. | `ly.img.effect` | Image, Shape, Text, Video | | `ly.img.blur.inspectorBar` | Blur button: Opens the Blur panel containing a selection of blur effects, and various sliders controlling the properties of the selected blur. | `ly.img.blur` | Image, Shape, Text, Video | | `ly.img.shadow.inspectorBar` | Shadow controls button: Opens the Shadow panel containing a “Color” Subinspector controlling the shadow color, an “Angle” number input controlling the shadow offset direction, a “Distance” number input controlling the shadow offset, and a “Blur” number input controlling the shadows blur intensity). | `ly.img.shadow` | All blocks expect Pages, Cutouts, and Groups | | `ly.img.position.inspectorBar` | Position button: Opens a dropdown containing a selection of position commands (Bring to Front, Bring Forward, Send Backward, Send to Back, Fixed to Front, Fixed to Back, Align Left/Centered/Right/Top/Middle/Bottom) | `ly.img.position` | Any block except: Pages. Selections containing multiple elements. | | `ly.img.options.inspectorBar` | More options button: Opens a dropdown containing an Opacity slider (if opacity is supported by the selected block), a Blend mode button opening a dropdown containing a selection of different blend modes (if blending is supported by the selected block), a “Copy Element” button to copy the element, a “Paste Element” button to insert a previously copied element, and a “Show Inspector” button that opens the Advanced UI Inspector when pressed. | `ly.img.options` | Every block | | `ly.img.inspectorToggle.inspectorBar` | Inspector Toggle Button: Opens the Advanced UI Inspector when pressed. | `ly.img.inspector.toggle` | Every block | ### Text[#](#text) These components are relevant for editing text blocks, and will only render when a text block is selected. | Component ID | Description | Feature ID | | --- | --- | --- | | `ly.img.text.typeFace.inspectorBar` | Typeface selection button: Opens a dropdown containing available typefaces. | `ly.img.text.typeface` | | `ly.img.text.fontSize.inspectorBar` | Font size controls: A labeled number input to set the font size, with a button to open a dropdown containing many preset sizes and the “Auto-Size” option. | `ly.img.text.fontSize` | | `ly.img.text.bold.inspectorBar` | Bold font toggle: Toggles the bold cut for the selected text, if available in the currently used font. | `ly.img.text.fontStyle` | | `ly.img.text.italic.inspectorBar` | Italic font toggle: Toggles the italic cut for the selected text, if available in the currently used font. | `ly.img.text.fontStyle` | | `ly.img.text.alignHorizontal.inspectorBar` | Text alignment button: Opens a dropdown containing options to align the selected text horizontally (to the left, to the right, or centered). | `ly.img.text.alignment` | ### Shape[#](#shape) These components are relevant for editing shape blocks, and will only render when a shape block is selected. | Component ID | Description | Feature ID | | --- | --- | --- | | `ly.img.shape.options.inspectorBar` | Shape options button: Opens a dropdown containing sliders to set number of sides, number of points, and inner diameter (depending on the specific shape selected). Only renders when a shape block is selected. | `ly.img.shape.options` | ### Cutout[#](#cutout) These components are relevant for editing cutouts, and will only render when a cutout block is selected. | Component ID | Description | Feature ID | | --- | --- | --- | | `ly.img.cutout.type.inspectorBar` | Cutout type button: Opens a dropdown to select the cut type (Cut, Perforated). | `ly.img.cutout` | | `ly.img.cutout.offset.inspectorBar` | Cutout offset button: Opens a dropdown containing a labeled number input to set the cutout offset. | `ly.img.cutout` | | `ly.img.cutout.smoothing.inspectorBar` | Cutout smoothing button: Opens a dropdown containing a labeled slider controlling the outline smoothing. | `ly.img.cutout` | ### Video, Audio[#](#video-audio) These components are relevant for editing video and audio. | Component ID | Description | Feature ID | Renders for | | --- | --- | --- | --- | | `ly.img.trim.inspectorBar` | Trim button: Enters Trim mode when pressed. See the section on Trim Mode below for components used during that mode. | `ly.img.trim` | Video, Audio | | `ly.img.volume.inspectorBar` | Volume control for audio and video. | `ly.img.volume` | Video, Audio | | `ly.img.audio.replace.inspectorBar` | Replace button: Opens the “Replace Audio” panel when pressed. | `ly.img.replace` | Audio | ### Groups[#](#groups) | Component ID | Description | Feature ID | Renders for | | --- | --- | --- | --- | | `ly.img.group.create.inspectorBar` | Group button: When pressed, creates a new group from all selected elements. | `ly.img.group` | Selections containing multiple elements | | `ly.img.group.ungroup.inspectorBar` | Ungroup button: When pressed, the selected group is dissolved, and all contained elements are released. | `ly.img.group` | Groups | ### Crop[#](#crop) This component only appears in Crop mode by default. Registering it for other edit modes is not meaningful. | Component ID | Description | Feature ID | | --- | --- | --- | | `ly.img.cropControls.inspectorBar` | Controls used when cropping images: These include a “Done” button to finish cropping, a “Straighten” slider for rotating the image, a “Turn” button to rotate the image by 90 degrees, “Mirror” buttons to flip the image vertically/horizontally, and a “Reset” button to reset the crop.) | `ly.img.crop` | ### Trim[#](#trim) This component only appears in Trim mode by default. Registering it for other edit modes is not meaningful. | Component ID | Description | Feature ID | | --- | --- | --- | | `ly.img.trimControls.inspectorBar` | Controls used when trimming video and audio: These include a “Play” button to preview the trimmed video, a “Duration” number input to set the trim duration, a video/audio strip visualizing the trimmed section in relation to the full media, and a “Done” button to finish trimming. | `ly.img.trim` | ## Default Order[#](#default-order) The default order of the Inspector Bar is the following: ### Transform Mode[#](#transform-mode) ``` [ 'ly.img.spacer', 'ly.img.shape.options.inspectorBar', 'ly.img.cutout.type.inspectorBar', 'ly.img.cutout.offset.inspectorBar', 'ly.img.cutout.smoothing.inspectorBar', 'ly.img.group.create.inspectorBar', 'ly.img.group.ungroup.inspectorBar', 'ly.img.audio.replace.inspectorBar', 'ly.img.separator', 'ly.img.text.typeFace.inspectorBar', 'ly.img.text.style.inspectorBar', 'ly.img.text.bold.inspectorBar', 'ly.img.text.italic.inspectorBar', 'ly.img.text.fontSize.inspectorBar', 'ly.img.text.alignHorizontal.inspectorBar', 'ly.img.text.advanced.inspectorBar', 'ly.img.combine.inspectorBar', 'ly.img.separator', 'ly.img.fill.inspectorBar', 'ly.img.trim.inspectorBar', 'ly.img.volume.inspectorBar', 'ly.img.crop.inspectorBar', 'ly.img.separator', 'ly.img.stroke.inspectorBar', 'ly.img.separator', 'ly.img.text.background.inspectorBar', 'ly.img.separator', 'ly.img.animations.inspectorBar', 'ly.img.separator', 'ly.img.adjustment.inspectorBar', 'ly.img.filter.inspectorBar', 'ly.img.effect.inspectorBar', 'ly.img.blur.inspectorBar', 'ly.img.separator', 'ly.img.shadow.inspectorBar', 'ly.img.separator', 'ly.img.opacityOptions.inspectorBar', 'ly.img.separator', 'ly.img.position.inspectorBar', 'ly.img.spacer', 'ly.img.separator', 'ly.img.inspectorToggle.inspectorBar',]; ``` ### Crop Mode[#](#crop-mode) ``` ['ly.img.cropControls.inspectorBar']; ``` ### Trim Mode[#](#trim-mode) ``` ['ly.img.trimControls.inspectorBar']; ``` ## API Reference[#](#api-reference) | Method | Parameters | Returns | Purpose | | --- | --- | --- | --- | | `cesdk.ui.getInspectorBarOrder()` | `orderContext?: { editMode: EditMode }` | `OrderComponent[]` | Get current component order | | `cesdk.ui.setInspectorBarOrder()` | `order: (string | OrderComponent)[], orderContext?: { editMode: EditMode }` | `void` | Set complete component order | | `cesdk.ui.insertInspectorBarOrderComponent()` | `matcher, component, location?, orderContext?` | `{ inserted: boolean, order: OrderComponent[] }` | Insert component at position | | `cesdk.ui.removeInspectorBarOrderComponent()` | `matcher, orderContext?` | `{ removed: number, order: OrderComponent[] }` | Remove matching components | | `cesdk.ui.updateInspectorBarOrderComponent()` | `matcher, update, orderContext?` | `{ updated: number, order: OrderComponent[] }` | Update component properties | | `cesdk.ui.registerComponent()` | `id: string | string[], renderFunction` | `void` | Register custom component | | `cesdk.ui.setView()` | `view: 'default' | 'advanced'` | `void` | Control inspector bar visibility | | `cesdk.ui.getView()` | none | `'default' | 'advanced'` | Get current view mode | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/hide-elements-fe945c) --- # Hide Elements CE.SDK provides flexible APIs to control the visibility of every UI element. You can hide elements using the Feature API (which completely removes features from the UI), ordering APIs (which control what appears in ordered lists like navigation bars), or panel management APIs (which control panel state). This guide demonstrates how to hide and show different UI elements to create custom editing experiences tailored to your application’s needs. ![Hide Elements example showing CE.SDK with various UI elements hidden](/docs/cesdk/_astro/browser.hero.B9eelvQX_215CXi.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) ## Understanding UI Element Hiding Approaches[#](#understanding-ui-element-hiding-approaches) CE.SDK provides three methods to control UI visibility: the Feature API for disabling features entirely, ordering APIs for controlling component layout, and panel management for dynamic show/hide behavior. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // ===== Hide Elements Examples ===== // Configure UI visibility before loading assets and creating scene // Show only navigation bar and inspector bar, hide everything else // Hide canvas bar and canvas menu using Feature API cesdk.feature.disable(['ly.img.canvas.bar', 'ly.img.canvas.menu']); // Hide the entire dock using Feature API cesdk.feature.disable('ly.img.dock'); // Hide UI elements using ordering APIs (alternative method) // Setting empty array removes all components cesdk.ui.setDockOrder([]); cesdk.ui.setCanvasBarOrder([], 'top'); cesdk.ui.setCanvasMenuOrder([]); // Close all panels using wildcard pattern cesdk.ui.closePanel('//ly.img.panel/*'); // Check if panels are open const inspectorPanelId = '//ly.img.panel/inspector'; const isInspectorOpen = cesdk.ui.isPanelOpen(inspectorPanelId); console.log('Inspector panel open:', isInspectorOpen); // Find all panels const allPanels = cesdk.ui.findAllPanels(); console.log('All panels:', allPanels); // Using glob patterns to control multiple features const navigationFeatures = cesdk.feature.list({ matcher: 'ly.img.navigation.*' }); console.log('Navigation features:', navigationFeatures); // Disable all navigation features at once using wildcard pattern // cesdk.feature.disable('ly.img.navigation.*'); // Check if features are enabled const isDockEnabled = cesdk.feature.isEnabled('ly.img.dock'); const isNavBarEnabled = cesdk.feature.isEnabled('ly.img.navigation.bar'); console.log('Dock enabled:', isDockEnabled); console.log('Navigation bar enabled:', isNavBarEnabled); // Hide notification toasts cesdk.feature.disable('ly.img.notifications'); // Or hide specific notification types: // cesdk.feature.disable('ly.img.notifications.undo'); // cesdk.feature.disable('ly.img.notifications.redo'); // ===== Load Assets and Create Scene ===== // Now that UI is configured, load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Add a single image to the canvas const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri); engine.block.appendChild(page, imageBlock); // Center and fit the image on the page const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const imageWidth = engine.block.getWidth(imageBlock); const imageHeight = engine.block.getHeight(imageBlock); // Calculate scale to fit image within page const scaleX = pageWidth / imageWidth; const scaleY = pageHeight / imageHeight; const scale = Math.min(scaleX, scaleY) * 0.8; // 80% of page size // Apply scale and center engine.block.setWidth(imageBlock, imageWidth * scale); engine.block.setHeight(imageBlock, imageHeight * scale); const scaledWidth = engine.block.getWidth(imageBlock); const scaledHeight = engine.block.getHeight(imageBlock); engine.block.setPositionX(imageBlock, (pageWidth - scaledWidth) / 2); engine.block.setPositionY(imageBlock, (pageHeight - scaledHeight) / 2); // Select the image to show the inspector engine.block.setSelected(imageBlock, true); console.log('Hide elements example loaded successfully!'); }} export default Example; ``` ## Hiding Elements with the Feature API[#](#hiding-elements-with-the-feature-api) The Feature API is the primary way to hide UI elements in CE.SDK. When you disable a feature, it’s completely removed from the interface. You can hide various UI components including: * **Navigation Bar** (`ly.img.navigation.bar`) - Contains buttons like back, close, undo/redo, and zoom controls * **Inspector Bar** (`ly.img.inspector.bar`) - Provides access to block properties and editing controls * **Dock** (`ly.img.dock`) - Sidebar that provides quick access to assets and tools * **Notifications** (`ly.img.notifications`) - Toast messages for actions like undo and redo Here’s how to disable and hide the dock: ``` // Hide the entire dock using Feature APIcesdk.feature.disable('ly.img.dock'); ``` And how to disable and hide canvas elements: ``` // Hide canvas bar and canvas menu using Feature APIcesdk.feature.disable(['ly.img.canvas.bar', 'ly.img.canvas.menu']); ``` By passing an array to `feature.disable()`, you can hide multiple features in a single call. The Feature API also supports glob patterns with wildcards (`*`) for batch operations, and you can check feature state with `isEnabled()` before making changes. ## Hiding Elements with Ordering APIs[#](#hiding-elements-with-ordering-apis) Ordering APIs provide an alternative method for hiding UI elements by controlling what appears in component lists. Setting an empty order array removes all components from that area. This approach doesn’t disable features - it just controls layout. ``` // Hide UI elements using ordering APIs (alternative method)// Setting empty array removes all componentscesdk.ui.setDockOrder([]);cesdk.ui.setCanvasBarOrder([], 'top');cesdk.ui.setCanvasMenuOrder([]); ``` The key difference between ordering APIs and the Feature API is that ordering APIs only affect visual layout. The underlying features remain enabled and can still be accessed programmatically. ## Managing Panel Visibility[#](#managing-panel-visibility) Panels in CE.SDK can be opened and closed dynamically using the panel management APIs. This allows you to create context-sensitive interfaces that show relevant panels based on user actions. ``` // Close all panels using wildcard patterncesdk.ui.closePanel('//ly.img.panel/*'); // Check if panels are openconst inspectorPanelId = '//ly.img.panel/inspector';const isInspectorOpen = cesdk.ui.isPanelOpen(inspectorPanelId);console.log('Inspector panel open:', isInspectorOpen); // Find all panelsconst allPanels = cesdk.ui.findAllPanels();console.log('All panels:', allPanels); ``` ## Using Glob Patterns[#](#using-glob-patterns) The Feature API supports glob patterns for batch operations. This makes it easy to enable or disable groups of related features. ``` // Using glob patterns to control multiple featuresconst navigationFeatures = cesdk.feature.list({ matcher: 'ly.img.navigation.*'});console.log('Navigation features:', navigationFeatures); ``` ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `cesdk.feature.enable(featureId)` | Enable a feature | | `cesdk.feature.disable(featureId)` | Disable a feature or array of features | | `cesdk.feature.isEnabled(featureId)` | Check if a feature is enabled | | `cesdk.feature.list({ matcher })` | List features matching a glob pattern | | `cesdk.ui.setNavigationBarOrder(order)` | Set navigation bar component order | | `cesdk.ui.setCanvasBarOrder(order, position)` | Set canvas bar component order | | `cesdk.ui.setCanvasMenuOrder(order)` | Set canvas menu component order | | `cesdk.ui.setDockOrder(order)` | Set dock component order | | `cesdk.ui.setInspectorBarOrder(order)` | Set inspector bar component order | | `cesdk.ui.openPanel(panelId)` | Open a panel | | `cesdk.ui.closePanel(panelId)` | Close a panel (supports wildcards) | | `cesdk.ui.isPanelOpen(panelId)` | Check if a panel is open | | `cesdk.ui.findAllPanels(options)` | Find all panels with optional filtering | ## Next Steps[#](#next-steps) After mastering UI element hiding, explore these related guides: * [UI Overview](vue/user-interface/overview-41101a/) \- Understanding CE.SDK’s UI architecture * [Disable or Enable Features](vue/user-interface/customization/disable-or-enable-f058e2/) \- Feature API deep dive * [Customize Dock](vue/user-interface/customization/dock-cb916c/) \- Dock customization patterns * [Customize Panels](vue/user-interface/customization/panel-7ce1ee/) \- Panel management patterns --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/force-crop-c2854e) --- # Force Crop Programmatically apply crop presets to enforce specific aspect ratios or dimensions on design blocks using the `applyForceCrop` API. ![Force Crop example showing CE.SDK with crop mode active](/docs/cesdk/_astro/browser.hero.DJZkJCoM_ZIs8PU.webp) 8 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) The `applyForceCrop` API lets you enforce specific dimensions or aspect ratios on blocks that support cropping. This is useful when building integrations that require content to match specific formats, such as Instagram portrait posts, LinkedIn profile photos, or Facebook shared images. You can control whether the crop UI appears after applying a preset through three different modes. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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; // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const page = engine.block.findByType('page')[0]; // Set page dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Add an image to demonstrate force crop const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri); engine.block.appendChild(page, imageBlock); // Position and size the image to fill the page engine.block.setWidth(imageBlock, 800); engine.block.setHeight(imageBlock, 600); engine.block.setPositionX(imageBlock, 0); engine.block.setPositionY(imageBlock, 0); // Create a custom crop preset with a fixed aspect ratio (4:5 for portrait) engine.asset.addAssetToSource('ly.img.crop.presets', { id: 'instagram-portrait', label: { en: 'Portrait Post (4:5)' }, payload: { transformPreset: { type: 'FixedAspectRatio', width: 4, height: 5, designUnit: 'Pixel' } } }); // Create a custom crop preset with fixed dimensions engine.asset.addAssetToSource('ly.img.crop.presets', { id: 'profile-photo', label: { en: 'Profile Photo (400x400)' }, payload: { transformPreset: { type: 'FixedSize', width: 400, height: 400, designUnit: 'Pixel' } } }); // Select the image block to demonstrate force crop engine.block.select(imageBlock); // Apply force crop with 'ifNeeded' mode // This will only enter crop mode if dimensions differ from target await cesdk.ui.applyForceCrop(imageBlock, { sourceId: 'ly.img.crop.presets', presetId: 'instagram-portrait', mode: 'ifNeeded' }); // If Needed mode - only enters crop mode when dimensions differ // await cesdk.ui.applyForceCrop(imageBlock, { // sourceId: 'ly.img.crop.presets', // presetId: 'instagram-portrait', // mode: 'ifNeeded' // }); // Example of silent mode - applies crop without showing UI // await cesdk.ui.applyForceCrop(imageBlock, { // sourceId: 'ly.img.crop.presets', // presetId: 'profile-photo', // mode: 'silent' // }); // Example of always mode - always enters crop mode // await cesdk.ui.applyForceCrop(imageBlock, { // sourceId: 'ly.img.crop.presets', // presetId: 'instagram-portrait', // mode: 'always' // }); console.log('Force crop example loaded successfully!'); }} export default Example; ``` This guide covers how to apply crop presets programmatically, create custom presets with fixed aspect ratios or dimensions, and control the crop mode behavior. ## Applying a Crop Preset[#](#applying-a-crop-preset) The `cesdk.ui.applyForceCrop()` method takes a block ID and an options object containing `sourceId`, `presetId`, and `mode`. The `sourceId` identifies the asset source containing crop presets, `presetId` specifies which preset to apply, and `mode` determines whether the crop UI appears. ``` // Apply force crop with 'ifNeeded' mode// This will only enter crop mode if dimensions differ from targetawait cesdk.ui.applyForceCrop(imageBlock, { sourceId: 'ly.img.crop.presets', presetId: 'instagram-portrait', mode: 'ifNeeded'}); ``` CE.SDK ships with default crop presets in the `ly.img.crop.presets` source. Common ratios like 1:1, 4:3, and 16:9 are available without additional configuration. ## Creating Custom Crop Presets[#](#creating-custom-crop-presets) You can create custom crop presets by adding assets to the `ly.img.crop.presets` source using `engine.asset.addAssetToSource()`. Two preset types are available: fixed aspect ratio and fixed size. ### Fixed Aspect Ratio Presets[#](#fixed-aspect-ratio-presets) Fixed aspect ratio presets maintain a ratio but allow flexible dimensions. Set `type: 'FixedAspectRatio'` with width and height values representing the ratio. ``` // Create a custom crop preset with a fixed aspect ratio (4:5 for portrait)engine.asset.addAssetToSource('ly.img.crop.presets', { id: 'instagram-portrait', label: { en: 'Portrait Post (4:5)' }, payload: { transformPreset: { type: 'FixedAspectRatio', width: 4, height: 5, designUnit: 'Pixel' } }}); ``` ### Fixed Size Presets[#](#fixed-size-presets) Fixed size presets enforce exact pixel dimensions. Set `type: 'FixedSize'` with specific width and height values. ``` // Create a custom crop preset with fixed dimensionsengine.asset.addAssetToSource('ly.img.crop.presets', { id: 'profile-photo', label: { en: 'Profile Photo (400x400)' }, payload: { transformPreset: { type: 'FixedSize', width: 400, height: 400, designUnit: 'Pixel' } }}); ``` ## Understanding Crop Modes[#](#understanding-crop-modes) The `mode` parameter controls how the editor responds after applying a crop preset. ### Silent Mode[#](#silent-mode) Silent mode applies the crop without any UI feedback. The editor remains in its current mode. Use this for batch operations or when cropping should be invisible to the user. ``` // Example of silent mode - applies crop without showing UI// await cesdk.ui.applyForceCrop(imageBlock, {// sourceId: 'ly.img.crop.presets',// presetId: 'profile-photo',// mode: 'silent'// }); ``` ### Always Mode[#](#always-mode) Always mode applies the crop and opens crop mode for user adjustment. Dimension inputs are hidden to prevent changing the preset. ``` // Example of always mode - always enters crop mode// await cesdk.ui.applyForceCrop(imageBlock, {// sourceId: 'ly.img.crop.presets',// presetId: 'instagram-portrait',// mode: 'always'// }); ``` ### If Needed Mode[#](#if-needed-mode) If needed mode applies the crop only if dimensions differ from the target. The crop mode opens only when changes occur. This prevents unnecessary user interaction when content already fits the required dimensions. ``` // If Needed mode - only enters crop mode when dimensions differ// await cesdk.ui.applyForceCrop(imageBlock, {// sourceId: 'ly.img.crop.presets',// presetId: 'instagram-portrait',// mode: 'ifNeeded'// }); ``` ## Applying Force Crop to Pages[#](#applying-force-crop-to-pages) When applying force crop to page blocks, the API automatically enables page resize interaction during the operation. In `silent` mode, resize interaction is disabled after completion. In `always` and `ifNeeded` modes, it remains enabled while crop mode is open. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `cesdk.ui.applyForceCrop(blockId, options)` | Apply a crop preset to a block | | `engine.asset.addAssetToSource(sourceId, asset)` | Add an asset to a source | | `engine.asset.findAllSources()` | List available asset sources | | `engine.block.supportsCrop(blockId)` | Check if block supports cropping | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/dock-cb916c) --- # Dock Customize the dock to control which asset libraries appear and how they are organized for your users. ![Dock Customization example showing a customized dock with modified libraries](/docs/cesdk/_astro/browser.hero.BX-kXOhj_ZSl0d8.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-dock-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-dock-browser) The dock is the vertical bar on the left side of the editor that provides quick access to asset libraries. You can configure dock appearance, rearrange components, and add or remove library entries programmatically using the Dock Order API. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * Dock Customization Example * * This example demonstrates how to use CE.SDK's Dock Order API to: * - Configure dock appearance (icon size, labels) * - Retrieve the current dock order * - Remove dock components (hide libraries) * - Update existing dock components * - Insert new dock components * - Use separators and spacers for visual organization * - Add custom components to the dock */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'); } // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); // Configure dock appearance with large icons cesdk.engine.editor.setSetting('dock/iconSize', 'large'); cesdk.engine.editor.setSetting('dock/hideLabels', false); // Get the current dock order to see all default components const currentOrder = cesdk.ui.getDockOrder(); console.log( 'Default dock order:', currentOrder.map((c) => c.key ?? c.id) ); // Remove stickers library from the dock cesdk.ui.removeDockOrderComponent({ key: 'ly.img.sticker' }); // Update the image library with a custom label cesdk.ui.updateDockOrderComponent( { key: 'ly.img.image' }, { label: 'Photos' } ); // Insert a custom featured library after images cesdk.ui.insertDockOrderComponent( { key: 'ly.img.image' }, { id: 'ly.img.assetLibrary.dock', key: 'custom.featured', label: 'Featured', icon: '@imgly/ShapeStar', entries: ['ly.img.sticker'] }, 'after' ); // Insert a separator before the upload library cesdk.ui.insertDockOrderComponent( { key: 'ly.img.upload' }, { id: 'ly.img.separator' }, 'before' ); // Register a custom theme toggle component cesdk.ui.registerComponent('custom.themeToggle', ({ builder }) => { const currentTheme = cesdk.ui.getTheme(); builder.Button('theme-toggle', { label: currentTheme === 'dark' ? 'Light Mode' : 'Dark Mode', icon: '@imgly/Appearance', onClick: () => { cesdk.ui.setTheme(currentTheme === 'dark' ? 'light' : 'dark'); } }); }); // Add a spacer and the theme toggle at the bottom of the dock cesdk.ui.insertDockOrderComponent('last', { id: 'ly.img.spacer' }, 'after'); cesdk.ui.insertDockOrderComponent( 'last', { id: 'custom.themeToggle' }, 'after' ); // Log the updated dock order const updatedOrder = cesdk.ui.getDockOrder(); console.log( 'Updated dock order:', updatedOrder.map((c) => c.key ?? c.id) ); }} export default Example; ``` This guide covers configuring dock appearance, retrieving the current dock order, removing and updating dock components, inserting new components, adding custom components, and using layout helpers for visual organization. ## Configuring Dock Appearance[#](#configuring-dock-appearance) You can configure dock appearance using the editor settings API. Two settings control how dock buttons look: * `dock/iconSize` - Sets button icon size to `'large'` (24px) or `'normal'` (16px) * `dock/hideLabels` - Shows or hides button labels (`true` to hide, `false` to show) ``` // Configure dock appearance with large iconscesdk.engine.editor.setSetting('dock/iconSize', 'large');cesdk.engine.editor.setSetting('dock/hideLabels', false); ``` ## Retrieving the Dock Order[#](#retrieving-the-dock-order) Use `cesdk.ui.getDockOrder()` to get the current dock configuration. This returns an array of dock components, each with properties like `id`, `key`, `icon`, `label`, and `entries`. ``` // Get the current dock order to see all default componentsconst currentOrder = cesdk.ui.getDockOrder();console.log( 'Default dock order:', currentOrder.map((c) => c.key ?? c.id)); ``` The dock order changes based on the current [edit mode](vue/concepts/edit-modes-1f5b6c/) (`'Transform'` (default), `'Text'`, `'Crop'`, `'Trim'`, or a custom value). All Dock Order APIs accept an optional `orderContext` parameter to specify which mode to configure. ## Removing Dock Components[#](#removing-dock-components) Use `cesdk.ui.removeDockOrderComponent()` to hide specific libraries from the dock. The matcher parameter locates which component to remove. ``` // Remove stickers library from the dockcesdk.ui.removeDockOrderComponent({ key: 'ly.img.sticker' }); ``` Matcher options include: * `'first'` or `'last'` - matches the first or last component * A number - matches the component at that index * A partial object like `{ key: 'ly.img.sticker' }` - matches by properties * A function `(component, index) => boolean` - custom matching logic ## Updating Dock Components[#](#updating-dock-components) Use `cesdk.ui.updateDockOrderComponent()` to modify existing dock components. You can change their label, icon, entries, or other properties. ``` // Update the image library with a custom labelcesdk.ui.updateDockOrderComponent( { key: 'ly.img.image' }, { label: 'Photos' }); ``` The update parameter can be: * A partial object with the properties to update * A function that receives the current component and returns the updated values ## Inserting Dock Components[#](#inserting-dock-components) Use `cesdk.ui.insertDockOrderComponent()` to add new dock components. You specify a matcher to find the position, the new component, and optionally the location (`'before'`, `'after'`, or `'replace'`). ``` // Insert a custom featured library after imagescesdk.ui.insertDockOrderComponent( { key: 'ly.img.image' }, { id: 'ly.img.assetLibrary.dock', key: 'custom.featured', label: 'Featured', icon: '@imgly/ShapeStar', entries: ['ly.img.sticker'] }, 'after'); ``` The following example adds a separator before the upload library for visual organization: ``` // Insert a separator before the upload librarycesdk.ui.insertDockOrderComponent( { key: 'ly.img.upload' }, { id: 'ly.img.separator' }, 'before'); ``` ## Adding Custom Components[#](#adding-custom-components) You can register custom components and add them to the dock. Use `cesdk.ui.registerComponent()` to define a component with the builder pattern, then insert it into the dock order. The following example creates a theme toggle button and places it at the bottom of the dock using a spacer: ``` // Register a custom theme toggle componentcesdk.ui.registerComponent('custom.themeToggle', ({ builder }) => { const currentTheme = cesdk.ui.getTheme(); builder.Button('theme-toggle', { label: currentTheme === 'dark' ? 'Light Mode' : 'Dark Mode', icon: '@imgly/Appearance', onClick: () => { cesdk.ui.setTheme(currentTheme === 'dark' ? 'light' : 'dark'); } });}); // Add a spacer and the theme toggle at the bottom of the dockcesdk.ui.insertDockOrderComponent('last', { id: 'ly.img.spacer' }, 'after');cesdk.ui.insertDockOrderComponent( 'last', { id: 'custom.themeToggle' }, 'after'); ``` ## Setting a Complete Dock Order[#](#setting-a-complete-dock-order) For full control over the dock, use `cesdk.ui.setDockOrder()` to replace the entire dock order with a custom arrangement: ``` cesdk.ui.setDockOrder([ { id: 'ly.img.assetLibrary.dock', key: 'ly.img.image', icon: '@imgly/Image', label: 'libraries.ly.img.image.label', entries: ['ly.img.image'], },]); ``` ## Dock Components Reference[#](#dock-components-reference) ### Layout Helpers[#](#layout-helpers) | Component ID | Description | | --- | --- | | `ly.img.separator` | Adds a small space in the dock to help visual separation. Separators follow special rules: adjacent separators collapse to one, separators at dock edges are hidden, and separators above a spacer are hidden. | | `ly.img.spacer` | Adds vertical spacing in the dock. Spacers distribute available whitespace evenly. | ### Asset Library Component[#](#asset-library-component) | Component ID | Description | | --- | --- | | `ly.img.assetLibrary.dock` | A button that opens the Asset Library Panel, showing the content of one or more associated asset entries. | #### Payload Properties[#](#payload-properties) | Key | Type | Description | | --- | --- | --- | | `key` | `string` | Unique identifier within the dock. | | `label` | `string` | Button label. If a matching I18N key is found, it is localized. | | `icon` | `CustomIcon` | A URL string pointing to an SVG, a built-in icon ID (see [Icons](vue/user-interface/appearance/icons-679e32/) ), or a callback returning a URL. | | `entries` | `string[]` | Asset entries shown when the button is pressed. A single entry opens directly; multiple entries show a group overview. | ## Edit Mode Context[#](#edit-mode-context) The dock order can vary based on the current edit mode. Pass an `orderContext` parameter to configure different dock orders for Transform, Text, Crop, Trim, or custom edit modes: ``` // Set dock order for Text edit modecesdk.ui.setDockOrder(components, { editMode: 'Text' }); // Get dock order for Crop edit modeconst cropOrder = cesdk.ui.getDockOrder({ editMode: 'Crop' }); ``` ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `cesdk.ui.getDockOrder()` | Get current dock component order | | `cesdk.ui.setDockOrder()` | Set complete dock component order | | `cesdk.ui.updateDockOrderComponent()` | Update existing dock components | | `cesdk.ui.removeDockOrderComponent()` | Remove dock components | | `cesdk.ui.insertDockOrderComponent()` | Insert new dock components | | `cesdk.ui.registerComponent()` | Register custom components for dock use | | `cesdk.engine.editor.setSetting()` | Configure dock appearance settings | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/disable-or-enable-f058e2) --- # Disable or Enable Features Control which editor features are available to users using the Feature API. ![Disable or Enable Features example showing the CE.SDK editor with feature controls](/docs/cesdk/_astro/browser.hero.CjO6nCqL_ZYJ1wF.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-disable-or-enable-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-disable-or-enable-browser) The Feature API provides control over the visibility and availability of editor functionality. Use it to hide delete buttons from certain users, disable crop controls based on context, or conditionally enable features based on user roles or selection state. Unlike hiding UI elements with ordering APIs, disabling features affects both the UI and underlying functionality. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * Disable or Enable Features Example * * This example demonstrates how to use CE.SDK's Feature API to: * - Enable and disable features with simple toggles * - Use glob patterns for bulk operations * - Create custom predicates based on selection * - Extend default predicates with additional conditions * - Check feature status and discover available features */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.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; // Enable delete feature with default predicate cesdk.feature.enable('ly.img.delete'); // Enable multiple features at once cesdk.feature.enable(['ly.img.duplicate', 'ly.img.group']); // Disable crop feature cesdk.feature.disable('ly.img.crop'); // Disable multiple features at once cesdk.feature.disable(['ly.img.notifications', 'ly.img.preview']); // Disable all transform features using glob pattern cesdk.feature.disable('ly.img.transform*'); // Enable all video features using glob pattern cesdk.feature.enable('ly.img.video*'); // Set feature with boolean (terminal predicate) cesdk.feature.set('ly.img.fill', true); // Set feature with custom predicate based on selection cesdk.feature.set('ly.img.duplicate', ({ engine }) => { return engine.block.findAllSelected().length > 0; }); // Extend default predicate with additional condition cesdk.feature.set('ly.img.delete', ({ defaultPredicate }) => { // Only allow delete in design mode return defaultPredicate() && engine.scene.getMode() === 'Design'; }); // Chain multiple predicates using isPreviousEnable cesdk.feature.set('ly.img.replace', ({ isPreviousEnable, engine }) => { const previousResult = isPreviousEnable(); const hasSelection = engine.block.findAllSelected().length > 0; return previousResult && hasSelection; }); // Check if a feature is enabled const isDeleteEnabled = cesdk.feature.isEnabled('ly.img.delete'); console.log('Delete feature enabled:', isDeleteEnabled); // Check if all video features are enabled (returns true only if ALL match) const allVideoEnabled = cesdk.feature.isEnabled('ly.img.video*'); console.log('All video features enabled:', allVideoEnabled); // List all registered feature IDs const allFeatures = cesdk.feature.list(); console.log('All features:', allFeatures.slice(0, 10), '...'); // List features matching a pattern const navigationFeatures = cesdk.feature.list({ matcher: 'ly.img.navigation*' }); console.log('Navigation features:', navigationFeatures); cesdk.ui.insertNavigationBarOrderComponent('last', { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'toggle-dock', label: 'Toggle Dock', onClick: () => { const enabled = cesdk.feature.isEnabled('ly.img.dock'); if (enabled) { cesdk.feature.disable('ly.img.dock'); console.log('Dock feature disabled'); } else { cesdk.feature.enable('ly.img.dock'); console.log('Dock feature enabled'); } } }, { id: 'ly.img.action.navigationBar', key: 'toggle-crop', label: 'Toggle Crop Features', icon: '@imgly/Crop', onClick: () => { const enabled = cesdk.feature.isEnabled('ly.img.crop'); if (enabled) { cesdk.feature.disable('ly.img.crop*'); console.log('All crop features disabled'); } else { cesdk.feature.enable('ly.img.crop*'); console.log('All crop features enabled'); } } }, { id: 'ly.img.action.navigationBar', key: 'log-status', label: 'Log Feature Status', icon: '@imgly/Info', onClick: () => { console.log('=== Feature Status ==='); console.log('Dock:', cesdk.feature.isEnabled('ly.img.dock')); console.log('Duplicate:', cesdk.feature.isEnabled('ly.img.duplicate')); console.log('Crop:', cesdk.feature.isEnabled('ly.img.crop')); console.log('Fill:', cesdk.feature.isEnabled('ly.img.fill')); console.log('Navigation features:', cesdk.feature.list({ matcher: 'ly.img.navigation*' })); } } ] }); const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setFill(page, gradientFill); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.99, g: 0.98, b: 0.97, a: 1 }, stop: 0 }, { color: { r: 0.97, g: 0.96, b: 0.94, a: 1 }, stop: 1 } ]); 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); const titleBlock = engine.block.create('text'); engine.block.appendChild(page, titleBlock); engine.block.replaceText(titleBlock, 'Disable or Enable Features'); engine.block.setTextFontSize(titleBlock, 24); engine.block.setTextColor(titleBlock, { r: 0.25, g: 0.22, b: 0.20, a: 1 }); engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(titleBlock, pageWidth * 0.8); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.setPositionX(titleBlock, pageWidth * 0.1); engine.block.setPositionY(titleBlock, pageHeight * 0.40); const subtitleBlock = engine.block.create('text'); engine.block.appendChild(page, subtitleBlock); engine.block.replaceText(subtitleBlock, 'IMG.LY'); engine.block.setTextFontSize(subtitleBlock, 12); engine.block.setTextColor(subtitleBlock, { r: 0.65, g: 0.45, b: 0.40, a: 1 }); engine.block.setEnum(subtitleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(subtitleBlock, pageWidth * 0.8); engine.block.setHeightMode(subtitleBlock, 'Auto'); engine.block.setPositionX(subtitleBlock, pageWidth * 0.1); engine.block.setPositionY(subtitleBlock, pageHeight * 0.52); engine.block.setSelected(titleBlock, true); console.log('Disable or Enable Features example loaded successfully!'); }} export default Example; ``` This guide covers how to enable and disable features with simple toggles, create custom predicates for conditional feature access, use glob patterns for bulk operations, and debug feature configurations. ## Feature API Methods[#](#feature-api-methods) The following table summarizes the main Feature API methods and when to use each: | Method | Use Case | | --- | --- | | `cesdk.feature.enable()` | Enable features with their default predicates | | `cesdk.feature.disable()` | Disable features (hide from UI) | | `cesdk.feature.set()` | Set features with custom predicates or boolean values | | `cesdk.feature.isEnabled()` | Check if a feature is currently enabled | | `cesdk.feature.list()` | Discover registered feature IDs | | `cesdk.feature.get()` | Get predicate chain for debugging | ## Enable Features[#](#enable-features) Use `cesdk.feature.enable()` to activate a feature with its default predicate behavior. The method accepts a single feature ID, an array of IDs, or a glob pattern. ``` // Enable delete feature with default predicatecesdk.feature.enable('ly.img.delete'); ``` You can enable multiple features at once by passing an array: ``` // Enable multiple features at oncecesdk.feature.enable(['ly.img.duplicate', 'ly.img.group']); ``` Glob patterns allow you to enable all features matching a pattern. The `*` wildcard matches any sequence of characters: ``` // Enable all video features using glob patterncesdk.feature.enable('ly.img.video*'); ``` ## Disable Features[#](#disable-features) Use `cesdk.feature.disable()` to hide features from the UI. Like `enable()`, it accepts single IDs, arrays, or glob patterns. ``` // Disable crop featurecesdk.feature.disable('ly.img.crop'); ``` Disable multiple features at once by passing an array: ``` // Disable multiple features at oncecesdk.feature.disable(['ly.img.notifications', 'ly.img.preview']); ``` Use glob patterns to disable all features matching a pattern: ``` // Disable all transform features using glob patterncesdk.feature.disable('ly.img.transform*'); ``` ## Custom Predicates[#](#custom-predicates) The `cesdk.feature.set()` method allows you to configure features with custom logic. You can pass a boolean value or a function predicate. ### Boolean Predicates[#](#boolean-predicates) Passing `true` or `false` creates a terminal predicate that overrides any `enable()` or `disable()` calls: ``` // Set feature with boolean (terminal predicate)cesdk.feature.set('ly.img.fill', true); ``` Boolean predicates are terminal. Once you use `set()` with a boolean, subsequent `enable()` or `disable()` calls won’t affect that feature because the boolean predicate evaluates first. ### Function Predicates[#](#function-predicates) Function predicates receive a context object with `engine`, `isPreviousEnable()`, and `defaultPredicate()`. Use them for dynamic conditions based on selection or other state: ``` // Set feature with custom predicate based on selectioncesdk.feature.set('ly.img.duplicate', ({ engine }) => { return engine.block.findAllSelected().length > 0;}); ``` This predicate enables the duplicate feature only when at least one block is selected. ### Extending Default Behavior[#](#extending-default-behavior) You can build on a feature’s default predicate using `defaultPredicate()`. This lets you add conditions while preserving the built-in logic: ``` // Extend default predicate with additional conditioncesdk.feature.set('ly.img.delete', ({ defaultPredicate }) => { // Only allow delete in design mode return defaultPredicate() && engine.scene.getMode() === 'Design';}); ``` ### Layering Conditions[#](#layering-conditions) Use `isPreviousEnable()` to chain with previously registered predicates. This enables layered conditions from multiple `set()` calls: ``` // Chain multiple predicates using isPreviousEnablecesdk.feature.set('ly.img.replace', ({ isPreviousEnable, engine }) => { const previousResult = isPreviousEnable(); const hasSelection = engine.block.findAllSelected().length > 0; return previousResult && hasSelection;}); ``` ## Evaluation Order[#](#evaluation-order) When multiple predicates are registered for a feature, they evaluate in this order: 1. Most recent `set()` predicates run first 2. Older `set()` predicates in reverse chronological order 3. `enable()`/`disable()` state runs last Boolean predicates are terminal and immediately return their value without continuing the chain. Function predicates control whether to continue by calling `isPreviousEnable()` or returning directly. ## Glob Patterns[#](#glob-patterns) All main Feature API methods support glob pattern matching for bulk operations. The `*` wildcard matches any sequence of characters within a segment. Supported methods: * `cesdk.feature.enable('ly.img.video.*')` - Enable all video features * `cesdk.feature.disable('ly.img.crop.*')` - Disable all crop features * `cesdk.feature.set('ly.img.navigation.*', predicate)` - Set all navigation features * `cesdk.feature.isEnabled('ly.img.video.*')` - Check if ALL matching features are enabled * `cesdk.feature.list({ matcher: 'ly.img.video.*' })` - List matching features ## Check Feature Status[#](#check-feature-status) Use `cesdk.feature.isEnabled()` to query if a feature is currently enabled: ``` // Check if a feature is enabledconst isDeleteEnabled = cesdk.feature.isEnabled('ly.img.delete');console.log('Delete feature enabled:', isDeleteEnabled); ``` When using a glob pattern with `isEnabled()`, it returns `true` only if all matching features are enabled: ``` // Check if all video features are enabled (returns true only if ALL match)const allVideoEnabled = cesdk.feature.isEnabled('ly.img.video*');console.log('All video features enabled:', allVideoEnabled); ``` ## Discover Features[#](#discover-features) Use `cesdk.feature.list()` to get all registered feature IDs. You can filter with the optional `matcher` parameter: ``` // List all registered feature IDsconst allFeatures = cesdk.feature.list();console.log('All features:', allFeatures.slice(0, 10), '...'); ``` Filter the list with a glob pattern: ``` // List features matching a patternconst navigationFeatures = cesdk.feature.list({ matcher: 'ly.img.navigation*'});console.log('Navigation features:', navigationFeatures); ``` ## Built-in Features[#](#built-in-features) CE.SDK includes many built-in features organized by category: ### Navigation Features[#](#navigation-features) | Feature ID | Description | | --- | --- | | `ly.img.navigation.bar` | Controls visibility of the Navigation Bar | | `ly.img.navigation.back` | Controls visibility of the “Back” button | | `ly.img.navigation.close` | Controls visibility of the “Close” button | | `ly.img.navigation.undoRedo` | Controls visibility of “Undo” and “Redo” buttons | | `ly.img.navigation.zoom` | Controls visibility of zoom controls | | `ly.img.navigation.actions` | Controls visibility of navigation actions | ### Inspector Features[#](#inspector-features) | Feature ID | Description | | --- | --- | | `ly.img.inspector.bar` | Controls visibility of the Inspector Bar | | `ly.img.inspector.panel` | Controls visibility of the Advanced Inspector | | `ly.img.inspector.toggle` | Controls presence of the Inspector Toggle button | ### Canvas Features[#](#canvas-features) | Feature ID | Description | | --- | --- | | `ly.img.canvas.bar` | Controls visibility of the Canvas Bar | | `ly.img.canvas.menu` | Controls visibility of the Canvas Menu | ### Editing Features[#](#editing-features) | Feature ID | Description | | --- | --- | | `ly.img.delete` | Controls ability to delete blocks | | `ly.img.duplicate` | Controls ability to duplicate blocks | | `ly.img.replace` | Controls presence of the Replace button | | `ly.img.group` | Controls grouping functionality | | `ly.img.placeholder` | Controls Placeholder button visibility | ### Video Features[#](#video-features) | Feature ID | Description | | --- | --- | | `ly.img.video.timeline` | Controls visibility of the Video Timeline | | `ly.img.video.clips` | Controls visibility of video clips track | | `ly.img.video.overlays` | Controls visibility of overlays track | | `ly.img.video.audio` | Controls visibility of audio track | | `ly.img.video.addClip` | Controls ability to add clips | | `ly.img.video.controls.*` | Controls various video controls | ### Text Features[#](#text-features) | Feature ID | Description | | --- | --- | | `ly.img.text.edit` | Controls presence of the Edit button | | `ly.img.text.typeface` | Controls typeface dropdown | | `ly.img.text.fontSize` | Controls font size input | | `ly.img.text.fontStyle` | Controls bold/italic toggles | | `ly.img.text.alignment` | Controls text alignment | | `ly.img.text.advanced` | Controls advanced text options | ### Effects Features[#](#effects-features) | Feature ID | Description | | --- | --- | | `ly.img.fill` | Controls Fill button | | `ly.img.stroke` | Controls Stroke controls | | `ly.img.adjustment` | Controls Adjustments button | | `ly.img.filter` | Controls Filter button | | `ly.img.effect` | Controls Effect button | | `ly.img.blur` | Controls Blur button | | `ly.img.shadow` | Controls Shadow button | | `ly.img.crop` | Controls Crop button | ### Transform Features[#](#transform-features) | Feature ID | Description | | --- | --- | | `ly.img.transform.position` | Controls X/Y position controls | | `ly.img.transform.size` | Controls width/height controls | | `ly.img.transform.rotation` | Controls rotation controls | | `ly.img.transform.flip` | Controls flip controls | ### Other Features[#](#other-features) | Feature ID | Description | | --- | --- | | `ly.img.dock` | Controls visibility of the Dock | | `ly.img.preview` | Controls Preview button | | `ly.img.page.add` | Controls Add Page button | | `ly.img.page.move` | Controls page move buttons | | `ly.img.page.resize` | Controls Resize button | | `ly.img.notifications` | Controls notification toasts | | `ly.img.notifications.undo` | Controls undo notifications | | `ly.img.notifications.redo` | Controls redo notifications | ## Troubleshooting[#](#troubleshooting) ### Feature Not Visible[#](#feature-not-visible) If a feature doesn’t appear in the UI after calling `enable()`: 1. Check if a `set()` call with a boolean is overriding it. Boolean predicates are terminal and take precedence. 2. Verify the feature ID spelling matches exactly. 3. Confirm the feature is relevant for the current context (e.g., video features only appear in Video mode). ### `disable()` Not Working[#](#disable-not-working) If `disable()` doesn’t hide a feature: 1. Check if a `set()` predicate exists for that feature. The `set()` predicates evaluate before `disable()`. 2. Use `cesdk.feature.get()` to inspect the predicate chain. ### Glob Pattern Not Matching[#](#glob-pattern-not-matching) If a glob pattern doesn’t affect expected features: 1. Verify the pattern syntax is correct. 2. Use `cesdk.feature.list({ matcher: 'your.pattern.*' })` to see which features match. 3. Check that features are registered before applying the pattern. ## API Reference[#](#api-reference) | Method | Signature | Purpose | | --- | --- | --- | | `cesdk.feature.enable()` | `enable(featureId: FeatureId | FeatureId[]): void` | Enable features with default predicates | | `cesdk.feature.disable()` | `disable(featureId: FeatureId | FeatureId[]): void` | Disable features | | `cesdk.feature.set()` | `set(featureId: FeatureId, enabled: boolean | FeaturePredicate): void` | Set feature state with custom predicates | | `cesdk.feature.isEnabled()` | `isEnabled(featureId: FeatureId, context?: FeatureContext): boolean` | Check if feature is enabled | | `cesdk.feature.list()` | `list(options?: { matcher?: string }): FeatureId[]` | List registered feature IDs | | `cesdk.feature.get()` | `get(featureId: FeatureId): FeaturePredicate[] | undefined` | Get predicate chain for debugging | ## Next Steps[#](#next-steps) * [Hide Elements](vue/user-interface/customization/hide-elements-fe945c/) \- Hide UI elements without disabling functionality * [Navigation Bar](vue/user-interface/customization/navigation-bar-4e5d39/) \- Customize navigation bar buttons * [Canvas Menu](vue/user-interface/customization/canvas-menu-0d2b5b/) \- Customize the canvas context menu * [Inspector Bar](vue/user-interface/customization/inspector-bar-8ca1cd/) \- Customize the inspector bar --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/crop-presets-f94f26) --- # Crop Presets Customize crop presets to provide users with aspect ratio options tailored to your application’s needs. ![Crop presets example showing custom aspect ratio options in the crop interface](/docs/cesdk/_astro/browser.hero.D-GVuY3c_ZlNF3v.webp) 5 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-crop-presets)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-crop-presets) Crop presets define the aspect ratios and dimensions users can select when cropping images or pages. CE.SDK includes a default set of common ratios (1:1, 16:9, 4:3, etc.), and you can replace or extend these with custom presets through asset sources. ``` import CreativeEditorSDK from '@cesdk/cesdk-js'; const config = { // license: import.meta.env.VITE_CESDK_LICENSE, userId: 'guides-user', // baseURL: `https://cdn.img.ly/packages/imgly/cesdk-js/${CreativeEditorSDK.version}/assets`, // Use local assets when developing with local packages ...(import.meta.env.CESDK_USE_LOCAL && { baseURL: '/assets/' }), ui: { stylesheets: { /* ... */ }, elements: { /* ... */ } }}; CreativeEditorSDK.create('#cesdk_container', config).then(async (instance) => { // Expose for hero image capture window.cesdk = instance; // Do something with the instance of CreativeEditor SDK // Set scale using the new API instance.ui.setScale('normal'); // Populate the asset library with default / demo asset sources. instance.addDefaultAssetSources(); instance.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // Add a custom crop preset asset source. instance.engine.asset.addLocalSource('my-custom-crop-presets'); instance.engine.asset.addAssetToSource( 'my-custom-crop-presets', { id: 'aspect-ratio-free', label: { en: 'Free' }, meta: { width: 80, height: 120, thumbUri: `${window.location.protocol}//${window.location.host}/ratio-free.png` }, payload: { transformPreset: { type: 'FreeAspectRatio' } } } ); instance.engine.asset.addAssetToSource( 'my-custom-crop-presets', { id: 'aspect-ratio-16-9', label: { en: '16:9' }, meta: { width: 80, height: 120, thumbUri: `${window.location.protocol}//${window.location.host}/ratio-16-9.png` }, payload: { transformPreset: { type: 'FixedAspectRatio', width: 16, height: 9 } } } ); instance.engine.asset.addAssetToSource( 'my-custom-crop-presets', { id: 'din-a1-portrait', label: { en: 'DIN A1 Portrait' }, meta: { width: 80, height: 120, thumbUri: `${window.location.protocol}//${window.location.host}/din-a1-portrait.png` }, payload: { transformPreset: { type: 'FixedSize', width: 594, height: 841, designUnit: 'Millimeter' } } } ); // Update crop presets library entry instance.ui.updateAssetLibraryEntry('ly.img.cropPresets', { sourceIds: [ // 'ly.img.crop.presets', 'my-custom-crop-presets' ] }); await instance.createDesignScene(); // Add an image and enable crop mode to show the presets const engine = instance.engine; const page = engine.scene.getCurrentPage(); // Get page dimensions for relative sizing const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create an image block at ~50% of page size const imageBlock = engine.block.create('graphic'); engine.block.appendChild(page, imageBlock); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); const imageWidth = pageWidth * 0.5; const imageHeight = pageHeight * 0.5; engine.block.setWidth(imageBlock, imageWidth); engine.block.setHeight(imageBlock, imageHeight); // Center the image on the page engine.block.setPositionX(imageBlock, (pageWidth - imageWidth) / 2); engine.block.setPositionY(imageBlock, (pageHeight - imageHeight) / 2); 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); // Select the image and enter crop mode engine.block.select(imageBlock); engine.editor.setEditMode('Crop');}); ``` This guide covers how to use the built-in crop UI, understand default presets, create custom crop preset sources, and configure which presets appear in the interface. ## Using the Built-in Crop UI[#](#using-the-built-in-crop-ui) The crop interface displays preset options when users enter crop mode. Presets appear as selectable thumbnails with aspect ratio labels. Users click a preset to constrain the crop frame to that ratio, or select the free preset for unconstrained cropping. To enable the default crop presets, we load them using `addDefaultAssetSources()`: ``` await instance.addDefaultAssetSources(); ``` This loads the `ly.img.crop.presets` asset source, which contains common ratios including free, 1:1, 9:16, 16:9, 4:3, and others. ## Creating Custom Crop Preset Sources[#](#creating-custom-crop-preset-sources) We can define custom crop presets by [creating a local asset source](vue/import-media/from-remote-source/unsplash-8f31f0/) and adding preset assets to it. Each preset requires a `payload.transformPreset` property that defines the crop behavior. First, we create a local asset source to hold our custom presets: ``` instance.engine.asset.addLocalSource('my-custom-crop-presets'); ``` ### Free Aspect Ratio[#](#free-aspect-ratio) A free aspect ratio preset enables unconstrained cropping, allowing users to drag the crop frame sides independently. ``` { id: 'aspect-ratio-free', label: { en: 'Free' }, meta: { width: 80, height: 120, thumbUri: `${window.location.protocol}//${window.location.host}/ratio-free.png` }, payload: { transformPreset: { type: 'FreeAspectRatio' } }} ``` The `type: 'FreeAspectRatio'` setting removes aspect ratio constraints from the crop frame. ### Fixed Aspect Ratio[#](#fixed-aspect-ratio) A fixed aspect ratio preset constrains the crop frame to a specific ratio. When applied, the crop frame maintains the `width` to `height` proportion. ``` { id: 'aspect-ratio-16-9', label: { en: '16:9' }, meta: { width: 80, height: 120, thumbUri: `${window.location.protocol}//${window.location.host}/ratio-16-9.png` }, payload: { transformPreset: { type: 'FixedAspectRatio', width: 16, height: 9 } }} ``` The `width` and `height` values define the ratio (16:9 in this example), not absolute dimensions. ### Fixed Size[#](#fixed-size) A fixed size preset sets exact pixel dimensions for the crop area. This resizes the selected block to match the specified `width` and `height`. ``` { id: 'din-a1-portrait', label: { en: 'DIN A1 Portrait' }, meta: { width: 80, height: 120, thumbUri: `${window.location.protocol}//${window.location.host}/din-a1-portrait.png` }, payload: { transformPreset: { type: 'FixedSize', width: 594, height: 841, designUnit: 'Millimeter' } }} ``` The `designUnit` property specifies the measurement unit. Valid options are `'Pixel'`, `'Millimeter'`, or `'Inch'`. ## Configuring the Crop Presets Library[#](#configuring-the-crop-presets-library) We control which presets appear in the crop interface by updating the `ly.img.cropPresets` asset library entry. ### Replace Default Presets[#](#replace-default-presets) To show only custom presets, we set `sourceIds` to our custom source: ``` instance.ui.updateAssetLibraryEntry('ly.img.cropPresets', { sourceIds: ['my-custom-crop-presets']}); ``` ### Add Presets Alongside Defaults[#](#add-presets-alongside-defaults) To add custom presets while keeping the defaults, we use a callback to append our source: ``` instance.ui.updateAssetLibraryEntry('ly.img.cropPresets', { sourceIds: ({ currentIds }) => [...currentIds, 'my-custom-crop-presets']}); ``` ## Localization[#](#localization) Each preset requires a `label` object with at least an `en` key for the English label. You can add additional language keys for localization: ``` label: { en: '16:9', de: '16:9'} ``` ## Troubleshooting[#](#troubleshooting) ### Presets Not Appearing[#](#presets-not-appearing) Verify the asset source exists by checking registered sources: ``` const sources = await instance.engine.asset.findAllSources();console.log(sources); // Should include your custom source ID ``` ### Invalid Preset Type[#](#invalid-preset-type) Ensure the `type` value is one of: * `'FreeAspectRatio'` - for unconstrained cropping * `'FixedAspectRatio'` - for ratio-locked cropping * `'FixedSize'` - for exact dimension cropping ### Missing Labels[#](#missing-labels) Each preset must have a `label` object. Missing labels cause presets to display without text. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `engine.asset.addLocalSource(sourceId)` | Create a local asset source for custom presets | | `engine.asset.addAssetToSource(sourceId, asset)` | Add a crop preset asset to the source | | `engine.asset.findAllSources()` | List all registered asset sources | | `cesdk.ui.updateAssetLibraryEntry(entryId, config)` | Configure which sources appear in a library entry | | `addDefaultAssetSources()` | Load default asset sources including crop presets | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/color-palette-429fd9) --- # Color Palette CE.SDK’s color palette system allows you to replace the default colors with custom brand colors, ensuring consistent visual identity across all user-created designs. By configuring color libraries through the asset system, you can provide users with predefined color sets that match your brand guidelines, simplify color selection, and maintain design consistency. ![Color Palette example showing custom brand colors organized in the CE.SDK color picker](/docs/cesdk/_astro/browser.hero.Mt9Zjyoa_Z2sYea2.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) The CE.SDK can be configured with a series of colors that can be directly used whenever a color needs to be chosen. These color libraries need to be provided as asset sources - see our guide on [Custom Color Libraries](vue/colors/create-color-palette-7012e0/) for more details on how this is achieved. CE.SDK organizes colors using an asset-based architecture. Colors are stored as assets in asset sources, and the `ly.img.colors` asset library entry controls which color sources appear in the color picker UI. This separation between color storage (asset sources) and color display (asset library entry) provides flexibility in organizing and presenting colors to users. By default, CE.SDK includes a predefined color palette accessible through the `ly.img.colors.defaultPalette` source ID. We can replace this with custom palettes, add multiple color libraries, or combine both approaches. ## Configure Internationalization[#](#configure-internationalization) Before creating color libraries, we set up internationalized names that will appear in the color picker UI. This ensures users see readable names for each color library in their preferred language. ``` // Set up internationalized names for custom color librariescesdk.i18n.setTranslations({ en: { 'libraries.brandPrimaryColors.label': 'Brand Primary', 'libraries.brandSecondaryColors.label': 'Brand Secondary', 'libraries.brandNeutralColors.label': 'Neutrals', 'libraries.accentColors.label': 'Accent Colors' }}); ``` The translation keys follow the pattern `libraries.{sourceId}.label`, where `sourceId` matches the asset source identifier we’ll create. ## Create Custom Color Libraries[#](#create-custom-color-libraries) We organize our brand colors into logical groups by creating separate asset sources for each category. This approach makes it easy for users to navigate between different color sets. ### Brand Primary Colors[#](#brand-primary-colors) We create the primary brand color library with the main brand color and its variations: ``` // Create Brand Primary color librarycesdk.engine.asset.addLocalSource('brandPrimaryColors'); // Add brand primary colorscesdk.engine.asset.addAssetToSource('brandPrimaryColors', { id: 'brand-blue', label: { en: 'Brand Blue' }, tags: { en: ['blue', 'primary'] }, payload: { color: { colorSpace: 'sRGB', r: 0.2, g: 0.4, b: 0.8 } }}); cesdk.engine.asset.addAssetToSource('brandPrimaryColors', { id: 'brand-blue-dark', label: { en: 'Brand Blue Dark' }, tags: { en: ['blue', 'dark', 'primary'] }, payload: { color: { colorSpace: 'sRGB', r: 0.1, g: 0.2, b: 0.5 } }}); cesdk.engine.asset.addAssetToSource('brandPrimaryColors', { id: 'brand-blue-light', label: { en: 'Brand Blue Light' }, tags: { en: ['blue', 'light', 'primary'] }, payload: { color: { colorSpace: 'sRGB', r: 0.6, g: 0.7, b: 0.9 } }}); ``` Each color asset includes: * **id**: Unique identifier for the color * **label**: Display name (internationalized) * **tags**: Searchable keywords for filtering * **payload.color**: The actual color data with colorSpace and RGB values ### Brand Secondary Colors[#](#brand-secondary-colors) Next, we add secondary brand colors that complement the primary palette: ``` // Create Brand Secondary color librarycesdk.engine.asset.addLocalSource('brandSecondaryColors'); cesdk.engine.asset.addAssetToSource('brandSecondaryColors', { id: 'brand-orange', label: { en: 'Brand Orange' }, tags: { en: ['orange', 'secondary'] }, payload: { color: { colorSpace: 'sRGB', r: 1.0, g: 0.6, b: 0.0 } }}); cesdk.engine.asset.addAssetToSource('brandSecondaryColors', { id: 'brand-orange-dark', label: { en: 'Brand Orange Dark' }, tags: { en: ['orange', 'dark', 'secondary'] }, payload: { color: { colorSpace: 'sRGB', r: 0.8, g: 0.4, b: 0.0 } }}); ``` ### Neutral Colors[#](#neutral-colors) We include a set of neutral colors for backgrounds, borders, and text: ``` // Create Brand Neutral colors librarycesdk.engine.asset.addLocalSource('brandNeutralColors'); cesdk.engine.asset.addAssetToSource('brandNeutralColors', { id: 'neutral-black', label: { en: 'Black' }, tags: { en: ['black', 'neutral'] }, payload: { color: { colorSpace: 'sRGB', r: 0.0, g: 0.0, b: 0.0 } }}); cesdk.engine.asset.addAssetToSource('brandNeutralColors', { id: 'neutral-gray', label: { en: 'Gray' }, tags: { en: ['gray', 'neutral'] }, payload: { color: { colorSpace: 'sRGB', r: 0.5, g: 0.5, b: 0.5 } }}); cesdk.engine.asset.addAssetToSource('brandNeutralColors', { id: 'neutral-white', label: { en: 'White' }, tags: { en: ['white', 'neutral'] }, payload: { color: { colorSpace: 'sRGB', r: 1.0, g: 1.0, b: 1.0 } }}); ``` ### Accent Colors[#](#accent-colors) We add accent colors for special use cases: ``` // Create Accent Colors librarycesdk.engine.asset.addLocalSource('accentColors'); cesdk.engine.asset.addAssetToSource('accentColors', { id: 'accent-green', label: { en: 'Accent Green' }, tags: { en: ['green', 'accent'] }, payload: { color: { colorSpace: 'sRGB', r: 0.2, g: 0.8, b: 0.3 } }}); cesdk.engine.asset.addAssetToSource('accentColors', { id: 'accent-gold', label: { en: 'Accent Gold' }, tags: { en: ['gold', 'accent'] }, payload: { color: { colorSpace: 'sRGB', r: 0.85, g: 0.65, b: 0.13 } }}); ``` ## Configure the Color Picker[#](#configure-the-color-picker) After creating all color libraries, we configure which ones appear in the color picker UI: ``` // Configure which color libraries appear in the color picker// The order in sourceIds determines the display order in the UIcesdk.ui.updateAssetLibraryEntry('ly.img.colors', { sourceIds: [ 'brandPrimaryColors', 'brandSecondaryColors', 'brandNeutralColors', 'accentColors' // Note: 'ly.img.colors.defaultPalette' is intentionally omitted // to replace the default palette completely ]}); ``` The `sourceIds` array determines both which libraries appear and their order in the UI. By omitting `ly.img.colors.defaultPalette`, we completely replace the default palette with our custom colors. ## Color Format Reference[#](#color-format-reference) CE.SDK supports three color formats in palette assets: ### RGB Colors (sRGB)[#](#rgb-colors-srgb) ``` payload: { color: { colorSpace: 'sRGB', r: 0.2, // Red: 0.0 to 1.0 g: 0.4, // Green: 0.0 to 1.0 b: 0.8 // Blue: 0.0 to 1.0 }} ``` ### CMYK Colors[#](#cmyk-colors) ``` payload: { color: { c: 0.0, // Cyan: 0.0 to 1.0 m: 1.0, // Magenta: 0.0 to 1.0 y: 0.0, // Yellow: 0.0 to 1.0 k: 0.0, // Black: 0.0 to 1.0 tint: 1.0 // Tint: 0.0 to 1.0 }} ``` ### Spot Colors[#](#spot-colors) ``` // First define the spot colorengine.editor.setSpotColorRGB('BrandGold', 0.85, 0.65, 0.13); // Then reference it in the assetpayload: { color: { name: 'BrandGold', tint: 1.0, externalReference: '' }} ``` ## Keep the Default Palette[#](#keep-the-default-palette) If you want to keep the default palette alongside your custom colors, include it in the `sourceIds` array: ``` cesdk.ui.updateAssetLibraryEntry('ly.img.colors', { sourceIds: [ 'brandPrimaryColors', 'brandSecondaryColors', 'ly.img.colors.defaultPalette', // Include default palette 'brandNeutralColors' ]}); ``` The position in the array determines where the default palette appears relative to your custom libraries. ## Advanced Techniques[#](#advanced-techniques) ### Dynamic Color Loading[#](#dynamic-color-loading) You can load colors from an external API or configuration file: ``` async function loadBrandColors(apiUrl: string) { const response = await fetch(apiUrl); const brandColors = await response.json(); cesdk.engine.asset.addLocalSource('dynamicBrandColors'); brandColors.forEach((color: any) => { cesdk.engine.asset.addAssetToSource('dynamicBrandColors', { id: color.id, label: { en: color.name }, tags: { en: color.tags }, payload: { color: { colorSpace: 'sRGB', r: color.r, g: color.g, b: color.b } } }); }); cesdk.ui.updateAssetLibraryEntry('ly.img.colors', { sourceIds: ['dynamicBrandColors'] });} ``` ### User-Specific Palettes[#](#user-specific-palettes) Provide different color palettes based on user roles or preferences: ``` function setupUserPalette(userRole: string) { const paletteMap: Record = { designer: ['fullBrandPalette', 'extendedColors'], marketer: ['limitedBrandPalette', 'templateColors'], viewer: ['basicColors'] }; cesdk.ui.updateAssetLibraryEntry('ly.img.colors', { sourceIds: paletteMap[userRole] || ['basicColors'] });} ``` ## API Reference[#](#api-reference) | Method | Description | Parameters | Returns | | --- | --- | --- | --- | | `updateAssetLibraryEntry(id, config)` | Controls which asset sources appear in specific UI components | `id: string, config: { sourceIds: string[] }` | `void` | | `addLocalSource(sourceId)` | Creates a new local asset source | `sourceId: string` | `void` | | `addAssetToSource(sourceId, asset)` | Adds an asset to a source | `sourceId: string, asset: object` | `void` | | `setSpotColorRGB(name, r, g, b)` | Defines a spot color for use in assets | `name: string, r: number, g: number, b: number` | `void` | ## Next Steps[#](#next-steps) * Explore [Custom Color Libraries](vue/colors/create-color-palette-7012e0/) for advanced asset source patterns * Learn about [Spot Colors](vue/colors/for-print/spot-c3a150/) for print workflows * Customize the overall UI with [Theming](vue/user-interface/appearance/theming-4b0938/) --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/canvas-menu-0d2b5b) --- # Canvas Menu Customize the canvas menu - the context menu that appears when clicking elements on the canvas - to create tailored editing experiences with custom actions and context-specific controls. ![Canvas Menu example showing CE.SDK with customized canvas menu](/docs/cesdk/_astro/browser.hero.DPNLIhMF_iOJCC.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-canvas-menu-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-canvas-menu-browser) The canvas menu supports different configurations for different edit modes (Transform, Text, Crop), allowing you to show context-specific controls based on what the user is editing. CE.SDK provides five ordering APIs to get, set, remove, insert, and update components in this menu. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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; // Get the current canvas menu order for Transform mode (default) const currentOrder = cesdk.ui.getCanvasMenuOrder(); console.log('Canvas menu order:', currentOrder); // Get order for a specific edit mode const textModeOrder = cesdk.ui.getCanvasMenuOrder({ editMode: 'Text' }); console.log('Text mode order:', textModeOrder); // Remove a component from the canvas menu cesdk.ui.removeCanvasMenuOrderComponent('ly.img.placeholder.canvasMenu'); // Update an existing component to add custom properties cesdk.ui.updateCanvasMenuOrderComponent('ly.img.delete.canvasMenu', { variant: 'regular' }); // Register a custom component for the canvas menu cesdk.ui.registerComponent( 'ly.img.download.canvasMenu', ({ builder, engine }) => { const selectedBlocks = engine.block.findAllSelected(); const hasSelection = selectedBlocks.length > 0; builder.Button('download-button', { label: 'Download', icon: '@imgly/Download', isDisabled: !hasSelection, onClick: async () => { if (selectedBlocks.length === 0) return; const block = selectedBlocks[0]; const blob = await engine.block.export(block, { mimeType: 'image/png' }); await cesdk.utils.downloadFile(blob, 'image/png'); } }); } ); // Insert the custom component after duplicate cesdk.ui.insertCanvasMenuOrderComponent( 'ly.img.duplicate.canvasMenu', 'ly.img.download.canvasMenu', 'after' ); // Add a custom action to the options dropdown cesdk.ui.insertCanvasMenuOrderComponent( 'ly.img.options.canvasMenu', { id: 'ly.img.action.canvasMenu', key: 'export-jpeg', label: 'Export JPEG', onClick: async () => { const selectedBlocks = engine.block.findAllSelected(); if (selectedBlocks.length === 0) return; const block = selectedBlocks[0]; const blob = await engine.block.export(block, { mimeType: 'image/jpeg' }); await cesdk.utils.downloadFile(blob, 'image/jpeg'); } }, 'asChild' ); // Set a complete order for Text edit mode cesdk.ui.setCanvasMenuOrder( [ 'ly.img.text.color.canvasMenu', 'ly.img.text.bold.canvasMenu', 'ly.img.text.italic.canvasMenu', 'ly.img.separator', 'ly.img.delete.canvasMenu' ], { editMode: 'Text' } ); // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); }} export default Example; ``` This guide covers how to retrieve the current order, modify components, insert custom actions, and configure edit mode-specific menus. ## Retrieving the Canvas Menu Order[#](#retrieving-the-canvas-menu-order) Before making changes, inspect the current canvas menu order. The `cesdk.ui.getCanvasMenuOrder()` method returns an array of component IDs and configurations. Pass an `orderContext` parameter to get orders for specific edit modes. ``` // Get the current canvas menu order for Transform mode (default)const currentOrder = cesdk.ui.getCanvasMenuOrder();console.log('Canvas menu order:', currentOrder); // Get order for a specific edit modeconst textModeOrder = cesdk.ui.getCanvasMenuOrder({ editMode: 'Text' });console.log('Text mode order:', textModeOrder); ``` The returned order includes built-in components like `ly.img.duplicate.canvasMenu`, `ly.img.delete.canvasMenu`, and layout helpers like `ly.img.separator`. ## Removing Components[#](#removing-components) Remove components from the canvas menu using `cesdk.ui.removeCanvasMenuOrderComponent()`. The matcher can be a component ID string, index number, ‘first’, ‘last’, a partial object, or a predicate function. ``` // Remove a component from the canvas menucesdk.ui.removeCanvasMenuOrderComponent('ly.img.placeholder.canvasMenu'); ``` Removing components only affects the layout. The underlying features remain enabled and accessible through other UI elements or APIs. ## Updating Components[#](#updating-components) Modify existing components in the order using `cesdk.ui.updateCanvasMenuOrderComponent()`. The update can be a new component ID, partial properties, or an updater function that receives the current component and returns a modified version. ``` // Update an existing component to add custom propertiescesdk.ui.updateCanvasMenuOrderComponent('ly.img.delete.canvasMenu', { variant: 'regular'}); ``` This is useful for replacing default components with custom versions or adjusting component properties without removing and re-inserting. ## Inserting Components[#](#inserting-components) Add components to the canvas menu using `cesdk.ui.insertCanvasMenuOrderComponent()`. The location parameter accepts ‘before’, ‘after’ (default), ‘replace’, or ‘asChild’. Before inserting custom functionality, register a component using `cesdk.ui.registerComponent()`: ``` // Register a custom component for the canvas menucesdk.ui.registerComponent( 'ly.img.download.canvasMenu', ({ builder, engine }) => { const selectedBlocks = engine.block.findAllSelected(); const hasSelection = selectedBlocks.length > 0; builder.Button('download-button', { label: 'Download', icon: '@imgly/Download', isDisabled: !hasSelection, onClick: async () => { if (selectedBlocks.length === 0) return; const block = selectedBlocks[0]; const blob = await engine.block.export(block, { mimeType: 'image/png' }); await cesdk.utils.downloadFile(blob, 'image/png'); } }); }); ``` Then insert the registered component at the desired position: ``` // Insert the custom component after duplicatecesdk.ui.insertCanvasMenuOrderComponent( 'ly.img.duplicate.canvasMenu', 'ly.img.download.canvasMenu', 'after'); ``` To add components to the options dropdown, use the ‘asChild’ location: ``` // Add a custom action to the options dropdowncesdk.ui.insertCanvasMenuOrderComponent( 'ly.img.options.canvasMenu', { id: 'ly.img.action.canvasMenu', key: 'export-jpeg', label: 'Export JPEG', onClick: async () => { const selectedBlocks = engine.block.findAllSelected(); if (selectedBlocks.length === 0) return; const block = selectedBlocks[0]; const blob = await engine.block.export(block, { mimeType: 'image/jpeg' }); await cesdk.utils.downloadFile(blob, 'image/jpeg'); } }, 'asChild'); ``` ## Setting a Complete Order[#](#setting-a-complete-order) Replace the entire canvas menu order using `cesdk.ui.setCanvasMenuOrder()`. Pass an array of component IDs or configurations to define the complete menu structure. ``` // Set a complete order for Text edit modecesdk.ui.setCanvasMenuOrder( [ 'ly.img.text.color.canvasMenu', 'ly.img.text.bold.canvasMenu', 'ly.img.text.italic.canvasMenu', 'ly.img.separator', 'ly.img.delete.canvasMenu' ], { editMode: 'Text' }); ``` This is useful when you need full control over the menu composition rather than incremental modifications. ## Layout Helpers[#](#layout-helpers) The canvas menu supports layout helper components for visual organization. | Component ID | Description | | --- | --- | | `ly.img.separator` | Adds a vertical separator. Consecutive separators collapse to one. Separators at start/end or adjacent to spacers are hidden. | ## Edit Mode Context[#](#edit-mode-context) Different canvas menu orders can be configured for each edit mode by passing an `orderContext` parameter. Transform mode is the default, but you can also configure Text, Crop, and Trim (for video editing) modes separately. All ordering methods accept an optional `orderContext` parameter with an `editMode` property. This allows you to show relevant controls based on what the user is editing. For example, Text mode can display formatting buttons while Transform mode shows positioning and duplication controls. ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `cesdk.ui.getCanvasMenuOrder(orderContext?)` | Get current canvas menu component order | | `cesdk.ui.setCanvasMenuOrder(order, orderContext?)` | Set complete canvas menu order | | `cesdk.ui.updateCanvasMenuOrderComponent(matcher, update, orderContext?)` | Update existing component in order | | `cesdk.ui.removeCanvasMenuOrderComponent(matcher, orderContext?)` | Remove component from order | | `cesdk.ui.insertCanvasMenuOrderComponent(matcher, component, location?, orderContext?)` | Insert component at position | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/customization/canvas-632c8e) --- # Canvas Bar Customize the Canvas Bar to add, remove, or rearrange components for your specific workflow. ![Canvas Bar customization example showing a customized toolbar above the canvas](/docs/cesdk/_astro/browser.hero.CFSGNAQ3_Z1T72ey.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-canvas-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-customization-canvas-browser) The Canvas Bar is a floating toolbar that appears above the canvas, providing quick access to document-level actions like adding pages or opening settings. It can appear at the top or bottom of the canvas, with each position maintaining independent component orders. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';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'); } // Load assets and create scene first await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); // ===== Canvas Bar Customization Examples ===== // Register custom components before using them in the Canvas Bar // The component can read properties from its payload for customization cesdk.ui.registerComponent( 'ly.img.export.canvasBar', ({ builder, payload }) => { // Read custom properties from payload with defaults const label = (payload?.label as string) ?? 'Export'; const icon = (payload?.icon as string) ?? '@imgly/Download'; builder.Button('ly.img.export.canvasBar.button', { label, icon, onClick: async () => { // Use the built-in export action await cesdk.actions.run('exportDesign', { mimeType: 'image/png' }); } }); } ); cesdk.ui.registerComponent( 'ly.img.themeSwitcher.canvasBar', ({ builder }) => { // Get current theme to show appropriate icon const currentTheme = cesdk.ui.getTheme(); const isDark = currentTheme === 'dark'; builder.Button('ly.img.themeSwitcher.canvasBar.button', { label: isDark ? 'Light Mode' : 'Dark Mode', icon: isDark ? '@imgly/Sun' : '@imgly/Moon', onClick: () => { // Toggle between light and dark themes const theme = cesdk.ui.getTheme(); cesdk.ui.setTheme(theme === 'dark' ? 'light' : 'dark'); } }); } ); cesdk.ui.registerComponent( 'ly.img.boldText.canvasBar', ({ builder, engine: eng }) => { builder.Button('ly.img.boldText.canvasBar.button', { label: 'Bold', icon: '@imgly/Bold', onClick: () => { const selection = eng.block.findAllSelected(); selection.forEach((blockId) => { if (eng.block.getType(blockId) === '//ly.img.ubq/text') { const [currentWeight] = eng.block.getTextFontWeights(blockId); const newWeight = currentWeight === 'bold' ? 'normal' : 'bold'; eng.block.setTextFontWeight(blockId, newWeight); } }); } }); } ); // Register a zoom button for the top bar cesdk.ui.registerComponent('ly.img.zoomFit.canvasBar', ({ builder }) => { builder.Button('ly.img.zoomFit.canvasBar.button', { label: 'Fit Page', icon: '@imgly/Fit', onClick: async () => { const currentPage = cesdk.engine.scene.getCurrentPage(); if (currentPage) { await cesdk.engine.scene.zoomToBlock(currentPage, { padding: 40 }); } } }); }); // Register an undo button for the top bar cesdk.ui.registerComponent('ly.img.undo.canvasBar', ({ builder }) => { const canUndo = cesdk.engine.editor.canUndo(); builder.Button('ly.img.undo.canvasBar.button', { label: 'Undo', icon: '@imgly/Undo', isDisabled: !canUndo, onClick: () => { cesdk.engine.editor.undo(); } }); }); // Register a redo button for the top bar cesdk.ui.registerComponent('ly.img.redo.canvasBar', ({ builder }) => { const canRedo = cesdk.engine.editor.canRedo(); builder.Button('ly.img.redo.canvasBar.button', { label: 'Redo', icon: '@imgly/Redo', isDisabled: !canRedo, onClick: () => { cesdk.engine.editor.redo(); } }); }); // Get the current Canvas Bar order for the bottom position const currentOrder = cesdk.ui.getCanvasBarOrder('bottom'); console.log('Current bottom Canvas Bar order:', currentOrder); // Get the order for a specific edit mode const textModeOrder = cesdk.ui.getCanvasBarOrder('bottom', { editMode: 'Text' }); console.log('Text mode Canvas Bar order:', textModeOrder); // Insert the registered export button after the settings button cesdk.ui.insertCanvasBarOrderComponent( 'ly.img.settings.canvasBar', 'ly.img.export.canvasBar', 'bottom', 'after' ); // Remove the add page button for a single-page workflow cesdk.ui.removeCanvasBarOrderComponent( 'ly.img.page.add.canvasBar', 'bottom' ); // Update the export button with custom properties // Components that read from payload can be customized this way cesdk.ui.updateCanvasBarOrderComponent( 'ly.img.export.canvasBar', { label: 'Export PNG', icon: '@imgly/Image' }, 'bottom' ); // Set a completely custom Canvas Bar order for the bottom position // Reference the registered components by their IDs // Use spacers on both sides of the theme toggle to center it cesdk.ui.setCanvasBarOrder( [ 'ly.img.settings.canvasBar', 'ly.img.separator', 'ly.img.export.canvasBar', 'ly.img.spacer', 'ly.img.themeSwitcher.canvasBar', 'ly.img.spacer' ], 'bottom' ); // Also customize the top Canvas Bar with undo/redo and zoom controls cesdk.ui.setCanvasBarOrder( [ 'ly.img.undo.canvasBar', 'ly.img.redo.canvasBar', 'ly.img.spacer', 'ly.img.zoomFit.canvasBar' ], 'top' ); // Configure different Canvas Bar for Text edit mode // Reference the registered bold button by its ID cesdk.ui.setCanvasBarOrder( [ 'ly.img.settings.canvasBar', 'ly.img.separator', 'ly.img.boldText.canvasBar', 'ly.img.spacer', 'ly.img.export.canvasBar' ], 'bottom', { editMode: 'Text' } ); // Using layout helpers: spacers and separators const updatedOrder = cesdk.ui.getCanvasBarOrder('bottom'); console.log('Updated bottom Canvas Bar order:', updatedOrder); // Insert a separator before the export button cesdk.ui.insertCanvasBarOrderComponent( 'ly.img.export.canvasBar', 'ly.img.separator', 'bottom', 'before' ); // Log final Canvas Bar configuration const finalOrder = cesdk.ui.getCanvasBarOrder('bottom'); console.log('Final bottom Canvas Bar order:', finalOrder); const topOrder = cesdk.ui.getCanvasBarOrder('top'); console.log('Top Canvas Bar order:', topOrder); console.log('Canvas Bar customization example loaded successfully!'); }} export default Example; ``` This guide covers how to retrieve the component order, remove or update existing components, insert new ones, and configure different layouts for each edit mode. ## Retrieving the Canvas Bar Order[#](#retrieving-the-canvas-bar-order) We can retrieve the current Canvas Bar component order using `cesdk.ui.getCanvasBarOrder()`. This returns an array of component configurations for the specified position. ``` // Get the current Canvas Bar order for the bottom positionconst currentOrder = cesdk.ui.getCanvasBarOrder('bottom');console.log('Current bottom Canvas Bar order:', currentOrder); // Get the order for a specific edit modeconst textModeOrder = cesdk.ui.getCanvasBarOrder('bottom', { editMode: 'Text'});console.log('Text mode Canvas Bar order:', textModeOrder); ``` The returned array contains objects with component IDs and any additional configuration. We log both the default Transform mode order and the Text edit mode order to see how they differ. ## Removing Components[#](#removing-components) We can remove components from the Canvas Bar using `cesdk.ui.removeCanvasBarOrderComponent()`. This is useful for creating focused editing experiences. ``` // Remove the add page button for a single-page workflowcesdk.ui.removeCanvasBarOrderComponent( 'ly.img.page.add.canvasBar', 'bottom'); ``` Here we remove the add page button for a single-page document workflow. The matcher can be a component ID string, an index number, `'first'`, `'last'`, a partial object, or a predicate function. ## Updating Components[#](#updating-components) We can modify existing components using `cesdk.ui.updateCanvasBarOrderComponent()`. This allows changing properties like icons, labels, or click handlers without removing and reinserting. ``` // Update the export button with custom properties// Components that read from payload can be customized this waycesdk.ui.updateCanvasBarOrderComponent( 'ly.img.export.canvasBar', { label: 'Export PNG', icon: '@imgly/Image' }, 'bottom'); ``` In this example, we update the custom export button’s label and icon. Custom components that read from their payload can be dynamically updated this way. ## Inserting Components[#](#inserting-components) We can add new components to the Canvas Bar using `cesdk.ui.insertCanvasBarOrderComponent()`. The method accepts a matcher to find the reference component, the new component to insert, the position, and optionally the location relative to the matched component. ``` // Insert the registered export button after the settings buttoncesdk.ui.insertCanvasBarOrderComponent( 'ly.img.settings.canvasBar', 'ly.img.export.canvasBar', 'bottom', 'after'); ``` In this example, we insert a custom export button after the settings button by referencing its component ID. The location options are: * `'before'` - Insert before the matched component * `'after'` - Insert after the matched component (default) * `'replace'` - Replace the matched component ## Setting a Complete Order[#](#setting-a-complete-order) For complete control over the Canvas Bar, we can replace the entire order using `cesdk.ui.setCanvasBarOrder()`. Pass an array of component IDs. ``` // Set a completely custom Canvas Bar order for the bottom position// Reference the registered components by their IDs// Use spacers on both sides of the theme toggle to center itcesdk.ui.setCanvasBarOrder( [ 'ly.img.settings.canvasBar', 'ly.img.separator', 'ly.img.export.canvasBar', 'ly.img.spacer', 'ly.img.themeSwitcher.canvasBar', 'ly.img.spacer' ], 'bottom'); // Also customize the top Canvas Bar with undo/redo and zoom controlscesdk.ui.setCanvasBarOrder( [ 'ly.img.undo.canvasBar', 'ly.img.redo.canvasBar', 'ly.img.spacer', 'ly.img.zoomFit.canvasBar' ], 'top'); ``` Here we create a custom Canvas Bar for the bottom position with the settings button, a separator, the export button, spacers, and a theme switcher button. ## Layout Helpers[#](#layout-helpers) CE.SDK provides two layout helper components to organize Canvas Bar content: ``` // Using layout helpers: spacers and separatorsconst updatedOrder = cesdk.ui.getCanvasBarOrder('bottom');console.log('Updated bottom Canvas Bar order:', updatedOrder); // Insert a separator before the export buttoncesdk.ui.insertCanvasBarOrderComponent( 'ly.img.export.canvasBar', 'ly.img.separator', 'bottom', 'before'); ``` **`ly.img.separator`** adds a vertical divider line with smart rendering rules: * Adjacent separators render as one * Separators at edges are hidden * Separators adjacent to spacers (on the left) are hidden **`ly.img.spacer`** distributes horizontal space evenly. Multiple spacers share the available space proportionally. ## Edit Mode Context[#](#edit-mode-context) The content of the Canvas Bar changes based on the current [edit mode](vue/concepts/edit-modes-1f5b6c/) (`'Transform'` (the default), `'Text'`, `'Crop'`, `'Trim'`, or a custom value), so all APIs accept an `orderContext` argument to specify the mode. Each Canvas Bar position (`'top'` or `'bottom'`) has its own component order. All ordering APIs require a `position` parameter and accept an optional `orderContext` to target specific edit modes. ``` // Configure different Canvas Bar for Text edit mode// Reference the registered bold button by its IDcesdk.ui.setCanvasBarOrder( [ 'ly.img.settings.canvasBar', 'ly.img.separator', 'ly.img.boldText.canvasBar', 'ly.img.spacer', 'ly.img.export.canvasBar' ], 'bottom', { editMode: 'Text' }); ``` In this example, we configure a different Canvas Bar for Text edit mode that includes a bold button relevant for text editing. ## API Reference[#](#api-reference) | Method | Purpose | | --- | --- | | `cesdk.ui.registerComponent(id, renderFunction)` | Register a custom component | | `cesdk.ui.getCanvasBarOrder(position, orderContext?)` | Get current component order | | `cesdk.ui.setCanvasBarOrder(order, position, orderContext?)` | Set complete component order | | `cesdk.ui.updateCanvasBarOrderComponent(matcher, update, position, orderContext?)` | Update matched components | | `cesdk.ui.removeCanvasBarOrderComponent(matcher, position, orderContext?)` | Remove matched components | | `cesdk.ui.insertCanvasBarOrderComponent(matcher, component, position, location?, orderContext?)` | Insert components | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/appearance/theming-4b0938) --- # Theming Customize the visual appearance of CE.SDK’s user interface through theming, allowing you to adapt colors, fonts, and sizes to match your brand identity or application design. ![CE.SDK editor with custom green and olive nature theme showing themed UI elements](/docs/cesdk/_astro/browser.hero.DGi4MK0a_yo0cW.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) CE.SDK provides comprehensive theming capabilities at two levels: built-in themes for immediate use, and a complete CSS theming API for detailed brand-specific styling. This guide demonstrates how to use each approach to customize the editor’s appearance. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; const GREEN_OLIVE_THEME_CSS = `.ubq-public[data-ubq-theme='light'][data-ubq-scale='normal'] { --ubq-canvas: hsl(95, 25%, 88%) !important; --ubq-elevation-1: hsl(92, 22%, 83%) !important; --ubq-elevation-2: hsl(88, 20%, 78%) !important; --ubq-elevation-3: hsl(85, 18%, 73%) !important; --ubq-interactive-default: hsl(88, 20%, 82%) !important; --ubq-interactive-hover: hsl(88, 24%, 75%) !important; --ubq-interactive-pressed: hsl(88, 28%, 68%) !important; --ubq-interactive-accent-default: hsl(135, 45%, 48%) !important; --ubq-interactive-accent-hover: hsl(135, 50%, 43%) !important; --ubq-interactive-accent-pressed: hsl(135, 55%, 38%) !important;}`; /** * CE.SDK Plugin: Theming Guide * * This example demonstrates: * - Setting theme (light, dark, system) * - Setting scale (normal, large, modern) * - Dynamic scale with callback * - Custom theme via CSS custom 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'); } // Set the theme to light, dark, or system // 'system' automatically follows the user's OS theme preference cesdk.ui.setTheme('light'); // Set a fixed scale: 'normal', 'large', or 'modern' (default) // - normal: Standard UI scaling for desktop // - large: Increased sizes for accessibility and touch devices // - modern: Modern theme with refined visual design cesdk.ui.setScale('normal'); // Apply custom green/olive theme after CE.SDK initialization const style = document.createElement('style'); style.textContent = GREEN_OLIVE_THEME_CSS; document.head.appendChild(style); // Force theme refresh to pick up custom colors cesdk.ui.setTheme('dark'); await new Promise((resolve) => setTimeout(resolve, 100)); cesdk.ui.setTheme('light'); // Or use a dynamic scale based on viewport and device cesdk.ui.setScale(({ containerWidth, isTouch }) => { // Use large scale for small screens or touch devices if ((containerWidth && containerWidth < 600) || isTouch) { return 'large'; } // Use normal scale for larger screens return 'normal'; }); // Get the current active theme const currentTheme = cesdk.ui.getTheme(); // Returns 'light' or 'dark' console.log('Current theme:', currentTheme); // Get the current scale setting const currentScale = cesdk.ui.getScale(); // Returns scale or callback function console.log('Current scale:', currentScale); // Create a design scene await cesdk.createDesignScene(); // Add asset sources await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); 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 dimensions engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Add a visual element to demonstrate the theme const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Add an image to show theme effects const imageBlock = await engine.block.addImage(imageUri, { x: 100, y: 100, size: { width: 600, height: 400 } }); engine.block.appendChild(page, imageBlock); // Note: The custom theme defined in custom-theme.css will automatically apply // when the theme/scale combination matches the CSS selectors // Example: .ubq-public[data-ubq-theme='dark'][data-ubq-scale='normal'] }} export default Example; ``` ## Built-in Themes and Scales[#](#built-in-themes-and-scales) We provide ready-to-use theme options and scale settings that can be configured at runtime, allowing you to adapt the editor’s appearance without writing custom CSS. ### Setting the Theme[#](#setting-the-theme) Use `cesdk.ui.setTheme()` to switch between light, dark, or system themes. The system option automatically follows the user’s operating system theme preference. ``` // Set the theme to light, dark, or system// 'system' automatically follows the user's OS theme preferencecesdk.ui.setTheme('light'); ``` By default, the light theme is active. When you set the theme to `'system'`, CE.SDK automatically adapts to the user’s OS preference and updates whenever the system theme changes. ### Reading the Current Theme[#](#reading-the-current-theme) To determine which theme is currently active, use `cesdk.ui.getTheme()`: ``` // Get the current active themeconst currentTheme = cesdk.ui.getTheme(); // Returns 'light' or 'dark'console.log('Current theme:', currentTheme); // Get the current scale settingconst currentScale = cesdk.ui.getScale(); // Returns scale or callback functionconsole.log('Current scale:', currentScale); ``` This method always returns either `'light'` or `'dark'`, never `'system'`. When the system theme is configured, `getTheme()` returns the resolved theme based on the current OS preference. ### Setting the Scale[#](#setting-the-scale) CE.SDK supports three scale modes that affect UI element sizes, spacing, and typography. Use `cesdk.ui.setScale()` to select a fixed scale: ``` // Set a fixed scale: 'normal', 'large', or 'modern' (default)// - normal: Standard UI scaling for desktop// - large: Increased sizes for accessibility and touch devices// - modern: Modern theme with refined visual designcesdk.ui.setScale('normal'); ``` The scale modes offer different visual experiences: * **`normal`**: Standard UI scaling optimized for desktop and larger screens * **`large`**: Increased element sizes, margins, and fonts for improved readability and accessibility, particularly beneficial for users with visual or motor impairments and those on small screens * **`modern`** (default): Modern theme with refined color palette, unified elevation surfaces, and improved visual hierarchy ### Dynamic Scale with Callbacks[#](#dynamic-scale-with-callbacks) For responsive designs, you can provide a callback function that returns the appropriate scale based on viewport properties: ``` // Or use a dynamic scale based on viewport and devicecesdk.ui.setScale(({ containerWidth, isTouch }) => { // Use large scale for small screens or touch devices if ((containerWidth && containerWidth < 600) || isTouch) { return 'large'; } // Use normal scale for larger screens return 'normal';}); ``` The callback receives an object with two properties: * `containerWidth`: The width in pixels of the HTML element containing CE.SDK * `isTouch`: Boolean indicating whether the user agent supports touch input This callback is evaluated when the viewport size changes or when the touch capability is detected, allowing CE.SDK to adapt the scale dynamically. ## Custom CSS Theming[#](#custom-css-theming) For complete control over the editor’s appearance, use CE.SDK’s CSS custom properties system. This approach allows you to define every aspect of the UI’s visual design through CSS variables. ### Theme Structure[#](#theme-structure) Custom themes use CSS selectors that combine the root class, theme attribute, and scale attribute: ``` .ubq-public[data-ubq-theme='dark'][data-ubq-scale='normal'] { /* Custom properties here */} ``` The selector components are: * `.ubq-public`: Root class that scopes all CE.SDK UI elements * `[data-ubq-theme]`: Attribute for theme variant (`'light'` or `'dark'`) * `[data-ubq-scale]`: Attribute for scale mode (`'normal'`, `'large'`, or `'modern'`) ### Loading Custom Themes[#](#loading-custom-themes) Load custom themes by linking a CSS file in your HTML or including a ` ``` ### CSS Custom Properties[#](#css-custom-properties) The theming API provides CSS custom properties organized into color and typography categories. #### Color Properties[#](#color-properties) Color properties control surfaces, interactive elements, borders, and notifications. Key color property categories: * **Canvas and Elevation**: `--ubq-canvas`, `--ubq-elevation-1`, `--ubq-elevation-2` * **Foreground**: `--ubq-foreground-default`, `--ubq-foreground-light`, `--ubq-foreground-info` * **Interactive States**: `--ubq-interactive-default`, `--ubq-interactive-hover`, `--ubq-interactive-pressed` * **Input Elements**: `--ubq-input-default`, `--ubq-input-hover` * **Borders and Strokes**: `--ubq-border-default`, `--ubq-stroke-contrast-1`, `--ubq-stroke-contrast-2` * **Notices**: `--ubq-notice-info`, `--ubq-notice-warning`, `--ubq-notice-error`, `--ubq-notice-success` #### Typography Properties[#](#typography-properties) Typography properties control font families, sizes, line heights, letter spacing, and weights for different text styles: * **Headlines**: `--ubq-typography-headline-l-*`, `--ubq-typography-headline-m-*` * **Labels**: `--ubq-typography-label-m-*`, `--ubq-typography-label-s-*` * **Body Text**: `--ubq-typography-body-m-*` * **Input Text**: `--ubq-typography-input-m-*` * **Buttons**: `--ubq-typography-button-m-*` Each typography style includes properties for: * `-size`: Font size in pixels * `-line_height`: Line height in pixels * `-letter_spacing`: Letter spacing (e.g., `0.02em`) * `-font_family`: Font family with fallbacks (uses `--ubq-typography-font_family` variable, defaults to `'Inter', sans-serif`) * `-weight`: Font weight (`normal`, `600`, etc.) ### Creating Theme Variants[#](#creating-theme-variants) We recommend providing both light and dark theme variants for the best user experience. Users expect to choose their preferred theme or have it follow their system preference. ``` /* Dark theme */.ubq-public[data-ubq-theme='dark'][data-ubq-scale='normal'] { --ubq-canvas: hsl(230.27, 52.11%, 13.92%); --ubq-foreground-default: hsla(0, 0%, 100%, 0.9); /* ... more properties ... */} /* Light theme */.ubq-public[data-ubq-theme='light'][data-ubq-scale='normal'] { --ubq-canvas: hsl(0, 0%, 98%); --ubq-foreground-default: hsla(0, 0%, 0%, 0.9); /* ... more properties ... */} ``` Similarly, consider providing scale variants for `normal`, `large`, and `modern` scales to ensure your theme looks correct at all scale settings. ## Best Practices[#](#best-practices) ### Design Considerations[#](#design-considerations) When creating custom themes: * **Provide both light and dark variants** to respect user preferences * **Support all three scale options** to ensure accessibility * **Maintain sufficient contrast ratios** for readability (WCAG AA: 4.5:1 for normal text, 3:1 for large text) * **Test with real content** to verify the theme works across all UI elements * **Use system fonts as fallbacks** for reliable cross-platform rendering ### Implementation Tips[#](#implementation-tips) For reliable theme implementation: * **Load themes before CE.SDK initialization** to prevent visual flashes * **Organize theme files** by theme variant and scale for maintainability * **Document custom property values** for team collaboration * **Version control theme files** to track changes over time ### Performance[#](#performance) Optimize theme performance by: * **Minimizing CSS specificity** for faster selector matching * **Using CSS custom properties** for efficient theme switching * **Avoiding unnecessary repaints** by grouping property updates * **Caching theme preferences** in localStorage to persist user choices ### Accessibility[#](#accessibility) Ensure themes are accessible: * **Respect system theme preference** by defaulting to `'system'` theme * **Meet WCAG AA contrast ratios** for all text and interactive elements * **Provide the `large` scale option** for users who need larger UI elements * **Test with screen readers** to verify theme doesn’t interfere with assistive technology * **Include theme switching controls** for users to customize their experience ## Troubleshooting[#](#troubleshooting) ### Theme Not Applying[#](#theme-not-applying) If your custom theme isn’t visible: 1. Verify CSS is loaded **before** CE.SDK initialization 2. Check CSS selector specificity matches or exceeds built-in theme selectors 3. Ensure `.ubq-public` class is used correctly in all selectors 4. Verify `data-ubq-theme` and `data-ubq-scale` attributes match the theme and scale you’ve set ### Theme Switching Not Working[#](#theme-switching-not-working) If `setTheme()` doesn’t update the UI: 1. Confirm the method is called **after** CE.SDK initialization completes 2. Verify CSS selectors cover all theme variants (light/dark) 3. Check the browser console for JavaScript errors 4. Inspect the DOM to ensure `data-ubq-theme` attribute is updating ### Colors Display Incorrectly[#](#colors-display-incorrectly) If colors appear wrong: 1. Check HSL/HSLA values are valid (hue: 0-360, saturation/lightness: 0%-100%, alpha: 0-1) 2. Verify alpha channel values are in the correct range (0.0 to 1.0) 3. Test color combinations for sufficient contrast 4. Look for typos in CSS property names (they fail silently) ### Typography Not Matching Design[#](#typography-not-matching-design) If fonts don’t match your design: 1. Verify font family names are spelled correctly and match loaded fonts 2. Check that custom fonts are loaded and available 3. Confirm fallback fonts are specified for reliability 4. Test that the specified font weights are available in the font family ### Scale Callback Not Triggering[#](#scale-callback-not-triggering) If dynamic scale doesn’t work: 1. Check callback function signature matches expected parameters 2. Verify `containerWidth` is being evaluated correctly (handle `undefined`) 3. Test on different viewport sizes to confirm callback execution 4. Add `console.log` statements to debug callback parameters ## API Reference[#](#api-reference) Quick reference for all theming-related APIs: ### Theme Management[#](#theme-management) | Method | Description | | --- | --- | | `cesdk.ui.setTheme('light' | 'dark' | 'system')` | Set the theme to light, dark, or system (follows OS preference) | | `cesdk.ui.getTheme()` | Get current active theme (returns `'light'` or `'dark'`) | ### Scale Management[#](#scale-management) | Method | Description | | --- | --- | | `cesdk.ui.setScale('normal' | 'large' | 'modern')` | Set a fixed scale: normal (standard), large (touch-friendly), modern (refined) | | `cesdk.ui.setScale(ScaleCallback)` | Set dynamic scale using callback based on viewport and device | | `cesdk.ui.getScale()` | Get current scale setting (returns scale value or callback function) | **ScaleCallback Type:** ``` type ScaleCallback = (context: { containerWidth: number; isTouch: boolean;}) => 'normal' | 'large' | 'modern' ``` ### CSS Custom Properties[#](#css-custom-properties-1) Complete reference of all available CSS custom properties for theming: #### Color Tokens[#](#color-tokens) **Canvas and Elevation** * `--ubq-canvas`: Main canvas background color * `--ubq-elevation-1`: First elevation level background * `--ubq-elevation-2`: Second elevation level background * `--ubq-elevation-3`: Third elevation level background **Foreground Colors** * `--ubq-foreground-default`: Default text and icon color * `--ubq-foreground-light`: Lighter text color for secondary content * `--ubq-foreground-info`: Informational text color * `--ubq-foreground-active`: Active state foreground color * `--ubq-foreground-accent`: Accent foreground color * `--ubq-foreground-danger-default`: Danger state foreground color * `--ubq-foreground-notice-default`: Notice foreground color **Interactive Elements** * `--ubq-interactive-default`: Default interactive element background * `--ubq-interactive-hover`: Hover state background * `--ubq-interactive-pressed`: Pressed state background * `--ubq-interactive-selected`: Selected state background * `--ubq-interactive-active-default`: Active element default state * `--ubq-interactive-active-hover`: Active element hover state * `--ubq-interactive-active-pressed`: Active element pressed state * `--ubq-interactive-accent-default`: Accent interactive default state * `--ubq-interactive-accent-hover`: Accent interactive hover state * `--ubq-interactive-accent-pressed`: Accent interactive pressed state * `--ubq-interactive-danger-default`: Danger interactive default state * `--ubq-interactive-danger-hover`: Danger interactive hover state * `--ubq-interactive-danger-pressed`: Danger interactive pressed state * `--ubq-interactive-group-default`: Group interactive default state * `--ubq-interactive-group-hover`: Group interactive hover state * `--ubq-interactive-group-active-default`: Active group default state * `--ubq-interactive-group-active-hover`: Active group hover state * `--ubq-interactive-template-default`: Template interactive default state * `--ubq-interactive-template-hover`: Template interactive hover state * `--ubq-interactive-template-pressed`: Template interactive pressed state **Input Elements** * `--ubq-input-default`: Default input background * `--ubq-input-hover`: Input hover state background **Progress Indicators** * `--ubq-progress`: Progress bar color **Borders and Strokes** * `--ubq-border-default`: Default border color * `--ubq-stroke-contrast-1`: Low contrast stroke color * `--ubq-stroke-contrast-2`: Medium contrast stroke color * `--ubq-stroke-contrast-3`: High contrast stroke color **Focus Indicators** * `--ubq-focus-default`: Default focus indicator color * `--ubq-focus-outline`: Focus outline color with transparency **Overlays** * `--ubq-overlay`: Modal and overlay background **Notices and Feedback** * `--ubq-notice-info`: Informational notice color * `--ubq-notice-warning`: Warning notice color * `--ubq-notice-error`: Error notice color * `--ubq-notice-success`: Success notice color **Effects** * `--ubq-effect-shadow`: Box shadow effect for elevation * `--ubq-effect-focus`: Focus state shadow effect **Static Colors** (theme-independent) * `--ubq-static-selection-frame`: Selection frame color * `--ubq-static-contrast-white`: Static white color * `--ubq-static-contrast-black`: Static black color * `--ubq-static-snapping`: Snapping guide color * `--ubq-static-bleed`: Bleed area color * `--ubq-static-text-variable`: Variable text indicator color * `--ubq-static-card-label-background`: Card label background gradient * `--ubq-static-card-background`: Card background gradient #### Typography Tokens[#](#typography-tokens) Each typography style supports the following properties: * `-size`: Font size in pixels * `-line_height`: Line height in pixels * `-letter_spacing`: Letter spacing (e.g., `0.02em`) * `-font_family`: Font family (uses `--ubq-typography-font_family` with fallback to `'Inter', sans-serif`) * `-weight`: Font weight (numeric values like `450`, `600`) **Headline Large** (`--ubq-typography-headline-l-*`) * Used for main headings and titles * Normal scale: 16px size, 20px line height, 0.01em letter spacing, 600 weight * Large scale: 20px size, 25px line height, 0.01em letter spacing, 600 weight **Headline Medium** (`--ubq-typography-headline-m-*`) * Used for secondary headings * Normal scale: 12px size, 16px line height, 0.03em letter spacing, 600 weight * Large scale: 15px size, 20px line height, 0.03em letter spacing, 600 weight **Label Medium** (`--ubq-typography-label-m-*`) * Used for form labels and UI labels * Normal scale: 12px size, 16px line height, 0.02em letter spacing, 450 weight * Large scale: 15px size, 20px line height, 0.02em letter spacing, normal weight **Label Small** (`--ubq-typography-label-s-*`) * Used for small labels and captions * Normal scale: 10px size, 14px line height, 0.02em letter spacing, 450 weight * Large scale: 12.5px size, 17.5px line height, 0.02em letter spacing, normal weight **Body Medium** (`--ubq-typography-body-m-*`) * Used for body text and descriptions * Normal scale: 12px size, 18px line height, 0.02em letter spacing, 450 weight * Large scale: 15px size, 22.5px line height, 0.02em letter spacing, normal weight **Input Medium** (`--ubq-typography-input-m-*`) * Used for input field text * Normal scale: 12px size, 16px line height, 0.02em letter spacing, 450 weight * Large scale: 16px size, 20px line height, 0.02em letter spacing, normal weight (16px minimum prevents iOS zoom) **Button Medium** (`--ubq-typography-button-m-*`) * Used for button text * Normal scale: 12px size, 16px line height, 0.02em letter spacing, 450 weight * Large scale: 15px size, 20px line height, 0.02em letter spacing, normal weight **Base Font Family** * `--ubq-typography-font_family`: Base font family variable used by all typography tokens #### Spacing and Measurements[#](#spacing-and-measurements) **Scale Base** * `--ubq-scale-base`: Base unit for spacing calculations (4px normal, 5px large) **Margins** * `--ubq-margin-base`: Base margin unit (4px normal, 5px large) * `--ubq-margin-xs`: Extra small margin (4px normal, 5px large) * `--ubq-margin-s`: Small margin (8px normal, 10px large) * `--ubq-margin-m`: Medium margin (12px normal, 15px large) * `--ubq-margin-l`: Large margin (16px normal, 20px large) * `--ubq-margin-xl`: Extra large margin (24px normal, 30px large) **Border Radius** * `--ubq-border_radius-base`: Base border radius (4px normal, 5px large) * `--ubq-border_radius-xs`: Extra small radius (2px normal, 2.5px large) * `--ubq-border_radius-s`: Small radius (4px normal, 5px large) * `--ubq-border_radius-m`: Medium radius (8px normal, 10px large) * `--ubq-border_radius-l`: Large radius (12px normal, 15px large) #### CSS Selector Pattern[#](#css-selector-pattern) All custom properties must be scoped using this pattern: ``` .ubq-public[data-ubq-theme='light' | 'dark'][data-ubq-scale='normal' | 'large' | 'modern'] { /* Custom properties here */} ``` ## Next Steps[#](#next-steps) Now that you understand theming, explore related customization features: * [Custom Labels](vue/user-interface/appearance/custom-labels-7794f7/) \- Customize UI text labels to match your brand voice * [Custom UI Components](vue/user-interface/ui-extensions-d194d1/) \- Build custom UI elements for your workflow * [Customize UI Behavior](vue/user-interface/ui-extensions/customize-behaviour-c09cb2/) \- Control and respond to CE.SDK’s interface at runtime --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/appearance/icons-679e32) --- # Icons Customize the editor’s icons by registering custom SVG icon sets and using them in dock entries, custom components, and other UI elements. ![Icons example showing the CE.SDK editor with custom icons in the dock and canvas menu](/docs/cesdk/_astro/browser.hero.rQnJB7Ts_Zcq5PY.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-appearance-icons-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-appearance-icons-browser) CE.SDK uses SVG sprites for icons throughout the editor interface. Each icon is referenced by a symbol ID that starts with `@`. You can register custom icon sets to replace built-in icons or add new ones for your own custom UI components. ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; class Example implements EditorPlugin { name = 'guides-user-interface-appearance-icons-browser'; version = '1.0.0'; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); // Set the editor view cesdk.ui.setView('advanced'); // Create a design scene await cesdk.createDesignScene(); const engine = cesdk.engine; // Register a custom SVG icon set with multiple symbols cesdk.ui.addIconSet( '@custom/icons', ` ` ); // Get the current dock order and replace the Images dock icon const dockOrder = cesdk.ui.getDockOrder(); cesdk.ui.setDockOrder( dockOrder.map((entry) => { if (entry.key === 'ly.img.image') { return { ...entry, icon: '@custom/icon/star' }; } return entry; }) ); // Register a custom component that uses a custom icon cesdk.ui.registerComponent( 'CustomIconButton', ({ builder: { Button } }) => { Button('heartButton', { label: 'Heart', icon: '@custom/icon/heart', onClick: () => { console.log('Heart icon button clicked'); } }); Button('diamondButton', { label: 'Diamond', icon: '@custom/icon/diamond', onClick: () => { console.log('Diamond icon button clicked'); } }); } ); // Add the custom component to the canvas menu cesdk.ui.setCanvasMenuOrder([ ...cesdk.ui.getCanvasMenuOrder(), 'CustomIconButton' ]); // Add an image block to the scene so the canvas menu is visible when selected const page = engine.block.findByType('page')[0]; if (page !== undefined) { const imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_1.jpg', { x: 50, y: 50, size: { width: 400, height: 300 } } ); engine.block.appendChild(page, imageBlock); engine.block.select(imageBlock); } }} export default Example; ``` This guide covers how to register custom SVG icon sets, replace dock entry icons with custom icons, and use custom icons in custom UI components. ## Registering Custom Icon Sets[#](#registering-custom-icon-sets) We add custom icons to CE.SDK using the `cesdk.ui.addIconSet()` method. The method takes two parameters: a unique identifier for the icon set and an SVG sprite string containing symbol definitions. ``` // Register a custom SVG icon set with multiple symbolscesdk.ui.addIconSet( '@custom/icons', ` `); ``` Each symbol in the SVG sprite must have an `id` attribute that starts with `@`. This ID is how you reference the icon throughout CE.SDK. In this example, we register three custom icons: `@custom/icon/star`, `@custom/icon/heart`, and `@custom/icon/diamond`. ### SVG Sprite Format Requirements[#](#svg-sprite-format-requirements) When creating custom icon SVG sprites, follow these requirements for proper rendering: * Wrap all symbols in an `` element with the `xmlns` attribute * Each `` must have a unique `id` starting with `@` * Include a `viewBox` attribute on each symbol for proper scaling * Use `currentColor` for `fill` and `stroke` values to inherit the current theme color * Avoid hardcoded width/height attributes on symbols—let CE.SDK handle sizing ### Security Considerations[#](#security-considerations) SVG content passed to `addIconSet()` is injected into the DOM without sanitization. Only use trusted SVG sources. If you need to load icons from untrusted sources, consider sanitizing the content with a library like DOMPurify before registration. ## Replacing Dock Entry Icons[#](#replacing-dock-entry-icons) Once you’ve registered a custom icon set, you can replace the icons of existing dock entries. We retrieve the current dock order using `cesdk.ui.getDockOrder()`, modify the entries we want to change, and apply the updated order with `cesdk.ui.setDockOrder()`. ``` // Get the current dock order and replace the Images dock iconconst dockOrder = cesdk.ui.getDockOrder();cesdk.ui.setDockOrder( dockOrder.map((entry) => { if (entry.key === 'ly.img.image') { return { ...entry, icon: '@custom/icon/star' }; } return entry; })); ``` This example replaces the Images dock entry icon with our custom star icon. The `key` property identifies the dock entry, and we update the `icon` property to reference our custom icon by its symbol ID. ## Using Icons in Custom Components[#](#using-icons-in-custom-components) You can use custom icons in your own UI components by referencing the icon’s symbol ID in the component builder. When registering a custom component with `cesdk.ui.registerComponent()`, buttons and other elements accept an `icon` property. ``` // Register a custom component that uses a custom iconcesdk.ui.registerComponent( 'CustomIconButton', ({ builder: { Button } }) => { Button('heartButton', { label: 'Heart', icon: '@custom/icon/heart', onClick: () => { console.log('Heart icon button clicked'); } }); Button('diamondButton', { label: 'Diamond', icon: '@custom/icon/diamond', onClick: () => { console.log('Diamond icon button clicked'); } }); }); // Add the custom component to the canvas menucesdk.ui.setCanvasMenuOrder([ ...cesdk.ui.getCanvasMenuOrder(), 'CustomIconButton']); ``` We register a custom component containing two buttons, each using a different custom icon. We then add this component to the canvas menu so the buttons appear when users select elements on the canvas. ## Built-In Icons[#](#built-in-icons) CE.SDK includes a built-in icon set named ‘Essentials’ with icons for common editor actions. Each icon is referenced by its symbol ID following the pattern `@imgly/IconName`. You can use these icons directly in your custom components or as references when replacing dock icons. ### Set ‘Essentials’[#](#set-essentials) | Name | Icon | | --- | --- | | `@imgly/Adjustments` | ![Adjustments Icon](/docs/cesdk/_astro/Adjustments.CMpGLfUm_68cTD.svg) | | `@imgly/Animation` | ![Animation Icon](/docs/cesdk/_astro/Animation.B2rhC3jX_68cTD.svg) | | `@imgly/Appearance` | ![Appearance Icon](/docs/cesdk/_astro/Appearance.DNDO2WuP_68cTD.svg) | | `@imgly/Apps` | ![Apps Icon](/docs/cesdk/_astro/Apps.DznTjKmh_68cTD.svg) | | `@imgly/ArrowDown` | ![ArrowDown Icon](/docs/cesdk/_astro/ArrowDown.DF55uy6X_68cTD.svg) | | `@imgly/ArrowLeft` | ![ArrowLeft Icon](/docs/cesdk/_astro/ArrowLeft.rNR_kJjE_68cTD.svg) | | `@imgly/ArrowRight` | ![ArrowRight Icon](/docs/cesdk/_astro/ArrowRight.9eua18dC_68cTD.svg) | | `@imgly/ArrowUp` | ![ArrowUp Icon](/docs/cesdk/_astro/ArrowUp.CVAHSy6F_68cTD.svg) | | `@imgly/ArrowsDirectionHorizontal` | ![ArrowsDirectionHorizontal Icon](/docs/cesdk/_astro/ArrowsDirectionHorizontal.B0bUSo3k_68cTD.svg) | | `@imgly/ArrowsDirectionVertical` | ![ArrowsDirectionVertical Icon](/docs/cesdk/_astro/ArrowsDirectionVertical.BpuSfMrI_68cTD.svg) | | `@imgly/AsClip` | ![AsClip Icon](/docs/cesdk/_astro/AsClip.CVGpviFn_68cTD.svg) | | `@imgly/AsOverlay` | ![AsOverlay Icon](/docs/cesdk/_astro/AsOverlay.5eEK9ZPF_68cTD.svg) | | `@imgly/Audio` | ![Audio Icon](/docs/cesdk/_astro/Audio.uOK5I2sE_68cTD.svg) | | `@imgly/AudioAdd` | ![AudioAdd Icon](/docs/cesdk/_astro/AudioAdd.B-ssll_o_68cTD.svg) | | `@imgly/Backward` | ![Backward Icon](/docs/cesdk/_astro/Backward.C2BFbDeQ_68cTD.svg) | | `@imgly/Blendmode` | ![Blendmode Icon](/docs/cesdk/_astro/Blendmode.z9rB9rxU_68cTD.svg) | | `@imgly/Blur` | ![Blur Icon](/docs/cesdk/_astro/Blur.DgAVmk6g_68cTD.svg) | | `@imgly/BooleanExclude` | ![BooleanExclude Icon](/docs/cesdk/_astro/BooleanExclude.By40Ey3m_68cTD.svg) | | `@imgly/BooleanIntersect` | ![BooleanIntersect Icon](/docs/cesdk/_astro/BooleanIntersect.CY4HM50Y_68cTD.svg) | | `@imgly/BooleanSubstract` | ![BooleanSubstract Icon](/docs/cesdk/_astro/BooleanSubstract.CwYzfMbe_68cTD.svg) | | `@imgly/BooleanUnion` | ![BooleanUnion Icon](/docs/cesdk/_astro/BooleanUnion.C-xUCyCc_68cTD.svg) | | `@imgly/CaseAsTyped` | ![CaseAsTyped Icon](/docs/cesdk/_astro/CaseAsTyped.l6bSq4oc_68cTD.svg) | | `@imgly/CaseLowercase` | ![CaseLowercase Icon](/docs/cesdk/_astro/CaseLowercase.BEN90R29_68cTD.svg) | | `@imgly/CaseSmallCaps` | ![CaseSmallCaps Icon](/docs/cesdk/_astro/CaseSmallCaps.BTdtiBHa_68cTD.svg) | | `@imgly/CaseTitleCase` | ![CaseTitleCase Icon](/docs/cesdk/_astro/CaseTitleCase.6GhYywMm_68cTD.svg) | | `@imgly/CaseUppercase` | ![CaseUppercase Icon](/docs/cesdk/_astro/CaseUppercase.B4vfd36q_68cTD.svg) | | `@imgly/CheckboxCheckmark` | ![CheckboxCheckmark Icon](/docs/cesdk/_astro/CheckboxCheckmark.pMjPiK5g_68cTD.svg) | | `@imgly/CheckboxMixed` | ![CheckboxMixed Icon](/docs/cesdk/_astro/CheckboxMixed.DOxJYYWZ_68cTD.svg) | | `@imgly/Checkmark` | ![Checkmark Icon](/docs/cesdk/_astro/Checkmark.CP0StQxb_68cTD.svg) | | `@imgly/ChevronDown` | ![ChevronDown Icon](/docs/cesdk/_astro/ChevronDown.f4OqdrJU_68cTD.svg) | | `@imgly/ChevronLeft` | ![ChevronLeft Icon](/docs/cesdk/_astro/ChevronLeft.BU6Cbcnc_68cTD.svg) | | `@imgly/ChevronRight` | ![ChevronRight Icon](/docs/cesdk/_astro/ChevronRight.DaVhuEy-_68cTD.svg) | | `@imgly/ChevronUp` | ![ChevronUp Icon](/docs/cesdk/_astro/ChevronUp.BcBhYGEB_68cTD.svg) | | `@imgly/Collage` | ![Collage Icon](/docs/cesdk/_astro/Collage.C9m_-pZR_68cTD.svg) | | `@imgly/ColorFill` | ![ColorFill Icon](/docs/cesdk/_astro/ColorFill.Due-2va6_68cTD.svg) | | `@imgly/ColorGradientAngular` | ![ColorGradientAngular Icon](/docs/cesdk/_astro/ColorGradientAngular.CWntlp_d_68cTD.svg) | | `@imgly/ColorGradientLinear` | ![ColorGradientLinear Icon](/docs/cesdk/_astro/ColorGradientLinear.DmT3R1oC_68cTD.svg) | | `@imgly/ColorGradientRadial` | ![ColorGradientRadial Icon](/docs/cesdk/_astro/ColorGradientRadial.DzApV2NU_68cTD.svg) | | `@imgly/ColorOpacity` | ![ColorOpacity Icon](/docs/cesdk/_astro/ColorOpacity.DMNZ-UhH_68cTD.svg) | | `@imgly/ColorSolid` | ![ColorSolid Icon](/docs/cesdk/_astro/ColorSolid.DVTQ0W3Q_68cTD.svg) | | `@imgly/ConnectionLostSlash` | ![ConnectionLostSlash Icon](/docs/cesdk/_astro/ConnectionLostSlash.DTcmxMhK_68cTD.svg) | | `@imgly/Copy` | ![Copy Icon](/docs/cesdk/_astro/Copy.BmABgrWc_68cTD.svg) | | `@imgly/CornerJoinBevel` | ![CornerJoinBevel Icon](/docs/cesdk/_astro/CornerJoinBevel.DqlY245R_68cTD.svg) | | `@imgly/CornerJoinMiter` | ![CornerJoinMiter Icon](/docs/cesdk/_astro/CornerJoinMiter.D3jd16NL_68cTD.svg) | | `@imgly/CornerJoinRound` | ![CornerJoinRound Icon](/docs/cesdk/_astro/CornerJoinRound.BwcufozW_68cTD.svg) | | `@imgly/Crop` | ![Crop Icon](/docs/cesdk/_astro/Crop.DYPLk0m-_68cTD.svg) | | `@imgly/CropCoverMode` | ![CropCoverMode Icon](/docs/cesdk/_astro/CropCoverMode.CGAJdwRR_68cTD.svg) | | `@imgly/CropCropMode` | ![CropCropMode Icon](/docs/cesdk/_astro/CropCropMode.DkkLoYje_68cTD.svg) | | `@imgly/CropFitMode` | ![CropFitMode Icon](/docs/cesdk/_astro/CropFitMode.BO_JImlA_68cTD.svg) | | `@imgly/CropFreefromMode` | ![CropFreefromMode Icon](/docs/cesdk/_astro/CropFreefromMode.BnRsz2W6_68cTD.svg) | | `@imgly/Cross` | ![Cross Icon](/docs/cesdk/_astro/Cross.txikJ9io_68cTD.svg) | | `@imgly/CrossRoundSolid` | ![CrossRoundSolid Icon](/docs/cesdk/_astro/CrossRoundSolid.BOTrEi6a_68cTD.svg) | | `@imgly/CustomLibrary` | ![CustomLibrary Icon](/docs/cesdk/_astro/CustomLibrary.CI29Obfk_68cTD.svg) | | `@imgly/Download` | ![Download Icon](/docs/cesdk/_astro/Download.CZGsxZgt_68cTD.svg) | | `@imgly/DropShadow` | ![DropShadow Icon](/docs/cesdk/_astro/DropShadow.C4Pf5WpM_68cTD.svg) | | `@imgly/Duplicate` | ![Duplicate Icon](/docs/cesdk/_astro/Duplicate.DrdC6vQb_68cTD.svg) | | `@imgly/Edit` | ![Edit Icon](/docs/cesdk/_astro/Edit.CUHjt2nS_68cTD.svg) | | `@imgly/Effects` | ![Effects Icon](/docs/cesdk/_astro/Effects.DRlbFdXp_68cTD.svg) | | `@imgly/ExternalLink` | ![ExternalLink Icon](/docs/cesdk/_astro/ExternalLink.25xxhZJX_68cTD.svg) | | `@imgly/EyeClosed` | ![EyeClosed Icon](/docs/cesdk/_astro/EyeClosed.Cu1m0jfI_68cTD.svg) | | `@imgly/EyeOpen` | ![EyeOpen Icon](/docs/cesdk/_astro/EyeOpen.Ds3PBojz_68cTD.svg) | | `@imgly/Filter` | ![Filter Icon](/docs/cesdk/_astro/Filter.O79QRETm_68cTD.svg) | | `@imgly/FlipHorizontal` | ![FlipHorizontal Icon](/docs/cesdk/_astro/FlipHorizontal.Ei-fC6h9_68cTD.svg) | | `@imgly/FlipVertical` | ![FlipVertical Icon](/docs/cesdk/_astro/FlipVertical.M9ofU4J4_68cTD.svg) | | `@imgly/FontAutoSizing` | ![FontAutoSizing Icon](/docs/cesdk/_astro/FontAutoSizing.DexdGxcq_68cTD.svg) | | `@imgly/FontSize` | ![FontSize Icon](/docs/cesdk/_astro/FontSize.S7lZw5Ga_68cTD.svg) | | `@imgly/Forward` | ![Forward Icon](/docs/cesdk/_astro/Forward.BcuMexaI_68cTD.svg) | | `@imgly/FullscreenEnter` | ![FullscreenEnter Icon](/docs/cesdk/_astro/FullscreenEnter.kF_8SSV7_68cTD.svg) | | `@imgly/FullscreenLeave` | ![FullscreenLeave Icon](/docs/cesdk/_astro/FullscreenLeave.DzxL3476_68cTD.svg) | | `@imgly/Group` | ![Group Icon](/docs/cesdk/_astro/Group.DAQiJIa__68cTD.svg) | | `@imgly/GroupEnter` | ![GroupEnter Icon](/docs/cesdk/_astro/GroupEnter.H1qCSB2q_68cTD.svg) | | `@imgly/GroupExit` | ![GroupExit Icon](/docs/cesdk/_astro/GroupExit.Cxgs51O9_68cTD.svg) | | `@imgly/Home` | ![Home Icon](/docs/cesdk/_astro/Home.DATK_4gX_68cTD.svg) | | `@imgly/Image` | ![Image Icon](/docs/cesdk/_astro/Image.D2nUt11G_68cTD.svg) | | `@imgly/Info` | ![Info Icon](/docs/cesdk/_astro/Info.DNnpRpS4_68cTD.svg) | | `@imgly/LayerBringForward` | ![LayerBringForward Icon](/docs/cesdk/_astro/LayerBringForward.CTq67SoU_68cTD.svg) | | `@imgly/LayerBringForwardArrow` | ![LayerBringForwardArrow Icon](/docs/cesdk/_astro/LayerBringForwardArrow.BcT_xGMM_68cTD.svg) | | `@imgly/LayerBringToFront` | ![LayerBringToFront Icon](/docs/cesdk/_astro/LayerBringToFront.BeM-z2uF_68cTD.svg) | | `@imgly/LayerBringToFrontArrow` | ![LayerBringToFrontArrow Icon](/docs/cesdk/_astro/LayerBringToFrontArrow.CnSyUJQI_68cTD.svg) | | `@imgly/LayerOpacity` | ![LayerOpacity Icon](/docs/cesdk/_astro/LayerOpacity.OWYnyPUZ_68cTD.svg) | | `@imgly/LayerSendBackward` | ![LayerSendBackward Icon](/docs/cesdk/_astro/LayerSendBackward.CFC8GyHY_68cTD.svg) | | `@imgly/LayerSendBackwardArrow` | ![LayerSendBackwardArrow Icon](/docs/cesdk/_astro/LayerSendBackwardArrow.5_XvmVuj_68cTD.svg) | | `@imgly/LayerSendToBack` | ![LayerSendToBack Icon](/docs/cesdk/_astro/LayerSendToBack.BvPugqHt_68cTD.svg) | | `@imgly/LayerSendToBackArrow` | ![LayerSendToBackArrow Icon](/docs/cesdk/_astro/LayerSendToBackArrow.YfQYh_l3_68cTD.svg) | | `@imgly/LayoutHorizontal` | ![LayoutHorizontal Icon](/docs/cesdk/_astro/LayoutHorizontal.CePOuWsz_68cTD.svg) | | `@imgly/LayoutVertical` | ![LayoutVertical Icon](/docs/cesdk/_astro/LayoutVertical.DxgvBSD0_68cTD.svg) | | `@imgly/Library` | ![Library Icon](/docs/cesdk/_astro/Library.xdK4TnnL_68cTD.svg) | | `@imgly/LineHeight` | ![LineHeight Icon](/docs/cesdk/_astro/LineHeight.CVWOG1lW_68cTD.svg) | | `@imgly/LinkClosed` | ![LinkClosed Icon](/docs/cesdk/_astro/LinkClosed.DSl2uQyi_68cTD.svg) | | `@imgly/LinkOpen` | ![LinkOpen Icon](/docs/cesdk/_astro/LinkOpen.DofM8u7F_68cTD.svg) | | `@imgly/LoopClip` | ![LoopClip Icon](/docs/cesdk/_astro/LoopClip.ClOhgNoV_68cTD.svg) | | `@imgly/LoadingSpinner` | ![LoadingSpinner Icon](/docs/cesdk/_astro/LoadingSpinner.DVqX5QnF_68cTD.svg) | | `@imgly/LockClosed` | ![LockClosed Icon](/docs/cesdk/_astro/LockClosed.CNalJFXd_68cTD.svg) | | `@imgly/LockOpen` | ![LockOpen Icon](/docs/cesdk/_astro/LockOpen.TJhEPjeL_68cTD.svg) | | `@imgly/Minus` | ![Minus Icon](/docs/cesdk/_astro/Minus.CqBSW4Hh_68cTD.svg) | | `@imgly/MoreOptionsHorizontal` | ![MoreOptionsHorizontal Icon](/docs/cesdk/_astro/MoreOptionsHorizontal.L63utfkH_68cTD.svg) | | `@imgly/MoreOptionsVertical` | ![MoreOptionsVertical Icon](/docs/cesdk/_astro/MoreOptionsVertical.CuZa1NAq_68cTD.svg) | | `@imgly/Move` | ![Move Icon](/docs/cesdk/_astro/Move.BcjZGlar_68cTD.svg) | | `@imgly/Next` | ![Next Icon](/docs/cesdk/_astro/Next.D6AnK3jG_68cTD.svg) | | `@imgly/None` | ![None Icon](/docs/cesdk/_astro/None.94s_6ShU_68cTD.svg) | | `@imgly/ObjectAlignBottom` | ![ObjectAlignBottom Icon](/docs/cesdk/_astro/ObjectAlignBottom.Q-hKPxE5_68cTD.svg) | | `@imgly/ObjectAlignDistributedHorizontal` | ![ObjectAlignDistributedHorizontal Icon](/docs/cesdk/_astro/ObjectAlignDistributedHorizontal.X0A9Wwpy_68cTD.svg) | | `@imgly/ObjectAlignDistributedVertical` | ![ObjectAlignDistributedVertical Icon](/docs/cesdk/_astro/ObjectAlignDistributedVertical.CNGw3lI8_68cTD.svg) | | `@imgly/ObjectAlignHorizontalCenter` | ![ObjectAlignHorizontalCenter Icon](/docs/cesdk/_astro/ObjectAlignHorizontalCenter.DYUFnb31_68cTD.svg) | | `@imgly/ObjectAlignLeft` | ![ObjectAlignLeft Icon](/docs/cesdk/_astro/ObjectAlignLeft.esiSYWS7_68cTD.svg) | | `@imgly/ObjectAlignRight` | ![ObjectAlignRight Icon](/docs/cesdk/_astro/ObjectAlignRight.GPKDNQHR_68cTD.svg) | | `@imgly/ObjectAlignTop` | ![ObjectAlignTop Icon](/docs/cesdk/_astro/ObjectAlignTop.l_WErmNU_68cTD.svg) | | `@imgly/ObjectAlignVerticalCenter` | ![ObjectAlignVerticalCenter Icon](/docs/cesdk/_astro/ObjectAlignVerticalCenter.8hE0T5x6_68cTD.svg) | | `@imgly/OrientationToggleLandscape` | ![OrientationToggleLandscape Icon](/docs/cesdk/_astro/OrientationToggleLandscape.CSDa8O0M_68cTD.svg) | | `@imgly/OrientationTogglePortrait` | ![OrientationTogglePortrait Icon](/docs/cesdk/_astro/OrientationTogglePortrait.DxVF38g4_68cTD.svg) | | `@imgly/PageResize` | ![PageResize Icon](/docs/cesdk/_astro/PageResize.Cu1xAsRj_68cTD.svg) | | `@imgly/Paste` | ![Paste Icon](/docs/cesdk/_astro/Paste.BuMonsY9_68cTD.svg) | | `@imgly/Pause` | ![Pause Icon](/docs/cesdk/_astro/Pause.DDGCVnKM_68cTD.svg) | | `@imgly/PlaceholderConnected` | ![PlaceholderConnected Icon](/docs/cesdk/_astro/PlaceholderConnected.BkUyWaQ7_68cTD.svg) | | `@imgly/PlaceholderStripes` | ![PlaceholderStripes Icon](/docs/cesdk/_astro/PlaceholderStripes.BFineOwo_68cTD.svg) | | `@imgly/Play` | ![Play Icon](/docs/cesdk/_astro/Play.DSpjfhWu_68cTD.svg) | | `@imgly/Plus` | ![Plus Icon](/docs/cesdk/_astro/Plus.D3IdVb1T_68cTD.svg) | | `@imgly/Position` | ![Position Icon](/docs/cesdk/_astro/Position.DhnoEfwQ_68cTD.svg) | | `@imgly/Previous` | ![Previous Icon](/docs/cesdk/_astro/Previous.CCrN_eRf_68cTD.svg) | | `@imgly/Redo` | ![Redo Icon](/docs/cesdk/_astro/Redo.CfjN5TI8_68cTD.svg) | | `@imgly/Rename` | ![Rename Icon](/docs/cesdk/_astro/Rename.CxI-t7Fn_68cTD.svg) | | `@imgly/Reorder` | ![Reorder Icon](/docs/cesdk/_astro/Reorder.CzUg_r09_68cTD.svg) | | `@imgly/Repeat` | ![Repeat Icon](/docs/cesdk/_astro/Repeat.B2e75pG7_68cTD.svg) | | `@imgly/RepeatOff` | ![RepeatOff Icon](/docs/cesdk/_astro/RepeatOff.B29qufK8_68cTD.svg) | | `@imgly/Replace` | ![Replace Icon](/docs/cesdk/_astro/Replace.B7aKEXkR_68cTD.svg) | | `@imgly/Reset` | ![Reset Icon](/docs/cesdk/_astro/Reset.Cxse5Eti_68cTD.svg) | | `@imgly/RotateCCW90` | ![RotateCCW90 Icon](/docs/cesdk/_astro/RotateCCW90.BIw29FPG_68cTD.svg) | | `@imgly/RotateCW` | ![RotateCW Icon](/docs/cesdk/_astro/RotateCW.BdtOYcHi_68cTD.svg) | | `@imgly/RotateCW90` | ![RotateCW90 Icon](/docs/cesdk/_astro/RotateCW90.Dr3zp4wc_68cTD.svg) | | `@imgly/Save` | ![Save Icon](/docs/cesdk/_astro/Save.C0V1utdu_68cTD.svg) | | `@imgly/Scale` | ![Scale Icon](/docs/cesdk/_astro/Scale.Ap3A7VA6_68cTD.svg) | | `@imgly/Search` | ![Search Icon](/docs/cesdk/_astro/Search.OK9LIXmS_68cTD.svg) | | `@imgly/Settings` | ![Settings Icon](/docs/cesdk/_astro/Settings.VScoKX0i_68cTD.svg) | | `@imgly/SettingsCog` | ![SettingsCog Icon](/docs/cesdk/_astro/SettingsCog.CHzkR7oC_68cTD.svg) | | `@imgly/ShapeOval` | ![ShapeOval Icon](/docs/cesdk/_astro/ShapeOval.CJRIxxg5_68cTD.svg) | | `@imgly/ShapePolygon` | ![ShapePolygon Icon](/docs/cesdk/_astro/ShapePolygon.BAIvfUsG_68cTD.svg) | | `@imgly/ShapeRectangle` | ![ShapeRectangle Icon](/docs/cesdk/_astro/ShapeRectangle.D_GcPdj4_68cTD.svg) | | `@imgly/ShapeStar` | ![ShapeStar Icon](/docs/cesdk/_astro/ShapeStar.ChTrfgau_68cTD.svg) | | `@imgly/Shapes` | ![Shapes Icon](/docs/cesdk/_astro/Shapes.bR8cU9cb_68cTD.svg) | | `@imgly/Share` | ![Share Icon](/docs/cesdk/_astro/Share.CxVmAh-Q_68cTD.svg) | | `@imgly/SidebarOpen` | ![SidebarOpen Icon](/docs/cesdk/_astro/SidebarOpen.DP_Nkz_M_68cTD.svg) | | `@imgly/Split` | ![Split Icon](/docs/cesdk/_astro/Split.CtHPO2rr_68cTD.svg) | | `@imgly/Sticker` | ![Sticker Icon](/docs/cesdk/_astro/Sticker.giyPqv3w_68cTD.svg) | | `@imgly/Stop` | ![Stop Icon](/docs/cesdk/_astro/Stop.Dk91ehJn_68cTD.svg) | | `@imgly/Straighten` | ![Straighten Icon](/docs/cesdk/_astro/Straighten.QTpTGn7c_68cTD.svg) | | `@imgly/StrokeDash` | ![StrokeDash Icon](/docs/cesdk/_astro/StrokeDash.G2iLl7Ql_68cTD.svg) | | `@imgly/StrokeDotted` | ![StrokeDotted Icon](/docs/cesdk/_astro/StrokeDotted.BNij6pGg_68cTD.svg) | | `@imgly/StrokePositionCenter` | ![StrokePositionCenter Icon](/docs/cesdk/_astro/StrokePositionCenter.DLGkhfZI_68cTD.svg) | | `@imgly/StrokePositionInside` | ![StrokePositionInside Icon](/docs/cesdk/_astro/StrokePositionInside.YLPd54Fr_68cTD.svg) | | `@imgly/StrokePositionOutside` | ![StrokePositionOutside Icon](/docs/cesdk/_astro/StrokePositionOutside.BToQBEp3_68cTD.svg) | | `@imgly/StrokeSolid` | ![StrokeSolid Icon](/docs/cesdk/_astro/StrokeSolid.BbQK_ttd_68cTD.svg) | | `@imgly/StrokeWeight` | ![StrokeWeight Icon](/docs/cesdk/_astro/StrokeWeight.p9i4NW18_68cTD.svg) | | `@imgly/Template` | ![Template Icon](/docs/cesdk/_astro/Template.DiqM4ifM_68cTD.svg) | | `@imgly/Text` | ![Text Icon](/docs/cesdk/_astro/Text.D20mu4op_68cTD.svg) | | `@imgly/TextAlignBottom` | ![TextAlignBottom Icon](/docs/cesdk/_astro/TextAlignBottom.Lrl-KvaG_68cTD.svg) | | `@imgly/TextAlignCenter` | ![TextAlignCenter Icon](/docs/cesdk/_astro/TextAlignCenter.GaFLxW6o_68cTD.svg) | | `@imgly/TextAlignLeft` | ![TextAlignLeft Icon](/docs/cesdk/_astro/TextAlignLeft.JPvbI_Iv_68cTD.svg) | | `@imgly/TextAlignMiddle` | ![TextAlignMiddle Icon](/docs/cesdk/_astro/TextAlignMiddle.Cc4CeA8h_68cTD.svg) | | `@imgly/TextAlignRight` | ![TextAlignRight Icon](/docs/cesdk/_astro/TextAlignRight.DtFvwWBW_68cTD.svg) | | `@imgly/TextAlignTop` | ![TextAlignTop Icon](/docs/cesdk/_astro/TextAlignTop.BlMx73E2_68cTD.svg) | | `@imgly/TextAutoHeight` | ![TextAutoHeight Icon](/docs/cesdk/_astro/TextAutoHeight.fSi8isZl_68cTD.svg) | | `@imgly/TextAutoSize` | ![TextAutoSize Icon](/docs/cesdk/_astro/TextAutoSize.Bp4copWt_68cTD.svg) | | `@imgly/TextBold` | ![TextBold Icon](/docs/cesdk/_astro/TextBold.DQnuwuNI_68cTD.svg) | | `@imgly/TextFixedSize` | ![TextFixedSize Icon](/docs/cesdk/_astro/TextFixedSize.8ULsWSkZ_68cTD.svg) | | `@imgly/TextItalic` | ![TextItalic Icon](/docs/cesdk/_astro/TextItalic.vCylf_bp_68cTD.svg) | | `@imgly/Timeline` | ![Timeline Icon](/docs/cesdk/_astro/Timeline.BZOGSaoW_68cTD.svg) | | `@imgly/ToggleIconOff` | ![ToggleIconOff Icon](/docs/cesdk/_astro/ToggleIconOff.tOLH9iio_68cTD.svg) | | `@imgly/ToggleIconOn` | ![ToggleIconOn Icon](/docs/cesdk/_astro/ToggleIconOn.C1vynusd_68cTD.svg) | | `@imgly/TransformSection` | ![TransformSection Icon](/docs/cesdk/_astro/TransformSection.Cd_1rtk4_68cTD.svg) | | `@imgly/TrashBin` | ![TrashBin Icon](/docs/cesdk/_astro/TrashBin.DIRoLX4y_68cTD.svg) | | `@imgly/TriangleDown` | ![TriangleDown Icon](/docs/cesdk/_astro/TriangleDown.qVuJ48qB_68cTD.svg) | | `@imgly/TriangleUp` | ![TriangleUp Icon](/docs/cesdk/_astro/TriangleUp.BZYFGYKM_68cTD.svg) | | `@imgly/TrimMedia` | ![TrimMedia Icon](/docs/cesdk/_astro/TrimMedia.BgO_0rmP_68cTD.svg) | | `@imgly/Typeface` | ![Typeface Icon](/docs/cesdk/_astro/Typeface.DqVOeIHT_68cTD.svg) | | `@imgly/Undo` | ![Undo Icon](/docs/cesdk/_astro/Undo.CPyRKh4X_68cTD.svg) | | `@imgly/Ungroup` | ![Ungroup Icon](/docs/cesdk/_astro/Ungroup.CFB9nh2f_68cTD.svg) | | `@imgly/Upload` | ![Upload Icon](/docs/cesdk/_astro/Upload.DXKuDONZ_68cTD.svg) | | `@imgly/Video` | ![Video Icon](/docs/cesdk/_astro/Video.DY9tRwlD_68cTD.svg) | | `@imgly/VideoCamera` | ![VideoCamera Icon](/docs/cesdk/_astro/VideoCamera.Dlw252lj_68cTD.svg) | | `@imgly/Volume` | ![Volume Icon](/docs/cesdk/_astro/Volume.DyYIA3JG_68cTD.svg) | | `@imgly/VolumeMute` | ![VolumeMute Icon](/docs/cesdk/_astro/VolumeMute.BPyJ4_Sl_68cTD.svg) | | `@imgly/ZoomIn` | ![ZoomIn Icon](/docs/cesdk/_astro/ZoomIn.BldHnJXW_68cTD.svg) | | `@imgly/ZoomOut` | ![ZoomOut Icon](/docs/cesdk/_astro/ZoomOut.CC-7T1Td_68cTD.svg) | ## Troubleshooting[#](#troubleshooting) ### Custom Icon Not Appearing[#](#custom-icon-not-appearing) If your custom icon doesn’t display: * Verify the symbol ID starts with `@` * Check that the SVG sprite syntax is valid XML * Confirm `addIconSet()` was called before using the icon * Inspect the browser console for SVG parsing errors ### Icon Not Scaling Correctly[#](#icon-not-scaling-correctly) If your icon renders at the wrong size: * Ensure the `viewBox` attribute is set on the symbol * Avoid hardcoding `width` and `height` attributes on the symbol * Verify the symbol uses relative coordinates within the viewBox ### Icon Color Not Matching Theme[#](#icon-color-not-matching-theme) If your icon doesn’t change color with the theme: * Use `currentColor` for `fill` and `stroke` values in SVG paths * Avoid hardcoded color values like `#000000` or `rgb()` * Check that `fillOpacity` and `strokeOpacity` use appropriate values for theme compatibility ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `cesdk.ui.addIconSet(id, svgSprite)` | Registers a custom SVG sprite icon set with the given identifier. The sprite should contain `` elements with IDs starting with `@`. | | `cesdk.ui.getDockOrder()` | Returns the current array of dock entries, each containing `id`, `key`, `label`, and `icon` properties. | | `cesdk.ui.setDockOrder(entries)` | Sets the dock order with the provided array of entries. Use this to modify icons or reorder dock items. | | `cesdk.ui.registerComponent(id, builder)` | Registers a custom UI component that can be used in menus, panels, and other UI locations. | | `cesdk.ui.setCanvasMenuOrder(entries)` | Sets the canvas menu entries that appear when users select elements on the canvas. | --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/appearance/custom-labels-7794f7) --- # Custom Labels Customize UI text labels in CE.SDK to match your brand voice and product terminology without changing the entire interface language. ![Custom Labels example showing CE.SDK with customized UI text](/docs/cesdk/_astro/browser.hero.CdaA72Ce_Z1gvBIK.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples)[ GitHub](https://github.com/imgly/cesdk-web-examples) ``` import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';import packageJson from './package.json'; /** * CE.SDK Plugin: Custom Labels Guide * * Demonstrates customizing UI text labels in CE.SDK: * - Overriding specific labels while keeping default locale * - Customizing action button labels (Export, Save, Delete, etc.) * - Changing navigation labels (Back, Close, Done) * - Modifying panel and component labels * - Runtime label updates * - Common customization scenarios for branding */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 features to demonstrate various UI labels cesdk.feature.enable('ly.img.fill'); cesdk.feature.enable('ly.img.adjustment'); cesdk.feature.enable('ly.img.layer'); cesdk.feature.enable('ly.img.settings'); // Load assets and create scene await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true }); await cesdk.createDesignScene(); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page dimensions const pageWidth = 800; const pageHeight = 600; engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); // ===== Custom Label Translations ===== // Apply all custom label translations in a single call // This demonstrates customizing multiple UI elements at once // Example 1: Undo Button Label // Visible in the navigation bar cesdk.i18n.setTranslations({ en: { 'common.undo': 'Revert' } }); // Example 2: Elements Dock Button // Visible in the dock/library panel cesdk.i18n.setTranslations({ en: { 'component.library.elements': 'Shapes' } }); // Example 3: Image Dock Button // Visible in the dock/library panel cesdk.i18n.setTranslations({ en: { 'libraries.ly.img.image.label': 'Photos' } }); // Example 4: Add Page Button // Visible in page management controls cesdk.i18n.setTranslations({ en: { 'action.page.add': 'New Page' } }); // Example 5: Preview Button // Visible in the navigation bar or view controls cesdk.i18n.setTranslations({ en: { 'common.mode.preview': 'View Mode' } }); // Create a single text block showing all customizations const textBlock = engine.block.create('text'); const labelText = `Custom Labels Applied: 1. "Undo" → "Revert"2. "Elements" → "Shapes"3. "Images" → "Photos"4. "Add Page" → "New Page"5. "Preview" → "View Mode" Check the navigation bar, dock, and menus to see these changes!`; engine.block.setString(textBlock, 'text/text', labelText); engine.block.setWidth(textBlock, pageWidth * 0.8); engine.block.setHeight(textBlock, pageHeight * 0.8); engine.block.setPositionX(textBlock, pageWidth * 0.1); engine.block.setPositionY(textBlock, pageHeight * 0.1); engine.block.setFloat(textBlock, 'text/fontSize', 30); engine.block.appendChild(page, textBlock); // To discover available translation keys: // 1. Download en.json from CDN: // https://cdn.img.ly/packages/imgly/cesdk-js/$VERSION$/assets/i18n/en.json // 2. Inspect UI with browser DevTools to find key names // 3. Check console logs when interacting with UI components // Select the text block to show it in the canvas engine.block.setSelected(textBlock, true); }} export default Example; ``` CreativeEditor SDK (CE.SDK) provides extensive customization of UI text through its internationalization system. Custom labels allow you to override specific UI text elements without changing the entire interface language. While the full localization system manages complete language translations, custom labels focus on individual text elements. You can customize button labels, menu items, tooltips, and other UI text to match your application’s terminology and brand voice. This guide demonstrates how to replace default labels like “Export” with “Download” or “Delete” with “Remove” to align the editor with your users’ expectations. ## Understanding Custom Labels[#](#understanding-custom-labels) Custom labels let you modify individual UI text strings while keeping the rest of the interface in its default language. You can change “Export” to “Download”, “Back” to “Close”, or “Delete” to “Remove” - adjusting terminology to match your users’ expectations and your product’s vocabulary. The CE.SDK uses a key-value translation system where each UI element has a unique translation key (e.g., `common.export`, `action.block.delete`). By providing custom values for these keys, you override the default text throughout the interface. ## Translation Key Structure[#](#translation-key-structure) Translation keys in CE.SDK follow a hierarchical naming convention: ``` category.component.property ``` **Common Categories:** * `action` - Action buttons and menu items (e.g., `action.block.delete`, `action.image.crop`) * `common` - Frequently used labels across the interface (e.g., `common.save`, `common.export`) * `panel` - Panel and inspector titles (e.g., `panel.inspector.title`, `panel.assetLibrary.title`) * `component` - Component-specific labels (e.g., `component.canvas.openLibrary`) * `input` - Form input labels and placeholders * `meta` - Special metadata keys (e.g., `meta.currentLanguage`) ## Customizing Undo Button Label[#](#customizing-undo-button-label) The undo button appears in the top navigation bar and is one of the most commonly used actions. Here we change “Undo” to “Revert” using the `cesdk.i18n.setTranslations()` API: ``` // Example 1: Undo Button Label// Visible in the navigation barcesdk.i18n.setTranslations({ en: { 'common.undo': 'Revert' }}); ``` The API accepts an object where the first key is the locale code (`en` for English), and the value is an object mapping translation keys to custom strings. The system merges your custom labels with the default translations, overriding only the keys you specify. ## Customizing Elements Dock Button[#](#customizing-elements-dock-button) The elements button appears in the dock/library panel and provides access to design elements. Here we change “Elements” to “Shapes”: ``` // Example 2: Elements Dock Button// Visible in the dock/library panelcesdk.i18n.setTranslations({ en: { 'component.library.elements': 'Shapes' }}); ``` This customization is immediately visible in the dock interface, making it clear that users can access shapes and design elements from this button. ## Customizing Image Dock Button[#](#customizing-image-dock-button) The image library button appears in the dock/library panel. Here we change “Images” to “Photos”: ``` // Example 3: Image Dock Button// Visible in the dock/library panelcesdk.i18n.setTranslations({ en: { 'libraries.ly.img.image.label': 'Photos' }}); ``` This change uses the library-specific translation key to customize how the image library appears in the dock, aligning with consumer-friendly terminology. ## Customizing Add Page Button[#](#customizing-add-page-button) The add page action appears in page management controls. Here we change “Add Page” to “New Page”: ``` // Example 4: Add Page Button// Visible in page management controlscesdk.i18n.setTranslations({ en: { 'action.page.add': 'New Page' }}); ``` This customization affects page management buttons and actions, providing clearer terminology for creating new pages in multi-page designs. ## Customizing Preview Button[#](#customizing-preview-button) The preview button appears in the navigation bar or view controls. Here we change “Preview” to “View Mode”: ``` // Example 5: Preview Button// Visible in the navigation bar or view controlscesdk.i18n.setTranslations({ en: { 'common.mode.preview': 'View Mode' }}); ``` This change affects the preview/view mode button, making it clearer that users are switching to a different viewing mode rather than just previewing their work. ## Discovering Available Labels[#](#discovering-available-labels) To find which labels you can customize, CE.SDK provides several approaches: **1\. Download the English Translation File** The complete list of translation keys is available in the English translation file hosted on CE.SDK’s CDN: ``` https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/i18n/en.json ``` This JSON file contains all translation keys with their default English values. You can search for specific labels or browse categories to find the keys you need. **2\. Inspect UI with Browser DevTools** Use browser developer tools to inspect UI elements and identify their translation keys: ``` // To discover available translation keys:// 1. Download en.json from CDN:// https://cdn.img.ly/packages/imgly/cesdk-js/$VERSION$/assets/i18n/en.json// 2. Inspect UI with browser DevTools to find key names// 3. Check console logs when interacting with UI components ``` The console logs and network requests often reveal translation key names when you interact with UI components. **3\. Reference the Translation Key Categories** Common patterns for finding translation keys: * **Action buttons**: Look for `action.{component}.{operation}` keys * **Common UI**: Check `common.{label}` keys for frequently used text * **Panel titles**: Use `panel.{panelName}.title` keys * **Component labels**: Search for `component.{componentName}.{property}` keys ## Best Practices[#](#best-practices) **Override Only What You Need** The translation system merges your custom labels with defaults, so you only need to specify the keys you want to change. Don’t copy the entire translation file - provide only the overrides you need: ``` // ✅ Good: Override only specific labelscesdk.i18n.setTranslations({ en: { 'common.undo': 'Revert', 'component.library.elements': 'Shapes' }}); // ❌ Avoid: Duplicating entire translation file// This creates maintenance burden and makes updates difficult ``` **Maintain Consistency Across Related Labels** When customizing labels, consider related operations and ensure consistent terminology: ``` cesdk.i18n.setTranslations({ en: { // Consistent library terminology across all dock buttons 'component.library.elements': 'Shapes', 'libraries.ly.img.image.label': 'Photos', 'libraries.ly.img.text.label': 'Typography', 'libraries.ly.img.sticker.label': 'Graphics' }}); ``` **Test Labels in Context** UI labels have length constraints based on their location. Test your custom labels in the actual interface to ensure they fit properly: * **Button labels**: Keep concise (1-2 words) * **Menu items**: Can be slightly longer (2-4 words) * **Tooltips**: Can include brief descriptions * **Panel titles**: Should be clear but compact **Document Your Custom Label Mappings** Maintain documentation of your custom label mappings for team reference: ``` /** * Custom Labels Configuration * * Brand-aligned terminology for immediately visible UI elements: * - "Undo" → "Revert" (navigation bar - clearer action terminology) * - "Elements" → "Shapes" (dock button - more specific) * - "Images" → "Photos" (dock button - consumer-friendly) * - "Add Page" → "New Page" (page controls - clearer action) */cesdk.i18n.setTranslations({ en: { 'common.undo': 'Revert', 'component.library.elements': 'Shapes', 'libraries.ly.img.image.label': 'Photos', 'action.page.add': 'New Page' }}); ``` ## Custom Labels vs. Full Localization[#](#custom-labels-vs-full-localization) Custom labels and full localization serve different purposes: **Custom Labels** (this guide): * Override specific UI text elements * Keep the default language (usually English) * Match your brand terminology * Refine specific translations * Use case: Branding, terminology alignment, UX refinement **Full Localization** ( [see Localization guide](vue/user-interface/localization-508e20/) ): * Switch entire interface to different languages * Provide complete translation sets * Support multiple language locales * Use case: International markets, multilingual users You can combine both approaches - set a locale for the primary language, then override specific labels for branding: ``` // Set German as the primary localecesdk.i18n.setLocale('de'); // Override specific labels even in Germancesdk.i18n.setTranslations({ de: { 'common.export': 'Herunterladen' // Custom German label }}); ``` ## API Reference[#](#api-reference) | Method | Description | | --- | --- | | `cesdk.i18n.setTranslations(definition)` | Sets or overrides UI text labels for specific locales | | `cesdk.i18n.getLocale()` | Gets the current interface locale | | `cesdk.i18n.setLocale(locale)` | Sets the interface language to a different locale | ### cesdk.i18n.setTranslations()[#](#cesdki18nsettranslations) **Signature:** ``` setTranslations(definition: { [locale: string]: object }): void ``` **Parameters:** * `definition` - Object mapping locale codes (e.g., `en`, `de`) to translation objects. Each translation object maps translation keys to custom strings. **Behavior:** * Merges with existing translations, overriding only the keys you specify * Changes apply immediately throughout the interface * Persists until changed or cleared * Does not reset when switching scenes **Example:** ``` cesdk.i18n.setTranslations({ en: { 'common.undo': 'Revert', 'libraries.ly.img.image.label': 'Photos' }}); ``` ### Common Translation Keys[#](#common-translation-keys) | Translation Key | Description | | --- | --- | | `common.undo` | Undo button label in navigation bar | | `common.redo` | Redo button label in navigation bar | | `common.export` | Export button label in navigation bar | | `common.mode.preview` | Preview/view mode button label | | `component.library.elements` | Elements library button in dock | | `libraries.ly.img.image.label` | Image library button in dock | | `libraries.ly.img.text.label` | Text library button in dock | | `libraries.ly.img.sticker.label` | Sticker library button in dock | | `action.block.duplicate` | Duplicate element action label | | `action.block.delete` | Delete element action label | | `action.page.add` | Add page button label | | `action.page.duplicate` | Duplicate page action label | | `action.page.delete` | Delete page action label | ### Discovering Translation Keys[#](#discovering-translation-keys) **Translation Files:** * English: `https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/i18n/en.json` * German: `https://cdn.img.ly/packages/imgly/cesdk-js/1.67.0/assets/i18n/de.json` **Special Syntax:** * `$t(key)` - References another translation key (e.g., `"action.block.delete": "$t(common.delete)"` uses the value of `common.delete`) ## Next Steps[#](#next-steps) * [Localization](vue/user-interface/localization-508e20/) \- Learn about managing multiple languages in CE.SDK * [Theming](vue/user-interface/appearance/theming-4b0938/) \- Customize the visual appearance of the interface * [Custom UI Components](vue/user-interface/ui-extensions-d194d1/) \- Build custom UI elements for your workflow --- [Source](https:/img.ly/docs/cesdk/vue/user-interface/appearance/change-ui-font-49b6fc) --- # Change UI Font Customize the font family used throughout the CE.SDK editor interface to match your application’s branding. ![CE.SDK editor with monospace font applied to all UI panels, buttons, and controls](/docs/cesdk/_astro/browser.hero.BwtTBMsW_Z1CVfdp.webp) 10 mins estimated time Live Demo[ Download](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip)[ StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-appearance-change-ui-font-browser)[ GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-appearance-change-ui-font-browser) CE.SDK’s UI uses CSS custom properties for typography, allowing you to apply a custom font to all editor elements—panels, buttons, labels, and inputs—through a single CSS variable. ``` import type CreativeEditorSDK from '@cesdk/cesdk-js'; export async function initialize(cesdk: CreativeEditorSDK) { // Create a design scene to showcase the UI font customization await cesdk.createDesignScene(); // Add a text block to demonstrate the design canvas const page = cesdk.engine.block.findByType('page')[0]; const text = cesdk.engine.block.create('text'); cesdk.engine.block.setString(text, 'text/text', 'Monospace UI Font'); cesdk.engine.block.setWidth(text, 400); cesdk.engine.block.setPositionX(text, cesdk.engine.block.getWidth(page) / 2 - 200); cesdk.engine.block.setPositionY(text, cesdk.engine.block.getHeight(page) / 2 - 50); cesdk.engine.block.appendChild(page, text); // You can verify the current theme and scale settings const currentTheme = cesdk.ui.getTheme(); // 'light' or 'dark' const currentScale = cesdk.ui.getScale(); // 'normal', 'large', or 'modern' console.log('Current theme:', currentTheme); console.log('Current scale:', currentScale); // Optional: Change theme to see font in different contexts // Uncomment to test: // cesdk.ui.setTheme('dark'); // cesdk.ui.setScale('large'); // Zoom to fit the page await cesdk.engine.scene.zoomToBlock(page);} ``` This guide covers loading custom fonts from various sources, setting the CSS variable, verifying the application, and troubleshooting common issues. ## Understanding the UI Theming System[#](#understanding-the-ui-theming-system) CE.SDK’s typography uses CSS custom properties scoped to `.ubq-public` with theme and scale attributes. The `--ubq-typography-font_family` variable controls all UI elements, providing centralized font customization. All UI text inherits from this single variable. When you set `--ubq-typography-font_family`, panels, buttons, dropdowns, inputs, and labels all update to use your chosen font. ## Loading Custom Fonts[#](#loading-custom-fonts) Before setting the CSS variable, fonts must be available in the browser. You can use system fonts, self-host font files, load from Google Fonts, or use other providers. ### Using Self-Hosted Fonts[#](#using-self-hosted-fonts) Define `@font-face` rules in your CSS with woff2 format for modern browsers and woff fallback: ``` @font-face { font-family: 'CustomFont'; src: url('/fonts/CustomFont-Regular.woff2') format('woff2'), url('/fonts/CustomFont-Regular.woff') format('woff'); font-weight: 400; font-style: normal;} @font-face { font-family: 'CustomFont'; src: url('/fonts/CustomFont-SemiBold.woff2') format('woff2'), url('/fonts/CustomFont-SemiBold.woff') format('woff'); font-weight: 600; font-style: normal;} ``` CE.SDK uses font weights 450 and 600 throughout the interface. Load these weights or the closest alternatives (400 and 600 work well). ### Using Google Fonts[#](#using-google-fonts) Add `` tags to your HTML `` to load fonts from Google Fonts CDN. Place these before CE.SDK initialization to prevent flash of unstyled text. ``` ``` The `preconnect` links establish early connections to Google’s servers, reducing latency. The main font link loads Roboto with multiple weights. The `display=swap` parameter shows fallback text immediately while the custom font loads. ### Using Adobe Fonts or Other Providers[#](#using-adobe-fonts-or-other-providers) Load fonts according to the provider’s documentation, typically via `` or `