---
title: "Animation"
description: "Add motion to designs with support for keyframes, timeline editing, and programmatic animation control."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/animation-ce900c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/node-native/animation-ce900c/)
---
---
## Related Pages
- [Supported Animation Types](https://img.ly/docs/cesdk/node-native/animation/types-4e5f41/) - Apply different animation types to design blocks in CE.SDK and configure their properties in server-side applications.
- [Create Animations](https://img.ly/docs/cesdk/node-native/animation/create-15cf50/) - Build animations manually or with presets to animate objects, text, and scenes within your design.
- [Edit Animations](https://img.ly/docs/cesdk/node-native/animation/edit-32c12a/) - Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Animations"
description: "Build animations manually or with presets to animate objects, text, and scenes within your design."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/animation/create-15cf50/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/node-native/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/node-native/animation/create-15cf50/)
---
Add motion to design elements by creating entrance, exit, and loop animations using CE.SDK's animation system.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-animation-create-server-js)
CE.SDK provides a unified animation system for adding motion to design elements. Animations are created as separate block instances and attached to target blocks using type-specific methods. You can apply entrance animations (how blocks appear), exit animations (how blocks leave), and loop animations (continuous motion while visible). Text blocks support additional properties for word-by-word or character-by-character reveals.
```typescript file=@cesdk_web_examples/guides-animation-create-server-js/server-js.ts reference-only
/**
* CE.SDK Server Guide: Create Animations
*
* 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
* - Saving the scene to a .scene file
*/
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { mkdirSync, writeFileSync } from 'fs';
// Load environment variables from .env file
config();
async function main(): Promise {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// 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 }
]);
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 = engine.block.create('graphic');
engine.block.setShape(logoBlock, engine.block.createShape('rect'));
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.setWidth(logoBlock, 200);
engine.block.setHeight(logoBlock, 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);
// Save the scene to a .scene file
mkdirSync('output', { recursive: true });
const sceneString = await engine.scene.saveToString();
writeFileSync('output/animated-scene.scene', sceneString);
console.log('Scene saved to output/animated-scene.scene');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers how to create and configure animations programmatically, including entrance, exit, loop, and text animations with customizable timing and easing.
## Animation Fundamentals
We first verify that a block supports animations before creating and attaching them. The basic pattern involves creating an animation instance with `engine.block.createAnimation()`, then attaching it using the appropriate setter method.
```typescript highlight-check-support
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Create Animations');
engine.block.setTextFontSize(titleBlock, 120);
engine.block.setTextColor(titleBlock, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.appendChild(page, titleBlock);
engine.block.setDuration(titleBlock, 5.0);
// Check if block supports animations before applying
if (engine.block.supportsAnimation(titleBlock)) {
// Create an entrance animation
const slideIn = engine.block.createAnimation('slide');
engine.block.setInAnimation(titleBlock, slideIn);
engine.block.setDuration(slideIn, 1.2);
engine.block.setFloat(
slideIn,
'animation/slide/direction',
(3 * Math.PI) / 2
);
engine.block.setEnum(slideIn, 'animationEasing', 'EaseOut');
}
```
Animation support is available for:
- **Graphic blocks** with image or video fills
- **Text blocks** with additional writing style options
- **Shape blocks** with fills
CE.SDK provides several animation presets:
- **Entrance animations**: slide, fade, blur, zoom, pop, wipe, pan
- **Exit animations**: same types as entrance
- **Loop animations**: breathing\_loop, spin\_loop, fade\_loop, pulsating\_loop, jump\_loop, squeeze\_loop, sway\_loop
## Entrance Animations
Entrance animations define how blocks appear on screen. We create the animation with `engine.block.createAnimation()`, attach it with `engine.block.setInAnimation()`, and configure the duration with `engine.block.setDuration()`.
```typescript highlight-entrance-animation
// Add fade entrance for the logo
const logoFadeIn = engine.block.createAnimation('fade');
engine.block.setInAnimation(logoBlock, logoFadeIn);
engine.block.setDuration(logoFadeIn, 0.8);
engine.block.setEnum(logoFadeIn, 'animationEasing', 'EaseOut');
```
The `animationEasing` property controls the animation curve. Available options include Linear, EaseIn, EaseOut, and EaseInOut.
## Exit Animations
Exit animations define how blocks leave the screen. We attach them with `engine.block.setOutAnimation()`. CE.SDK manages timing automatically to prevent overlap between entrance and exit animations.
```typescript highlight-exit-animation
const footerBlock = engine.block.create('text');
engine.block.replaceText(footerBlock, 'Powered by CE.SDK');
engine.block.setTextFontSize(footerBlock, 32);
engine.block.setTextColor(footerBlock, { r: 1.0, g: 1.0, b: 1.0, a: 0.7 });
engine.block.setEnum(footerBlock, 'text/horizontalAlignment', 'Right');
engine.block.setWidthMode(footerBlock, 'Auto');
engine.block.setHeightMode(footerBlock, 'Auto');
engine.block.appendChild(page, footerBlock);
// Footer appears at start and fades out at the end
engine.block.setTimeOffset(footerBlock, 0);
engine.block.setDuration(footerBlock, 5.0);
// Create exit animation that plays at the end of the block's duration
const fadeOut = engine.block.createAnimation('fade');
engine.block.setOutAnimation(footerBlock, fadeOut);
engine.block.setDuration(fadeOut, 1.0);
engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn');
```
When a block has both entrance and exit animations, CE.SDK adjusts their timing based on the block's duration in the composition.
## Loop Animations
Loop animations run continuously while the block is visible. We use animation types ending in `_loop` and attach them with `engine.block.setLoopAnimation()`.
```typescript highlight-loop-animation
const logoBlock = engine.block.create('graphic');
engine.block.setShape(logoBlock, engine.block.createShape('rect'));
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.setWidth(logoBlock, 200);
engine.block.setHeight(logoBlock, 200);
engine.block.appendChild(page, logoBlock);
engine.block.setDuration(logoBlock, 5.0);
// Contain the image within the block frame
engine.block.setEnum(logoBlock, 'contentFill/mode', 'Contain');
// Create a pulsating loop animation
const pulsating = engine.block.createAnimation('pulsating_loop');
engine.block.setLoopAnimation(logoBlock, pulsating);
engine.block.setDuration(pulsating, 1.5);
```
Loop animations continue throughout the block's visible duration, creating continuous motion effects like breathing, spinning, or pulsating.
## Animation Properties
Each animation type exposes configurable properties. We use `engine.block.setFloat()` and `engine.block.setEnum()` to adjust these properties, and `engine.block.findAllProperties()` to discover available options.
```typescript highlight-animation-properties
// Create slide animation and configure direction for title
const titleInAnim = engine.block.getInAnimation(titleBlock);
if (titleInAnim !== 0) {
// Discover all available properties for this animation
const properties = engine.block.findAllProperties(titleInAnim);
console.log('Slide animation properties:', properties);
}
```
Common configurable properties include:
- **Direction**: Set in radians for slide animations (0=right, PI/2=bottom, PI=left, 3\*PI/2=top)
- **Easing**: Linear, EaseIn, EaseOut, EaseInOut
## Text Animations
Text blocks support additional animation properties for granular control over how text appears. The `textAnimationWritingStyle` property controls whether the animation applies to the entire text, line by line, word by word, or character by character.
```typescript highlight-text-animation
const subtitleBlock = engine.block.create('text');
engine.block.replaceText(subtitleBlock, 'Entrance • Exit • Loop');
engine.block.setTextFontSize(subtitleBlock, 48);
engine.block.setTextColor(subtitleBlock, {
r: 0.9,
g: 0.9,
b: 1.0,
a: 0.9
});
engine.block.setEnum(subtitleBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
engine.block.setDuration(subtitleBlock, 5.0);
// Create text animation with word-by-word reveal
const textAnim = engine.block.createAnimation('fade');
engine.block.setInAnimation(subtitleBlock, textAnim);
engine.block.setDuration(textAnim, 1.5);
// Configure text animation writing style (Line, Word, or Character)
engine.block.setEnum(textAnim, 'textAnimationWritingStyle', 'Word');
// Set overlap for cascading effect (0 = sequential, 0-1 = cascading)
engine.block.setFloat(textAnim, 'textAnimationOverlap', 0.3);
```
Writing style options:
- **Line**: Animate entire lines together
- **Word**: Animate word by word
- **Character**: Animate character by character
The `textAnimationOverlap` property (0 to 1) controls the cascading effect. A value of 0 means sequential animation, while values closer to 1 create more overlap between segments.
## Managing Animation Lifecycle
We can retrieve current animations using `engine.block.getInAnimation()`, `engine.block.getOutAnimation()`, and `engine.block.getLoopAnimation()`. A return value of 0 indicates no animation is attached.
```typescript highlight-manage-lifecycle
// Example: Retrieve animations to verify they're attached
const currentTitleIn = engine.block.getInAnimation(titleBlock);
const currentLogoLoop = engine.block.getLoopAnimation(logoBlock);
const currentFooterOut = engine.block.getOutAnimation(footerBlock);
console.log(
'Animation IDs - Title In:',
currentTitleIn,
'Logo Loop:',
currentLogoLoop,
'Footer Out:',
currentFooterOut
);
// Get available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
```
When replacing animations, destroy the old instance with `engine.block.destroy()` to prevent memory leaks.
## Saving the Scene
After creating animations, we save the scene to a `.scene` file for later use or distribution. The `engine.scene.saveToString()` method serializes the scene to a string that can be written to a file.
```typescript highlight-export
// Save the scene to a .scene file
mkdirSync('output', { recursive: true });
const sceneString = await engine.scene.saveToString();
writeFileSync('output/animated-scene.scene', sceneString);
console.log('Scene saved to output/animated-scene.scene');
```
The saved `.scene` file can be loaded later using `engine.scene.loadFromString()` or `engine.scene.loadFromURL()`.
## Troubleshooting
### Animation Not Playing
Verify the block supports animations with `engine.block.supportsAnimation()`. Ensure playback is active on the page.
### Duration Issues
Ensure the animation is attached to a block before setting its duration. Duration is set on the animation instance, not the block.
### Memory Leaks
When replacing an animation, destroy the old animation instance before creating a new one:
```typescript
const current = engine.block.getInAnimation(block);
if (current !== 0) {
engine.block.destroy(current);
}
const newAnim = engine.block.createAnimation('fade');
engine.block.setInAnimation(block, newAnim);
```
### Timing Conflicts
If entrance and exit animations seem to overlap incorrectly, CE.SDK automatically adjusts durations to prevent conflicts. Reduce individual animation durations if needed.
## API Reference
| Method | Description |
| --------------------------------------------- | ------------------------------------------ |
| `engine.block.createAnimation(type)` | Create animation instance |
| `engine.block.supportsAnimation(block)` | Check if block supports animations |
| `engine.block.setInAnimation(block, anim)` | Attach entrance animation |
| `engine.block.setOutAnimation(block, anim)` | Attach exit animation |
| `engine.block.setLoopAnimation(block, anim)` | Attach loop animation |
| `engine.block.getInAnimation(block)` | Get entrance animation (0 if none) |
| `engine.block.getOutAnimation(block)` | Get exit animation (0 if none) |
| `engine.block.getLoopAnimation(block)` | Get loop animation (0 if none) |
| `engine.block.setDuration(anim, seconds)` | Set animation duration |
| `engine.block.setEnum(anim, prop, value)` | Set enum property (easing, writing style) |
| `engine.block.setFloat(anim, prop, value)` | Set float property (direction, overlap) |
| `engine.block.findAllProperties(anim)` | List available properties |
| `engine.block.getEnumValues(prop)` | Get enum options |
| `engine.block.destroy(anim)` | Destroy animation instance |
| `engine.scene.saveToString()` | Save scene to string |
---
## Related Pages
- [Base Animations](https://img.ly/docs/cesdk/node-native/animation/create/base-0fc5c4/) - Apply movement, scaling, rotation, or opacity changes to elements using time-based keyframes.
- [Text Animations](https://img.ly/docs/cesdk/node-native/animation/create/text-d6f4aa/) - Animate text elements with effects like fade, typewriter, and bounce for dynamic visual presentation.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Base Animations"
description: "Apply movement, scaling, rotation, or opacity changes to elements using time-based keyframes."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/animation/create/base-0fc5c4/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/node-native/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/node-native/animation/create-15cf50/) > [Base Animations](https://img.ly/docs/cesdk/node-native/animation/create/base-0fc5c4/)
---
Add motion to design blocks with entrance, exit, and loop animations using CE.SDK's animation system.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-base-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-animation-create-base-server-js)
Base animations in CE.SDK add motion to design blocks through entrance (In), exit (Out), and loop animations. Animations are created as separate objects and attached to blocks, enabling reusable configurations across multiple elements.
```typescript file=@cesdk_web_examples/guides-animation-create-base-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
// Helper to create an image block
const createImageBlock = (
x: number,
y: number,
width: number,
height: 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, width);
engine.block.setHeight(graphic, height);
engine.block.setPositionX(graphic, x);
engine.block.setPositionY(graphic, y);
engine.block.appendChild(page, graphic);
return graphic;
};
// Layout configuration
const margin = 100;
const spacing = 50;
const blockWidth = 500;
const blockHeight = 350;
// Sample images
const imageUrls = [
'https://img.ly/static/ubq_samples/sample_1.jpg',
'https://img.ly/static/ubq_samples/sample_2.jpg',
'https://img.ly/static/ubq_samples/sample_3.jpg',
'https://img.ly/static/ubq_samples/sample_4.jpg',
'https://img.ly/static/ubq_samples/sample_5.jpg',
'https://img.ly/static/ubq_samples/sample_6.jpg'
];
// Block 1: Check animation support and create entrance animation
const block1 = createImageBlock(
margin,
margin,
blockWidth,
blockHeight,
imageUrls[0]
);
// Check if block supports animations before applying
if (engine.block.supportsAnimation(block1)) {
// Create an entrance animation
const slideAnimation = engine.block.createAnimation('slide');
engine.block.setInAnimation(block1, slideAnimation);
engine.block.setDuration(slideAnimation, 1.0);
}
// Block 2: Entrance animation with easing configuration
const block2 = createImageBlock(
margin + blockWidth + spacing,
margin,
blockWidth,
blockHeight,
imageUrls[1]
);
// Create a fade entrance animation with easing
const fadeInAnimation = engine.block.createAnimation('fade');
engine.block.setInAnimation(block2, fadeInAnimation);
engine.block.setDuration(fadeInAnimation, 1.0);
engine.block.setEnum(fadeInAnimation, 'animationEasing', 'EaseOut');
// Block 3: Exit animation
const block3 = createImageBlock(
margin + 2 * (blockWidth + spacing),
margin,
blockWidth,
blockHeight,
imageUrls[2]
);
// Create both entrance and exit animations
const zoomInAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block3, zoomInAnimation);
engine.block.setDuration(zoomInAnimation, 1.0);
const fadeOutAnimation = engine.block.createAnimation('fade');
engine.block.setOutAnimation(block3, fadeOutAnimation);
engine.block.setDuration(fadeOutAnimation, 1.0);
engine.block.setEnum(fadeOutAnimation, 'animationEasing', 'EaseIn');
// Block 4: Loop animation
const block4 = createImageBlock(
margin,
margin + blockHeight + spacing,
blockWidth,
blockHeight,
imageUrls[3]
);
// Create a breathing loop animation
const breathingLoop = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block4, breathingLoop);
engine.block.setDuration(breathingLoop, 1.0);
// Block 5: Animation properties and slide direction
const block5 = createImageBlock(
margin + blockWidth + spacing,
margin + blockHeight + spacing,
blockWidth,
blockHeight,
imageUrls[4]
);
// Create slide animation and configure direction
const slideFromTop = engine.block.createAnimation('slide');
engine.block.setInAnimation(block5, slideFromTop);
engine.block.setDuration(slideFromTop, 1.0);
// Set slide direction (in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top)
engine.block.setFloat(
slideFromTop,
'animation/slide/direction',
Math.PI / 2
);
engine.block.setEnum(slideFromTop, 'animationEasing', 'EaseInOut');
// Discover all available properties for this animation
const properties = engine.block.findAllProperties(slideFromTop);
console.log('Slide animation properties:', properties);
// Block 6: Get animations and replace them
const block6 = createImageBlock(
margin + 2 * (blockWidth + spacing),
margin + blockHeight + spacing,
blockWidth,
blockHeight,
imageUrls[5]
);
// Set initial animations
const initialIn = engine.block.createAnimation('pan');
engine.block.setInAnimation(block6, initialIn);
engine.block.setDuration(initialIn, 1.0);
const spinLoop = engine.block.createAnimation('spin_loop');
engine.block.setLoopAnimation(block6, spinLoop);
engine.block.setDuration(spinLoop, 1.0);
// Get current animations
const currentIn = engine.block.getInAnimation(block6);
const currentLoop = engine.block.getLoopAnimation(block6);
const currentOut = engine.block.getOutAnimation(block6);
console.log(
'Animation IDs - In:',
currentIn,
'Loop:',
currentLoop,
'Out:',
currentOut
);
// Replace in animation (destroy old one first to avoid memory leaks)
if (currentIn !== 0) {
engine.block.destroy(currentIn);
}
const newInAnimation = engine.block.createAnimation('wipe');
engine.block.setInAnimation(block6, newInAnimation);
engine.block.setDuration(newInAnimation, 1.0);
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
// Save the scene with all animations to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const sceneString = await engine.scene.saveToString();
writeFileSync(`${outputDir}/base-animations.scene`, sceneString);
console.log('Saved scene to output/base-animations.scene');
console.log('Base Animations example completed successfully');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers creating animations, attaching them to blocks, configuring properties like duration and easing, and managing animation lifecycle.
## Initialize the Engine
Set up the headless Creative Engine for server-side animation operations.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
```
## Animation Fundamentals
Before applying animations to a block, we verify it supports them using `supportsAnimation()`. Once confirmed, we create an animation instance and attach it to the block.
```typescript highlight=highlight-supports-animation
// Check if block supports animations before applying
if (engine.block.supportsAnimation(block1)) {
// Create an entrance animation
const slideAnimation = engine.block.createAnimation('slide');
engine.block.setInAnimation(block1, slideAnimation);
engine.block.setDuration(slideAnimation, 1.0);
}
```
We use `createAnimation()` with an animation type like `'slide'`, `'fade'`, or `'zoom'`. The animation is then attached using `setInAnimation()` for entrance animations. Duration is set with `setDuration()` in seconds.
CE.SDK provides several animation types:
- **Entrance animations**: `slide`, `fade`, `blur`, `grow`, `zoom`, `pop`, `wipe`, `pan`, `baseline`, `spin`
- **Loop animations**: `spin_loop`, `fade_loop`, `blur_loop`, `pulsating_loop`, `breathing_loop`, `jump_loop`, `squeeze_loop`, `sway_loop`
## Entrance Animations
Entrance animations (In animations) define how a block appears on screen. We create the animation, attach it with `setInAnimation()`, and configure its properties.
```typescript highlight=highlight-entrance-animation
// Create a fade entrance animation with easing
const fadeInAnimation = engine.block.createAnimation('fade');
engine.block.setInAnimation(block2, fadeInAnimation);
engine.block.setDuration(fadeInAnimation, 1.0);
engine.block.setEnum(fadeInAnimation, 'animationEasing', 'EaseOut');
```
We use `setEnum()` to configure the easing function. Available easing options include `'Linear'`, `'EaseIn'`, `'EaseOut'`, and `'EaseInOut'`. The `'EaseOut'` easing starts fast and slows down toward the end, creating a natural deceleration effect.
## Exit Animations
Exit animations (Out animations) define how a block leaves the screen. We use `setOutAnimation()` to attach them.
```typescript highlight=highlight-exit-animation
// Create both entrance and exit animations
const zoomInAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block3, zoomInAnimation);
engine.block.setDuration(zoomInAnimation, 1.0);
const fadeOutAnimation = engine.block.createAnimation('fade');
engine.block.setOutAnimation(block3, fadeOutAnimation);
engine.block.setDuration(fadeOutAnimation, 1.0);
engine.block.setEnum(fadeOutAnimation, 'animationEasing', 'EaseIn');
```
When using both entrance and exit animations, CE.SDK automatically manages their timing to prevent overlap. Changing the duration of an In animation may adjust the Out animation's duration to maintain valid timing.
## Loop Animations
Loop animations run continuously while the block is visible. We use `setLoopAnimation()` to attach them.
```typescript highlight=highlight-loop-animation
// Create a breathing loop animation
const breathingLoop = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block4, breathingLoop);
engine.block.setDuration(breathingLoop, 1.0);
```
The duration for loop animations defines the length of each cycle. A 1-second breathing loop will complete one full pulse every second.
## Animation Properties
Each animation type has specific configurable properties. We use `findAllProperties()` to discover available properties for an animation.
```typescript highlight=highlight-animation-properties
// Create slide animation and configure direction
const slideFromTop = engine.block.createAnimation('slide');
engine.block.setInAnimation(block5, slideFromTop);
engine.block.setDuration(slideFromTop, 1.0);
// Set slide direction (in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top)
engine.block.setFloat(
slideFromTop,
'animation/slide/direction',
Math.PI / 2
);
engine.block.setEnum(slideFromTop, 'animationEasing', 'EaseInOut');
// Discover all available properties for this animation
const properties = engine.block.findAllProperties(slideFromTop);
console.log('Slide animation properties:', properties);
```
For slide animations, the `animation/slide/direction` property controls the entry direction in radians:
- `0` — From the right
- `Math.PI / 2` — From the bottom
- `Math.PI` — From the left
- `3 * Math.PI / 2` — From the top
## Managing Animation Lifecycle
Animation objects must be properly managed to avoid memory leaks. When replacing an animation, we destroy the old one before setting the new one. We can retrieve current animations using `getInAnimation()`, `getOutAnimation()`, and `getLoopAnimation()`.
```typescript highlight=highlight-manage-animations
// Set initial animations
const initialIn = engine.block.createAnimation('pan');
engine.block.setInAnimation(block6, initialIn);
engine.block.setDuration(initialIn, 1.0);
const spinLoop = engine.block.createAnimation('spin_loop');
engine.block.setLoopAnimation(block6, spinLoop);
engine.block.setDuration(spinLoop, 1.0);
// Get current animations
const currentIn = engine.block.getInAnimation(block6);
const currentLoop = engine.block.getLoopAnimation(block6);
const currentOut = engine.block.getOutAnimation(block6);
console.log(
'Animation IDs - In:',
currentIn,
'Loop:',
currentLoop,
'Out:',
currentOut
);
// Replace in animation (destroy old one first to avoid memory leaks)
if (currentIn !== 0) {
engine.block.destroy(currentIn);
}
const newInAnimation = engine.block.createAnimation('wipe');
engine.block.setInAnimation(block6, newInAnimation);
engine.block.setDuration(newInAnimation, 1.0);
```
A return value of `0` indicates no animation is attached. Destroying a design block also destroys all its attached animations, but detached animations must be destroyed manually.
## Easing Functions
We can query available easing options using `getEnumValues()`.
```typescript highlight=highlight-easing-options
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
```
Easing functions control animation acceleration:
| Easing | Description |
| ----------- | ----------------------------------------------- |
| `Linear` | Constant speed throughout |
| `EaseIn` | Starts slow, accelerates toward the end |
| `EaseOut` | Starts fast, decelerates toward the end |
| `EaseInOut` | Starts slow, speeds up, then slows down again |
## Save and Cleanup
After applying animations, we save the scene to preserve all animation configurations. The scene file can be loaded later for further editing or rendering. Always dispose of the engine when done to release resources.
```typescript highlight=highlight-export
// Save the scene with all animations to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const sceneString = await engine.scene.saveToString();
writeFileSync(`${outputDir}/base-animations.scene`, sceneString);
console.log('Saved scene to output/base-animations.scene');
```
```typescript highlight=highlight-cleanup
engine.dispose();
```
## API Reference
| Method | Description |
| ---------------------------- | -------------------------------------------------- |
| `createAnimation(type)` | Create a new animation instance |
| `supportsAnimation(block)` | Check if block supports animations |
| `setInAnimation(block, anim)`| Apply entrance animation to block |
| `setOutAnimation(block, anim)` | Apply exit animation to block |
| `setLoopAnimation(block, anim)` | Apply loop animation to block |
| `getInAnimation(block)` | Get entrance animation (returns 0 if none) |
| `getOutAnimation(block)` | Get exit animation (returns 0 if none) |
| `getLoopAnimation(block)` | Get loop animation (returns 0 if none) |
| `setDuration(anim, seconds)` | Set animation duration |
| `getDuration(anim)` | Get animation duration |
| `setEnum(anim, prop, value)` | Set enum property (easing, etc.) |
| `setFloat(anim, prop, value)`| Set float property (direction, etc.) |
| `findAllProperties(anim)` | Get all available properties for animation |
| `getEnumValues(prop)` | Get available values for enum property |
| `destroy(anim)` | Destroy animation instance |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Text Animations"
description: "Animate text elements with effects like fade, typewriter, and bounce for dynamic visual presentation."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/animation/create/text-d6f4aa/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/node-native/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/node-native/animation/create-15cf50/) > [Text Animations](https://img.ly/docs/cesdk/node-native/animation/create/text-d6f4aa/)
---
Create engaging text animations that reveal content line by line, word by word, or character by character with granular control over timing and overlap.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-create-text-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-animation-create-text-server-js)
Text animations in CE.SDK allow you to animate text blocks with granular control over how the text appears. Unlike standard block animations, text animations support writing styles that determine whether animation applies to the entire text, line by line, word by word, or character by character.
```typescript file=@cesdk_web_examples/guides-animation-create-text-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFile, mkdir } from 'fs/promises';
import { config } from 'dotenv';
config();
/**
* CE.SDK Server Guide: Text Animations
*
* Demonstrates text-specific animation features in CE.SDK:
* - Creating and applying animations to text blocks
* - Text animation writing styles (line, word, character)
* - Segment overlap configuration
* - Combining with easing and duration properties
*/
async function main() {
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
// Set page duration to accommodate all animations
engine.block.setDuration(page, 10);
// Create a text block with a baseline animation
const text1 = engine.block.create('text');
engine.block.setWidth(text1, 600);
engine.block.setHeight(text1, 200);
engine.block.appendChild(page, text1);
engine.block.setPositionX(text1, 100);
engine.block.setPositionY(text1, 100);
engine.block.replaceText(text1, 'Creating\nText\nAnimations');
engine.block.setFloat(text1, 'text/fontSize', 48);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text1, 'text/verticalAlignment', 'Center');
// Create an animation instance with the 'baseline' type
const animation1 = engine.block.createAnimation('baseline');
// Apply the animation to the text block's entrance
engine.block.setInAnimation(text1, animation1);
// Set basic animation properties
engine.block.setDuration(animation1, 2.0);
console.log('Created baseline animation attached to text block');
// Writing Style: Line-by-line animation
const text2 = engine.block.create('text');
engine.block.setWidth(text2, 600);
engine.block.setHeight(text2, 200);
engine.block.appendChild(page, text2);
engine.block.setPositionX(text2, 700);
engine.block.setPositionY(text2, 100);
engine.block.replaceText(text2, 'Line by line\nanimation\nfor text');
engine.block.setFloat(text2, 'text/fontSize', 42);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text2, 'text/verticalAlignment', 'Center');
const animation2 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text2, animation2);
engine.block.setDuration(animation2, 2.0);
// Set writing style to 'Line' for line-by-line animation
engine.block.setEnum(animation2, 'textAnimationWritingStyle', 'Line');
engine.block.setEnum(animation2, 'animationEasing', 'EaseOut');
console.log('Created line-by-line animation');
// Writing Style: Word-by-word animation
const text3 = engine.block.create('text');
engine.block.setWidth(text3, 600);
engine.block.setHeight(text3, 200);
engine.block.appendChild(page, text3);
engine.block.setPositionX(text3, 1300);
engine.block.setPositionY(text3, 100);
engine.block.replaceText(text3, 'Animate word by word for emphasis');
engine.block.setFloat(text3, 'text/fontSize', 42);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text3, 'text/verticalAlignment', 'Center');
const animation3 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text3, animation3);
engine.block.setDuration(animation3, 2.5);
// Set writing style to 'Word' for word-by-word animation
engine.block.setEnum(animation3, 'textAnimationWritingStyle', 'Word');
engine.block.setEnum(animation3, 'animationEasing', 'EaseOut');
console.log('Created word-by-word animation');
// Writing Style: Character-by-character animation
const text4 = engine.block.create('text');
engine.block.setWidth(text4, 600);
engine.block.setHeight(text4, 200);
engine.block.appendChild(page, text4);
engine.block.setPositionX(text4, 100);
engine.block.setPositionY(text4, 400);
engine.block.replaceText(
text4,
'Character by character for typewriter effect'
);
engine.block.setFloat(text4, 'text/fontSize', 38);
engine.block.setEnum(text4, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text4, 'text/verticalAlignment', 'Center');
const animation4 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text4, animation4);
engine.block.setDuration(animation4, 3.0);
// Set writing style to 'Character' for character-by-character animation
engine.block.setEnum(animation4, 'textAnimationWritingStyle', 'Character');
engine.block.setEnum(animation4, 'animationEasing', 'Linear');
console.log('Created character-by-character animation');
// Segment Overlap: Sequential animation (overlap = 0)
const text5 = engine.block.create('text');
engine.block.setWidth(text5, 600);
engine.block.setHeight(text5, 200);
engine.block.appendChild(page, text5);
engine.block.setPositionX(text5, 700);
engine.block.setPositionY(text5, 400);
engine.block.replaceText(text5, 'Sequential animation with zero overlap');
engine.block.setFloat(text5, 'text/fontSize', 40);
engine.block.setEnum(text5, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text5, 'text/verticalAlignment', 'Center');
const animation5 = engine.block.createAnimation('pan');
engine.block.setInAnimation(text5, animation5);
engine.block.setDuration(animation5, 2.0);
engine.block.setEnum(animation5, 'textAnimationWritingStyle', 'Word');
// Set overlap to 0 for sequential animation
engine.block.setFloat(animation5, 'textAnimationOverlap', 0.0);
engine.block.setEnum(animation5, 'animationEasing', 'EaseOut');
console.log('Created sequential animation (overlap = 0)');
// Segment Overlap: Cascading animation (overlap = 0.4)
const text6 = engine.block.create('text');
engine.block.setWidth(text6, 600);
engine.block.setHeight(text6, 200);
engine.block.appendChild(page, text6);
engine.block.setPositionX(text6, 1300);
engine.block.setPositionY(text6, 400);
engine.block.replaceText(text6, 'Cascading animation with partial overlap');
engine.block.setFloat(text6, 'text/fontSize', 40);
engine.block.setEnum(text6, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text6, 'text/verticalAlignment', 'Center');
const animation6 = engine.block.createAnimation('pan');
engine.block.setInAnimation(text6, animation6);
engine.block.setDuration(animation6, 1.5);
engine.block.setEnum(animation6, 'textAnimationWritingStyle', 'Word');
// Set overlap to 0.4 for cascading effect
engine.block.setFloat(animation6, 'textAnimationOverlap', 0.4);
engine.block.setEnum(animation6, 'animationEasing', 'EaseOut');
console.log('Created cascading animation (overlap = 0.4)');
// Query available writing style and easing options
const writingStyleOptions = engine.block.getEnumValues(
'textAnimationWritingStyle'
);
console.log('Available writing style options:', writingStyleOptions);
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
// Ensure output directory exists
await mkdir('output', { recursive: true });
// Export first frame as PNG to verify the scene setup
const blob = await engine.block.export(page, 'image/png');
const buffer = Buffer.from(await blob.arrayBuffer());
await writeFile('output/text-animations.png', buffer);
console.log('');
console.log('Text Animations guide complete.');
console.log('Scene created with:');
console.log(' - 6 text blocks with different animation configurations');
console.log(' - Writing styles: default, Line, Word, Character');
console.log(' - Overlap values: 0.0 (sequential), 0.4 (cascading)');
console.log(' - Exported preview: output/text-animations.png');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers text-specific animation properties like writing styles and segment overlap, enabling dynamic and engaging text presentations in your designs.
## Text Animation Fundamentals
We create animations by first creating an animation instance, then attaching it to a text block. The animation block defines how the text will animate, while the text block contains the content and styling.
```typescript highlight-create-animation
// Create a text block with a baseline animation
const text1 = engine.block.create('text');
engine.block.setWidth(text1, 600);
engine.block.setHeight(text1, 200);
engine.block.appendChild(page, text1);
engine.block.setPositionX(text1, 100);
engine.block.setPositionY(text1, 100);
engine.block.replaceText(text1, 'Creating\nText\nAnimations');
engine.block.setFloat(text1, 'text/fontSize', 48);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text1, 'text/verticalAlignment', 'Center');
// Create an animation instance with the 'baseline' type
const animation1 = engine.block.createAnimation('baseline');
// Apply the animation to the text block's entrance
engine.block.setInAnimation(text1, animation1);
// Set basic animation properties
engine.block.setDuration(animation1, 2.0);
console.log('Created baseline animation attached to text block');
```
Animations are created separately using `engine.block.createAnimation()` with an animation type like 'baseline', 'fade', or 'pan'. We then attach the animation to the text block's entrance using `engine.block.setInAnimation()`. The animation duration is set with `engine.block.setDuration()`.
## Writing Style Control
Text animations support different granularity levels through the `textAnimationWritingStyle` property. This controls whether the animation applies to the entire text at once, or breaks it into segments (lines, words, or characters). We can query available options using `engine.block.getEnumValues('textAnimationWritingStyle')`.
### Line-by-Line Animation
The `Line` writing style animates text one line at a time from top to bottom. Each line appears sequentially, creating a structured reveal effect.
```typescript highlight-writing-style-line
// Writing Style: Line-by-line animation
const text2 = engine.block.create('text');
engine.block.setWidth(text2, 600);
engine.block.setHeight(text2, 200);
engine.block.appendChild(page, text2);
engine.block.setPositionX(text2, 700);
engine.block.setPositionY(text2, 100);
engine.block.replaceText(text2, 'Line by line\nanimation\nfor text');
engine.block.setFloat(text2, 'text/fontSize', 42);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text2, 'text/verticalAlignment', 'Center');
const animation2 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text2, animation2);
engine.block.setDuration(animation2, 2.0);
// Set writing style to 'Line' for line-by-line animation
engine.block.setEnum(animation2, 'textAnimationWritingStyle', 'Line');
engine.block.setEnum(animation2, 'animationEasing', 'EaseOut');
console.log('Created line-by-line animation');
```
We use `engine.block.setEnum()` to set the writing style to `'Line'`. This is ideal for revealing multi-line text in a clear, organized manner.
### Word-by-Word Animation
The `Word` writing style animates text one word at a time in reading order. This creates emphasis and draws attention to individual words.
```typescript highlight-writing-style-word
// Writing Style: Word-by-word animation
const text3 = engine.block.create('text');
engine.block.setWidth(text3, 600);
engine.block.setHeight(text3, 200);
engine.block.appendChild(page, text3);
engine.block.setPositionX(text3, 1300);
engine.block.setPositionY(text3, 100);
engine.block.replaceText(text3, 'Animate word by word for emphasis');
engine.block.setFloat(text3, 'text/fontSize', 42);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text3, 'text/verticalAlignment', 'Center');
const animation3 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text3, animation3);
engine.block.setDuration(animation3, 2.5);
// Set writing style to 'Word' for word-by-word animation
engine.block.setEnum(animation3, 'textAnimationWritingStyle', 'Word');
engine.block.setEnum(animation3, 'animationEasing', 'EaseOut');
console.log('Created word-by-word animation');
```
Setting the writing style to `'Word'` is perfect for creating dynamic, engaging text reveals that emphasize key phrases.
### Character-by-Character Animation
The `Character` writing style animates text one character at a time, creating a classic typewriter effect. This is the most granular animation option.
```typescript highlight-writing-style-character
// Writing Style: Character-by-character animation
const text4 = engine.block.create('text');
engine.block.setWidth(text4, 600);
engine.block.setHeight(text4, 200);
engine.block.appendChild(page, text4);
engine.block.setPositionX(text4, 100);
engine.block.setPositionY(text4, 400);
engine.block.replaceText(
text4,
'Character by character for typewriter effect'
);
engine.block.setFloat(text4, 'text/fontSize', 38);
engine.block.setEnum(text4, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text4, 'text/verticalAlignment', 'Center');
const animation4 = engine.block.createAnimation('baseline');
engine.block.setInAnimation(text4, animation4);
engine.block.setDuration(animation4, 3.0);
// Set writing style to 'Character' for character-by-character animation
engine.block.setEnum(animation4, 'textAnimationWritingStyle', 'Character');
engine.block.setEnum(animation4, 'animationEasing', 'Linear');
console.log('Created character-by-character animation');
```
The `'Character'` writing style is ideal for typewriter effects and when you want maximum control over the animation timing.
## Segment Overlap Configuration
The `textAnimationOverlap` property controls timing between animation segments. A value of 0 means segments animate sequentially, while values between 0 and 1 create cascading effects where segments overlap partially. We use `engine.block.setFloat()` to set the overlap value.
### Sequential Animation (Overlap = 0)
When overlap is set to 0, each segment completes before the next begins, creating a clear, structured reveal effect.
```typescript highlight-overlap-sequential
// Segment Overlap: Sequential animation (overlap = 0)
const text5 = engine.block.create('text');
engine.block.setWidth(text5, 600);
engine.block.setHeight(text5, 200);
engine.block.appendChild(page, text5);
engine.block.setPositionX(text5, 700);
engine.block.setPositionY(text5, 400);
engine.block.replaceText(text5, 'Sequential animation with zero overlap');
engine.block.setFloat(text5, 'text/fontSize', 40);
engine.block.setEnum(text5, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text5, 'text/verticalAlignment', 'Center');
const animation5 = engine.block.createAnimation('pan');
engine.block.setInAnimation(text5, animation5);
engine.block.setDuration(animation5, 2.0);
engine.block.setEnum(animation5, 'textAnimationWritingStyle', 'Word');
// Set overlap to 0 for sequential animation
engine.block.setFloat(animation5, 'textAnimationOverlap', 0.0);
engine.block.setEnum(animation5, 'animationEasing', 'EaseOut');
console.log('Created sequential animation (overlap = 0)');
```
Sequential animation ensures each text segment fully appears before the next one starts, making it perfect for emphasis and readability.
### Cascading Animation (Overlap = 0.4)
When overlap is set to a value between 0 and 1, segments animate in a cascading pattern, creating a smooth, flowing effect as they blend together.
```typescript highlight-overlap-cascading
// Segment Overlap: Cascading animation (overlap = 0.4)
const text6 = engine.block.create('text');
engine.block.setWidth(text6, 600);
engine.block.setHeight(text6, 200);
engine.block.appendChild(page, text6);
engine.block.setPositionX(text6, 1300);
engine.block.setPositionY(text6, 400);
engine.block.replaceText(text6, 'Cascading animation with partial overlap');
engine.block.setFloat(text6, 'text/fontSize', 40);
engine.block.setEnum(text6, 'text/horizontalAlignment', 'Center');
engine.block.setEnum(text6, 'text/verticalAlignment', 'Center');
const animation6 = engine.block.createAnimation('pan');
engine.block.setInAnimation(text6, animation6);
engine.block.setDuration(animation6, 1.5);
engine.block.setEnum(animation6, 'textAnimationWritingStyle', 'Word');
// Set overlap to 0.4 for cascading effect
engine.block.setFloat(animation6, 'textAnimationOverlap', 0.4);
engine.block.setEnum(animation6, 'animationEasing', 'EaseOut');
console.log('Created cascading animation (overlap = 0.4)');
```
Cascading animation with partial overlap creates dynamic, fluid text reveals that feel natural and engaging.
## Combining with Animation Properties
Text animations can be enhanced with standard animation properties like duration and easing. Duration controls the overall timing of the animation, while easing controls the acceleration curve.
```typescript highlight-duration-easing
// Query available writing style and easing options
const writingStyleOptions = engine.block.getEnumValues(
'textAnimationWritingStyle'
);
console.log('Available writing style options:', writingStyleOptions);
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
```
We use `engine.block.setEnum()` to set the easing function ('EaseIn', 'EaseOut', 'EaseInOut', 'Linear'). We can query available easing options using `engine.block.getEnumValues('animationEasing')`. Combining writing style, overlap, duration, and easing gives us complete control over how text animates.
## API Reference
| Method | Description |
| ------------------------------------------------- | -------------------------------------------------- |
| `createAnimation(type)` | Create a new animation instance |
| `setInAnimation(block, animation)` | Apply animation to block entrance |
| `setLoopAnimation(block, animation)` | Apply looping animation to block |
| `setOutAnimation(block, animation)` | Apply animation to block exit |
| `getInAnimation(block)` | Get the entrance animation of a block |
| `getLoopAnimation(block)` | Get the looping animation of a block |
| `getOutAnimation(block)` | Get the exit animation of a block |
| `setDuration(animation, seconds)` | Set animation duration in seconds |
| `getDuration(animation)` | Get animation duration |
| `setEnum(animation, property, value)` | Set enum property (writing style, easing) |
| `getEnum(animation, property)` | Get enum property value |
| `setFloat(animation, property, value)` | Set float property (overlap value) |
| `getFloat(animation, property)` | Get float property value |
| `getEnumValues(property)` | Get available enum options for a property |
| `supportsAnimation(block)` | Check if block supports animations |
| `replaceText(block, text)` | Set text content of a text block |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Edit Animations"
description: "Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/animation/edit-32c12a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/node-native/animation-ce900c/) > [Edit Animations](https://img.ly/docs/cesdk/node-native/animation/edit-32c12a/)
---
Modify existing animations by reading properties, changing duration and easing, and replacing or removing animations from blocks.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-edit-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-animation-edit-server-js)
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.
```typescript file=@cesdk_web_examples/guides-animation-edit-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Edit Animations
*
* 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
*/
async function main() {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
// Sample image URL
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Helper to create an image block with an initial slide animation
const createAnimatedBlock = async (
x: number,
y: number,
width: number,
height: number
) => {
const graphic = engine.block.create('graphic');
const imageFill = engine.block.createFill('image');
engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUri);
engine.block.setFill(graphic, imageFill);
engine.block.setShape(graphic, engine.block.createShape('rect'));
engine.block.setWidth(graphic, width);
engine.block.setHeight(graphic, height);
engine.block.setPositionX(graphic, x);
engine.block.setPositionY(graphic, 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;
};
// Create blocks for demonstration
const block1 = await createAnimatedBlock(100, 100, 400, 300);
// 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);
}
// Create second block for reading properties
const block2 = await createAnimatedBlock(550, 100, 400, 300);
// 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);
}
// Create third block for modifying duration
const block3 = await createAnimatedBlock(1000, 100, 400, 300);
// 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');
}
// Create fourth block for changing easing
const block4 = await createAnimatedBlock(100, 450, 400, 300);
// 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');
}
// Create fifth block for adjusting animation-specific properties
const block5 = await createAnimatedBlock(550, 450, 400, 300);
// 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
);
}
// Create sixth block for replacing and removing animations
const block6 = await createAnimatedBlock(1000, 450, 400, 300);
// 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 the result to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/edited-animations.png`, buffer);
console.log('Exported result to output/edited-animations.png');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers retrieving animations, reading and modifying properties, changing easing functions, adjusting animation-specific settings, and replacing or removing animations.
## Retrieving Animations
Before modifying an animation, we retrieve it from the block using `getInAnimation()`, `getOutAnimation()`, or `getLoopAnimation()`. A return value of `0` indicates no animation is attached.
```typescript highlight-retrieve-animations
// Retrieve animations from a block
const inAnimation = engine.block.getInAnimation(block1);
const outAnimation = engine.block.getOutAnimation(block1);
const loopAnimation = engine.block.getLoopAnimation(block1);
// Check if animations exist (0 means no animation)
console.log('In animation:', inAnimation !== 0 ? 'exists' : 'none');
console.log('Out animation:', outAnimation !== 0 ? 'exists' : 'none');
console.log('Loop animation:', loopAnimation !== 0 ? 'exists' : 'none');
// Get animation type if it exists
if (inAnimation !== 0) {
const animationType = engine.block.getType(inAnimation);
console.log('Animation type:', animationType);
}
```
We use `getType()` to identify the animation type (slide, fade, zoom, etc.). This is useful when you need to apply type-specific modifications.
## Reading Animation Properties
We can inspect current animation settings using property getters. `getDuration()` returns the animation length in seconds, while `getEnum()` retrieves values like easing functions.
```typescript highlight-read-properties
// Read animation properties
const animation2 = engine.block.getInAnimation(block2);
if (animation2 !== 0) {
// Get current duration
const duration = engine.block.getDuration(animation2);
console.log('Duration:', duration, 'seconds');
// Get current easing
const easing = engine.block.getEnum(animation2, 'animationEasing');
console.log('Easing:', easing);
// Discover all available properties
const allProperties = engine.block.findAllProperties(animation2);
console.log('Available properties:', allProperties);
}
```
Use `findAllProperties()` to discover all configurable properties for an animation. Different animation types expose different properties—slide animations have direction, while loop animations may have intensity or scale properties.
## Modifying Animation Duration
Change animation timing with `setDuration()`. The duration is specified in seconds.
```typescript highlight-modify-duration
// Modify animation duration
const animation3 = engine.block.getInAnimation(block3);
if (animation3 !== 0) {
// Change duration to 1.5 seconds
engine.block.setDuration(animation3, 1.5);
// Verify the change
const newDuration = engine.block.getDuration(animation3);
console.log('Updated duration:', newDuration, 'seconds');
}
```
When modifying In or Out animation durations, CE.SDK automatically adjusts the paired animation to prevent overlap. For loop animations, the duration defines the cycle length.
## Changing Easing Functions
Easing controls animation acceleration. We use `setEnum()` with the `'animationEasing'` property to change it.
```typescript highlight-change-easing
// Change animation easing
const animation4 = engine.block.getInAnimation(block4);
if (animation4 !== 0) {
// Query available easing options
const easingOptions = engine.block.getEnumValues('animationEasing');
console.log('Available easing options:', easingOptions);
// Set easing to EaseInOut for smooth acceleration and deceleration
engine.block.setEnum(animation4, 'animationEasing', 'EaseInOut');
}
```
Use `getEnumValues('animationEasing')` to discover available options:
| Easing | Description |
| ----------- | ----------------------------------------------- |
| `Linear` | Constant speed throughout |
| `EaseIn` | Starts slow, accelerates toward the end |
| `EaseOut` | Starts fast, decelerates toward the end |
| `EaseInOut` | Starts slow, speeds up, then slows down again |
## Adjusting Animation-Specific Properties
Each animation type has unique configurable properties. For slide animations, we can change the entry direction.
```typescript highlight-adjust-properties
// Adjust animation-specific properties
const animation5 = engine.block.getInAnimation(block5);
if (animation5 !== 0) {
// Get current direction (for slide animations)
const currentDirection = engine.block.getFloat(
animation5,
'animation/slide/direction'
);
console.log('Current direction (radians):', currentDirection);
// Change direction to slide from top (3*PI/2 radians)
engine.block.setFloat(
animation5,
'animation/slide/direction',
(3 * Math.PI) / 2
);
}
```
The `animation/slide/direction` property uses radians:
- `0` — From the right
- `Math.PI / 2` — From the bottom
- `Math.PI` — From the left
- `3 * Math.PI / 2` — From the top
For text animations, you can adjust `textAnimationWritingStyle` (Line, Word, Character) and `textAnimationOverlap` (0 for sequential, 1 for simultaneous).
## Replacing Animations
To swap an animation type, destroy the existing animation before setting a new one. This prevents memory leaks from orphaned animation objects.
```typescript highlight-replace-animation
// Replace an existing animation
const oldAnimation = engine.block.getInAnimation(block6);
if (oldAnimation !== 0) {
// Destroy the old animation to prevent memory leaks
engine.block.destroy(oldAnimation);
}
// Create and set a new animation
const newAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block6, newAnimation);
engine.block.setDuration(newAnimation, 1.2);
engine.block.setEnum(newAnimation, 'animationEasing', 'EaseOut');
```
We first retrieve and destroy the old animation, then create and attach a new one with the desired type and properties.
## Removing Animations
Remove an animation by destroying it. After destruction, the getter returns `0`.
```typescript highlight-remove-animation
// Add a loop animation to demonstrate removal
const loopAnim = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block6, loopAnim);
engine.block.setDuration(loopAnim, 1.0);
// Remove the loop animation by destroying it
const currentLoop = engine.block.getLoopAnimation(block6);
if (currentLoop !== 0) {
engine.block.destroy(currentLoop);
// Verify removal - should now return 0
const verifyLoop = engine.block.getLoopAnimation(block6);
console.log(
'Loop animation after removal:',
verifyLoop === 0 ? 'none' : 'exists'
);
}
```
Destroying a design block automatically destroys all its attached animations. However, detached animations must be destroyed manually to free memory.
## API Reference
| Method | Description |
| ------------------------------------- | -------------------------------------------------- |
| `block.getInAnimation(block)` | Get entrance animation (returns 0 if none) |
| `block.getOutAnimation(block)` | Get exit animation (returns 0 if none) |
| `block.getLoopAnimation(block)` | Get loop animation (returns 0 if none) |
| `block.getType(anim)` | Get animation type string |
| `block.getDuration(anim)` | Get animation duration in seconds |
| `block.setDuration(anim, seconds)` | Set animation duration |
| `block.getEnum(anim, prop)` | Get enum property value |
| `block.setEnum(anim, prop, value)` | Set enum property value |
| `block.getFloat(anim, prop)` | Get float property value |
| `block.setFloat(anim, prop, value)` | Set float property value |
| `block.findAllProperties(anim)` | Get all available properties |
| `block.getEnumValues(prop)` | Get available values for enum property |
| `block.destroy(anim)` | Destroy animation and free memory |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Supported Animation Types"
description: "Apply different animation types to design blocks in CE.SDK and configure their properties in server-side applications."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/animation/types-4e5f41/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/node-native/animation-ce900c/) > [Supported Animation Types](https://img.ly/docs/cesdk/node-native/animation/types-4e5f41/)
---
Apply entrance, exit, and loop animations to design blocks programmatically using the available animation types in CE.SDK for Node.js.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-animation-types-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-animation-types-server-js)
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 how to apply different animation types and configure their properties in server-side applications.
```typescript file=@cesdk_web_examples/guides-animation-types-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.setDuration(page, 10);
// Set white background
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 margin = 40;
const spacing = 20;
const cols = 3;
const rows = 2;
const blockWidth = (pageWidth - 2 * margin - (cols - 1) * spacing) / cols;
const blockHeight = (pageHeight - 2 * margin - (rows - 1) * spacing) / rows;
// Helper to create an image block
const createImageBlock = (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 col = index % cols;
const row = Math.floor(index / cols);
engine.block.setPositionX(graphic, margin + col * (blockWidth + spacing));
engine.block.setPositionY(
graphic,
margin + row * (blockHeight + spacing)
);
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 = 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 = 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 = 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 = 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 = 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 = 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);
// Export the scene to a .scene file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Save as scene file - preserves all animation data
const sceneData = await engine.scene.saveToString();
writeFileSync(`${outputDir}/animation-types.scene`, sceneData);
console.log('Exported to output/animation-types.scene');
console.log('Animation Types example completed successfully');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
The example creates a scene with multiple blocks, each demonstrating different animation types. The scene is exported to a `.scene` file that preserves all animation data for later playback.
## Setting Up the Scene
We start by initializing CE.SDK and creating a scene.
```typescript highlight-setup
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
```
We create a scene using `engine.scene.create()`, then create a page and append it to the scene. This sets up the time-based environment required for animations.
```typescript highlight-create-scene
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.setDuration(page, 10);
```
## Entrance Animations
Entrance animations define how a block appears. We use `engine.block.createAnimation()` with the animation type and attach it using `engine.block.setInAnimation()`.
### Slide Animation
The slide animation moves a block in from a specified direction. The `direction` property uses radians where 0 is right, π/2 is bottom, π is left, and 3π/2 is top.
```typescript highlight-entrance-slide
// Create a slide animation that enters from the left
const slideAnimation = engine.block.createAnimation('slide');
engine.block.setInAnimation(block1, slideAnimation);
engine.block.setDuration(slideAnimation, 1.0);
// Direction in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top
engine.block.setFloat(slideAnimation, 'animation/slide/direction', Math.PI);
engine.block.setEnum(slideAnimation, 'animationEasing', 'EaseOut');
```
### Fade Animation
The fade animation transitions opacity from invisible to fully visible. Easing controls the animation curve.
```typescript highlight-entrance-fade
// Create a fade entrance animation
const fadeAnimation = engine.block.createAnimation('fade');
engine.block.setInAnimation(block2, fadeAnimation);
engine.block.setDuration(fadeAnimation, 1.0);
engine.block.setEnum(fadeAnimation, 'animationEasing', 'EaseInOut');
```
### Zoom Animation
The zoom animation scales the block from a smaller size to its final dimensions. The `fade` property adds an opacity transition during scaling.
```typescript highlight-entrance-zoom
// Create a zoom animation with fade effect
const zoomAnimation = engine.block.createAnimation('zoom');
engine.block.setInAnimation(block3, zoomAnimation);
engine.block.setDuration(zoomAnimation, 1.0);
engine.block.setBool(zoomAnimation, 'animation/zoom/fade', true);
```
Other entrance animation types include:
- `blur` — Transitions from blurred to clear
- `wipe` — Reveals with a directional wipe
- `pop` — Bouncy scale effect
- `spin` — Rotates the block into view
- `grow` — Scales up from a point
## Exit Animations
Exit animations define how a block leaves the screen. We use `engine.block.setOutAnimation()` to attach them. CE.SDK prevents overlap between entrance and exit durations automatically.
```typescript highlight-exit-animation
// Create entrance and exit animations
const wipeIn = engine.block.createAnimation('wipe');
engine.block.setInAnimation(block4, wipeIn);
engine.block.setDuration(wipeIn, 1.0);
engine.block.setEnum(wipeIn, 'animation/wipe/direction', 'Right');
// Exit animation plays before block disappears
const fadeOut = engine.block.createAnimation('fade');
engine.block.setOutAnimation(block4, fadeOut);
engine.block.setDuration(fadeOut, 1.0);
engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn');
```
In this example, a wipe entrance transitions to a fade exit. Mirror entrance effects for visual consistency, or use contrasting effects for emphasis.
## Loop Animations
Loop animations run continuously while the block is visible. They can combine with entrance and exit animations. We use `engine.block.setLoopAnimation()` to attach them.
```typescript highlight-loop-animation
// Create a breathing loop animation
const breathingLoop = engine.block.createAnimation('breathing_loop');
engine.block.setLoopAnimation(block5, breathingLoop);
engine.block.setDuration(breathingLoop, 2.0);
// Intensity: 0 = 1.25x max scale, 1 = 2.5x max scale
engine.block.setFloat(
breathingLoop,
'animation/breathing_loop/intensity',
0.3
);
```
The duration controls each cycle length. Loop animation types include:
- `breathing_loop` — Slow scale pulse
- `pulsating_loop` — Rhythmic scale
- `spin_loop` — Continuous rotation
- `fade_loop` — Opacity cycling
- `sway_loop` — Rotational oscillation
- `jump_loop` — Jumping motion
- `blur_loop` — Blur cycling
- `squeeze_loop` — Squeezing effect
## Combined Animations
A single block can have entrance, exit, and loop animations running together. The loop animation runs throughout the block's visibility while entrance and exit animations play at the appropriate times.
```typescript highlight-combined-animations
// Apply entrance, exit, and loop animations together
const spinIn = engine.block.createAnimation('spin');
engine.block.setInAnimation(block6, spinIn);
engine.block.setDuration(spinIn, 1.0);
engine.block.setEnum(spinIn, 'animation/spin/direction', 'Clockwise');
engine.block.setFloat(spinIn, 'animation/spin/intensity', 0.5);
const blurOut = engine.block.createAnimation('blur');
engine.block.setOutAnimation(block6, blurOut);
engine.block.setDuration(blurOut, 1.0);
const swayLoop = engine.block.createAnimation('sway_loop');
engine.block.setLoopAnimation(block6, swayLoop);
engine.block.setDuration(swayLoop, 1.5);
```
## Configuring Animation Properties
Each animation type has specific configurable properties. We use `engine.block.findAllProperties()` to discover available properties and `engine.block.getEnumValues()` to query options for enum properties.
```typescript highlight-discover-properties
// 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);
```
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
## Exporting the Scene
After creating animations, we export the scene to a `.scene` file. This format preserves all animation data and can be loaded in a browser environment for playback.
```typescript highlight-export
// Export the scene to a .scene file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Save as scene file - preserves all animation data
const sceneData = await engine.scene.saveToString();
writeFileSync(`${outputDir}/animation-types.scene`, sceneData);
console.log('Exported to output/animation-types.scene');
```
The `.scene` format stores the complete scene state including:
- All blocks and their properties
- Animation configurations
- Time-based settings
- Asset references
When loaded in a browser, these animations will play back exactly as configured.
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.scene.create()` | Create a scene for animations |
| `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 |
| `engine.scene.saveToString()` | Export scene to string format |
## Next Steps
- **Base Animations** — Create and attach animations to blocks
- **Text Animations** — Animate text with writing styles
- **Animation Overview** — Animation concepts and capabilities
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "API Reference"
description: "Find out how to use the API of the CESDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/api-reference/overview-8f24e1/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [API Reference](https://img.ly/docs/cesdk/node-native/api-reference/overview-8f24e1/)
---
For , the @cesdk/node-native package is available.
With this package you can load, edit, and export CE.SDK scene files with native C++ performance, including GPU-accelerated video export.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Automate Workflows"
description: "Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/automation-715209/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/node-native/automation-715209/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/node-native/automation/overview-34d971/) - Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale.
- [Auto-Resize](https://img.ly/docs/cesdk/node-native/automation/auto-resize-4c2d58/) - Configure blocks to dynamically adjust dimensions using Absolute, Percent, and Auto sizing modes for responsive layouts and content-driven expansion.
- [Data Merge](https://img.ly/docs/cesdk/node-native/automation/data-merge-ae087c/) - Generate personalized designs from templates by merging external data using text variables and placeholder blocks
- [Automate Design Generation](https://img.ly/docs/cesdk/node-native/automation/design-generation-98a99e/) - Generate on-brand designs programmatically using templates, variables, and CE.SDK’s headless API.
- [Multiple Image Generation](https://img.ly/docs/cesdk/node-native/automation/multi-image-generation-2a0de4/) - Create many image variants from structured data by interpolating content into reusable design templates.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Auto-Resize"
description: "Configure blocks to dynamically adjust dimensions using Absolute, Percent, and Auto sizing modes for responsive layouts and content-driven expansion."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/automation/auto-resize-4c2d58/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/node-native/automation-715209/) > [Auto-Resize](https://img.ly/docs/cesdk/node-native/automation/auto-resize-4c2d58/)
---
Configure blocks to dynamically adjust their dimensions using three sizing modes: Absolute for fixed values, Percent for parent-relative sizing, and Auto for content-driven expansion.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-automation-auto-resize-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-automation-auto-resize-server-js)
CE.SDK provides three sizing modes for controlling block dimensions. Absolute mode uses fixed pixel values. Percent mode sizes blocks relative to their parent container. Auto mode automatically expands blocks to fit their content. You can set width and height modes independently, allowing flexible combinations like fixed width with auto height for text that wraps.
```typescript file=@cesdk_web_examples/guides-automation-auto-resize-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Auto-Resize
*
* 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
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create output directory
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Create a text block with Auto sizing mode
// Auto mode makes the block expand to fit its content
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Auto-Resize Demo');
engine.block.setTextFontSize(titleBlock, 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);
// eslint-disable-next-line no-console
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.setTextFontSize(subtitleBlock, 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);
// eslint-disable-next-line no-console
console.log(`Title modes: width=${titleWidthMode}, height=${titleHeightMode}`);
// eslint-disable-next-line no-console
console.log(`Background modes: width=${bgWidthMode}, height=${bgHeightMode}`);
// Export the result
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/auto-resize-demo.png`, buffer);
// eslint-disable-next-line no-console
console.log(`\n✓ Exported auto-resize-demo.png to ${outputDir}/`);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
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.
## Setup
Initialize the CE.SDK engine for server-side automation:
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Create a Scene
Create a scene with a page to work with:
```typescript highlight-create-scene
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
```
## Size Modes
CE.SDK supports three sizing modes for block dimensions:
- **Absolute**: Fixed dimensions in design units. The default mode where `setWidth()` and `setHeight()` set exact pixel values.
- **Percent**: Dimensions relative to parent container. A value of 80 makes the block 80% of its parent's size.
- **Auto**: Content-driven sizing. The block expands or contracts to fit its content, primarily useful for text blocks.
## Setting Size Modes
Use `setWidthMode()` and `setHeightMode()` to configure how a block calculates its dimensions. Width and height modes can be set independently.
### Auto Mode for Text
Auto mode makes text blocks expand to fit their content:
```typescript highlight-auto-mode
// Create a text block with Auto sizing mode
// Auto mode makes the block expand to fit its content
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Auto-Resize Demo');
engine.block.setTextFontSize(titleBlock, 64);
// Set width and height modes to Auto
// The block will automatically size to fit the text content
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.appendChild(page, titleBlock);
```
With Auto mode, the block's dimensions are calculated automatically based on the content. This is useful when the text content varies and you want the block to always fit exactly.
### Percent Mode for Responsive Layouts
Percent mode sizes blocks relative to their parent:
```typescript highlight-percent-mode
// Create a block using Percent mode for responsive sizing
// Percent mode sizes the block relative to its parent
const backgroundBlock = engine.block.create('graphic');
engine.block.setShape(backgroundBlock, engine.block.createShape('rect'));
const fill = engine.block.createFill('color');
engine.block.setColor(fill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 0.3 });
engine.block.setFill(backgroundBlock, fill);
// Set to Percent mode - values are normalized (0-1)
engine.block.setWidthMode(backgroundBlock, 'Percent');
engine.block.setHeightMode(backgroundBlock, 'Percent');
engine.block.setWidth(backgroundBlock, 0.8); // 80% of parent width
engine.block.setHeight(backgroundBlock, 0.3); // 30% of parent height
// Center the background block
engine.block.setPositionX(backgroundBlock, pageWidth * 0.1); // 10% margin
engine.block.setPositionY(backgroundBlock, pageHeight * 0.6);
engine.block.appendChild(page, backgroundBlock);
```
Percent values represent the percentage of the parent container. A width of 80 with Percent mode means 80% of the parent's width.
## Reading Frame Dimensions
After layout, use `getFrameWidth()` and `getFrameHeight()` to read the computed dimensions:
```typescript highlight-read-frame-dimensions
// Read computed frame dimensions after layout
// getFrameWidth/getFrameHeight return the actual rendered size
const titleWidth = engine.block.getFrameWidth(titleBlock);
const titleHeight = engine.block.getFrameHeight(titleBlock);
// eslint-disable-next-line no-console
console.log(`Title dimensions: ${titleWidth.toFixed(0)}x${titleHeight.toFixed(0)} pixels`);
```
Frame dimensions return the actual rendered size regardless of the sizing mode. This is essential when using Auto mode since you need the computed size for positioning calculations.
## Centering Blocks
Combine Auto mode with frame dimensions to center blocks based on their actual size:
```typescript highlight-center-block
// Calculate centered position using frame dimensions
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const centerX = (pageWidth - titleWidth) / 2;
const centerY = (pageHeight - titleHeight) / 2 - 100; // Offset up for layout
// Position the title at center
engine.block.setPositionX(titleBlock, centerX);
engine.block.setPositionY(titleBlock, centerY);
```
This pattern reads the computed dimensions after Auto sizing and calculates the centered position.
## Additional Auto-Sized Content
You can create multiple auto-sized blocks and position them relative to each other:
```typescript highlight-subtitle-auto
// Create a subtitle with Auto mode
const subtitleBlock = engine.block.create('text');
engine.block.replaceText(subtitleBlock, 'Text automatically sizes to fit content');
engine.block.setTextFontSize(subtitleBlock, 32);
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
// Read computed dimensions and center
const subtitleWidth = engine.block.getFrameWidth(subtitleBlock);
const subtitleCenterX = (pageWidth - subtitleWidth) / 2;
engine.block.setPositionX(subtitleBlock, subtitleCenterX);
engine.block.setPositionY(subtitleBlock, pageHeight * 0.7);
```
## Verifying Size Modes
Query the current size modes to verify your configuration:
```typescript highlight-check-modes
// Verify sizing modes
const titleWidthMode = engine.block.getWidthMode(titleBlock);
const titleHeightMode = engine.block.getHeightMode(titleBlock);
const bgWidthMode = engine.block.getWidthMode(backgroundBlock);
const bgHeightMode = engine.block.getHeightMode(backgroundBlock);
// eslint-disable-next-line no-console
console.log(`Title modes: width=${titleWidthMode}, height=${titleHeightMode}`);
// eslint-disable-next-line no-console
console.log(`Background modes: width=${bgWidthMode}, height=${bgHeightMode}`);
```
## Export
Export the result to verify the layout:
```typescript highlight-export
// Export the result
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/auto-resize-demo.png`, buffer);
// eslint-disable-next-line no-console
console.log(`\n✓ Exported auto-resize-demo.png to ${outputDir}/`);
```
## Troubleshooting
**Frame dimensions return 0**: Layout may not have updated yet. Read frame dimensions after all content is set and the block is attached to the scene hierarchy.
**Percent mode not working**: The block must have a parent container. Percent mode calculates size relative to the parent's dimensions.
**Auto mode not resizing**: Auto mode works with content that has intrinsic size, primarily text blocks. Graphics require explicit dimensions.
**Unexpected dimensions**: Check which mode is active using `getWidthMode()` and `getHeightMode()`. The mode affects how width and height values are interpreted.
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.block.getWidth(block)` | Get block width in current mode |
| `engine.block.setWidth(block, value)` | Set block width in current mode |
| `engine.block.getWidthMode(block)` | Get current width mode: Absolute, Percent, or Auto |
| `engine.block.setWidthMode(block, mode)` | Set width mode: Absolute, Percent, or Auto |
| `engine.block.getHeight(block)` | Get block height in current mode |
| `engine.block.setHeight(block, value)` | Set block height in current mode |
| `engine.block.getHeightMode(block)` | Get current height mode: Absolute, Percent, or Auto |
| `engine.block.setHeightMode(block, mode)` | Set height mode: Absolute, Percent, or Auto |
| `engine.block.getFrameWidth(block)` | Get computed width after layout |
| `engine.block.getFrameHeight(block)` | Get computed height after layout |
| `engine.block.setPositionX(block, value)` | Set block X position |
| `engine.block.setPositionY(block, value)` | Set block Y position |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Data Merge"
description: "Generate personalized designs from templates by merging external data using text variables and placeholder blocks"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/automation/data-merge-ae087c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/node-native/automation-715209/) > [Data Merge](https://img.ly/docs/cesdk/node-native/automation/data-merge-ae087c/)
---
Generate personalized designs at scale using CE.SDK's headless Node.js API to batch process templates with external data.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-automation-data-merge-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-automation-data-merge-server-js)
Data merge generates multiple personalized designs from a single template by replacing variable content with external data. Server-side processing enables batch operations for certificates, badges, team cards, or any design requiring consistent layout with varying content.
```typescript file=@cesdk_web_examples/guides-automation-data-merge-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Data Merge
*
* Demonstrates batch processing of personalized designs:
* - Loading templates with text variables
* - Setting variable values from data records
* - Finding and updating placeholder blocks by name
* - Exporting personalized designs to PNG
* - Processing multiple records in a batch
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Sample data records for the merge operation
// In production, this data would come from a CSV, database, or API
const dataRecords = [
{
name: 'Alex Smith',
title: 'Creative Developer',
email: 'alex.smith@example.com',
photoUrl: 'https://img.ly/static/ubq_samples/sample_1.jpg'
},
{
name: 'Jordan Lee',
title: 'Product Designer',
email: 'jordan.lee@example.com',
photoUrl: 'https://img.ly/static/ubq_samples/sample_2.jpg'
},
{
name: 'Taylor Chen',
title: 'UX Engineer',
email: 'taylor.chen@example.com',
photoUrl: 'https://img.ly/static/ubq_samples/sample_3.jpg'
}
];
// Prepare output directory
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Store exported blobs for batch processing results
const outputs: { name: string; blob: Blob }[] = [];
// Process each data record
for (const record of dataRecords) {
// Create a fresh scene for each record
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 400 } }
});
const page = engine.block.findByType('page')[0];
// Set text variables from the data record
engine.variable.setString('name', record.name);
engine.variable.setString('title', record.title);
engine.variable.setString('email', record.email);
// Create the template layout with placeholder blocks
// Create a profile photo block on the left
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', record.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 bindings
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);
// Verify which variables exist in the scene
const variables = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log(`Processing ${record.name}, variables:`, variables);
// Check if the text block references any variables
const hasVariables = engine.block.referencesAnyVariables(textBlock);
// eslint-disable-next-line no-console
console.log(`Text block has variables: ${hasVariables}`);
// Export the personalized design to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
// Save the result
const filename = record.name.toLowerCase().replace(/\s+/g, '-');
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/${filename}.png`, buffer);
outputs.push({ name: record.name, blob });
// eslint-disable-next-line no-console
console.log(`✓ Exported ${filename}.png`);
}
// eslint-disable-next-line no-console
console.log(`\n✓ Batch complete: ${outputs.length} designs exported to ${outputDir}/`);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to prepare data, build templates with variables, and process multiple records in a batch workflow.
## Initialize the Engine
We start by initializing the headless Creative Engine. In production, you would typically pass your license key.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Prepare Data Records
Data typically comes from a CSV file, database query, or API response. Here we define sample records with the fields we want to merge into the template.
```typescript highlight=highlight-sample-data
// Sample data records for the merge operation
// In production, this data would come from a CSV, database, or API
const dataRecords = [
{
name: 'Alex Smith',
title: 'Creative Developer',
email: 'alex.smith@example.com',
photoUrl: 'https://img.ly/static/ubq_samples/sample_1.jpg'
},
{
name: 'Jordan Lee',
title: 'Product Designer',
email: 'jordan.lee@example.com',
photoUrl: 'https://img.ly/static/ubq_samples/sample_2.jpg'
},
{
name: 'Taylor Chen',
title: 'UX Engineer',
email: 'taylor.chen@example.com',
photoUrl: 'https://img.ly/static/ubq_samples/sample_3.jpg'
}
];
```
Each record contains field names that map to template variables and placeholder blocks.
## Batch Processing Loop
We iterate through each data record, creating a fresh scene for each personalized output. This ensures variable values from previous records don't carry over.
```typescript highlight=highlight-batch-loop
// Process each data record
for (const record of dataRecords) {
// Create a fresh scene for each record
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 400 } }
});
const page = engine.block.findByType('page')[0];
```
For each record, we create a new scene with the appropriate dimensions for our template design.
## Set Variable Values
We use `engine.variable.setString()` to define the value for each variable. When a variable is set, all text blocks referencing that variable update automatically.
```typescript highlight=highlight-set-variables
// Set text variables from the data record
engine.variable.setString('name', record.name);
engine.variable.setString('title', record.title);
engine.variable.setString('email', record.email);
```
Variable values persist within the current scene. Creating a new scene clears previous variable bindings.
## Build the Template
We create the template layout with placeholder blocks. The profile photo block gets a semantic name using `setName()`. Text blocks use variable placeholders with double curly brace syntax: `{{variableName}}`.
```typescript highlight=highlight-create-template
// Create the template layout with placeholder blocks
// Create a profile photo block on the left
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', record.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 bindings
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);
```
Using semantic names and variables makes templates reusable across different data records.
## Verify Variables
Use `engine.variable.findAll()` to discover which variables exist in the scene. Use `engine.block.referencesAnyVariables()` to check if a specific block contains variable references.
```typescript highlight=highlight-check-variables
// Verify which variables exist in the scene
const variables = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log(`Processing ${record.name}, variables:`, variables);
// Check if the text block references any variables
const hasVariables = engine.block.referencesAnyVariables(textBlock);
// eslint-disable-next-line no-console
console.log(`Text block has variables: ${hasVariables}`);
```
This is useful for validating that data fields match template requirements before processing.
## Export Each Design
After merging data into the template, export the personalized design using `engine.block.export()`. We save each result to the file system with a unique filename.
```typescript highlight=highlight-export
// Export the personalized design to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
// Save the result
const filename = record.name.toLowerCase().replace(/\s+/g, '-');
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/${filename}.png`, buffer);
outputs.push({ name: record.name, blob });
// eslint-disable-next-line no-console
console.log(`✓ Exported ${filename}.png`);
```
You can export to PNG, JPEG, WebP, or PDF formats. For high-volume processing, consider writing to cloud storage or a CDN.
## Cleanup Resources
Always dispose of the engine when batch processing completes. Using a `finally` block ensures cleanup happens even if an error occurs during processing.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
This frees memory and other resources held by the engine instance.
## Troubleshooting
### Variables Not Rendering
If variable placeholders show instead of values in exported images:
- 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 export
- Create a new scene for each record to avoid stale variable state
### Export Failures
If exports fail for individual records:
- Wrap the export call in a try-catch to handle errors gracefully
- Log the record data to identify problematic inputs
- Verify image URLs in data records are accessible from the server
- Check that the page block exists after scene creation
### Memory Issues
If processing many records causes memory issues:
- Dispose and recreate the engine periodically for very large batches
- Process records in smaller chunks
- Monitor memory usage during batch operations
## 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.findByType(type)` | Find blocks by their type |
| `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.setFill(block, fill)` | Set 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 |
| `engine.scene.create(layout, options)` | Create a new scene with specified layout |
| `engine.dispose()` | Dispose the engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Automate Design Generation"
description: "Generate on-brand designs programmatically using templates, variables, and CE.SDK’s headless API."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/automation/design-generation-98a99e/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/node-native/automation-715209/) > [Design Generation](https://img.ly/docs/cesdk/node-native/automation/design-generation-98a99e/)
---
The CE.SDK for **Node.js** provides automated design generation to streamline production by populating templates with data. Use this to create tailored assets at scale for everything from ads to direct mail.
With IMG.LY, you can use templates to define dynamic elements. The generation process then populates these elements with:
- Real-time data
- User inputs
This guide shows you how to use the CE.SDK for programmatic design generation.
[Launch Web Demo](https://img.ly/showcases/cesdk/headless-design/web)
## What’s a Design Template
A **design template** is a pre-configured layout that includes placeholders for dynamic elements such as:
- Text
- Images
- Icons
- Background graphics
- Vector shapes
- Etc
These **placeholders** define where and how specific content appears in the final design.
## Populate a Template
The generation process **replaces** the placeholders with actual data to create a completed output.
### Create or Edit Templates
You can create or edit design templates programmatically in a Node.js app. Learn more in the [Create Templates guide](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/).
### Dynamic Content Sources
Populate templates with data from 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:** Where the user directly provides data through a UI.
For detailed information on using and managing templates, see [Use Templates](https://img.ly/docs/cesdk/node-native/use-templates/overview-ae74e1/).
### Data Merge Workflow
Below is a diagram illustrating how data is merged into a template to produce a final design:

## Example Workflow
### 1. Prepare the Template
Start by designing a template with text variables. This scene example contains:
- A postcard template
- Placeholders for the recipient’s details

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

Need help with exports? Check out the [Export Guide](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) for detailed instructions and options.
Full code
```ts
// Load a template from your server or a CDN
const sceneUrl = 'https://cdn.img.ly/assets/demo/v4/ly.img.template/templates/cesdk_postcard_2.scene';
await engine.scene.loadFromURL(sceneUrl);
// Option 1: Prepare your data as a JavaScript object
const data = {
textVariables: {
first_name: 'John',
last_name: 'Doe',
address: '123 Main St.',
city: 'Anytown',
},
};
// Option 2: Fetch from an API
// const data = await fetch('https://api.example.com/design-data').then(res => res.json());
engine.variable.setString('first_name', data.textVariables.first_name);
engine.variable.setString('last_name', data.textVariables.last_name);
engine.variable.setString('address', data.textVariables.address);
engine.variable.setString('city', data.textVariables.city);
const output = await engine.block.export(engine.scene.get(), {
mimeType: 'application/pdf',
});
// Success: 'output' contains your generated design as a PDF Blob
```
## Troubleshooting
If you have issues during the generation process:
- Verify that all your **variable names** exactly match those in your template.
- Make sure your template is accessible from the **URL** you provided.
- Check the **format** for your data values: for text variables, **strings** are the correct format.
- Look at the **console** for detailed error messages from the CE.SDK API.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Multiple Image Generation"
description: "Create many image variants from structured data by interpolating content into reusable design templates."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/automation/multi-image-generation-2a0de4/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/node-native/automation-715209/) > [Multiple Image Generation](https://img.ly/docs/cesdk/node-native/automation/multi-image-generation-2a0de4/)
---
Generate multiple image variants from a single data record using CE.SDK's headless Node.js API to create format variations for different platforms.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-automation-multi-image-generation-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-automation-multi-image-generation-server-js)
Multi-image generation produces multiple design variants from a single data source. This pattern creates format variations (square, portrait, landscape) from one input to serve multiple channels like Instagram posts, stories, and Facebook posts.
```typescript file=@cesdk_web_examples/guides-automation-multi-image-generation-server-js/server-js.ts reference-only
import CreativeEngine, { isRGBAColor, type RGBAColor } from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Multi-Image Generation
*
* Demonstrates generating multiple image variants from a single data record:
* - Processing multiple templates sequentially (square, portrait, landscape)
* - Setting text variables from structured data
* - Replacing images dynamically by block name
* - Applying brand colors across template elements
* - Exporting variants to different formats
*/
// Define the data structure for a restaurant review card
// In production, this would come from an API, database, or JSON file
interface RestaurantData {
name: string;
price: string;
reviewCount: number;
rating: number;
imageUrl: string;
primaryColor: string;
secondaryColor: string;
}
// Define template configurations for different formats
// Each template targets a different aspect ratio for various platforms
interface TemplateConfig {
label: string;
width: number;
height: number;
}
// Helper function to convert hex color to RGBA (0-1 range)
function hexToRgba01(hex: string): RGBAColor {
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());
if (!m) throw new Error(`Invalid hex color: ${hex}`);
return {
r: parseInt(m[1], 16) / 255,
g: parseInt(m[2], 16) / 255,
b: parseInt(m[3], 16) / 255,
a: 1
};
}
// Helper to check if color is black (r=0, g=0, b=0)
function isBlackColor(color: unknown): boolean {
if (!isRGBAColor(color as RGBAColor)) return false;
const c = color as RGBAColor;
return c.r === 0 && c.g === 0 && c.b === 0;
}
// Helper to check if color is white (r=1, g=1, b=1)
function isWhiteColor(color: unknown): boolean {
if (!isRGBAColor(color as RGBAColor)) return false;
const c = color as RGBAColor;
return c.r === 1 && c.g === 1 && c.b === 1;
}
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
// Helper function to apply text variables from data
function applyVariables(data: RestaurantData): void {
const safeName = data.name?.trim() || 'Restaurant';
const safePrice = data.price?.trim() || '$';
const safeCount =
data.reviewCount !== undefined && data.reviewCount !== null
? data.reviewCount.toString()
: '0';
engine.variable.setString('Name', safeName);
engine.variable.setString('Price', safePrice);
engine.variable.setString('Count', safeCount);
}
// Helper function to replace an image by block name
function replaceImageByName(
blockName: string,
newUrl: string,
mode: 'Cover' | 'Contain' = 'Cover'
): void {
const blocks = engine.block.findByName(blockName);
if (blocks.length === 0) {
// eslint-disable-next-line no-console
console.warn(`Block "${blockName}" not found`);
return;
}
const imageBlock = blocks[0];
let fill = engine.block.getFill(imageBlock);
if (!fill) {
fill = engine.block.createFill('image');
engine.block.setFill(imageBlock, fill);
}
engine.block.setString(fill, 'fill/image/imageFileURI', newUrl);
engine.block.resetCrop(imageBlock);
engine.block.setContentFillMode(imageBlock, mode);
}
// Helper function to apply brand colors to template elements
function applyBrandColors(primaryHex: string, secondaryHex: string): void {
const primary = hexToRgba01(primaryHex);
const secondary = hexToRgba01(secondaryHex);
const blocks = engine.block.findAll();
for (const id of blocks) {
const type = engine.block.getType(id);
// Text blocks: swap black/white text colors
if (type === '//ly.img.ubq/text') {
const colors = engine.block.getTextColors(id) || [];
colors.forEach((c, i) => {
if (isBlackColor(c)) {
engine.block.setTextColor(id, primary, i, colors.length);
} else if (isWhiteColor(c)) {
engine.block.setTextColor(id, secondary, i, colors.length);
}
});
continue;
}
// Shapes: swap black/white fills
if (engine.block.supportsFill(id)) {
const fill = engine.block.getFill(id);
if (fill && engine.block.getType(fill) === '//ly.img.ubq/fill/color') {
const fillColor = engine.block.getColor(fill, 'fill/color/value');
if (isBlackColor(fillColor)) {
engine.block.setColor(fill, 'fill/color/value', primary);
} else if (isWhiteColor(fillColor)) {
engine.block.setColor(fill, 'fill/color/value', secondary);
}
}
}
}
}
// Helper function to visualize rating with colored indicators
function applyRating(rating: number, maxRating: number = 5): void {
const onColor = hexToRgba01('#FFD700'); // Gold for active
const offColor = hexToRgba01('#E0E0E0'); // Gray for inactive
for (let i = 1; i <= maxRating; i++) {
const blocks = engine.block.findByName(`Rating${i}`);
if (blocks.length === 0) continue;
const ratingBlock = blocks[0];
if (engine.block.supportsFill(ratingBlock)) {
const fill = engine.block.getFill(ratingBlock);
if (fill) {
const color = i <= rating ? onColor : offColor;
engine.block.setColor(fill, 'fill/color/value', color);
}
}
}
}
try {
const restaurantData: RestaurantData = {
name: 'The Golden Fork',
price: '$$$',
reviewCount: 127,
rating: 4,
imageUrl: 'https://img.ly/static/ubq_samples/sample_4.jpg',
primaryColor: '#2D5A27',
secondaryColor: '#F5E6D3'
};
const templates: TemplateConfig[] = [
{ label: 'square', width: 1080, height: 1080 }, // Instagram post (1:1)
{ label: 'portrait', width: 1080, height: 1920 }, // Instagram story (9:16)
{ label: 'landscape', width: 1200, height: 630 } // Facebook/X post (1.91:1)
];
// Prepare output directory
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Store results for summary
const results: { label: string; filename: string }[] = [];
// Process each template format sequentially
for (const template of templates) {
// Create a fresh scene for each template variant
engine.scene.create('Free', {
page: { size: { width: template.width, height: template.height } }
});
const page = engine.block.findByType('page')[0];
// Create template content with text variables
// In production, you would load a pre-designed template instead
// Create background
const bgRect = engine.block.create('graphic');
engine.block.setShape(bgRect, engine.block.createShape('rect'));
const bgFill = engine.block.createFill('color');
engine.block.setColor(bgFill, 'fill/color/value', {
r: 1,
g: 1,
b: 1,
a: 1
});
engine.block.setFill(bgRect, bgFill);
engine.block.setWidth(bgRect, template.width);
engine.block.setHeight(bgRect, template.height);
engine.block.setPositionX(bgRect, 0);
engine.block.setPositionY(bgRect, 0);
engine.block.appendChild(page, bgRect);
// Create hero image block
const heroBlock = engine.block.create('graphic');
engine.block.setShape(heroBlock, engine.block.createShape('rect'));
const heroFill = engine.block.createFill('image');
engine.block.setString(
heroFill,
'fill/image/imageFileURI',
restaurantData.imageUrl
);
engine.block.setFill(heroBlock, heroFill);
engine.block.setName(heroBlock, 'HeroImage');
// Adjust hero image size based on format
const heroHeight = template.height * 0.5;
engine.block.setWidth(heroBlock, template.width);
engine.block.setHeight(heroBlock, heroHeight);
engine.block.setPositionX(heroBlock, 0);
engine.block.setPositionY(heroBlock, 0);
engine.block.appendChild(page, heroBlock);
// Create title text with variable binding
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, '{{Name}}');
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.setFloat(titleBlock, 'text/fontSize', 48);
engine.block.setPositionX(titleBlock, 40);
engine.block.setPositionY(titleBlock, heroHeight + 30);
engine.block.appendChild(page, titleBlock);
// Create price and review count text
const detailsBlock = engine.block.create('text');
engine.block.replaceText(detailsBlock, '{{Price}} · {{Count}} reviews');
engine.block.setWidthMode(detailsBlock, 'Auto');
engine.block.setHeightMode(detailsBlock, 'Auto');
engine.block.setFloat(detailsBlock, 'text/fontSize', 24);
engine.block.setPositionX(detailsBlock, 40);
engine.block.setPositionY(detailsBlock, heroHeight + 100);
engine.block.appendChild(page, detailsBlock);
// Create rating stars
const starSize = 30;
const starSpacing = 35;
const starY = heroHeight + 150;
for (let i = 1; i <= 5; i++) {
const star = engine.block.create('graphic');
engine.block.setShape(star, engine.block.createShape('rect'));
const starFill = engine.block.createFill('color');
engine.block.setColor(starFill, 'fill/color/value', {
r: 0.88,
g: 0.88,
b: 0.88,
a: 1
});
engine.block.setFill(star, starFill);
engine.block.setWidth(star, starSize);
engine.block.setHeight(star, starSize);
engine.block.setPositionX(star, 40 + (i - 1) * starSpacing);
engine.block.setPositionY(star, starY);
engine.block.setName(star, `Rating${i}`);
engine.block.appendChild(page, star);
}
// Apply data to the template
applyVariables(restaurantData);
replaceImageByName('HeroImage', restaurantData.imageUrl, 'Cover');
applyBrandColors(
restaurantData.primaryColor,
restaurantData.secondaryColor
);
applyRating(restaurantData.rating);
// Export the populated template
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
// Save to file system
const filename = `restaurant-${template.label}.png`;
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/${filename}`, buffer);
results.push({ label: template.label, filename });
// eslint-disable-next-line no-console
console.log(
`✓ Exported ${filename} (${template.width}x${template.height})`
);
}
// eslint-disable-next-line no-console
console.log(
`\n✓ Multi-image generation complete: ${results.length} variants exported to ${outputDir}/`
);
// eslint-disable-next-line no-console
console.log(' Variants:', results.map((r) => r.label).join(', '));
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers defining data structures, configuring templates for different formats, populating templates with variables and images, applying brand colors, and exporting variants.
## Define the Data Structure
Start by defining the structure for your input data. Type-safe interfaces clarify requirements and prevent runtime errors.
```typescript highlight=highlight-data-structure
// Define the data structure for a restaurant review card
// In production, this would come from an API, database, or JSON file
interface RestaurantData {
name: string;
price: string;
reviewCount: number;
rating: number;
imageUrl: string;
primaryColor: string;
secondaryColor: string;
}
```
The restaurant review example includes text fields (name, price, review count), image URLs, rating, and brand colors. In production, this data comes from an API, database, or JSON file.
## Configure Template Formats
Define template configurations for each output format. Different aspect ratios serve different platforms and use cases.
```typescript highlight=highlight-template-config
// Define template configurations for different formats
// Each template targets a different aspect ratio for various platforms
interface TemplateConfig {
label: string;
width: number;
height: number;
}
```
Common format targets include:
- **Square (1:1)**: Instagram posts, profile images
- **Portrait (9:16)**: Instagram stories, TikTok, mobile displays
- **Landscape (1.91:1)**: Facebook posts, Twitter/X cards, web banners
## Initialize the Engine
Initialize the headless Creative Engine for server-side processing.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
The engine runs without a browser context, making it suitable for batch processing and API integrations.
## Create Helper Functions
Build reusable functions for common template operations to keep the main generation loop clean.
### Text Variable Substitution
Use `engine.variable.setString()` to populate text placeholders. Include null checks for robust data handling.
```typescript highlight=highlight-helper-variables
// Helper function to apply text variables from data
function applyVariables(data: RestaurantData): void {
const safeName = data.name?.trim() || 'Restaurant';
const safePrice = data.price?.trim() || '$';
const safeCount =
data.reviewCount !== undefined && data.reviewCount !== null
? data.reviewCount.toString()
: '0';
engine.variable.setString('Name', safeName);
engine.variable.setString('Price', safePrice);
engine.variable.setString('Count', safeCount);
}
```
Variables update all text blocks that reference them using the `{{variableName}}` syntax.
### Dynamic Image Replacement
Replace images by finding blocks with semantic names and updating their fill URI.
```typescript highlight=highlight-helper-image
// Helper function to replace an image by block name
function replaceImageByName(
blockName: string,
newUrl: string,
mode: 'Cover' | 'Contain' = 'Cover'
): void {
const blocks = engine.block.findByName(blockName);
if (blocks.length === 0) {
// eslint-disable-next-line no-console
console.warn(`Block "${blockName}" not found`);
return;
}
const imageBlock = blocks[0];
let fill = engine.block.getFill(imageBlock);
if (!fill) {
fill = engine.block.createFill('image');
engine.block.setFill(imageBlock, fill);
}
engine.block.setString(fill, 'fill/image/imageFileURI', newUrl);
engine.block.resetCrop(imageBlock);
engine.block.setContentFillMode(imageBlock, mode);
}
```
Using `findByName()` instead of `findByType()` targets specific blocks without affecting other images in the template.
### Brand Color Theming
Apply consistent brand colors across template elements by replacing standard black/white values with brand primary/secondary colors.
```typescript highlight=highlight-helper-colors
// Helper function to convert hex color to RGBA (0-1 range)
function hexToRgba01(hex: string): RGBAColor {
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());
if (!m) throw new Error(`Invalid hex color: ${hex}`);
return {
r: parseInt(m[1], 16) / 255,
g: parseInt(m[2], 16) / 255,
b: parseInt(m[3], 16) / 255,
a: 1
};
}
// Helper to check if color is black (r=0, g=0, b=0)
function isBlackColor(color: unknown): boolean {
if (!isRGBAColor(color as RGBAColor)) return false;
const c = color as RGBAColor;
return c.r === 0 && c.g === 0 && c.b === 0;
}
// Helper to check if color is white (r=1, g=1, b=1)
function isWhiteColor(color: unknown): boolean {
if (!isRGBAColor(color as RGBAColor)) return false;
const c = color as RGBAColor;
return c.r === 1 && c.g === 1 && c.b === 1;
}
```
This approach preserves other colors while systematically applying brand theming to text and shapes.
### Rating Visualization
Display ratings visually using conditional coloring of indicator blocks.
```typescript highlight=highlight-helper-rating
// Helper function to visualize rating with colored indicators
function applyRating(rating: number, maxRating: number = 5): void {
const onColor = hexToRgba01('#FFD700'); // Gold for active
const offColor = hexToRgba01('#E0E0E0'); // Gray for inactive
for (let i = 1; i <= maxRating; i++) {
const blocks = engine.block.findByName(`Rating${i}`);
if (blocks.length === 0) continue;
const ratingBlock = blocks[0];
if (engine.block.supportsFill(ratingBlock)) {
const fill = engine.block.getFill(ratingBlock);
if (fill) {
const color = i <= rating ? onColor : offColor;
engine.block.setColor(fill, 'fill/color/value', color);
}
}
}
}
```
Rating blocks use a naming convention (`Rating1`, `Rating2`, etc.) to identify and color them based on the rating value.
## Process Templates Sequentially
Iterate through each template format, creating a fresh scene for each variant. Sequential processing keeps memory usage predictable.
```typescript highlight=highlight-generation-loop
// Process each template format sequentially
for (const template of templates) {
// Create a fresh scene for each template variant
engine.scene.create('Free', {
page: { size: { width: template.width, height: template.height } }
});
const page = engine.block.findByType('page')[0];
```
Creating a new scene for each template ensures variable values and block states don't carry over between variants.
## Build Template Content
For each format, build the template layout with properly sized and positioned blocks. In production, you would load pre-designed templates instead of creating them programmatically.
```typescript highlight=highlight-create-content
// Create template content with text variables
// In production, you would load a pre-designed template instead
// Create background
const bgRect = engine.block.create('graphic');
engine.block.setShape(bgRect, engine.block.createShape('rect'));
const bgFill = engine.block.createFill('color');
engine.block.setColor(bgFill, 'fill/color/value', {
r: 1,
g: 1,
b: 1,
a: 1
});
engine.block.setFill(bgRect, bgFill);
engine.block.setWidth(bgRect, template.width);
engine.block.setHeight(bgRect, template.height);
engine.block.setPositionX(bgRect, 0);
engine.block.setPositionY(bgRect, 0);
engine.block.appendChild(page, bgRect);
// Create hero image block
const heroBlock = engine.block.create('graphic');
engine.block.setShape(heroBlock, engine.block.createShape('rect'));
const heroFill = engine.block.createFill('image');
engine.block.setString(
heroFill,
'fill/image/imageFileURI',
restaurantData.imageUrl
);
engine.block.setFill(heroBlock, heroFill);
engine.block.setName(heroBlock, 'HeroImage');
// Adjust hero image size based on format
const heroHeight = template.height * 0.5;
engine.block.setWidth(heroBlock, template.width);
engine.block.setHeight(heroBlock, heroHeight);
engine.block.setPositionX(heroBlock, 0);
engine.block.setPositionY(heroBlock, 0);
engine.block.appendChild(page, heroBlock);
// Create title text with variable binding
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, '{{Name}}');
engine.block.setWidthMode(titleBlock, 'Auto');
engine.block.setHeightMode(titleBlock, 'Auto');
engine.block.setFloat(titleBlock, 'text/fontSize', 48);
engine.block.setPositionX(titleBlock, 40);
engine.block.setPositionY(titleBlock, heroHeight + 30);
engine.block.appendChild(page, titleBlock);
// Create price and review count text
const detailsBlock = engine.block.create('text');
engine.block.replaceText(detailsBlock, '{{Price}} · {{Count}} reviews');
engine.block.setWidthMode(detailsBlock, 'Auto');
engine.block.setHeightMode(detailsBlock, 'Auto');
engine.block.setFloat(detailsBlock, 'text/fontSize', 24);
engine.block.setPositionX(detailsBlock, 40);
engine.block.setPositionY(detailsBlock, heroHeight + 100);
engine.block.appendChild(page, detailsBlock);
// Create rating stars
const starSize = 30;
const starSpacing = 35;
const starY = heroHeight + 150;
for (let i = 1; i <= 5; i++) {
const star = engine.block.create('graphic');
engine.block.setShape(star, engine.block.createShape('rect'));
const starFill = engine.block.createFill('color');
engine.block.setColor(starFill, 'fill/color/value', {
r: 0.88,
g: 0.88,
b: 0.88,
a: 1
});
engine.block.setFill(star, starFill);
engine.block.setWidth(star, starSize);
engine.block.setHeight(star, starSize);
engine.block.setPositionX(star, 40 + (i - 1) * starSpacing);
engine.block.setPositionY(star, starY);
engine.block.setName(star, `Rating${i}`);
engine.block.appendChild(page, star);
}
```
The template includes:
- Background fill
- Hero image with semantic name for replacement
- Text blocks with variable bindings
- Rating indicator blocks with naming convention
## Populate and Export
After building the template structure, apply the data and export the result.
```typescript highlight=highlight-populate-template
// Apply data to the template
applyVariables(restaurantData);
replaceImageByName('HeroImage', restaurantData.imageUrl, 'Cover');
applyBrandColors(
restaurantData.primaryColor,
restaurantData.secondaryColor
);
applyRating(restaurantData.rating);
```
The population step:
1. Sets text variables from the data record
2. Updates the hero image URL
3. Applies brand colors to text and shapes
4. Colors rating indicators based on the rating value
Then export to the file system:
```typescript highlight=highlight-export
// Export the populated template
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
// Save to file system
const filename = `restaurant-${template.label}.png`;
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/${filename}`, buffer);
results.push({ label: template.label, filename });
// eslint-disable-next-line no-console
console.log(
`✓ Exported ${filename} (${template.width}x${template.height})`
);
```
For production deployments, write to cloud storage or return blobs for API responses instead of the local file system.
## Cleanup Resources
Always dispose the engine when processing completes. The `finally` block ensures cleanup happens even if errors occur.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Troubleshooting
### Variables Not Updating
If text shows `{{variableName}}` instead of the actual value:
- Variable names are case-sensitive—verify exact match
- Ensure `setString()` is called before export
- Use `engine.variable.findAll()` to check defined variables
- Create a new scene for each variant to clear previous state
### Images Not Appearing
If images fail to load in exported designs:
- Verify image URLs are accessible from the server
- Check CORS headers if loading from external domains
- Ensure the fill type is `image` before setting `fill/image/imageFileURI`
- Call `resetCrop()` after changing the image URI
### Colors Not Applying
If brand colors don't appear correctly:
- The color comparison checks for exact black (0,0,0) and white (1,1,1)
- Use `isRGBAColor()` type guard for safe color property access
- Check block type before applying text vs fill color updates
- Verify fill type is `//ly.img.ubq/fill/color` for shape fills
### Memory Issues
For high-volume processing:
- Process formats sequentially (not in parallel) to limit memory
- Dispose and recreate the engine for very large batches
- Monitor memory usage in production environments
- Consider worker processes for parallel format generation
## API Reference
| Method | Description |
|--------|-------------|
| `engine.variable.setString(name, value)` | Set a text variable's value |
| `engine.variable.findAll()` | List all variable names in the scene |
| `engine.block.findByName(name)` | Find blocks by semantic name |
| `engine.block.findByType(type)` | Find blocks by type |
| `engine.block.setName(block, name)` | Set a block's semantic name |
| `engine.block.getFill(block)` | Get the fill block ID |
| `engine.block.setFill(block, fill)` | Set a block's fill |
| `engine.block.createFill(type)` | Create a new fill of specified type |
| `engine.block.setString(block, property, value)` | Set a string property |
| `engine.block.setColor(block, property, color)` | Set a color property (RGBA, 0-1 range) |
| `engine.block.getColor(block, property)` | Get a color property |
| `engine.block.setTextColor(block, color, start, length)` | Set text color for a range |
| `engine.block.getTextColors(block)` | Get all text colors in a block |
| `engine.block.supportsFill(block)` | Check if block supports fill |
| `engine.block.setContentFillMode(block, mode)` | Set image fill mode (Cover, Contain, Crop) |
| `engine.block.resetCrop(block)` | Reset image crop to default |
| `engine.block.export(block, options)` | Export block to image format |
| `engine.scene.create(layout, options)` | Create a new scene |
| `engine.dispose()` | Dispose engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/automation/overview-34d971/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/node-native/automation-715209/) > [Overview](https://img.ly/docs/cesdk/node-native/automation/overview-34d971/)
---
## Automation Contexts
### 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.
### 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
### Output Formats
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Colors"
description: "Manage color usage in your designs, from applying brand palettes to handling print and screen formats."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors-a9b79c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/node-native/colors/overview-16a177/) - Manage color usage in your designs, from applying brand palettes to handling print and screen formats.
- [Color Basics](https://img.ly/docs/cesdk/node-native/colors/basics-307115/) - Learn how color works in CE.SDK, including the three supported color spaces (sRGB, CMYK, and Spot) and when to use each for screen display or print workflows.
- [For Print](https://img.ly/docs/cesdk/node-native/colors/for-print-59bc05/) - Use print-ready color models and settings for professional-quality, production-ready exports.
- [For Screen](https://img.ly/docs/cesdk/node-native/colors/for-screen-1911f8/) - Documentation for For Screen
- [Apply Colors](https://img.ly/docs/cesdk/node-native/colors/apply-2211e3/) - Apply solid colors to shapes, backgrounds, and other design elements.
- [Create a Color Palette](https://img.ly/docs/cesdk/node-native/colors/create-color-palette-7012e0/) - Build reusable color palettes to maintain consistency and streamline user choices.
- [Replace Individual Colors](https://img.ly/docs/cesdk/node-native/colors/replace-48cd71/) - Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects to swap colors or remove backgrounds.
- [Adjust Colors](https://img.ly/docs/cesdk/node-native/colors/adjust-590d1e/) - Fine-tune images and design elements by adjusting brightness, contrast, saturation, exposure, and other color properties using CE.SDK's adjustments effect system.
- [Color Conversion](https://img.ly/docs/cesdk/node-native/colors/conversion-bcd82b/) - Learn how to convert colors between color spaces in CE.SDK. Convert sRGB, CMYK, and spot colors programmatically for screen display or print workflows.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Adjust Colors"
description: "Fine-tune images and design elements by adjusting brightness, contrast, saturation, exposure, and other color properties using CE.SDK's adjustments effect system."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/adjust-590d1e/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [Adjust Colors](https://img.ly/docs/cesdk/node-native/colors/adjust-590d1e/)
---
Fine-tune images programmatically using CE.SDK's color adjustments system to control brightness, contrast, saturation, and other visual properties in server-side applications.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-adjust-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-adjust-server-js)
Color adjustments allow you to modify the visual appearance of images and graphics by changing properties like brightness, contrast, saturation, and color temperature. CE.SDK implements color adjustments as an "adjustments" effect type that you can apply to compatible blocks.
```typescript file=@cesdk_web_examples/guides-colors-adjust-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Adjust Colors
*
* Demonstrates how to adjust color properties of images programmatically:
* - Creating adjustments effects
* - Setting brightness, contrast, saturation, and other properties
* - Enabling/disabling adjustments
* - Reading adjustment values
* - Applying different adjustment styles
* - Exporting the result
*/
async function main() {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Sample image URL
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);
// Export the result to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/adjusted-colors.png`, buffer);
console.log('Exported result to output/adjusted-colors.png');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers how to apply color adjustments programmatically using the block API in server-side Node.js applications.
## Initialize the Engine
We start by initializing the headless Creative Engine with a scene and page for our image processing workflow.
```typescript highlight=highlight-setup
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
```
## Check Block Compatibility
Before applying adjustments, we verify the block supports effects. Not all block types support adjustments—for example, page blocks don't support effects directly, but image and graphic blocks do.
```typescript highlight=highlight-check-support
// Check if a block supports effects before applying adjustments
const imageBlock = await engine.block.addImage(imageUri, {
size: { width: 400, height: 300 }
});
engine.block.appendChild(page, imageBlock);
engine.block.setPositionX(imageBlock, 200);
engine.block.setPositionY(imageBlock, 150);
const supportsEffects = engine.block.supportsEffects(imageBlock);
console.log('Block supports effects:', supportsEffects);
```
## Create and Apply Adjustments Effect
Once we've confirmed a block supports effects, we create an adjustments effect and attach it to the block using `appendEffect()`.
```typescript highlight=highlight-create-adjustments
// Create an adjustments effect
const adjustmentsEffect = engine.block.createEffect('adjustments');
// Attach the adjustments effect to the image block
engine.block.appendEffect(imageBlock, adjustmentsEffect);
```
Each block can have one adjustments effect in its effect stack. The adjustments effect provides access to all color adjustment properties through a single effect instance.
## Modify Adjustment Properties
We set individual adjustment values using `setFloat()` with the effect block ID and property path. Each property uses the `effect/adjustments/` prefix followed by the property name.
```typescript highlight=highlight-set-properties
// Set brightness - positive values lighten, negative values darken
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/brightness',
0.4
);
// Set contrast - increases or decreases tonal range
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/contrast',
0.35
);
// Set saturation - increases or decreases color intensity
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/saturation',
0.5
);
// Set temperature - positive for warmer, negative for cooler tones
engine.block.setFloat(
adjustmentsEffect,
'effect/adjustments/temperature',
0.25
);
```
CE.SDK provides the following adjustment properties:
| Property | Description |
|----------|-------------|
| `brightness` | Overall lightness—positive values lighten, negative values darken |
| `contrast` | Tonal range—increases or decreases the difference between light and dark |
| `saturation` | Color intensity—positive values increase vibrancy, negative values desaturate |
| `exposure` | Exposure compensation—simulates camera exposure adjustments |
| `gamma` | Gamma curve—adjusts midtone brightness |
| `highlights` | Bright area intensity—controls the lightest parts of the image |
| `shadows` | Dark area intensity—controls the darkest parts of the image |
| `whites` | White point—adjusts the brightest pixels |
| `blacks` | Black point—adjusts the darkest pixels |
| `temperature` | Warm/cool color cast—positive for warmer, negative for cooler tones |
| `sharpness` | Edge sharpness—enhances or softens edges |
| `clarity` | Midtone contrast—increases local contrast for more definition |
All properties accept float values. Experiment with different values to achieve the desired visual result.
## Read Adjustment Values
We can read current adjustment values using `getFloat()` with the same property paths. Use `findAllProperties()` to discover all available properties on an adjustments effect.
```typescript highlight=highlight-read-values
// Read current adjustment values
const brightness = engine.block.getFloat(
adjustmentsEffect,
'effect/adjustments/brightness'
);
console.log('Current brightness:', brightness);
// Discover all available adjustment properties
const allProperties = engine.block.findAllProperties(adjustmentsEffect);
console.log('Available adjustment properties:', allProperties);
```
This is useful for building batch processing pipelines or logging adjustment configurations.
## 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 or conditional processing.
```typescript highlight=highlight-enable-disable
// Disable adjustments temporarily (effect remains attached)
engine.block.setEffectEnabled(adjustmentsEffect, false);
console.log(
'Adjustments enabled:',
engine.block.isEffectEnabled(adjustmentsEffect)
);
// Re-enable adjustments
engine.block.setEffectEnabled(adjustmentsEffect, true);
```
When you disable an adjustments effect, it remains attached to the block but won't be rendered until you enable it again. This preserves all adjustment values while giving you control over when adjustments are applied.
## Applying Different Adjustment Styles
You can apply different adjustment combinations to create distinct visual styles. This example demonstrates a contrasting moody look using negative brightness, high contrast, and desaturation.
```typescript highlight=highlight-combine-effects
// Create a second image to demonstrate a different adjustment style
const secondImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
engine.block.appendChild(page, secondImageBlock);
engine.block.setPositionX(secondImageBlock, 50);
engine.block.setPositionY(secondImageBlock, 50);
// Apply a contrasting style: darker, high contrast, desaturated (moody look)
const combinedAdjustments = engine.block.createEffect('adjustments');
engine.block.appendEffect(secondImageBlock, combinedAdjustments);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/brightness',
-0.15
);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/contrast',
0.4
);
engine.block.setFloat(
combinedAdjustments,
'effect/adjustments/saturation',
-0.3
);
// List all effects on the block
const effects = engine.block.getEffects(secondImageBlock);
console.log('Effects on second image:', effects.length);
```
By combining different adjustment properties, you can create warm and vibrant looks, cool and desaturated styles, or high-contrast dramatic effects.
## Refinement Adjustments
Beyond basic color corrections, CE.SDK provides refinement adjustments for fine-tuning image detail and tonal balance.
```typescript highlight=highlight-refinement-adjustments
// Add refinement adjustments to demonstrate subtle enhancement properties
const refinementEffect = engine.block.createEffect('adjustments');
engine.block.appendEffect(tempBlock, refinementEffect);
// Sharpness - enhances edge definition
engine.block.setFloat(
refinementEffect,
'effect/adjustments/sharpness',
0.4
);
// Clarity - increases mid-tone contrast for more detail
engine.block.setFloat(refinementEffect, 'effect/adjustments/clarity', 0.35);
// Highlights - adjusts bright areas
engine.block.setFloat(
refinementEffect,
'effect/adjustments/highlights',
-0.2
);
// Shadows - adjusts dark areas
engine.block.setFloat(refinementEffect, 'effect/adjustments/shadows', 0.3);
```
Refinement properties include:
- **Sharpness** - Enhances edge definition for crisper details
- **Clarity** - Increases mid-tone contrast for more depth and definition
- **Highlights** - Controls the intensity of bright areas
- **Shadows** - Controls the intensity of dark areas
These adjustments are particularly useful for batch processing workflows where consistent enhancement is needed.
## Remove Adjustments
When you no longer need adjustments, you can remove them from the effect stack and free resources. Always destroy effects that are no longer in use to prevent memory leaks.
```typescript highlight=highlight-remove-adjustments
// Demonstrate removing an effect
const tempBlock = await engine.block.addImage(imageUri, {
size: { width: 150, height: 100 }
});
engine.block.appendChild(page, tempBlock);
engine.block.setPositionX(tempBlock, 550);
engine.block.setPositionY(tempBlock, 50);
const tempEffect = engine.block.createEffect('adjustments');
engine.block.appendEffect(tempBlock, tempEffect);
engine.block.setFloat(tempEffect, 'effect/adjustments/brightness', 0.5);
// Remove the effect by index
const tempEffects = engine.block.getEffects(tempBlock);
const effectIndex = tempEffects.indexOf(tempEffect);
if (effectIndex !== -1) {
engine.block.removeEffect(tempBlock, effectIndex);
}
// Destroy the removed effect to free memory
engine.block.destroy(tempEffect);
```
The `removeEffect()` method takes an index position. After removal, destroy the effect instance to ensure proper cleanup.
## Export and Cleanup
After applying adjustments, we export the result to a file and dispose of the engine to free resources.
```typescript highlight=highlight-export
// Export the result to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/adjusted-colors.png`, buffer);
console.log('Exported result to output/adjusted-colors.png');
```
Always dispose of the engine in a finally block to ensure resources are freed even if an error occurs.
```typescript highlight=highlight-cleanup
// Always dispose of the engine to free resources
engine.dispose();
```
## Troubleshooting
### Adjustments Not Visible
If adjustments don't appear in exported images:
- Verify the block supports effects using `supportsEffects()`
- Check that the effect is enabled with `isEffectEnabled()`
- Ensure the adjustments effect was appended to the block, not just created
- Confirm adjustment values are non-zero
### Unexpected Results
If adjustments produce unexpected visual results:
- Check the effect stack order—adjustments applied before or after other effects may produce different results
- Verify property paths include the `effect/adjustments/` prefix
- Use `findAllProperties()` to verify correct property names
### Property Not Found
If you encounter property not found errors:
- Use `findAllProperties()` to list all available properties
- Ensure property paths use the correct `effect/adjustments/` prefix format
## API Reference
| Method | Description |
|--------|-------------|
| `block.supportsEffects(block)` | Check if a block supports effects |
| `block.createEffect('adjustments')` | Create an adjustments effect |
| `block.appendEffect(block, effect)` | Add effect to the end of the effect stack |
| `block.insertEffect(block, effect, index)` | Insert effect at a specific position |
| `block.getEffects(block)` | Get all effects applied to a block |
| `block.removeEffect(block, index)` | Remove effect at the specified index |
| `block.setEffectEnabled(effect, enabled)` | Enable or disable an effect |
| `block.isEffectEnabled(effect)` | Check if an effect is enabled |
| `block.setFloat(effect, property, value)` | Set a float property value |
| `block.getFloat(effect, property)` | Get a float property value |
| `block.findAllProperties(effect)` | List all properties of an effect |
| `block.destroy(effect)` | Destroy an effect and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Apply Colors"
description: "Apply solid colors to shapes, backgrounds, and other design elements."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/apply-2211e3/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [Apply Color](https://img.ly/docs/cesdk/node-native/colors/apply-2211e3/)
---
Apply solid colors to design elements programmatically using CE.SDK's color system with support for RGB, CMYK, and spot colors in server-side Node.js applications.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-apply-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-apply-server-js)
Colors in CE.SDK are applied to block properties like fill, stroke, and shadow using `engine.block.setColor()`. The engine supports three color spaces: sRGB for screen display, CMYK for print production, and spot colors for specialized printing requirements.
```typescript file=@cesdk_web_examples/guides-colors-apply-server-js/server-js.ts reference-only
import CreativeEngine, { RGBAColor } from '@cesdk/node';
import * as fs from 'fs';
async function main() {
const engine = await CreativeEngine.init({
// license: 'YOUR_CESDK_LICENSE_KEY'
});
try {
// Create a 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 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, 8);
engine.block.setColor(block, 'stroke/color', cmykRed);
// Enable and apply drop shadow color
engine.block.setDropShadowEnabled(block, true);
engine.block.setDropShadowOffsetX(block, 10);
engine.block.setDropShadowOffsetY(block, 10);
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');
// Export the scene with applied colors
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
fs.writeFileSync('output.png', buffer);
console.log('Exported scene to output.png');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
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.
## Initialize the Engine
We start by initializing the headless Creative Engine with a scene and page for our color application workflow.
```typescript highlight=highlight-setup
async function main() {
const engine = await CreativeEngine.init({
// license: 'YOUR_CESDK_LICENSE_KEY'
});
try {
// Create a 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 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 Color Objects
CE.SDK represents colors as JavaScript objects with properties specific to each color space. We create color objects that match our target output—RGB for screens, CMYK for print, or spot colors for precise color matching.
```typescript highlight=highlight-create-colors
// Create RGB color (values 0.0-1.0)
const rgbaBlue: RGBAColor = { r: 0.0, g: 0.0, b: 1.0, a: 1.0 };
// Create CMYK color (cyan, magenta, yellow, black, tint)
const cmykRed = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 };
// Create spot color reference
const spotPink = {
name: 'Pink-Flamingo',
tint: 1.0,
externalReference: 'Pantone'
};
```
RGB colors use `{ r, g, b, a }` with values from 0.0 to 1.0 for each channel, where `a` is alpha (opacity). CMYK colors use `{ c, m, y, k, tint }` where tint controls the overall intensity. Spot colors use `{ name, tint, externalReference }` to reference a defined spot color by name.
## Define Spot Colors
Before applying a spot color, we must define its screen preview approximation. The engine needs to know how to display the color since spot colors represent inks that can't be directly rendered on screens.
```typescript highlight=highlight-define-spot
// Define spot colors with screen preview approximations
engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.41, 0.71);
engine.editor.setSpotColorCMYK('Corporate-Blue', 1.0, 0.5, 0.0, 0.2);
```
Use `engine.editor.setSpotColorRGB()` to define the RGB approximation with red, green, and blue values from 0.0 to 1.0. Use `engine.editor.setSpotColorCMYK()` for the CMYK approximation with cyan, magenta, yellow, black, and tint values. A spot color can have both RGB and CMYK approximations defined.
## Apply Fill Colors
To set a block's fill color, we first get the fill block using `engine.block.getFill()`, then apply the color using `engine.block.setColor()` with the `'fill/color/value'` property.
```typescript highlight=highlight-apply-fill
// Apply RGB color to fill
const fill = engine.block.getFill(block);
engine.block.setColor(fill, 'fill/color/value', rgbaBlue);
// Read the current fill color
const currentFillColor = engine.block.getColor(fill, 'fill/color/value');
console.log('Current fill color:', currentFillColor);
```
The fill block is a separate entity from the design block. We can read the current color using `engine.block.getColor()` with the same property path.
## Apply Stroke Colors
Stroke colors are applied directly to the design block using the `'stroke/color'` property. We enable the stroke first using `engine.block.setStrokeEnabled()`.
```typescript highlight=highlight-apply-stroke
// Enable and apply stroke color
engine.block.setStrokeEnabled(block, true);
engine.block.setStrokeWidth(block, 8);
engine.block.setColor(block, 'stroke/color', cmykRed);
```
The stroke renders around the edges of the block with the specified color. Set the stroke width using `engine.block.setStrokeWidth()` to control the line thickness.
## Apply Shadow Colors
Drop shadow colors use the `'dropShadow/color'` property on the design block. Enable shadows first using `engine.block.setDropShadowEnabled()`.
```typescript highlight=highlight-apply-shadow
// Enable and apply drop shadow color
engine.block.setDropShadowEnabled(block, true);
engine.block.setDropShadowOffsetX(block, 10);
engine.block.setDropShadowOffsetY(block, 10);
engine.block.setColor(block, 'dropShadow/color', spotPink);
```
Control the shadow position using `setDropShadowOffsetX()` and `setDropShadowOffsetY()`. Spot colors work with shadows just like RGB or CMYK colors.
## Convert Between Color Spaces
Use `engine.editor.convertColorToColorSpace()` to convert any color to a different color space. This is useful when you need to output designs in a specific color format.
```typescript highlight=highlight-convert-color
// Convert colors between color spaces
const cmykFromRgb = engine.editor.convertColorToColorSpace(
rgbaBlue,
'CMYK'
);
console.log('CMYK from RGB:', cmykFromRgb);
const rgbFromCmyk = engine.editor.convertColorToColorSpace(cmykRed, 'sRGB');
console.log('RGB from CMYK:', rgbFromCmyk);
```
Pass the source color object and target color space (`'sRGB'` or `'CMYK'`). Spot colors convert to their defined approximation in the target space. Note that color conversions are approximations—CMYK has a smaller color gamut than sRGB.
## List Defined Spot Colors
Query all spot colors currently defined in the editor using `engine.editor.findAllSpotColors()`. This returns an array of spot color names.
```typescript highlight=highlight-list-spot
// List all defined spot colors
const allSpotColors = engine.editor.findAllSpotColors();
console.log('Defined spot colors:', allSpotColors);
```
This is useful for batch processing workflows or validating that required spot colors are defined before export.
## Update Spot Color Definitions
Redefine a spot color's approximation by calling `setSpotColorRGB()` or `setSpotColorCMYK()` with the same name. All blocks using that spot color automatically update their rendered appearance.
```typescript highlight=highlight-update-spot
// Update a spot color definition
engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.6, 0.8);
console.log('Updated Pink-Flamingo spot color');
```
This allows you to adjust how spot colors appear without modifying every block that uses them.
## Remove Spot Color Definitions
Remove a spot color definition using `engine.editor.removeSpotColor()`. Blocks still referencing that color fall back to the default magenta approximation.
```typescript highlight=highlight-remove-spot
// Remove a spot color definition (falls back to magenta)
engine.editor.removeSpotColor('Corporate-Blue');
console.log('Removed Corporate-Blue spot color');
```
This is useful when cleaning up unused spot colors or when you need to signal that a spot color is no longer valid.
## Export and Cleanup
After applying colors, we export the result to a file and dispose of the engine to free resources.
```typescript highlight=highlight-export
// Export the scene with applied colors
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
fs.writeFileSync('output.png', buffer);
console.log('Exported scene to output.png');
```
Always dispose of the engine in a finally block to ensure resources are freed even if an error occurs.
## Troubleshooting
### Spot Color Appears Magenta
The spot color wasn't defined before use. Call `setSpotColorRGB()` or `setSpotColorCMYK()` with the exact spot color name before applying it to blocks.
### Stroke or Shadow Color Not Visible
The effect isn't enabled. Call `setStrokeEnabled(block, true)` or `setDropShadowEnabled(block, true)` before setting the color.
### Color Looks Different After Conversion
Color space conversions are approximations. CMYK has a smaller gamut than sRGB, so vibrant colors may appear muted after conversion.
### Can't Apply Color to Fill
Apply colors to the fill block obtained from `getFill()`, not the parent design block. The fill is a separate entity with its own color property.
## API Reference
| Method | Description |
|--------|-------------|
| `block.setColor(block, property, color)` | Set a color property on a block |
| `block.getColor(block, property)` | Get a color property from a block |
| `block.getFill(block)` | Get the fill block of a design block |
| `block.setStrokeEnabled(block, enabled)` | Enable or disable stroke on a block |
| `block.setDropShadowEnabled(block, enabled)` | Enable or disable drop shadow on a block |
| `editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with RGB approximation |
| `editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with CMYK approximation |
| `editor.findAllSpotColors()` | List all defined spot colors |
| `editor.removeSpotColor(name)` | Remove a spot color definition |
| `editor.convertColorToColorSpace(color, colorSpace)` | Convert a color to a different color space |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Color Basics"
description: "Learn how color works in CE.SDK, including the three supported color spaces (sRGB, CMYK, and Spot) and when to use each for screen display or print workflows."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/basics-307115/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [Basics](https://img.ly/docs/cesdk/node-native/colors/basics-307115/)
---
Understand the three color spaces in CE.SDK and when to use each for screen or print workflows.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-basics-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-basics-server-js)
CE.SDK supports three color spaces: **sRGB** for screen display, **CMYK** for print workflows, and **Spot Color** for specialized printing. Each color space serves different output types and has its own object format for the `setColor()` API.
```typescript file=@cesdk_web_examples/guides-colors-basics-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Calculate block sizes for three columns
const margin = 40;
const spacing = 30;
const availableWidth = pageWidth - 2 * margin - 2 * spacing;
const blockWidth = availableWidth / 3;
const blockHeight = pageHeight - 2 * margin;
// 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: ''
});
// 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);
// Export the scene to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/color-basics.png`, buffer);
console.log('Exported to output/color-basics.png');
console.log('Color Basics example completed successfully');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
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.
## Initialize the Engine
Set up the headless Creative Engine for server-side color operations.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
```
## Color Spaces Overview
CE.SDK represents colors as objects with different properties depending on the color space. Use `engine.block.setColor()` to apply any color type to supported properties.
**Supported color properties:**
- `'fill/color/value'` - Fill color of a block
- `'stroke/color'` - Stroke/outline color
- `'dropShadow/color'` - Drop shadow color
- `'backgroundColor/color'` - Background color
- `'camera/clearColor'` - Canvas clear color
## sRGB Colors
sRGB is the default color space for screen display. Pass an `RGBAColor` object with `r`, `g`, `b`, `a` components, each in the range 0.0 to 1.0. The `a` (alpha) component controls transparency.
```typescript highlight=highlight-srgb-color
// Block 1: sRGB color (for screen display)
const srgbBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
srgbBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using RGBAColor object (values 0.0-1.0)
engine.block.setColor(srgbFill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1.0
});
engine.block.setFill(srgbBlock, srgbFill);
engine.block.setWidth(srgbBlock, blockWidth);
engine.block.setHeight(srgbBlock, blockHeight);
engine.block.appendChild(page, srgbBlock);
```
sRGB colors are ideal for web and digital content where the output is displayed on screens.
## CMYK Colors
CMYK is the color space for print workflows. Pass a `CMYKColor` object with `c`, `m`, `y`, `k` components (0.0 to 1.0) plus a `tint` value that controls opacity.
```typescript highlight=highlight-cmyk-color
// Block 2: CMYK color (for print workflows)
const cmykBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
cmykBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using CMYKColor object (values 0.0-1.0, tint controls opacity)
engine.block.setColor(cmykFill, 'fill/color/value', {
c: 0.0,
m: 0.8,
y: 0.95,
k: 0.0,
tint: 1.0
});
engine.block.setFill(cmykBlock, cmykFill);
engine.block.setWidth(cmykBlock, blockWidth);
engine.block.setHeight(cmykBlock, blockHeight);
engine.block.appendChild(page, cmykBlock);
```
When rendered on screen, CMYK colors are converted to RGB using standard conversion formulas. The `tint` value (0.0 to 1.0) is rendered as transparency.
> **Note:** During PDF export, CMYK colors are currently converted to RGB using the standard conversion. Tint values are retained in the alpha channel.
## Spot Colors
Spot colors are named colors used for specialized printing. Before using a spot color, you must define it with an RGB or CMYK approximation for screen preview.
### Defining Spot Colors
Use `engine.editor.setSpotColorRGB()` or `engine.editor.setSpotColorCMYK()` to register a spot color with its screen preview approximation.
```typescript highlight=highlight-define-spot-color
// Define a spot color with RGB approximation for screen preview
engine.editor.setSpotColorRGB('MyBrand Red', 0.95, 0.25, 0.21);
```
### Applying Spot Colors
Reference a defined spot color using a `SpotColor` object with the `name`, `tint`, and `externalReference` properties.
```typescript highlight=highlight-spot-color
// Block 3: Spot color (for specialized printing)
const spotBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
spotBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const spotFill = engine.block.createFill('//ly.img.ubq/fill/color');
// Set fill color using SpotColor object (references the defined spot color)
engine.block.setColor(spotFill, 'fill/color/value', {
name: 'MyBrand Red',
tint: 1.0,
externalReference: ''
});
engine.block.setFill(spotBlock, spotFill);
engine.block.setWidth(spotBlock, blockWidth);
engine.block.setHeight(spotBlock, blockHeight);
engine.block.appendChild(page, spotBlock);
```
When rendered on screen, the spot color uses its RGB or CMYK approximation. During PDF export, spot colors are saved as a [Separation Color Space](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.6.pdf#G9.1850648) that preserves print information.
> **Note:** If a block references an undefined spot color, CE.SDK displays magenta (RGB: 1, 0, 1) as a fallback.
## Applying Stroke Colors
Strokes support all three color spaces. Enable the stroke, set its width, then apply a color using the `'stroke/color'` property.
```typescript highlight=highlight-stroke-color
// Add strokes to demonstrate stroke color property
engine.block.setStrokeEnabled(srgbBlock, true);
engine.block.setStrokeWidth(srgbBlock, 4);
engine.block.setColor(srgbBlock, 'stroke/color', {
r: 0.1,
g: 0.2,
b: 0.5,
a: 1.0
});
engine.block.setStrokeEnabled(cmykBlock, true);
engine.block.setStrokeWidth(cmykBlock, 4);
engine.block.setColor(cmykBlock, 'stroke/color', {
c: 0.0,
m: 0.5,
y: 0.6,
k: 0.2,
tint: 1.0
});
engine.block.setStrokeEnabled(spotBlock, true);
engine.block.setStrokeWidth(spotBlock, 4);
engine.block.setColor(spotBlock, 'stroke/color', {
name: 'MyBrand Red',
tint: 0.7,
externalReference: ''
});
```
## Reading Color Values
Use `engine.block.getColor()` to retrieve the current color value from a property. The returned object's shape indicates the color space (RGBAColor, CMYKColor, or SpotColor).
```typescript highlight=highlight-get-color
// Retrieve and log color values to demonstrate getColor()
const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value');
const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value');
const spotColor = engine.block.getColor(spotFill, 'fill/color/value');
console.log('sRGB Color:', srgbColor);
console.log('CMYK Color:', cmykColor);
console.log('Spot Color:', spotColor);
```
## Exporting with Colors
After applying colors, export the scene to a file. The export respects the color space settings.
```typescript highlight=highlight-export
// Export the scene to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/color-basics.png`, buffer);
console.log('Exported to output/color-basics.png');
```
## Cleanup
Always dispose of the engine when done to release resources.
```typescript highlight=highlight-cleanup
engine.dispose();
```
## Choosing the Right Color Space
| Color Space | Use Case | Output |
|-------------|----------|--------|
| **sRGB** | Web, digital, screen display | PNG, JPEG, WebP |
| **CMYK** | Print workflows (converts to RGB) | PDF (converted) |
| **Spot Color** | Specialized printing, brand colors | PDF (Separation Color Space) |
## API Reference
| Method | Description |
|--------|-------------|
| `engine.block.setColor(id, property, value)` | Set a color property on a block. Pass an `RGBAColor`, `CMYKColor`, or `SpotColor` object. |
| `engine.block.getColor(id, property)` | Get the current color value from a property. Returns an `RGBAColor`, `CMYKColor`, or `SpotColor` object. |
| `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with an RGB approximation for screen preview. Components range from 0.0 to 1.0. |
| `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with a CMYK approximation for screen preview. Components range from 0.0 to 1.0. |
| Type | Properties | Description |
|------|------------|-------------|
| `RGBAColor` | `r`, `g`, `b`, `a` (0.0-1.0) | sRGB color for screen display. Alpha controls transparency. |
| `CMYKColor` | `c`, `m`, `y`, `k`, `tint` (0.0-1.0) | CMYK color for print. Tint controls opacity. |
| `SpotColor` | `name`, `tint`, `externalReference` | Named color for specialized printing. |
## Next Steps
- [Apply Colors](https://img.ly/docs/cesdk/node-native/colors/apply-2211e3/) - Apply colors to design elements programmatically
- [CMYK Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/cmyk-8a1334/) - Work with CMYK for print workflows
- [Spot Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/spot-c3a150/) - Define and manage spot colors for specialized printing
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Color Conversion"
description: "Learn how to convert colors between color spaces in CE.SDK. Convert sRGB, CMYK, and spot colors programmatically for screen display or print workflows."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/conversion-bcd82b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [Color Conversion](https://img.ly/docs/cesdk/node-native/colors/conversion-bcd82b/)
---
Convert colors between sRGB, CMYK, and spot color spaces programmatically in CE.SDK.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-conversion-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-conversion-server-js)
CE.SDK supports three color spaces: sRGB, CMYK, and SpotColor. When building color interfaces or preparing designs for export, you may need to convert colors between these spaces. The engine handles the mathematical conversion automatically through the `convertColorToColorSpace()` API.
```typescript file=@cesdk_web_examples/guides-colors-conversion-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
// Type guard helpers for identifying color types
function isRGBAColor(
color: any
): color is { r: number; g: number; b: number; a: number } {
return 'r' in color && 'g' in color && 'b' in color && 'a' in color;
}
function isCMYKColor(
color: any
): color is { c: number; m: number; y: number; k: number; tint: number } {
return 'c' in color && 'm' in color && 'y' in color && 'k' in color;
}
function isSpotColor(
color: any
): color is { name: string; tint: number; externalReference: string } {
return 'name' in color && 'tint' in color && 'externalReference' in color;
}
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Calculate block sizes for three columns
const margin = 40;
const spacing = 30;
const availableWidth = pageWidth - 2 * margin - 2 * spacing;
const blockWidth = availableWidth / 3;
const blockHeight = pageHeight - 2 * margin;
// 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);
// 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);
}
// Export the scene to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/color-conversion.png`, buffer);
console.log('Exported to output/color-conversion.png');
console.log('Color Conversion example completed successfully');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
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.
## Initialize the Engine
Set up the headless Creative Engine for server-side color operations.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
```
## Supported Color Spaces
CE.SDK supports conversion between three color spaces:
| Color Space | Format | Use Case |
|-------------|--------|----------|
| **sRGB** | `RGBAColor` with `r`, `g`, `b`, `a` (0.0-1.0) | Screen display, web output |
| **CMYK** | `CMYKColor` with `c`, `m`, `y`, `k`, `tint` (0.0-1.0) | Print workflows |
| **SpotColor** | `SpotColor` with `name`, `tint`, `externalReference` | Specialized printing |
## Setting Up Colors
Before converting colors, you need colors in different spaces. This example creates blocks with sRGB, CMYK, and spot colors.
First, define a spot color with its RGB approximation for screen preview:
```typescript highlight=highlight-define-spot-color
// Define a spot color with RGB approximation for screen preview
engine.editor.setSpotColorRGB('Brand Red', 0.95, 0.25, 0.21);
```
Create an sRGB color block for screen display:
```typescript highlight=highlight-srgb-color
// Block 1: sRGB color (for screen display)
const srgbBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
srgbBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(srgbFill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1.0
});
engine.block.setFill(srgbBlock, srgbFill);
engine.block.setWidth(srgbBlock, blockWidth);
engine.block.setHeight(srgbBlock, blockHeight);
engine.block.appendChild(page, srgbBlock);
```
Create a CMYK color block for print workflows:
```typescript highlight=highlight-cmyk-color
// Block 2: CMYK color (for print workflows)
const cmykBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
cmykBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(cmykFill, 'fill/color/value', {
c: 0.0,
m: 0.8,
y: 0.95,
k: 0.0,
tint: 1.0
});
engine.block.setFill(cmykBlock, cmykFill);
engine.block.setWidth(cmykBlock, blockWidth);
engine.block.setHeight(cmykBlock, blockHeight);
engine.block.appendChild(page, cmykBlock);
```
Create a spot color block for specialized printing:
```typescript highlight=highlight-spot-color
// Block 3: Spot color (for specialized printing)
const spotBlock = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
spotBlock,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
const spotFill = engine.block.createFill('//ly.img.ubq/fill/color');
engine.block.setColor(spotFill, 'fill/color/value', {
name: 'Brand Red',
tint: 1.0,
externalReference: ''
});
engine.block.setFill(spotBlock, spotFill);
engine.block.setWidth(spotBlock, blockWidth);
engine.block.setHeight(spotBlock, blockHeight);
engine.block.appendChild(page, spotBlock);
```
## Converting to sRGB
Use `engine.editor.convertColorToColorSpace(color, 'sRGB')` to convert any color to sRGB format. This is useful for displaying color values on screen or when you need RGB components for CSS or other web-based color operations.
```typescript highlight=highlight-convert-to-srgb
// Convert colors to sRGB for screen display
const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value');
const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value');
const spotColor = engine.block.getColor(spotFill, 'fill/color/value');
// Convert CMYK to sRGB
const cmykToRgba = engine.editor.convertColorToColorSpace(
cmykColor,
'sRGB'
);
console.log('CMYK converted to sRGB:', cmykToRgba);
// Convert Spot color to sRGB (uses defined RGB approximation)
const spotToRgba = engine.editor.convertColorToColorSpace(
spotColor,
'sRGB'
);
console.log('Spot color converted to sRGB:', spotToRgba);
```
When converting CMYK or spot colors to sRGB, the engine returns an `RGBAColor` object with `r`, `g`, `b`, `a` properties. The tint value from CMYK or spot colors becomes the alpha value in the returned sRGB color.
## Converting to CMYK
Use `engine.editor.convertColorToColorSpace(color, 'CMYK')` to convert any color to CMYK format. This is essential for print workflows where you need to ensure colors are in the correct space before export.
```typescript highlight=highlight-convert-to-cmyk
// Convert colors to CMYK for print workflows
const srgbToCmyk = engine.editor.convertColorToColorSpace(
srgbColor,
'CMYK'
);
console.log('sRGB converted to CMYK:', srgbToCmyk);
// Convert Spot color to CMYK for print output
// First define CMYK approximation for the spot color
engine.editor.setSpotColorCMYK('Brand Red', 0.0, 0.85, 0.9, 0.05);
const spotToCmyk = engine.editor.convertColorToColorSpace(
spotColor,
'CMYK'
);
console.log('Spot color converted to CMYK:', spotToCmyk);
```
When converting sRGB colors to CMYK, the alpha value becomes the tint value in the returned CMYK color. For spot colors, define a CMYK approximation with `setSpotColorCMYK()` before converting.
> **Note:** Color space conversions may not be perfectly reversible. Some sRGB colors cannot be exactly represented in CMYK due to different color gamuts.
## Identifying Color Types
Before converting a color, you may need to identify its current color space. CE.SDK provides type guard functions to check the color type.
```typescript highlight=highlight-type-guards
// Use type guards to identify color space before conversion
if (isRGBAColor(srgbColor)) {
console.log(
'sRGB color components:',
`R: ${srgbColor.r}, G: ${srgbColor.g}, B: ${srgbColor.b}, A: ${srgbColor.a}`
);
}
if (isCMYKColor(cmykColor)) {
console.log(
'CMYK color components:',
`C: ${cmykColor.c}, M: ${cmykColor.m}, Y: ${cmykColor.y}, K: ${cmykColor.k}, Tint: ${cmykColor.tint}`
);
}
if (isSpotColor(spotColor)) {
console.log('Spot color name:', spotColor.name, 'Tint:', spotColor.tint);
}
```
Import the type guards from `@cesdk/node`:
- `isRGBAColor()` - Returns true if the color is an sRGB color
- `isCMYKColor()` - Returns true if the color is a CMYK color
- `isSpotColor()` - Returns true if the color is a spot color
## Handling Tint and Alpha
The tint and alpha values represent transparency in different color spaces:
| Source | Target | Transformation |
|--------|--------|----------------|
| sRGB (alpha) | CMYK | Alpha becomes tint |
| CMYK (tint) | sRGB | Tint becomes alpha |
| SpotColor (tint) | sRGB | Tint becomes alpha |
| SpotColor (tint) | CMYK | Tint is preserved |
## Exporting with Colors
After applying and converting colors, export the scene to a file.
```typescript highlight=highlight-export
// Export the scene to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/color-conversion.png`, buffer);
console.log('Exported to output/color-conversion.png');
```
## Cleanup
Always dispose of the engine when done to release resources.
```typescript highlight=highlight-cleanup
engine.dispose();
```
## Practical Use Cases
### Batch Color Conversion
When processing multiple designs for print, convert all colors to CMYK:
```typescript
const blocks = engine.block.findAll();
for (const blockId of blocks) {
if (engine.block.hasFill(blockId)) {
const fillId = engine.block.getFill(blockId);
const color = engine.block.getColor(fillId, 'fill/color/value');
const cmykColor = engine.editor.convertColorToColorSpace(color, 'CMYK');
console.log(`Block ${blockId} CMYK: C${cmykColor.c} M${cmykColor.m} Y${cmykColor.y} K${cmykColor.k}`);
}
}
```
### Color Analysis
Analyze colors in a design to report their color space distribution:
```typescript
const color = engine.block.getColor(blockId, 'fill/color/value');
if (isRGBAColor(color)) {
console.log('sRGB color detected');
} else if (isCMYKColor(color)) {
console.log('CMYK color detected');
} else if (isSpotColor(color)) {
console.log(`Spot color detected: ${color.name}`);
}
```
## Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| Spot color converts to unexpected values | Spot color not defined | Call `setSpotColorRGB()` or `setSpotColorCMYK()` before conversion |
| Colors look different after conversion | Color gamut differences | Some sRGB colors cannot be exactly represented in CMYK |
| Type errors with converted colors | Wrong type assumption | Use type guards (`isRGBAColor`, `isCMYKColor`, `isSpotColor`) before accessing properties |
## API Reference
| Method | Description |
|--------|-------------|
| `engine.editor.convertColorToColorSpace(color, colorSpace)` | Convert a color to the target color space. Returns an `RGBAColor` for 'sRGB' or `CMYKColor` for 'CMYK'. |
| `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with an RGB approximation. Components range from 0.0 to 1.0. |
| `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with a CMYK approximation. Components range from 0.0 to 1.0. |
| Type Guard | Description |
|------------|-------------|
| `isRGBAColor(color)` | Returns true if the color is an `RGBAColor` object |
| `isCMYKColor(color)` | Returns true if the color is a `CMYKColor` object |
| `isSpotColor(color)` | Returns true if the color is a `SpotColor` object |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create a Color Palette"
description: "Build reusable color palettes to maintain consistency and streamline user choices."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/create-color-palette-7012e0/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [Create a Color Palette](https://img.ly/docs/cesdk/node-native/colors/create-color-palette-7012e0/)
---
Build custom color palettes programmatically using CE.SDK's asset system with
support for sRGB, CMYK, and Spot colors in server-side Node.js applications.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-create-color-palette-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-create-color-palette-server-js)
Color libraries in CE.SDK are implemented as local 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 can create, populate, query, and manage these libraries programmatically using the engine's asset API.
```typescript file=@cesdk_web_examples/guides-colors-create-color-palette-server-js/server-js.ts reference-only
import CreativeEngine, {
AssetDefinition,
AssetRGBColor,
AssetCMYKColor,
AssetSpotColor
} from '@cesdk/node';
import * as fs from 'fs';
async function main() {
let engine;
try {
engine = await CreativeEngine.init({
// license: 'YOUR_CESDK_LICENSE_KEY'
});
// Create a 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);
// Define color assets for each color space type
const colors: AssetDefinition[] = [
{
id: 'brand-blue',
label: { en: 'Brand Blue' },
tags: { en: ['brand', 'blue', 'primary'] },
payload: {
color: {
colorSpace: 'sRGB',
r: 0.2,
g: 0.4,
b: 0.8
} as AssetRGBColor
}
},
{
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
} as AssetRGBColor
}
},
{
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
} as AssetCMYKColor
}
},
{
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
}
} as AssetSpotColor
}
}
];
// 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);
}
// Query colors from the library
const queryResult = await engine.asset.findAssets('my-brand-colors', {
page: 0,
perPage: 100
});
console.log('Colors in library:', queryResult.assets.length);
for (const asset of queryResult.assets) {
console.log(` - ${asset.id}: ${asset.label ?? 'No label'}`);
}
// Remove a color from the library
engine.asset.removeAssetFromSource('my-brand-colors', 'brand-coral');
console.log('Removed brand-coral from library');
// Verify removal
const updatedResult = await engine.asset.findAssets('my-brand-colors', {
page: 0,
perPage: 100
});
console.log('Colors after removal:', updatedResult.assets.length);
// Create a graphic block and apply a color from the palette
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, 300);
engine.block.setPositionY(block, 225);
engine.block.appendChild(page, block);
// Apply Brand Blue from our palette
const fill = engine.block.getFill(block);
engine.block.setColor(fill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.8,
a: 1.0
});
// Export the scene
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
fs.writeFileSync('output.png', buffer);
console.log('Exported scene to output.png');
} finally {
if (engine) {
engine.dispose();
}
}
}
main().catch(console.error);
```
This guide covers how to define colors in different color spaces, create and configure color libraries, query colors from libraries, and remove colors when needed.
## Initialize the Engine
We start by initializing the headless Creative Engine with a scene and page for demonstrating our color library workflow.
```typescript highlight=highlight-setup
engine = await CreativeEngine.init({
// license: 'YOUR_CESDK_LICENSE_KEY'
});
// Create a 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);
```
## Defining Color Assets
Colors are added to libraries as `AssetDefinition` objects. Each color asset has an `id`, optional `label` and `tags` for organization, and a `payload.color` property containing the color data. The color type determines which color space is used.
### sRGB Colors
sRGB colors use the `AssetRGBColor` type with `colorSpace: 'sRGB'` and `r`, `g`, `b` components as floats from 0.0 to 1.0. Use sRGB colors for screen-based designs and web content.
```typescript highlight=highlight-definitions
// Define color assets for each color space type
const colors: AssetDefinition[] = [
{
id: 'brand-blue',
label: { en: 'Brand Blue' },
tags: { en: ['brand', 'blue', 'primary'] },
payload: {
color: {
colorSpace: 'sRGB',
r: 0.2,
g: 0.4,
b: 0.8
} as AssetRGBColor
}
},
{
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
} as AssetRGBColor
}
},
{
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
} as AssetCMYKColor
}
},
{
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
}
} as AssetSpotColor
}
}
];
```
The example defines four colors demonstrating different color spaces. The first two colors—"Brand Blue" and "Brand Coral"—use sRGB for screen display.
### CMYK Colors
CMYK colors use the `AssetCMYKColor` type with `colorSpace: 'CMYK'` and `c`, `m`, `y`, `k` components as floats from 0.0 to 1.0. Use CMYK colors for print workflows where color accuracy in printing is critical.
The "Print Magenta" color in the example demonstrates the CMYK color space with cyan at 0, magenta at 0.9, yellow at 0.2, and black at 0.
### Spot Colors
Spot colors use the `AssetSpotColor` type with `colorSpace: 'SpotColor'`, a `name` that identifies the spot color, an `externalReference` indicating the color book or ink system, and a `representation` using sRGB or CMYK for screen preview.
The "Metallic Gold" color demonstrates the spot color format, using a custom ink reference with an sRGB representation for on-screen preview.
## Creating a Color Library
We create a local asset source using `engine.asset.addLocalSource()` with a unique source ID. Then we add each color asset using `engine.asset.addAssetToSource()`.
```typescript highlight=highlight-add-library
// Create a local asset source 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);
}
```
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.
## Querying Colors from a Library
Use `engine.asset.findAssets()` to retrieve colors from a library. This is useful for batch processing workflows, validating library contents, or building custom color selection interfaces.
```typescript highlight=highlight-query-colors
// Query colors from the library
const queryResult = await engine.asset.findAssets('my-brand-colors', {
page: 0,
perPage: 100
});
console.log('Colors in library:', queryResult.assets.length);
for (const asset of queryResult.assets) {
console.log(` - ${asset.id}: ${asset.label ?? 'No label'}`);
}
```
The query returns an `AssetsQueryResult` containing the matching assets. You can filter by tags, search terms, or pagination parameters to find specific colors.
## Removing Colors from a Library
Remove individual colors from a library using `engine.asset.removeAssetFromSource()` with the source ID and the color's asset ID.
```typescript highlight=highlight-remove-color
// Remove a color from the library
engine.asset.removeAssetFromSource('my-brand-colors', 'brand-coral');
```
This removes the color from the library immediately. Query the library again to verify the removal.
## Applying Colors from the Palette
After creating a color library, you can apply colors to design elements. While the library stores color definitions as assets, you apply them to blocks using the standard color API.
```typescript highlight=highlight-apply-color
// Create a graphic block and apply a color from the palette
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, 300);
engine.block.setPositionY(block, 225);
engine.block.appendChild(page, block);
// Apply Brand Blue from our palette
const fill = engine.block.getFill(block);
engine.block.setColor(fill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.8,
a: 1.0
});
```
The library serves as a centralized source of truth for color values. In production workflows, you would query the library to get the exact color values before applying them to blocks.
## Export and Cleanup
After working with colors, we export the result and dispose of the engine to free resources.
```typescript highlight=highlight-export
// Export the scene
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
fs.writeFileSync('output.png', buffer);
console.log('Exported scene to output.png');
} finally {
if (engine) {
engine.dispose();
}
}
```
Always dispose of the engine in a finally block to ensure resources are freed even if an error occurs.
## Troubleshooting
### Colors Not Added to Library
If colors aren't appearing in your library:
- Verify the source ID passed to `addAssetToSource()` matches the ID used in `addLocalSource()`
- Ensure `addLocalSource()` was called before adding colors
- Check that each color asset has a unique `id` property
### Query Returns Empty Results
If `findAssets()` returns no results:
- Verify the source ID matches exactly (case-sensitive)
- Check pagination parameters—`page` starts at 0
- Ensure colors were added successfully before querying
### Spot Color Representation Not Displaying
If a spot color appears incorrectly:
- Verify the `representation` property contains a valid sRGB or CMYK color
- Check that `colorSpace` is set to `'SpotColor'`
- Ensure the representation color values are in the 0.0-1.0 range
### Color Not Removed
If `removeAssetFromSource()` doesn't seem to work:
- Verify the asset ID matches the `id` property of the color exactly
- Check that the source ID is correct
- Query the library after removal to confirm the change
## 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 |
| `engine.asset.findAssets(sourceId, query)` | Query colors from a source |
| Type | Properties | Description |
| ----------------- | ----------------------------------------------------------- | ----------------------------------------- |
| `AssetRGBColor` | `colorSpace`, `r`, `g`, `b` | sRGB color for screen display |
| `AssetCMYKColor` | `colorSpace`, `c`, `m`, `y`, `k` | CMYK color for print workflows |
| `AssetSpotColor` | `colorSpace`, `name`, `externalReference`, `representation` | Named spot color for specialized printing |
| `AssetDefinition` | `id`, `label`, `tags`, `payload` | Color asset structure with metadata |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "For Print"
description: "Use print-ready color models and settings for professional-quality, production-ready exports."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/for-print-59bc05/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/node-native/colors/for-print-59bc05/)
---
---
## Related Pages
- [CMYK Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/cmyk-8a1334/) - Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control.
- [Spot Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/spot-c3a150/) - Define, apply, and manage spot colors for professional print workflows with premixed inks.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "CMYK Colors"
description: "Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/for-print/cmyk-8a1334/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/node-native/colors/for-print-59bc05/) > [CMYK Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/cmyk-8a1334/)
---
Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control in headless Node.js environments.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-print-cmyk-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-for-print-cmyk-server-js)
CMYK (Cyan, Magenta, Yellow, Key/Black) is the standard color model for print production. Unlike RGB which is additive and designed for screens, CMYK uses subtractive color mixing to represent how inks combine on paper. CE.SDK supports CMYK colors natively, allowing you to prepare designs for professional print output while maintaining accurate color representation.
```typescript file=@cesdk_web_examples/guides-colors-for-print-cmyk-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import type { CMYKColor, RGBAColor } from '@cesdk/node';
// Type guard to check if a color is CMYK
// Color can be RGBAColor, CMYKColor, or SpotColor
const isCMYKColor = (color: unknown): color is CMYKColor => {
return (
typeof color === 'object' &&
color !== null &&
'c' in color &&
'm' in color &&
'y' in color &&
'k' in color
);
};
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: CMYK Colors
*
* This example demonstrates:
* - Creating CMYK color values for print workflows
* - Applying CMYK colors to fills, strokes, and shadows
* - Using the tint property for color intensity control
* - Converting between RGB and CMYK color spaces
* - Checking color types with type guards
* - Exporting designs with CMYK colors for print
*/
// Initialize CE.SDK engine with baseURL for asset loading
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
// Create a 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);
// Set page background to light gray (using CMYK)
const pageFill = engine.block.getFill(page);
engine.block.setColor(pageFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.04,
tint: 1.0
});
// Helper function to create a graphic block with a color fill
const createColorBlock = (
x: number,
y: number,
width: number,
height: number,
shape: 'rect' | 'ellipse' = 'rect'
): { block: number; fill: number } => {
const block = engine.block.create('graphic');
const blockShape = engine.block.createShape(shape);
engine.block.setShape(block, blockShape);
engine.block.setWidth(block, width);
engine.block.setHeight(block, height);
engine.block.setPositionX(block, x);
engine.block.setPositionY(block, y);
engine.block.appendChild(page, block);
const colorFill = engine.block.createFill('color');
engine.block.setFill(block, colorFill);
return { block, fill: colorFill };
};
// Create CMYK color objects for print production
// CMYK values range from 0.0 to 1.0
const cmykCyan: CMYKColor = { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 };
const cmykMagenta: CMYKColor = { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 };
const cmykYellow: CMYKColor = { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 };
const cmykBlack: CMYKColor = { c: 0.0, m: 0.0, y: 0.0, k: 1.0, tint: 1.0 };
// Apply CMYK colors to fills
const { fill: cyanFill } = createColorBlock(50, 50, 150, 150);
engine.block.setColor(cyanFill, 'fill/color/value', cmykCyan);
// Apply remaining CMYK primary colors
const { fill: magentaFill } = createColorBlock(220, 50, 150, 150);
engine.block.setColor(magentaFill, 'fill/color/value', cmykMagenta);
const { fill: yellowFill } = createColorBlock(390, 50, 150, 150);
engine.block.setColor(yellowFill, 'fill/color/value', cmykYellow);
const { fill: blackFill } = createColorBlock(560, 50, 150, 150);
engine.block.setColor(blackFill, 'fill/color/value', cmykBlack);
// Use tint for partial color intensity
// Tint of 0.5 gives 50% color intensity
const cmykHalfMagenta: CMYKColor = {
c: 0.0,
m: 1.0,
y: 0.0,
k: 0.0,
tint: 0.5
};
const { fill: tintedFill } = createColorBlock(50, 220, 150, 150, 'ellipse');
engine.block.setColor(tintedFill, 'fill/color/value', cmykHalfMagenta);
// Apply CMYK color to stroke
const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock(
220,
220,
150,
150
);
// Set fill to white (using CMYK)
engine.block.setColor(strokeBlockFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.0,
tint: 1.0
});
// Enable stroke and set CMYK color
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 8);
const cmykStrokeColor: CMYKColor = {
c: 0.8,
m: 0.2,
y: 0.0,
k: 0.1,
tint: 1.0
};
engine.block.setColor(strokeBlock, 'stroke/color', cmykStrokeColor);
// Apply CMYK color to drop shadow
const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock(
390,
220,
150,
150
);
// Set fill to light gray (using CMYK)
engine.block.setColor(shadowBlockFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.05,
tint: 1.0
});
// Enable drop shadow and set CMYK color
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowOffsetX(shadowBlock, 10);
engine.block.setDropShadowOffsetY(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 15);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 15);
const cmykShadowColor: CMYKColor = {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.6,
tint: 0.8
};
engine.block.setColor(shadowBlock, 'dropShadow/color', cmykShadowColor);
// Read CMYK color from a block and check with type guard
const { fill: readFill } = createColorBlock(560, 220, 150, 150, 'ellipse');
const cmykOrange: CMYKColor = { c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0 };
engine.block.setColor(readFill, 'fill/color/value', cmykOrange);
// Retrieve and check the color
const retrievedColor = engine.block.getColor(readFill, 'fill/color/value');
if (isCMYKColor(retrievedColor)) {
console.log(
`CMYK Color - C: ${retrievedColor.c}, M: ${retrievedColor.m}, Y: ${retrievedColor.y}, K: ${retrievedColor.k}, Tint: ${retrievedColor.tint}`
);
}
// Convert RGB to CMYK
const rgbBlue: RGBAColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 };
const convertedCmyk = engine.editor.convertColorToColorSpace(rgbBlue, 'CMYK');
const { fill: convertedFill } = createColorBlock(50, 390, 150, 150);
engine.block.setColor(convertedFill, 'fill/color/value', convertedCmyk);
console.log('RGB to CMYK conversion:', convertedCmyk);
// Convert CMYK to RGB (for demonstration)
const cmykGreen: CMYKColor = { c: 0.7, m: 0.0, y: 1.0, k: 0.2, tint: 1.0 };
const convertedRgb = engine.editor.convertColorToColorSpace(
cmykGreen,
'sRGB'
);
console.log('CMYK to RGB conversion:', convertedRgb);
// Display using original CMYK color
const { fill: previewFill } = createColorBlock(220, 390, 150, 150);
engine.block.setColor(previewFill, 'fill/color/value', cmykGreen);
// Use CMYK colors in gradients
const gradientBlock = engine.block.create('graphic');
const gradientShape = engine.block.createShape('rect');
engine.block.setShape(gradientBlock, gradientShape);
engine.block.setWidth(gradientBlock, 320);
engine.block.setHeight(gradientBlock, 150);
engine.block.setPositionX(gradientBlock, 390);
engine.block.setPositionY(gradientBlock, 390);
engine.block.appendChild(page, gradientBlock);
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setFill(gradientBlock, gradientFill);
// Set gradient stops with CMYK colors
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0 },
{ color: { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0.5 },
{ color: { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 }
]);
// Export the design
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Export as PNG for preview
const pngBlob = await engine.block.export(page, { mimeType: 'image/png' });
const pngBuffer = Buffer.from(await pngBlob.arrayBuffer());
writeFileSync(`${outputDir}/cmyk-colors.png`, pngBuffer);
console.log(`\n✓ PNG exported: ${outputDir}/cmyk-colors.png`);
// Export as PDF for print (preserves CMYK colors)
const pdfBlob = await engine.block.export(page, {
mimeType: 'application/pdf'
});
const pdfBuffer = Buffer.from(await pdfBlob.arrayBuffer());
writeFileSync(`${outputDir}/cmyk-colors.pdf`, pdfBuffer);
console.log(`✓ PDF exported: ${outputDir}/cmyk-colors.pdf`);
console.log('\n✓ CMYK Colors example completed successfully!');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to create CMYK color values, apply them to fills, strokes, and shadows, use the tint property for color intensity control, and convert between color spaces.
## Understanding CMYK Colors
### When to Use CMYK
Use CMYK colors when preparing designs for commercial printing or when print service providers require CMYK values. Screen displays convert CMYK to RGB for preview, but exported PDFs retain the original CMYK values for accurate print reproduction.
CMYK colors in CE.SDK have five properties:
- `c` (Cyan): 0.0 to 1.0
- `m` (Magenta): 0.0 to 1.0
- `y` (Yellow): 0.0 to 1.0
- `k` (Key/Black): 0.0 to 1.0
- `tint`: 0.0 to 1.0 (controls overall color intensity)
## Creating CMYK Colors
Create CMYK color objects using the `CMYKColor` interface. All component values range from 0.0 to 1.0:
```typescript highlight-create-cmyk
// Create CMYK color objects for print production
// CMYK values range from 0.0 to 1.0
const cmykCyan: CMYKColor = { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 };
const cmykMagenta: CMYKColor = { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 };
const cmykYellow: CMYKColor = { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 };
const cmykBlack: CMYKColor = { c: 0.0, m: 0.0, y: 0.0, k: 1.0, tint: 1.0 };
```
The tint property acts as a multiplier for the entire color—a tint of 1.0 applies the full color, while 0.5 applies 50% intensity.
## Applying CMYK Colors to Fills
Apply CMYK colors to color fills using `engine.block.setColor()`. First create a color fill with `engine.block.createFill('color')`, then set its color value:
```typescript highlight-apply-fill
// Apply CMYK colors to fills
const { fill: cyanFill } = createColorBlock(50, 50, 150, 150);
engine.block.setColor(cyanFill, 'fill/color/value', cmykCyan);
```
The same method works for any color type—RGBA, CMYK, or SpotColor. CE.SDK automatically handles the color representation internally.
## Using the Tint Property
The tint property provides fine-grained control over color intensity without modifying the base CMYK values. This is useful for creating lighter variations of a color:
```typescript highlight-tint
// Use tint for partial color intensity
// Tint of 0.5 gives 50% color intensity
const cmykHalfMagenta: CMYKColor = {
c: 0.0,
m: 1.0,
y: 0.0,
k: 0.0,
tint: 0.5
};
const { fill: tintedFill } = createColorBlock(50, 220, 150, 150, 'ellipse');
engine.block.setColor(tintedFill, 'fill/color/value', cmykHalfMagenta);
```
A tint of 0.5 creates a 50% lighter version of the color, useful for secondary elements or subtle backgrounds.
## Applying CMYK to Strokes
Apply CMYK colors to strokes using the `stroke/color` property path:
```typescript highlight-stroke
// Apply CMYK color to stroke
const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock(
220,
220,
150,
150
);
// Set fill to white (using CMYK)
engine.block.setColor(strokeBlockFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.0,
tint: 1.0
});
// Enable stroke and set CMYK color
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 8);
const cmykStrokeColor: CMYKColor = {
c: 0.8,
m: 0.2,
y: 0.0,
k: 0.1,
tint: 1.0
};
engine.block.setColor(strokeBlock, 'stroke/color', cmykStrokeColor);
```
Enable the stroke first with `setStrokeEnabled()`, set the stroke width, then apply the CMYK color.
## Applying CMYK to Drop Shadows
Apply CMYK colors to drop shadows using the `dropShadow/color` property path:
```typescript highlight-shadow
// Apply CMYK color to drop shadow
const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock(
390,
220,
150,
150
);
// Set fill to light gray (using CMYK)
engine.block.setColor(shadowBlockFill, 'fill/color/value', {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.05,
tint: 1.0
});
// Enable drop shadow and set CMYK color
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowOffsetX(shadowBlock, 10);
engine.block.setDropShadowOffsetY(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 15);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 15);
const cmykShadowColor: CMYKColor = {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.6,
tint: 0.8
};
engine.block.setColor(shadowBlock, 'dropShadow/color', cmykShadowColor);
```
Enable the drop shadow first, configure its offset and blur radius, then apply the CMYK color.
## Reading CMYK Colors
Retrieve color values from blocks using `engine.block.getColor()`. The returned color could be RGBA, CMYK, or SpotColor, so use the `isCMYKColor()` type guard to check:
```typescript highlight-read-color
// Read CMYK color from a block and check with type guard
const { fill: readFill } = createColorBlock(560, 220, 150, 150, 'ellipse');
const cmykOrange: CMYKColor = { c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0 };
engine.block.setColor(readFill, 'fill/color/value', cmykOrange);
// Retrieve and check the color
const retrievedColor = engine.block.getColor(readFill, 'fill/color/value');
if (isCMYKColor(retrievedColor)) {
console.log(
`CMYK Color - C: ${retrievedColor.c}, M: ${retrievedColor.m}, Y: ${retrievedColor.y}, K: ${retrievedColor.k}, Tint: ${retrievedColor.tint}`
);
}
```
The `isCMYKColor()` type guard checks if a color has the CMYK properties (`c`, `m`, `y`, `k`).
## Converting Between Color Spaces
Use `engine.editor.convertColorToColorSpace()` to convert colors between 'sRGB' and 'CMYK':
```typescript highlight-convert
// Convert RGB to CMYK
const rgbBlue: RGBAColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 };
const convertedCmyk = engine.editor.convertColorToColorSpace(rgbBlue, 'CMYK');
const { fill: convertedFill } = createColorBlock(50, 390, 150, 150);
engine.block.setColor(convertedFill, 'fill/color/value', convertedCmyk);
console.log('RGB to CMYK conversion:', convertedCmyk);
// Convert CMYK to RGB (for demonstration)
const cmykGreen: CMYKColor = { c: 0.7, m: 0.0, y: 1.0, k: 0.2, tint: 1.0 };
const convertedRgb = engine.editor.convertColorToColorSpace(
cmykGreen,
'sRGB'
);
console.log('CMYK to RGB conversion:', convertedRgb);
// Display using original CMYK color
const { fill: previewFill } = createColorBlock(220, 390, 150, 150);
engine.block.setColor(previewFill, 'fill/color/value', cmykGreen);
```
Note that color conversions may not be perfectly reversible due to differences in color gamuts between RGB and CMYK. Some RGB colors cannot be accurately represented in CMYK and vice versa.
## Using CMYK in Gradients
CMYK colors work in gradient color stops. Create a gradient fill and set stops using `engine.block.setGradientColorStops()`:
```typescript highlight-gradient
// Use CMYK colors in gradients
const gradientBlock = engine.block.create('graphic');
const gradientShape = engine.block.createShape('rect');
engine.block.setShape(gradientBlock, gradientShape);
engine.block.setWidth(gradientBlock, 320);
engine.block.setHeight(gradientBlock, 150);
engine.block.setPositionX(gradientBlock, 390);
engine.block.setPositionY(gradientBlock, 390);
engine.block.appendChild(page, gradientBlock);
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setFill(gradientBlock, gradientFill);
// Set gradient stops with CMYK colors
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0 },
{ color: { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0.5 },
{ color: { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 }
]);
```
This creates a gradient transitioning through the primary CMYK colors—cyan, magenta, and yellow.
## Troubleshooting
### Colors Look Different on Screen vs Print
Screen displays convert CMYK to RGB for preview. The exported PDF retains original CMYK values. For accurate color preview, use calibrated monitors and proof prints.
### Tint Not Having Expected Effect
Ensure the tint value is between 0 and 1. A tint of 0 makes the color fully transparent, while 1 applies full intensity.
### Type Guard Returns False
Make sure you're checking a `Color` value returned from `engine.block.getColor()`. The `isCMYKColor()` function only works with color values, not arbitrary objects.
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.block.setColor()` | Set a color property value |
| `engine.block.getColor()` | Get a color property from a block |
| `engine.editor.convertColorToColorSpace()` | Convert color to a different color space |
| `engine.block.createFill()` | Create a color fill |
| `engine.block.setFill()` | Assign a fill to a block |
| `engine.block.getFill()` | Get the fill from a block |
| `engine.block.setGradientColorStops()` | Set gradient color stops |
| `isCMYKColor()` | Check if a color is CMYK |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Spot Colors"
description: "Define, apply, and manage spot colors for professional print workflows with premixed inks."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/for-print/spot-c3a150/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/node-native/colors/for-print-59bc05/) > [Spot Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/spot-c3a150/)
---
Define and manage spot colors programmatically for professional print workflows with exact color matching through premixed inks.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-print-spot-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-for-print-spot-server-js)
Spot colors are named colors reproduced using premixed inks in print production, providing exact color matching that CMYK process colors cannot guarantee. CE.SDK maintains a registry of spot color definitions at the editor level, where each spot color has a name and screen approximations (RGB and/or CMYK) for display purposes. The actual premixed ink is used during printing based on the color name.
```typescript file=@cesdk_web_examples/guides-colors-for-print-spot-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import type { SpotColor } from '@cesdk/node';
// Type guard to check if a color is a SpotColor
// Color can be RGBAColor, CMYKColor, or SpotColor
const isSpotColor = (color: unknown): color is SpotColor => {
return (
typeof color === 'object' &&
color !== null &&
'name' in color &&
'tint' in color &&
'externalReference' in color
);
};
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Spot Colors
*
* This example demonstrates:
* - Defining spot colors with RGB and CMYK approximations
* - Applying spot colors to fills, strokes, and shadows
* - Using tints for lighter color variations
* - Querying and updating spot color definitions
* - Removing spot colors and handling the magenta fallback
* - Assigning spot colors to cutout types
* - Exporting designs with spot colors for print
*/
// Initialize CE.SDK engine with baseURL for asset loading
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
// Create a 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);
// Set page background to light gray for visibility
const pageFill = engine.block.getFill(page);
engine.block.setColor(pageFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
// Helper function to create a graphic block with a color fill
const createColorBlock = (
x: number,
y: number,
width: number,
height: number,
shape: 'rect' | 'ellipse' = 'rect'
): { block: number; fill: number } => {
const block = engine.block.create('graphic');
const blockShape = engine.block.createShape(shape);
engine.block.setShape(block, blockShape);
engine.block.setWidth(block, width);
engine.block.setHeight(block, height);
engine.block.setPositionX(block, x);
engine.block.setPositionY(block, y);
engine.block.appendChild(page, block);
const colorFill = engine.block.createFill('color');
engine.block.setFill(block, colorFill);
return { block, fill: colorFill };
};
// Define a spot color with RGB approximation
// RGB values range from 0.0 to 1.0
engine.editor.setSpotColorRGB('Brand-Primary', 0.8, 0.1, 0.2);
// Add CMYK approximation for the same spot color
// This provides print-accurate preview in addition to screen display
engine.editor.setSpotColorCMYK('Brand-Primary', 0.05, 0.95, 0.85, 0.0);
// Define another spot color with both approximations
engine.editor.setSpotColorRGB('Brand-Accent', 0.2, 0.4, 0.8);
engine.editor.setSpotColorCMYK('Brand-Accent', 0.75, 0.5, 0.0, 0.0);
// Apply spot colors to fills using SpotColor objects
// The tint property (0.0 to 1.0) controls color intensity
// The externalReference field stores metadata like color system origin
const brandPrimary: SpotColor = {
name: 'Brand-Primary',
tint: 1.0,
externalReference: ''
};
// Create a block and apply the Brand-Primary spot color
const { fill: primaryFill } = createColorBlock(50, 50, 150, 150);
engine.block.setColor(primaryFill, 'fill/color/value', brandPrimary);
// Apply Brand-Accent to another block
const brandAccent: SpotColor = {
name: 'Brand-Accent',
tint: 1.0,
externalReference: ''
};
const { fill: accentFill } = createColorBlock(220, 50, 150, 150);
engine.block.setColor(accentFill, 'fill/color/value', brandAccent);
// Use tints for lighter variations without defining new spot colors
// Tint of 0.5 gives 50% color intensity
const brandPrimaryHalfTint: SpotColor = {
name: 'Brand-Primary',
tint: 0.5,
externalReference: ''
};
const { fill: tintedFill } = createColorBlock(390, 50, 150, 150, 'ellipse');
engine.block.setColor(tintedFill, 'fill/color/value', brandPrimaryHalfTint);
// Create a gradient of tints
const brandAccentLightTint: SpotColor = {
name: 'Brand-Accent',
tint: 0.3,
externalReference: ''
};
const { fill: lightTintFill } = createColorBlock(560, 50, 150, 150);
engine.block.setColor(lightTintFill, 'fill/color/value', brandAccentLightTint);
// Apply spot colors to strokes and shadows
const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock(
50,
220,
150,
150
);
// Set fill to white
engine.block.setColor(strokeBlockFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Enable stroke and apply spot color
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 8);
const strokeColor: SpotColor = {
name: 'Brand-Primary',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(strokeBlock, 'stroke/color', strokeColor);
// Apply spot color to drop shadow
const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock(
220,
220,
150,
150
);
engine.block.setColor(shadowBlockFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowOffsetX(shadowBlock, 10);
engine.block.setDropShadowOffsetY(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 15);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 15);
const shadowColor: SpotColor = {
name: 'Brand-Accent',
tint: 0.8,
externalReference: ''
};
engine.block.setColor(shadowBlock, 'dropShadow/color', shadowColor);
// Query all defined spot colors
const spotColors = engine.editor.findAllSpotColors();
console.log('Defined spot colors:', spotColors);
// Query RGB approximation for a spot color
const rgbaApprox = engine.editor.getSpotColorRGBA('Brand-Primary');
console.log('Brand-Primary RGB approximation:', rgbaApprox);
// Query CMYK approximation for a spot color
const cmykApprox = engine.editor.getSpotColorCMYK('Brand-Primary');
console.log('Brand-Primary CMYK approximation:', cmykApprox);
// Read back the color from a block and check if it's a spot color
const retrievedColor = engine.block.getColor(primaryFill, 'fill/color/value');
if (isSpotColor(retrievedColor)) {
console.log(
`Retrieved SpotColor - Name: ${retrievedColor.name}, Tint: ${retrievedColor.tint}`
);
}
// Update an existing spot color's approximation
// This changes how the color appears on screen without affecting the color name
engine.editor.setSpotColorRGB('Brand-Accent', 0.3, 0.5, 0.9);
console.log('Updated Brand-Accent RGB approximation');
// Show the updated color in a new block
const { fill: updatedFill } = createColorBlock(390, 220, 150, 150);
const updatedAccent: SpotColor = {
name: 'Brand-Accent',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(updatedFill, 'fill/color/value', updatedAccent);
// Define a temporary spot color
engine.editor.setSpotColorRGB('Temporary-Color', 0.5, 0.8, 0.3);
// Create a block using the temporary color
const { fill: tempFill } = createColorBlock(560, 220, 150, 150);
const tempColor: SpotColor = {
name: 'Temporary-Color',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(tempFill, 'fill/color/value', tempColor);
// Remove the spot color definition
// Blocks using this color will display magenta (default fallback)
engine.editor.removeSpotColor('Temporary-Color');
console.log('Removed Temporary-Color - block now shows magenta fallback');
// Verify the color is no longer defined
const remainingSpotColors = engine.editor.findAllSpotColors();
console.log('Remaining spot colors:', remainingSpotColors);
// Assign spot colors to cutout types for die-cutting operations
// First define a spot color for the die line
engine.editor.setSpotColorRGB('DieLine', 1.0, 0.0, 1.0);
engine.editor.setSpotColorCMYK('DieLine', 0.0, 1.0, 0.0, 0.0);
// Associate the spot color with a cutout type
// CutoutType can be 'Solid' or 'Dashed'
engine.editor.setSpotColorForCutoutType('Solid', 'DieLine');
// Query the assigned spot color
const cutoutSpotColor = engine.editor.getSpotColorForCutoutType('Solid');
console.log('Cutout type Solid uses spot color:', cutoutSpotColor);
// Export the design
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Export as PNG for preview
const pngBlob = await engine.block.export(page, { mimeType: 'image/png' });
const pngBuffer = Buffer.from(await pngBlob.arrayBuffer());
writeFileSync(`${outputDir}/spot-colors.png`, pngBuffer);
console.log(`\nPNG exported: ${outputDir}/spot-colors.png`);
// Export as PDF for print (preserves spot colors)
const pdfBlob = await engine.block.export(page, {
mimeType: 'application/pdf'
});
const pdfBuffer = Buffer.from(await pdfBlob.arrayBuffer());
writeFileSync(`${outputDir}/spot-colors.pdf`, pdfBuffer);
console.log(`PDF exported: ${outputDir}/spot-colors.pdf`);
console.log('\nSpot Colors example completed successfully!');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to define spot colors with RGB and CMYK approximations, apply them to design elements with varying tints, query and update definitions, assign spot colors to cutout types for die-cutting operations, and export designs with spot colors for print production.
## Setup
Initialize the CE.SDK engine for server-side spot color management:
```typescript highlight-setup
// Initialize CE.SDK engine with baseURL for asset loading
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
```
## Understanding Spot Colors
A spot color in CE.SDK consists of:
- **Name**: A unique identifier for the spot color (e.g., "PANTONE 185 C", "Brand-Primary")
- **Tint**: A value from 0.0 to 1.0 controlling color intensity
- **External Reference**: Optional metadata linking to external color systems
Screen approximations (RGB and CMYK values) are stored separately and define how the spot color appears on screen during design. The actual printed color comes from the premixed ink specified by the spot color name.
## Defining Spot Colors
### RGB Approximation
Define how a spot color should appear on screen using RGB values:
```typescript highlight-define-spot-rgb
// Define a spot color with RGB approximation
// RGB values range from 0.0 to 1.0
engine.editor.setSpotColorRGB('Brand-Primary', 0.8, 0.1, 0.2);
```
RGB values range from 0.0 to 1.0. This approximation is used for screen display in applications that work in RGB color space.
### CMYK Approximation
Add a CMYK approximation for print preview accuracy:
```typescript highlight-define-spot-cmyk
// Add CMYK approximation for the same spot color
// This provides print-accurate preview in addition to screen display
engine.editor.setSpotColorCMYK('Brand-Primary', 0.05, 0.95, 0.85, 0.0);
// Define another spot color with both approximations
engine.editor.setSpotColorRGB('Brand-Accent', 0.2, 0.4, 0.8);
engine.editor.setSpotColorCMYK('Brand-Accent', 0.75, 0.5, 0.0, 0.0);
```
CMYK values also range from 0.0 to 1.0. Having both RGB and CMYK approximations ensures accurate preview in both screen-based and print-oriented workflows.
## Applying Spot Colors
### To Fills
Apply spot colors to block fills using the `SpotColor` type:
```typescript highlight-apply-spot-fill
// Apply spot colors to fills using SpotColor objects
// The tint property (0.0 to 1.0) controls color intensity
// The externalReference field stores metadata like color system origin
const brandPrimary: SpotColor = {
name: 'Brand-Primary',
tint: 1.0,
externalReference: ''
};
// Create a block and apply the Brand-Primary spot color
const { fill: primaryFill } = createColorBlock(50, 50, 150, 150);
engine.block.setColor(primaryFill, 'fill/color/value', brandPrimary);
```
The `SpotColor` object contains:
- `name`: References a defined spot color
- `tint`: Controls intensity (1.0 = full strength)
- `externalReference`: Optional metadata for color system integration
### Using Tints
Create lighter variations without defining new spot colors:
```typescript highlight-tint
// Use tints for lighter variations without defining new spot colors
// Tint of 0.5 gives 50% color intensity
const brandPrimaryHalfTint: SpotColor = {
name: 'Brand-Primary',
tint: 0.5,
externalReference: ''
};
const { fill: tintedFill } = createColorBlock(390, 50, 150, 150, 'ellipse');
engine.block.setColor(tintedFill, 'fill/color/value', brandPrimaryHalfTint);
// Create a gradient of tints
const brandAccentLightTint: SpotColor = {
name: 'Brand-Accent',
tint: 0.3,
externalReference: ''
};
const { fill: lightTintFill } = createColorBlock(560, 50, 150, 150);
engine.block.setColor(lightTintFill, 'fill/color/value', brandAccentLightTint);
```
Tints are particularly useful for creating consistent color hierarchies in designs while maintaining a single spot color definition for print production.
### To Strokes and Shadows
Spot colors work with all color properties:
```typescript highlight-stroke-shadow
// Apply spot colors to strokes and shadows
const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock(
50,
220,
150,
150
);
// Set fill to white
engine.block.setColor(strokeBlockFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Enable stroke and apply spot color
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 8);
const strokeColor: SpotColor = {
name: 'Brand-Primary',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(strokeBlock, 'stroke/color', strokeColor);
// Apply spot color to drop shadow
const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock(
220,
220,
150,
150
);
engine.block.setColor(shadowBlockFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowOffsetX(shadowBlock, 10);
engine.block.setDropShadowOffsetY(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 15);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 15);
const shadowColor: SpotColor = {
name: 'Brand-Accent',
tint: 0.8,
externalReference: ''
};
engine.block.setColor(shadowBlock, 'dropShadow/color', shadowColor);
```
## Querying Spot Colors
### List All Spot Colors
Retrieve all defined spot colors in the current editor session:
```typescript highlight-query-spot
// Query all defined spot colors
const spotColors = engine.editor.findAllSpotColors();
console.log('Defined spot colors:', spotColors);
// Query RGB approximation for a spot color
const rgbaApprox = engine.editor.getSpotColorRGBA('Brand-Primary');
console.log('Brand-Primary RGB approximation:', rgbaApprox);
// Query CMYK approximation for a spot color
const cmykApprox = engine.editor.getSpotColorCMYK('Brand-Primary');
console.log('Brand-Primary CMYK approximation:', cmykApprox);
// Read back the color from a block and check if it's a spot color
const retrievedColor = engine.block.getColor(primaryFill, 'fill/color/value');
if (isSpotColor(retrievedColor)) {
console.log(
`Retrieved SpotColor - Name: ${retrievedColor.name}, Tint: ${retrievedColor.tint}`
);
}
```
### Reading Back Colors
When reading colors from blocks, `engine.block.getColor()` can return an `RGBAColor`, `CMYKColor`, or `SpotColor`. Use a type guard to check if the color is a spot color:
```typescript
const isSpotColor = (color: unknown): color is SpotColor => {
return (
typeof color === 'object' &&
color !== null &&
'name' in color &&
'tint' in color &&
'externalReference' in color
);
};
const color = engine.block.getColor(fill, 'fill/color/value');
if (isSpotColor(color)) {
console.log(`Spot color: ${color.name} at ${color.tint * 100}% tint`);
}
```
## Updating Spot Colors
Modify a spot color's screen approximation. All blocks using this spot color will automatically display the updated appearance:
```typescript highlight-update-spot
// Update an existing spot color's approximation
// This changes how the color appears on screen without affecting the color name
engine.editor.setSpotColorRGB('Brand-Accent', 0.3, 0.5, 0.9);
console.log('Updated Brand-Accent RGB approximation');
// Show the updated color in a new block
const { fill: updatedFill } = createColorBlock(390, 220, 150, 150);
const updatedAccent: SpotColor = {
name: 'Brand-Accent',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(updatedFill, 'fill/color/value', updatedAccent);
```
This updates only the screen preview. The actual printed output depends on the physical premixed ink identified by the spot color name.
## Removing Spot Colors
Remove a spot color definition when it's no longer needed:
```typescript highlight-remove-spot
// Define a temporary spot color
engine.editor.setSpotColorRGB('Temporary-Color', 0.5, 0.8, 0.3);
// Create a block using the temporary color
const { fill: tempFill } = createColorBlock(560, 220, 150, 150);
const tempColor: SpotColor = {
name: 'Temporary-Color',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(tempFill, 'fill/color/value', tempColor);
// Remove the spot color definition
// Blocks using this color will display magenta (default fallback)
engine.editor.removeSpotColor('Temporary-Color');
console.log('Removed Temporary-Color - block now shows magenta fallback');
// Verify the color is no longer defined
const remainingSpotColors = engine.editor.findAllSpotColors();
console.log('Remaining spot colors:', remainingSpotColors);
```
Blocks that reference a removed spot color will display in magenta (the default fallback color) to indicate the missing definition.
## Spot Colors for Cutouts
Assign spot colors to cutout types for die-cutting operations in packaging and label production:
```typescript highlight-cutout
// Assign spot colors to cutout types for die-cutting operations
// First define a spot color for the die line
engine.editor.setSpotColorRGB('DieLine', 1.0, 0.0, 1.0);
engine.editor.setSpotColorCMYK('DieLine', 0.0, 1.0, 0.0, 0.0);
// Associate the spot color with a cutout type
// CutoutType can be 'Solid' or 'Dashed'
engine.editor.setSpotColorForCutoutType('Solid', 'DieLine');
// Query the assigned spot color
const cutoutSpotColor = engine.editor.getSpotColorForCutoutType('Solid');
console.log('Cutout type Solid uses spot color:', cutoutSpotColor);
```
Available cutout types are `'Solid'` and `'Dashed'`, representing different die-line styles used in print finishing.
## Exporting with Spot Colors
Export designs while preserving spot color information:
```typescript highlight-export
// Export the design
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Export as PNG for preview
const pngBlob = await engine.block.export(page, { mimeType: 'image/png' });
const pngBuffer = Buffer.from(await pngBlob.arrayBuffer());
writeFileSync(`${outputDir}/spot-colors.png`, pngBuffer);
console.log(`\nPNG exported: ${outputDir}/spot-colors.png`);
// Export as PDF for print (preserves spot colors)
const pdfBlob = await engine.block.export(page, {
mimeType: 'application/pdf'
});
const pdfBuffer = Buffer.from(await pdfBlob.arrayBuffer());
writeFileSync(`${outputDir}/spot-colors.pdf`, pdfBuffer);
console.log(`PDF exported: ${outputDir}/spot-colors.pdf`);
```
When exporting to PDF, spot colors are embedded as named separations that RIP software can extract for plate generation. PNG exports use the RGB approximation for raster output.
## Best Practices
**Define early** - Register spot colors at initialization before applying them to blocks. Undefined colors display as magenta, which can confuse users.
**Use descriptive names** - Match your print vendor's reference (e.g., "Pantone-485-C") to ensure correct ink matching in production.
**Provide both approximations** - RGB for screen display, CMYK for print-accurate previews. This gives designers the best experience across different workflows.
**Use tints sparingly** - Prefer tints (0.0-1.0) for lighter variations rather than defining separate spot colors for each shade. This keeps your spot color list manageable.
**Validate before export** - Query `findAllSpotColors()` to verify all expected spot colors are defined before exporting for print.
## Troubleshooting
### Spot Color Displays as Magenta
The spot color hasn't been defined. Call `setSpotColorRGB()` or `setSpotColorCMYK()` with the color name before applying it to blocks.
### Color Approximation Looks Wrong
Update the approximation values using `setSpotColorRGB()` or `setSpotColorCMYK()`. Remember that RGB values are for screen display while CMYK values are for print preview.
### Spot Color Not in Output
Verify the spot color name matches exactly (names are case-sensitive). Check that the block is using a SpotColor object, not an RGB or CMYK color value.
### Can't Remove Spot Color
Ensure you're using the exact name string. Note that removing a spot color doesn't update existing blocks—they'll show magenta until redefined or replaced with a different color.
## API Reference
| Method | Description |
| ------------------------------------------------- | ------------------------------------------------ |
| `engine.editor.setSpotColorRGB(name, r, g, b)` | Define/update spot color with RGB approximation |
| `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define/update spot color with CMYK approximation |
| `engine.editor.findAllSpotColors()` | Get array of all defined spot color names |
| `engine.editor.getSpotColorRGBA(name)` | Query RGB approximation for a spot color |
| `engine.editor.getSpotColorCMYK(name)` | Query CMYK approximation for a spot color |
| `engine.editor.removeSpotColor(name)` | Remove a spot color from the registry |
| `engine.editor.setSpotColorForCutoutType(type, color)` | Assign spot color to a cutout type |
| `engine.editor.getSpotColorForCutoutType(type)` | Get spot color assigned to a cutout type |
| `engine.block.setColor(block, property, color)` | Apply color (including SpotColor) to a property |
| `engine.block.getColor(block, property)` | Read color from a block property |
## Next Steps
- [Export for Printing](https://img.ly/docs/cesdk/node-native/export-save-publish/for-printing-bca896/) - Export designs with spot colors for professional print production
- [Apply Colors](https://img.ly/docs/cesdk/node-native/colors/apply-2211e3/) - Apply colors to fills, strokes, and shadows
- [CMYK Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/cmyk-8a1334/) - Work with CMYK process colors
## Cleanup
Always dispose the engine when done to free resources:
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "For Screen"
description: "Documentation for For Screen"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/for-screen-1911f8/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/node-native/colors/for-screen-1911f8/)
---
---
## Related Pages
- [sRGB Colors](https://img.ly/docs/cesdk/node-native/colors/for-screen/srgb-e6f59b/) - Work with sRGB colors for screen-based designs including creating RGBA colors, applying them to design elements, and converting from other color spaces.
- [P3 Colors](https://img.ly/docs/cesdk/node-native/colors/for-screen/p3-706127/) - Understand the P3 wide color gamut, its platform availability in CE.SDK, and alternatives for Web development.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "P3 Colors"
description: "Understand the P3 wide color gamut, its platform availability in CE.SDK, and alternatives for Web development."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/for-screen/p3-706127/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/node-native/colors/for-screen-1911f8/) > [P3 Colors](https://img.ly/docs/cesdk/node-native/colors/for-screen/p3-706127/)
---
Understand the P3 wide color gamut and its availability across CE.SDK platforms.
> **Not Available on Web:** P3 colors are not currently supported on Web platforms. Use [sRGB colors](https://img.ly/docs/cesdk/node-native/colors/for-screen/srgb-e6f59b/) instead.
P3 enables more vibrant reds, greens, and other colors beyond the standard sRGB gamut. This is valuable for displays that support the DCI-P3 color space, including modern Apple devices and high-end monitors.
## What is P3?
The DCI-P3 color space was developed for digital cinema and has been widely adopted in consumer displays, particularly by Apple since 2016. P3 covers roughly 25% more visible colors than sRGB, especially in the red, orange, and green-cyan regions.
Key differences from sRGB:
- **Gamut size**: P3 encompasses a larger color range
- **Primary colors**: P3 red and green are more saturated
- **Backwards compatibility**: P3 content on sRGB displays is automatically converted
P3 colors only appear more vibrant on P3-capable displays. On sRGB displays, colors are converted and may appear less saturated.
## Platform Support
**Supported platforms:**
- **Android**: `supportsP3()` and `checkP3Support()` APIs
- **iOS/Swift**: `supportsP3()` and `checkP3Support()` APIs
**Not supported:**
- Browser
- Server (Node.js)
On Web platforms, CE.SDK uses sRGB as the working color space. The Web binding supports sRGB, CMYK, and Spot Colors.
## P3 vs sRGB: When to Use Each
| Use Case | Recommended |
| --- | --- |
| Native mobile apps (Apple devices) | P3 |
| Photo/video editing with color accuracy | P3 |
| Server-side rendering | sRGB |
| Cross-platform consistency | sRGB |
## Next Steps
- [sRGB Colors](https://img.ly/docs/cesdk/node-native/colors/for-screen/srgb-e6f59b/) — Apply sRGB colors for screen output
- [Color Conversion](https://img.ly/docs/cesdk/node-native/colors/conversion-bcd82b/) — Convert between color spaces
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "sRGB Colors"
description: "Work with sRGB colors for screen-based designs including creating RGBA colors, applying them to design elements, and converting from other color spaces."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/for-screen/srgb-e6f59b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/node-native/colors/for-screen-1911f8/) > [sRGB Colors](https://img.ly/docs/cesdk/node-native/colors/for-screen/srgb-e6f59b/)
---
Apply sRGB colors to design elements for screen-based output using RGBA color values with red, green, blue, and alpha components.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-for-screen-srgb-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-for-screen-srgb-server-js)
sRGB is the standard color space for screen displays. CE.SDK represents sRGB colors as RGBA objects where each component (red, green, blue, alpha) uses floating-point values between 0.0 and 1.0. This differs from the traditional 0-255 integer range used in many design tools.
```typescript file=@cesdk_web_examples/guides-colors-for-screen-srgb-server-js/server-js.ts reference-only
import CreativeEngine, { isRGBAColor } from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Example: sRGB Colors
*
* This example demonstrates:
* - Creating sRGB/RGBA colors
* - Applying sRGB colors to fills, strokes, and shadows
* - Retrieving colors from design elements
* - Converting colors to sRGB
* - Working with alpha transparency
* - Using type guards to identify RGBA colors
*/
async function main(): Promise {
// Initialize the Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
// Get the page
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Create RGBA color objects for sRGB color space
// Values are floating-point numbers between 0.0 and 1.0
const blueColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 };
const redColor = { r: 0.9, g: 0.2, b: 0.2, a: 1.0 };
const greenColor = { r: 0.2, g: 0.8, b: 0.3, a: 1.0 };
// Create semi-transparent colors using the alpha channel
// Alpha of 0.5 means 50% opacity
const semiTransparentPurple = { r: 0.6, g: 0.2, b: 0.8, a: 0.5 };
const semiTransparentOrange = { r: 1.0, g: 0.5, b: 0.0, a: 0.7 };
// Create blocks to demonstrate color application
const block1 = engine.block.create('graphic');
engine.block.setShape(block1, engine.block.createShape('rect'));
engine.block.setWidth(block1, 150);
engine.block.setHeight(block1, 150);
engine.block.setPositionX(block1, 50);
engine.block.setPositionY(block1, 50);
engine.block.appendChild(page, block1);
const block2 = engine.block.create('graphic');
engine.block.setShape(block2, engine.block.createShape('ellipse'));
engine.block.setWidth(block2, 150);
engine.block.setHeight(block2, 150);
engine.block.setPositionX(block2, 250);
engine.block.setPositionY(block2, 50);
engine.block.appendChild(page, block2);
const block3 = engine.block.create('graphic');
engine.block.setShape(block3, engine.block.createShape('rect'));
engine.block.setWidth(block3, 150);
engine.block.setHeight(block3, 150);
engine.block.setPositionX(block3, 450);
engine.block.setPositionY(block3, 50);
engine.block.appendChild(page, block3);
// Apply sRGB colors to block fills
// First create a color fill, then set its color value
const fill1 = engine.block.createFill('color');
engine.block.setFill(block1, fill1);
engine.block.setColor(fill1, 'fill/color/value', blueColor);
const fill2 = engine.block.createFill('color');
engine.block.setFill(block2, fill2);
engine.block.setColor(fill2, 'fill/color/value', redColor);
const fill3 = engine.block.createFill('color');
engine.block.setFill(block3, fill3);
engine.block.setColor(fill3, 'fill/color/value', greenColor);
// Create blocks for stroke demonstration
const strokeBlock = engine.block.create('graphic');
engine.block.setShape(strokeBlock, engine.block.createShape('rect'));
engine.block.setWidth(strokeBlock, 150);
engine.block.setHeight(strokeBlock, 150);
engine.block.setPositionX(strokeBlock, 50);
engine.block.setPositionY(strokeBlock, 250);
engine.block.appendChild(page, strokeBlock);
const strokeFill = engine.block.createFill('color');
engine.block.setFill(strokeBlock, strokeFill);
engine.block.setColor(strokeFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0
});
// Apply sRGB color to stroke
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 5);
engine.block.setColor(strokeBlock, 'stroke/color', {
r: 0.1,
g: 0.1,
b: 0.5,
a: 1.0
});
// Create block for drop shadow demonstration
const shadowBlock = engine.block.create('graphic');
engine.block.setShape(shadowBlock, engine.block.createShape('rect'));
engine.block.setWidth(shadowBlock, 150);
engine.block.setHeight(shadowBlock, 150);
engine.block.setPositionX(shadowBlock, 250);
engine.block.setPositionY(shadowBlock, 250);
engine.block.appendChild(page, shadowBlock);
const shadowFill = engine.block.createFill('color');
engine.block.setFill(shadowBlock, shadowFill);
engine.block.setColor(shadowFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0
});
// Apply sRGB color to drop shadow
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 10);
engine.block.setDropShadowOffsetX(shadowBlock, 5);
engine.block.setDropShadowOffsetY(shadowBlock, 5);
engine.block.setColor(shadowBlock, 'dropShadow/color', {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.4
});
// Create blocks for transparency demonstration
const transparentBlock1 = engine.block.create('graphic');
engine.block.setShape(transparentBlock1, engine.block.createShape('rect'));
engine.block.setWidth(transparentBlock1, 150);
engine.block.setHeight(transparentBlock1, 150);
engine.block.setPositionX(transparentBlock1, 450);
engine.block.setPositionY(transparentBlock1, 250);
engine.block.appendChild(page, transparentBlock1);
const transparentFill1 = engine.block.createFill('color');
engine.block.setFill(transparentBlock1, transparentFill1);
engine.block.setColor(
transparentFill1,
'fill/color/value',
semiTransparentPurple
);
// Overlapping block to show transparency
const transparentBlock2 = engine.block.create('graphic');
engine.block.setShape(
transparentBlock2,
engine.block.createShape('ellipse')
);
engine.block.setWidth(transparentBlock2, 150);
engine.block.setHeight(transparentBlock2, 150);
engine.block.setPositionX(transparentBlock2, 500);
engine.block.setPositionY(transparentBlock2, 300);
engine.block.appendChild(page, transparentBlock2);
const transparentFill2 = engine.block.createFill('color');
engine.block.setFill(transparentBlock2, transparentFill2);
engine.block.setColor(
transparentFill2,
'fill/color/value',
semiTransparentOrange
);
// Retrieve the current color from a design element
const currentColor = engine.block.getColor(fill1, 'fill/color/value');
console.log('Current color:', currentColor);
// Use type guard to check if color is RGBA (sRGB)
if (isRGBAColor(currentColor)) {
console.log('Color is sRGB/RGBA');
console.log('Red:', currentColor.r);
console.log('Green:', currentColor.g);
console.log('Blue:', currentColor.b);
console.log('Alpha:', currentColor.a);
}
// Convert a CMYK color to sRGB
const cmykColor = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 };
const convertedToSrgb = engine.editor.convertColorToColorSpace(
cmykColor,
'sRGB'
);
console.log('Converted to sRGB:', convertedToSrgb);
// Export the scene as a PNG image
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('./output')) {
mkdirSync('./output', { recursive: true });
}
writeFileSync('./output/srgb-colors.png', buffer);
console.log('Exported: ./output/srgb-colors.png');
} finally {
// Always dispose the engine
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers creating RGBA color objects, applying them to fills, strokes, and shadows, retrieving colors from elements, converting colors to sRGB, and exporting the result.
## Setting Up the Engine
We initialize the Creative Engine and create a scene with a page.
```typescript highlight=highlight-setup
// Initialize the Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
```
## Creating sRGB Colors Programmatically
We create RGBA color objects by specifying r, g, b, and a properties. All four components are required and use values from 0.0 to 1.0.
```typescript highlight=highlight-create-rgba
// Create RGBA color objects for sRGB color space
// Values are floating-point numbers between 0.0 and 1.0
const blueColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 };
const redColor = { r: 0.9, g: 0.2, b: 0.2, a: 1.0 };
const greenColor = { r: 0.2, g: 0.8, b: 0.3, a: 1.0 };
```
The alpha channel controls transparency. A value of 1.0 is fully opaque, while 0.0 is fully transparent.
```typescript highlight=highlight-create-transparent
// Create semi-transparent colors using the alpha channel
// Alpha of 0.5 means 50% opacity
const semiTransparentPurple = { r: 0.6, g: 0.2, b: 0.8, a: 0.5 };
const semiTransparentOrange = { r: 1.0, g: 0.5, b: 0.0, a: 0.7 };
```
## Applying sRGB Colors to Fills
We use `engine.block.setColor()` to apply colors to block properties. For fills, we first create a color fill and then set its color value.
```typescript highlight=highlight-apply-fill
// Apply sRGB colors to block fills
// First create a color fill, then set its color value
const fill1 = engine.block.createFill('color');
engine.block.setFill(block1, fill1);
engine.block.setColor(fill1, 'fill/color/value', blueColor);
const fill2 = engine.block.createFill('color');
engine.block.setFill(block2, fill2);
engine.block.setColor(fill2, 'fill/color/value', redColor);
const fill3 = engine.block.createFill('color');
engine.block.setFill(block3, fill3);
engine.block.setColor(fill3, 'fill/color/value', greenColor);
```
## Applying sRGB Colors to Strokes
We can apply sRGB colors to strokes using the `'stroke/color'` property path.
```typescript highlight=highlight-apply-stroke
// Apply sRGB color to stroke
engine.block.setStrokeEnabled(strokeBlock, true);
engine.block.setStrokeWidth(strokeBlock, 5);
engine.block.setColor(strokeBlock, 'stroke/color', {
r: 0.1,
g: 0.1,
b: 0.5,
a: 1.0
});
```
## Applying sRGB Colors to Shadows
Drop shadows also support sRGB colors. We use the `'dropShadow/color'` property path. A semi-transparent black creates a natural shadow effect.
```typescript highlight=highlight-apply-shadow
// Apply sRGB color to drop shadow
engine.block.setDropShadowEnabled(shadowBlock, true);
engine.block.setDropShadowBlurRadiusX(shadowBlock, 10);
engine.block.setDropShadowBlurRadiusY(shadowBlock, 10);
engine.block.setDropShadowOffsetX(shadowBlock, 5);
engine.block.setDropShadowOffsetY(shadowBlock, 5);
engine.block.setColor(shadowBlock, 'dropShadow/color', {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.4
});
```
## Retrieving Colors from Elements
We use `engine.block.getColor()` to read the current color from a design element. The returned color could be RGBA, CMYK, or a spot color depending on what was set.
```typescript highlight=highlight-get-color
// Retrieve the current color from a design element
const currentColor = engine.block.getColor(fill1, 'fill/color/value');
console.log('Current color:', currentColor);
```
## Identifying sRGB Colors
We use the `isRGBAColor()` type guard to check if a color is sRGB. This is useful when working with colors that could be from any supported color space.
```typescript highlight=highlight-identify-rgba
// Use type guard to check if color is RGBA (sRGB)
if (isRGBAColor(currentColor)) {
console.log('Color is sRGB/RGBA');
console.log('Red:', currentColor.r);
console.log('Green:', currentColor.g);
console.log('Blue:', currentColor.b);
console.log('Alpha:', currentColor.a);
}
```
## Converting Colors to sRGB
We use `engine.editor.convertColorToColorSpace()` to convert CMYK or spot colors to sRGB for screen display.
```typescript highlight=highlight-convert-to-srgb
// Convert a CMYK color to sRGB
const cmykColor = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 };
const convertedToSrgb = engine.editor.convertColorToColorSpace(
cmykColor,
'sRGB'
);
console.log('Converted to sRGB:', convertedToSrgb);
```
## Exporting the Result
We export the scene as a PNG image and save it to the filesystem.
```typescript highlight=highlight-export
// Export the scene as a PNG image
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('./output')) {
mkdirSync('./output', { recursive: true });
}
writeFileSync('./output/srgb-colors.png', buffer);
console.log('Exported: ./output/srgb-colors.png');
```
## Cleanup
We always dispose the engine when finished to free resources.
```typescript highlight=highlight-cleanup
} finally {
// Always dispose the engine
engine.dispose();
}
```
## API Reference
| Method | Description |
| ---------------------------------------- | ---------------------------------------------- |
| `createFill('color')` | Create a new color fill object |
| `setFill(block, fill)` | Assign fill to a block |
| `setColor(block, property, color)` | Set color value (RGBA) |
| `getColor(block, property)` | Get current color value |
| `setStrokeEnabled(block, enabled)` | Enable or disable stroke rendering |
| `setStrokeWidth(block, width)` | Set stroke width |
| `setDropShadowEnabled(block, enabled)` | Enable or disable drop shadow |
| `convertColorToColorSpace(color, space)` | Convert color to specified color space |
| `isRGBAColor(color)` | Type guard to check if color is RGBA |
## Troubleshooting
**Colors appear incorrect:** Verify values are in the 0.0-1.0 range, not 0-255. A value of 255 would be clamped to 1.0.
**Color not visible:** Check that the alpha value is not 0.0 and that the fill or stroke is enabled on the block.
**Type errors:** Ensure all four components (r, g, b, a) are provided for RGBA colors. Omitting alpha will cause validation errors.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Manage color usage in your designs, from applying brand palettes to handling print and screen formats."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/overview-16a177/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [Overview](https://img.ly/docs/cesdk/node-native/colors/overview-16a177/)
---
Colors are a fundamental part of design in the CreativeEditor SDK (CE.SDK). Whether you're designing for digital screens or printed materials, consistent color management ensures your creations look the way you intend. CE.SDK offers flexible tools for working with colors through both the user interface and programmatically, making it easy to manage color workflows at any scale.
[Launch Web Demo](https://img.ly/showcases/cesdk)
[Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/)
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Replace Individual Colors"
description: "Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects to swap colors or remove backgrounds."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/colors/replace-48cd71/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) > [Replace Individual Colors](https://img.ly/docs/cesdk/node-native/colors/replace-48cd71/)
---
Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-colors-replace-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-colors-replace-server-js)
CE.SDK provides two specialized effects for color replacement: the **Recolor** effect swaps pixels matching a source color with a target color, while the **Green Screen** effect removes pixels matching a specified color to create transparency. Both effects use configurable tolerance parameters to control which pixels are affected, enabling use cases from product color variations to background removal.
```typescript file=@cesdk_web_examples/guides-colors-replace-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { calculateGridLayout } from './utils';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Replace Colors
*
* Demonstrates color replacement using Recolor and Green Screen effects:
* - Creating and applying Recolor effects
* - Creating and applying Green Screen effects
* - Configuring effect properties
* - Managing multiple effects
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } },
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// 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);
// Configure color matching precision for Recolor effect
const block2 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block2);
const recolorEffect2 = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.6,
b: 0.4,
a: 1.0,
}); // Skin tone source
engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', {
r: 0.3,
g: 0.7,
b: 0.3,
a: 1.0,
}); // Green tint
// Adjust color match tolerance (0-1, higher = more inclusive)
engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3);
// Adjust brightness match tolerance
engine.block.setFloat(recolorEffect2, 'effect/recolor/brightnessMatch', 0.2);
// Adjust edge smoothness
engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1);
engine.block.appendEffect(block2, recolorEffect2);
// Create a Green Screen effect to remove green backgrounds
const block3 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block3);
const greenScreenEffect = engine.block.createEffect('green_screen');
// Specify the color to remove (green)
engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
engine.block.appendEffect(block3, greenScreenEffect);
// Fine-tune Green Screen removal parameters
const block4 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block4);
const greenScreenEffect2 = engine.block.createEffect('green_screen');
engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', {
r: 0.2,
g: 0.8,
b: 0.3,
a: 1.0,
}); // Specific green shade
// Adjust color match tolerance
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/colorMatch',
0.4
);
// Adjust edge smoothness for cleaner removal
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/smoothness',
0.2
);
// Reduce color spill from green background
engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5);
engine.block.appendEffect(block4, greenScreenEffect2);
// Demonstrate managing multiple effects on a block
const block5 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block5);
// Add multiple effects to the same block
const recolor1 = engine.block.createEffect('recolor');
engine.block.setColor(recolor1, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
});
engine.block.setColor(recolor1, 'effect/recolor/toColor', {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
});
engine.block.appendEffect(block5, recolor1);
const recolor2 = engine.block.createEffect('recolor');
engine.block.setColor(recolor2, 'effect/recolor/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
engine.block.setColor(recolor2, 'effect/recolor/toColor', {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0,
});
engine.block.appendEffect(block5, recolor2);
// Get all effects on the block
const effects = engine.block.getEffects(block5);
// eslint-disable-next-line no-console
console.log('Number of effects:', effects.length); // 2
// Disable the first effect without removing it
engine.block.setEffectEnabled(effects[0], false);
// Check if effect is enabled
const isEnabled = engine.block.isEffectEnabled(effects[0]);
// eslint-disable-next-line no-console
console.log('First effect enabled:', isEnabled); // false
// Apply consistent color replacement across multiple blocks
const block6 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block6);
// Find all image blocks in the scene
const allBlocks = engine.block.findByType('//ly.img.ubq/graphic');
// Apply a consistent recolor effect to each block
allBlocks.forEach((blockId) => {
// Skip if block already has effects
if (engine.block.getEffects(blockId).length > 0) {
return;
}
const batchRecolor = engine.block.createEffect('recolor');
engine.block.setColor(batchRecolor, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.7,
b: 0.6,
a: 1.0,
});
engine.block.setColor(batchRecolor, 'effect/recolor/toColor', {
r: 0.6,
g: 0.7,
b: 0.9,
a: 1.0,
});
engine.block.setFloat(batchRecolor, 'effect/recolor/colorMatch', 0.25);
engine.block.appendEffect(blockId, batchRecolor);
});
// Position all blocks in a grid layout
const blocks = [block1, block2, block3, block4, block5, block6];
blocks.forEach((block, index) => {
const pos = getPosition(index);
engine.block.setPositionX(block, pos.x);
engine.block.setPositionY(block, pos.y);
});
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/replace-colors-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported result to output/replace-colors-result.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to apply and manage color replacement effects programmatically using the Block API in a headless server environment.
## Programmatic Color Replacement
Create and apply color replacement effects programmatically using the Block API. Effects are created as blocks, configured with properties, and appended to target blocks.
### Creating a Recolor Effect
The Recolor effect replaces pixels matching a source color with a target color. Use `engine.block.createEffect('recolor')` to create the effect, then set the `fromColor` and `toColor` properties using `engine.block.setColor()`.
```typescript highlight=highlight-create-recolor-effect
// Create a Recolor effect to swap red colors to blue
const block1 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block1);
const recolorEffect = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
}); // Red source color
engine.block.setColor(recolorEffect, 'effect/recolor/toColor', {
r: 0.0,
g: 0.5,
b: 1.0,
a: 1.0,
}); // Blue target color
engine.block.appendEffect(block1, recolorEffect);
```
The `fromColor` specifies which color to match in the image, and `toColor` defines the replacement color. Colors use RGBA format with values from 0 to 1.
### Configuring Color Matching Precision
Fine-tune which pixels are affected by adjusting the tolerance parameters with `engine.block.setFloat()`:
```typescript highlight=highlight-configure-recolor-matching
// Configure color matching precision for Recolor effect
const block2 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block2);
const recolorEffect2 = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.6,
b: 0.4,
a: 1.0,
}); // Skin tone source
engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', {
r: 0.3,
g: 0.7,
b: 0.3,
a: 1.0,
}); // Green tint
// Adjust color match tolerance (0-1, higher = more inclusive)
engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3);
// Adjust brightness match tolerance
engine.block.setFloat(recolorEffect2, 'effect/recolor/brightnessMatch', 0.2);
// Adjust edge smoothness
engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1);
engine.block.appendEffect(block2, recolorEffect2);
```
The Recolor effect has three precision parameters:
- **colorMatch** (0-1): Controls hue tolerance. Higher values include more color variations around the source color.
- **brightnessMatch** (0-1): Controls luminance tolerance. Higher values include pixels with different brightness levels.
- **smoothness** (0-1): Controls edge blending. Higher values create softer transitions at the boundaries of affected areas.
### Creating a Green Screen Effect
The Green Screen effect removes pixels matching a specified color, making them transparent. This is commonly used for background removal.
```typescript highlight=highlight-create-green-screen-effect
// Create a Green Screen effect to remove green backgrounds
const block3 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block3);
const greenScreenEffect = engine.block.createEffect('green_screen');
// Specify the color to remove (green)
engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
engine.block.appendEffect(block3, greenScreenEffect);
```
Set the `fromColor` property to specify which color to remove. The effect will make matching pixels transparent.
### Configuring Green Screen Parameters
Control the precision of color removal using these parameters:
```typescript highlight=highlight-configure-green-screen
// Fine-tune Green Screen removal parameters
const block4 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block4);
const greenScreenEffect2 = engine.block.createEffect('green_screen');
engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', {
r: 0.2,
g: 0.8,
b: 0.3,
a: 1.0,
}); // Specific green shade
// Adjust color match tolerance
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/colorMatch',
0.4
);
// Adjust edge smoothness for cleaner removal
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/smoothness',
0.2
);
// Reduce color spill from green background
engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5);
engine.block.appendEffect(block4, greenScreenEffect2);
```
The Green Screen effect parameters:
- **colorMatch**: Tolerance for matching the background color
- **smoothness**: Edge softness for cleaner cutouts around subjects
- **spill**: Reduces color bleed from the removed background onto the subject, useful when the background color reflects onto edges
## Managing Multiple Effects
A single block can have multiple effects applied. Use the effect management APIs to list, toggle, and remove effects.
```typescript highlight=highlight-manage-effects
// Demonstrate managing multiple effects on a block
const block5 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block5);
// Add multiple effects to the same block
const recolor1 = engine.block.createEffect('recolor');
engine.block.setColor(recolor1, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
});
engine.block.setColor(recolor1, 'effect/recolor/toColor', {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
});
engine.block.appendEffect(block5, recolor1);
const recolor2 = engine.block.createEffect('recolor');
engine.block.setColor(recolor2, 'effect/recolor/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
engine.block.setColor(recolor2, 'effect/recolor/toColor', {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0,
});
engine.block.appendEffect(block5, recolor2);
// Get all effects on the block
const effects = engine.block.getEffects(block5);
// eslint-disable-next-line no-console
console.log('Number of effects:', effects.length); // 2
// Disable the first effect without removing it
engine.block.setEffectEnabled(effects[0], false);
// Check if effect is enabled
const isEnabled = engine.block.isEffectEnabled(effects[0]);
// eslint-disable-next-line no-console
console.log('First effect enabled:', isEnabled); // false
```
Key effect management methods:
- `engine.block.getEffects(blockId)`: Returns an array of all effect IDs attached to a block
- `engine.block.setEffectEnabled(effectId, enabled)`: Toggle an effect on/off without removing it
- `engine.block.isEffectEnabled(effectId)`: Check whether an effect is currently active
- `engine.block.removeEffect(blockId, index)`: Remove an effect by its index in the effects array
Stacking multiple Recolor effects enables complex color transformations, such as replacing multiple colors in a single image or creating variations.
## Exporting Results
After applying color replacement effects, export the processed image to a file:
```typescript highlight=highlight-export
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/replace-colors-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported result to output/replace-colors-result.png');
```
The export uses `engine.block.export()` to render the page with all effects applied, then writes the result to the file system.
## Cleanup
Always dispose the engine when processing is complete to free resources:
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## 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 in export**: Verify that the effect is enabled using `isEffectEnabled()` and that it has been appended to the block using `appendEffect()`.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "System Compatibility"
description: "Learn how device performance and hardware limits affect CE.SDK editing, rendering, and export capabilities."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/compatibility-139ef9/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/node-native/compatibility-fef719/) > [System Compatibility](https://img.ly/docs/cesdk/node-native/compatibility-139ef9/)
---
CE.SDK makes use of hardware acceleration provided within that environment. Therefore, the hardware always acts as an upper bound of what’s achievable.
The editor's performance scales with scene complexity. We generally found scenes with up to 200 blocks well usable, but complex blocks like auto-sizing text or high-resolution image fills may affect performance negatively. This is always constrained by the processing power available on the device, so for low-end devices, the experience may suffer earlier. Therefore, it’s generally desirable to keep scenes only as complex as needed.
## Hardware Limitations
Each device has a limited amount of high performance hardware decoders and encoders. If the maximum number is reached it will fall back to (slow) software de- and encoding. Therefore users may encounter slow export performance when trying to export in parallel with other software that utilizes encoders and decoders. This, unfortunately, is a limitation of hardware, operating system and browser and cannot be solved.
## Version
- **Node.js 20** or later
The reliance on CPU processing may yield lower export speeds when compared to web browsers.
## Recommended Hardware
No specific CPU requirements, but export time scales with processing power and we recommend at least 2GB of memory.
## Video
Video is **not supported** in Node.js.
## Export Limitations
The export size is limited by the hardware capabilities of the device, e.g., due to the maximum texture size that can be allocated. The maximum possible export size can be queried via API, see [export guide](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/).
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Compatibility & Security"
description: "Learn about CE.SDK's compatibility and security features."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/compatibility-fef719/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/node-native/compatibility-fef719/)
---
CE.SDK provides robust compatibility and security features across platforms.
Learn about supported browsers, frameworks, file formats, language support,
and how CE.SDK ensures secure operation in your applications.
---
## Related Pages
- [System Compatibility](https://img.ly/docs/cesdk/node-native/compatibility-139ef9/) - Learn how device performance and hardware limits affect CE.SDK editing, rendering, and export capabilities.
- [File Format Support](https://img.ly/docs/cesdk/node-native/file-format-support-3c4b2a/) - See which image, video, audio, font, and template formats CE.SDK supports for import and export.
- [Security](https://img.ly/docs/cesdk/node-native/security-777bfd/) - Learn how CE.SDK keeps your data private with client-side processing, secure licensing, and GDPR-compliant practices.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Concepts"
description: "Key concepts and principles of CE.SDK"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts-c9ff51/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/)
---
Key Concepts and principles of CE.SDK.
---
## Related Pages
- [Key Concepts](https://img.ly/docs/cesdk/node-native/key-concepts-21a270/) - Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control.
- [Key Capabilities](https://img.ly/docs/cesdk/node-native/key-capabilities-dbb5b1/) - Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control.
- [Architecture](https://img.ly/docs/cesdk/node-native/concepts/architecture-6ea9b2/) - Understand how CE.SDK is structured around the CreativeEngine—the core runtime with six APIs for scenes, blocks, assets, events, variables, and editor state.
- [Terminology](https://img.ly/docs/cesdk/node-native/concepts/terminology-99e82d/) - Definitions for the core terms and concepts used throughout CE.SDK documentation, including Engine, Scene, Block, Fill, Shape, Effect, and more.
- [Editing Workflow](https://img.ly/docs/cesdk/node-native/concepts/editing-workflow-032d27/) - Control editing access with Creator, Adopter, Viewer, and Presenter roles using global and block-level scopes for tailored permissions.
- [Blocks](https://img.ly/docs/cesdk/node-native/concepts/blocks-90241e/) - Learn how blocks define elements in a scene and how to structure them for rendering in CE.SDK.
- [Scenes](https://img.ly/docs/cesdk/node-native/concepts/scenes-e8596d/) - Create, configure, save, and load scenes—the root container for all design elements in CE.SDK.
- [Pages](https://img.ly/docs/cesdk/node-native/concepts/pages-7b6bae/) - Pages structure scenes in CE.SDK and must share the same dimensions to ensure consistent rendering.
- [Assets](https://img.ly/docs/cesdk/node-native/concepts/assets-a84fdd/) - Learn how assets provide external content to CE.SDK designs and how asset sources make them available programmatically.
- [Templating](https://img.ly/docs/cesdk/node-native/concepts/templating-f94385/) - Templates enable dynamic, reusable designs with text variables and placeholder media. Learn to create, load, and personalize templates programmatically.
- [Events](https://img.ly/docs/cesdk/node-native/concepts/events-353f97/) - Subscribe to block creation, update, and deletion events to track changes in your CE.SDK scene.
- [Buffers](https://img.ly/docs/cesdk/node-native/concepts/buffers-9c565b/) - Use buffers to store temporary, non-serializable data in CE.SDK via the CreativeEngine API.
- [Resources](https://img.ly/docs/cesdk/node-native/concepts/resources-a58d71/) - Learn how CE.SDK loads and manages external media files, including preloading for performance, handling transient data, and relocating resources when URLs change.
- [Undo and History](https://img.ly/docs/cesdk/node-native/concepts/undo-and-history-99479d/) - Manage undo and redo stacks in CE.SDK using multiple histories, callbacks, and API-based controls.
- [Design Units](https://img.ly/docs/cesdk/node-native/concepts/design-units-cc6597/) - Configure design units (pixels, millimeters, inches) and DPI settings for print-ready output in CE.SDK.
- [Headless](https://img.ly/docs/cesdk/node-native/concepts/headless-mode-24ab98/) - Run CE.SDK programmatically without any user interface (UI) using native Node.js bindings.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Architecture"
description: "Understand how CE.SDK is structured around the CreativeEngine—the core runtime with six APIs for scenes, blocks, assets, events, variables, and editor state."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/architecture-6ea9b2/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Architecture](https://img.ly/docs/cesdk/node-native/concepts/architecture-6ea9b2/)
---
Understand how CE.SDK is structured around the CreativeEngine and its six interconnected APIs.
CE.SDK is built around the **CreativeEngine**—a single-threaded core runtime that manages state, rendering, and coordination between six specialized APIs. Understanding how these pieces connect helps you navigate the SDK effectively.
## The CreativeEngine
The *Engine* is the central coordinator. All operations—creating content, manipulating blocks, rendering, and exporting—flow through it. Initialize it once and access everything else through its API namespaces.
The *Engine* manages:
- **One active scene** containing all design content
- **Six API namespaces** for different domains of functionality
- **Event dispatching** for reactive state management
- **Resource loading** and caching
- **Rendering** to a canvas element (browser) or headless export (server)
## Content Hierarchy
CE.SDK organizes content in a tree: *Scene* → *Pages* → *Blocks*.
- **Scene**: The root container. One scene per engine instance. Supports both static designs and time-based video editing.
- **Pages**: Containers within a scene. Artboards for static designs, time-based compositions for video editing.
- **Blocks**: The atomic units—graphics, text, audio, video. Everything visible is a block.
The **Scene API** manages this hierarchy. The **Block API** manipulates individual blocks within it. See [Scenes](https://img.ly/docs/cesdk/node-native/concepts/scenes-e8596d/), [Pages](https://img.ly/docs/cesdk/node-native/concepts/pages-7b6bae/), and [Blocks](https://img.ly/docs/cesdk/node-native/concepts/blocks-90241e/) for details.
## The Six APIs
The engine exposes six API namespaces. Here's how they interconnect:
### Scene API (`engine.scene`)
Creates and manages the content hierarchy. Works with the *Block API* to populate scenes with content and the *Event API* to notify when structure changes.
### Block API (`engine.block`)
The most-used API. Creates, modifies, and queries blocks. Every visual element flows through here. Blocks reference *Assets* loaded through the *Asset API* and can contain *Variables* managed by the *Variable API*.
### Asset API (`engine.asset`)
Provides content to the *Block API*. Registers asset sources (images, videos, stickers, templates) and handles queries. When you add an image to a block, the *Asset API* resolves it and the *Block API* applies it.
### Variable API (`engine.variable`)
Enables data-driven designs. Define variables at the scene level; reference them in text blocks with `{{variableName}}` syntax. When variable values change, affected blocks update automatically—coordinated through the *Event API*.
### Editor API (`engine.editor`)
Controls application state: edit modes, undo/redo history, user roles, and permissions. The *Editor API* determines what operations the *Block API* can perform based on current role and scope settings.
### Event API (`engine.event`)
The reactive backbone. Subscribe to changes across all other APIs—block modifications, selection changes, history updates. Build UIs that stay synchronized with engine state.
## How They Connect
A typical flow shows the interconnection:
1. **Scene API** creates the content structure
2. **Asset API** provides images, templates, or other content
3. **Block API** creates blocks and applies assets to them
4. **Variable API** injects dynamic data into text blocks
5. **Editor API** controls what users can modify
6. **Event API** notifies your UI of every change
Each API focuses on one domain but works through the others. The *Engine* coordinates these interactions.
## Scene Capabilities
CE.SDK scenes support a range of capabilities:
- **Static designs**: Social posts, print materials, graphics. Blocks positioned spatially.
- **Time-based content**: Duration, playback time, and animation. Blocks arranged across time.
The scene configuration determines which *Block API* properties and *Editor API* capabilities are available. See [Scenes](https://img.ly/docs/cesdk/node-native/concepts/scenes-e8596d/) for details.
## Integration Patterns
CE.SDK runs in two contexts:
- **Browser**: The engine renders to a canvas element. Append `engine.element` to your DOM. Use with the built-in UI or build your own.
- **Headless**: No rendering, just processing. Use for server-side exports, automation, and batch operations. See [Headless Mode](https://img.ly/docs/cesdk/node-native/concepts/headless-mode-24ab98/).
Both contexts use the same six APIs—only rendering differs.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Assets"
description: "Learn how assets provide external content to CE.SDK designs and how asset sources make them available programmatically."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/assets-a84fdd/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Assets](https://img.ly/docs/cesdk/node-native/concepts/assets-a84fdd/)
---
Understand the asset system—how external media and resources like images, stickers, or videos are handled in CE.SDK.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-assets-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-assets-server-js)
Images, videos, audio, fonts, stickers, and templates—every premade resource you can add to a design is what we call an *Asset*. The editor gets access to these Assets through *Asset Sources*. When you apply an Asset, CE.SDK creates or modifies a Block to display that content.
```typescript file=@cesdk_web_examples/guides-concepts-assets-server-js/server-js.ts reference-only
import CreativeEngine, {
AssetSource,
AssetQueryData,
AssetsQueryResult,
AssetResult
} from '@cesdk/node';
import { config } from 'dotenv';
// Load environment variables from .env file
config();
/**
* CE.SDK Server Example: 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
*/
async function main(): Promise {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a scene with a page
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 600);
// 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
if (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();
console.log('Assets guide completed successfully.');
console.log('Created custom asset source and applied sticker asset.');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers the core concepts of the Asset system: what assets are, how to create asset sources, and how to query and apply assets programmatically.
## Assets vs Blocks
**Assets** are content definitions with metadata (URIs, dimensions, labels) that exist outside the scene. **Blocks** are the visual elements in the scene tree that display content.
When you apply an asset, CE.SDK creates a block configured according to the asset's properties. Multiple blocks can reference the same asset, and assets can exist without being used in any block.
## The Asset Data Model
An asset describes content that can be added to designs. Each asset has an `id` and optional properties:
```typescript highlight-asset-definition
// An asset is a content definition with metadata
// It describes content that can be added to designs
const stickerAsset: AssetResult = {
id: 'sticker-smile',
label: 'Smile Sticker',
tags: ['emoji', 'happy'],
groups: ['stickers'],
meta: {
uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg',
thumbUri:
'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg',
blockType: '//ly.img.ubq/graphic',
fillType: '//ly.img.ubq/fill/image',
width: 62,
height: 58,
mimeType: 'image/svg+xml'
}
};
```
Key properties include:
- **`id`** — Unique identifier for the asset
- **`label`** — Display name (can be localized)
- **`tags`** — Searchable keywords
- **`groups`** — Categories for filtering
- **`meta`** — Content-specific data including `uri`, `thumbUri`, `blockType`, `fillType`, `width`, `height`, and `mimeType`
> **Note:** See the [Content JSON Schema](https://img.ly/docs/cesdk/node-native/import-media/content-json-schema-a7b3d2/) guide for the complete property reference.
## Asset Sources
Asset sources provide assets to the editor. Each source has an `id` and implements a `findAssets()` method that returns paginated results.
```typescript highlight-asset-source
// Asset sources provide assets to the editor
// Each source has an id and a findAssets() method
const customSource: AssetSource = {
id: 'my-assets',
async findAssets(query: AssetQueryData): Promise {
// Return paginated results matching the query
return {
assets: [stickerAsset],
total: 1,
currentPage: query.page,
nextPage: undefined
};
}
};
engine.asset.addSource(customSource);
```
The `findAssets()` callback receives query parameters (`page`, `perPage`, `query`, `tags`, `groups`) and returns a result object with `assets`, `total`, `currentPage`, and `nextPage`.
Sources can also implement optional methods like `getGroups()`, `getSupportedMimeTypes()`, and `applyAsset()` for custom behavior.
## Querying Assets
Search and filter assets from registered sources using `findAssets()`:
```typescript highlight-query-assets
// Query assets from a source
const results = await engine.asset.findAssets('my-assets', {
page: 0,
perPage: 10
});
console.log('Found assets:', results.total);
```
Results include pagination info. Loop through pages until `nextPage` is undefined to retrieve all matching assets.
## Applying Assets
Use `apply()` to create a new block from an asset:
```typescript highlight-apply-asset
// Apply an asset to create a block in the scene
if (results.assets.length > 0) {
const blockId = await engine.asset.apply('my-assets', results.assets[0]);
console.log('Created block:', blockId);
// Center the sticker on the page
if (blockId) {
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// SVG is 62x58, scale to fit nicely
const stickerWidth = 62;
const stickerHeight = 58;
engine.block.setWidth(blockId, stickerWidth);
engine.block.setHeight(blockId, stickerHeight);
engine.block.setPositionX(blockId, (pageWidth - stickerWidth) / 2);
engine.block.setPositionY(blockId, (pageHeight - stickerHeight) / 2);
}
}
```
The method returns the new block ID, which you can use to position and configure the block.
## Local Asset Sources
Local sources store assets in memory and support dynamic add/remove operations. Use these for user uploads or runtime-generated content:
```typescript highlight-local-source
// Local sources support dynamic add/remove operations
engine.asset.addLocalSource('uploads', ['image/svg+xml', 'image/png']);
engine.asset.addAssetToSource('uploads', {
id: 'uploaded-1',
label: { en: 'Heart Sticker' },
meta: {
uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg',
thumbUri:
'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg',
blockType: '//ly.img.ubq/graphic',
fillType: '//ly.img.ubq/fill/image',
mimeType: 'image/svg+xml'
}
});
```
## Source Events
Subscribe to asset source lifecycle events for reactive UIs:
```typescript highlight-source-events
// Subscribe to asset source lifecycle events
const unsubscribe = engine.asset.onAssetSourceUpdated((sourceId) => {
console.log('Source updated:', sourceId);
});
// Notify that source contents changed
engine.asset.assetSourceContentsChanged('uploads');
unsubscribe();
```
Call `assetSourceContentsChanged()` after modifying a source to notify subscribers.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Blocks"
description: "Learn how blocks define elements in a scene and how to structure them for rendering in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/blocks-90241e/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Blocks](https://img.ly/docs/cesdk/node-native/concepts/blocks-90241e/)
---
Work with blocks—the fundamental building units for all visual elements in
CE.SDK designs.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-blocks-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-blocks-server-js)
Every visual element in CE.SDK—images, text, shapes, and audio—is represented as a block. Blocks are organized in a tree structure within scenes and pages, where parent-child relationships determine rendering order and visibility. Each block has properties you can read and modify, a `Type` that defines its core behavior, and an optional `Kind` for custom categorization.
```typescript file=@cesdk_web_examples/guides-concepts-blocks-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
// Load environment variables from .env file
config();
/**
* CE.SDK Server Example: 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)
*/
async function main(): Promise {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a new scene and page
const scene = engine.scene.create();
// Create and add a page block - pages contain all design elements
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// Set page dimensions
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
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
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
// 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'
// Save blocks to a string for persistence
// Include 'bundle' scheme to allow serialization of blocks with bundled fonts
const savedString = await engine.block.saveToString(
[graphic, textBlock],
['buffer', 'http', 'https', 'bundle']
);
console.log('Blocks saved to string, length:', savedString.length);
// Alternatively, blocks can also be saved with their assets to an archive
// const savedBlocksArchive = await engine.block.saveToArchive([
// graphic,
// textBlock
// ]);
// Load blocks from string (creates new blocks, not attached to scene)
const loadedBlocks = await engine.block.loadFromString(savedString);
console.log('Loaded blocks from string:', loadedBlocks.length);
// Alternatively, blocks can also be loaded from an archive
// const loadedBlocks = await engine.block.loadFromArchiveURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1_blocks.zip'
// );
// console.log('Loaded blocks from archive URL:', loadedBlocks.length);
// Alternatively, blocks can be loaded from an extracted zip file created with block.saveToArchive
// const loadedBlocks = await engine.block.loadFromURL(
// 'https://cdn.img.ly/assets/v6/ly.img.text.components/box/blocks.blocks'
// );
// console.log('Loaded blocks from URL:', loadedBlocks.length);
// Loaded blocks must be parented to appear in the scene
// For demo purposes, we won't add them to avoid duplicates
for (const block of loadedBlocks) {
engine.block.destroy(block);
}
console.log('Blocks guide completed successfully.');
console.log('Created graphic block with image fill and text block.');
console.log(
'Demonstrated: types, hierarchy, properties, selection, state, and serialization.'
);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers block types and their uses, how to create and manage blocks programmatically, how to work with block properties using the reflection system, and how to handle selection, visibility, and state changes.
## Block Types
CE.SDK provides several block types, each designed for specific content:
- **graphic** (`//ly.img.ubq/graphic`): Visual blocks for images, shapes, and graphics
- **text** (`//ly.img.ubq/text`): Text content with typography controls
- **audio** (`//ly.img.ubq/audio`): Audio content for video scenes
- **page** (`//ly.img.ubq/page`): Container blocks representing canvases or artboards
- **cutout** (`//ly.img.ubq/cutout`): Blocks for masking operations
We query a block's type using `getType()` and find blocks of a specific type with `findByType()`:
```typescript highlight-block-types
// Create a new scene and page
const scene = engine.scene.create();
// Create and add a page block - pages contain all design elements
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// Set page dimensions
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'
```
Block types are immutable—once created, a block's type cannot change. This distinguishes type from kind.
## Type vs Kind
Type and kind serve different purposes. The **type** is determined at creation and defines core behavior. The **kind** is a custom string label you assign for application-specific categorization.
```typescript highlight-type-vs-kind
// Type is immutable, determined at creation
// Kind is a custom label you can set and change
engine.block.setKind(page, 'main-canvas');
const pageKind = engine.block.getKind(page);
console.log('Page kind:', pageKind); // 'main-canvas'
// Find blocks by kind
const mainCanvasBlocks = engine.block.findByKind('main-canvas');
console.log('Blocks with kind "main-canvas":', mainCanvasBlocks.length);
```
Use kind to tag blocks for your application's logic. You can set it with `setKind()`, query it with `getKind()`, and find blocks by kind with `findByKind()`.
## Block Hierarchy
Blocks form a tree structure where scenes contain pages, and pages contain design elements.
```typescript highlight-block-hierarchy
// Blocks form a tree: scene > page > elements
// Append the graphic to the page to make it visible
engine.block.appendChild(page, graphic);
// Query parent-child relationships
const graphicParent = engine.block.getParent(graphic);
console.log('Graphic parent is page:', graphicParent === page); // true
const pageChildren = engine.block.getChildren(page);
console.log('Page has children:', pageChildren.length);
```
Only blocks that are direct or indirect children of a page block are rendered. A scene without any page children won't display content in the editor. Use `appendChild()` to add blocks to parents, `getParent()` to query a block's parent, and `getChildren()` to get a block's children.
## Block Lifecycle
Create new blocks with `create()`, duplicate existing blocks with `duplicate()`, and remove blocks with `destroy()`. After destroying a block, `isValid()` returns `false` for that block ID.
```typescript highlight-block-lifecycle
// Create a graphic block for an image
const graphic = engine.block.create('graphic');
// Duplicate creates a copy with a new UUID
const graphicCopy = engine.block.duplicate(graphic);
// Destroy removes a block - the duplicate is no longer needed
engine.block.destroy(graphicCopy);
// Check if a block ID is still valid after operations
const isOriginalValid = engine.block.isValid(graphic);
const isCopyValid = engine.block.isValid(graphicCopy);
console.log('Original valid:', isOriginalValid); // true
console.log('Copy valid after destroy:', isCopyValid); // false
```
When duplicating a block, all children are included, and the duplicate receives a new UUID.
## Working with Fills
Graphic blocks display content through fills. We create a fill, attach it to a block, and configure its source.
```typescript highlight-fill
// Create a rect shape to define the graphic's bounds
const rectShape = engine.block.createShape('rect');
engine.block.setShape(graphic, rectShape);
// Position and size the graphic
engine.block.setPositionX(graphic, 200);
engine.block.setPositionY(graphic, 100);
engine.block.setWidth(graphic, 400);
engine.block.setHeight(graphic, 300);
// Create an image fill and attach it to the graphic
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(graphic, imageFill);
// Set content fill mode so the image fills the block bounds
engine.block.setEnum(graphic, 'contentFill/mode', 'Cover');
```
CE.SDK supports several fill types including image, video, color, and gradient fills. See the [Fills guide](https://img.ly/docs/cesdk/node-native/filters-and-effects/gradients-0ff079/) for details on available fill types.
## Creating Text Blocks
Text blocks display formatted text content. We create a text block, position it, and set its content.
```typescript highlight-text-block
// Create a text block with content
const textBlock = engine.block.create('text');
engine.block.appendChild(page, textBlock);
// Position the text block
engine.block.setPositionX(textBlock, 200);
engine.block.setPositionY(textBlock, 450);
engine.block.setWidth(textBlock, 400);
engine.block.setHeight(textBlock, 80);
// Set text content
engine.block.setString(
textBlock,
'text/text',
'Blocks are the building units of CE.SDK designs'
);
// Set font size to 72pt
engine.block.setFloat(textBlock, 'text/fontSize', 72);
// Center-align the text
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
// Check the text block type
const textType = engine.block.getType(textBlock);
console.log('Text block type:', textType); // '//ly.img.ubq/text'
```
Text blocks support extensive typography controls covered in the [Text guides](https://img.ly/docs/cesdk/node-native/text-8a993a/).
## Block Properties
The reflection system lets you discover and manipulate any block property dynamically. Use `findAllProperties()` to get all available properties for a block—they're prefixed by category like `shape/star/points` or `text/fontSize`.
```typescript highlight-block-properties
// Use reflection to discover available properties
const graphicProperties = engine.block.findAllProperties(graphic);
console.log('Graphic block has', graphicProperties.length, 'properties');
// Get property type information
const opacityType = engine.block.getPropertyType('opacity');
console.log('Opacity property type:', opacityType); // 'Float'
// Check if properties are readable/writable
const isOpacityReadable = engine.block.isPropertyReadable('opacity');
const isOpacityWritable = engine.block.isPropertyWritable('opacity');
console.log(
'Opacity readable:',
isOpacityReadable,
'writable:',
isOpacityWritable
);
```
Query property types with `getPropertyType()`. Returns include `Bool`, `Int`, `Float`, `Double`, `String`, `Color`, `Enum`, or `Struct`. For enum properties, use `getEnumValues()` to get allowed values.
### Property Accessors
Use type-specific getters and setters matching the property type:
```typescript highlight-property-accessors
// Use type-specific getters and setters
// Float properties
engine.block.setFloat(graphic, 'opacity', 0.9);
const opacity = engine.block.getFloat(graphic, 'opacity');
console.log('Graphic opacity:', opacity);
// Bool properties
engine.block.setBool(page, 'page/marginEnabled', false);
const marginEnabled = engine.block.getBool(page, 'page/marginEnabled');
console.log('Page margin enabled:', marginEnabled);
// Enum properties - get allowed values first
const blendModes = engine.block.getEnumValues('blend/mode');
console.log(
'Available blend modes:',
blendModes.slice(0, 3).join(', '),
'...'
);
engine.block.setEnum(graphic, 'blend/mode', 'Multiply');
const blendMode = engine.block.getEnum(graphic, 'blend/mode');
console.log('Graphic blend mode:', blendMode);
```
Using the wrong accessor type for a property will cause an error. Always check `getPropertyType()` if you're unsure which accessor to use.
## UUID, Names, and Identity
Each block has a UUID that remains stable across save and load operations. Block names are mutable labels for organization.
```typescript highlight-uuid-identity
// Each block has a stable UUID across save/load cycles
const graphicUUID = engine.block.getUUID(graphic);
console.log('Graphic UUID:', graphicUUID);
// Block names are mutable labels for organization
engine.block.setName(graphic, 'Hero Image');
engine.block.setName(textBlock, 'Caption');
const graphicName = engine.block.getName(graphic);
console.log('Graphic name:', graphicName); // 'Hero Image'
```
Use `getUUID()` when you need a persistent identifier for a block. Names are useful for user-facing labels and can be changed freely with `setName()`.
## Selection
Control which blocks are selected programmatically. Use `select()` to select a single block (deselecting others) or `setSelected()` to modify selection without affecting other blocks.
```typescript highlight-selection
// Select a block programmatically
engine.block.select(graphic); // Selects graphic, deselects others
// Check selection state
const isGraphicSelected = engine.block.isSelected(graphic);
console.log('Graphic is selected:', isGraphicSelected); // true
// Add to selection without deselecting others
engine.block.setSelected(textBlock, true);
// Get all selected blocks
const selectedBlocks = engine.block.findAllSelected();
console.log('Selected blocks count:', selectedBlocks.length); // 2
```
Subscribe to selection changes with `onSelectionChanged()` to update your UI when the selection state changes.
## Visibility
Control whether blocks appear on the canvas and are included in exports.
```typescript highlight-visibility
// Control block visibility
engine.block.setVisible(graphic, true);
const isVisible = engine.block.isVisible(graphic);
console.log('Graphic is visible:', isVisible);
// Control export inclusion
engine.block.setIncludedInExport(graphic, true);
const inExport = engine.block.isIncludedInExport(graphic);
console.log('Graphic included in export:', inExport);
```
A block with `isVisible()` returning true may still not appear if it hasn't been added to a parent, the parent is hidden, or another block obscures it.
### Clipping
Clipping determines whether a block's content is constrained to its parent's bounds. When `setClipped(block, true)` is set, any portion of the block extending beyond its parent's boundaries is hidden. When clipping is disabled, the block renders fully even if it overflows its parent container.
```typescript highlight-clipping
// Control clipping behavior
engine.block.setClipped(graphic, false);
const isClipped = engine.block.isClipped(graphic);
console.log('Graphic is clipped:', isClipped);
```
## Block State
Blocks track loading progress and error conditions through a state system with three possible states:
- **Ready**: Normal state, no pending operations
- **Pending**: Operation in progress with optional progress value (0-1)
- **Error**: Operation failed (`ImageDecoding`, `VideoDecoding`, `FileFetch`, `AudioDecoding`, `Unknown`)
```typescript highlight-block-state
// Query block state - indicates loading status
const graphicState = engine.block.getState(graphic);
console.log('Graphic state:', graphicState.type); // 'Ready', 'Pending', or 'Error'
```
Subscribe to state changes with `onStateChanged()` to show loading indicators or handle errors in your UI.
## Serialization
Save blocks to strings for persistence and restore them later.
```typescript highlight-serialization
// Save blocks to a string for persistence
// Include 'bundle' scheme to allow serialization of blocks with bundled fonts
const savedString = await engine.block.saveToString(
[graphic, textBlock],
['buffer', 'http', 'https', 'bundle']
);
console.log('Blocks saved to string, length:', savedString.length);
// Alternatively, blocks can also be saved with their assets to an archive
// const savedBlocksArchive = await engine.block.saveToArchive([
// graphic,
// textBlock
// ]);
// Load blocks from string (creates new blocks, not attached to scene)
const loadedBlocks = await engine.block.loadFromString(savedString);
console.log('Loaded blocks from string:', loadedBlocks.length);
// Alternatively, blocks can also be loaded from an archive
// const loadedBlocks = await engine.block.loadFromArchiveURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1_blocks.zip'
// );
// console.log('Loaded blocks from archive URL:', loadedBlocks.length);
// Alternatively, blocks can be loaded from an extracted zip file created with block.saveToArchive
// const loadedBlocks = await engine.block.loadFromURL(
// 'https://cdn.img.ly/assets/v6/ly.img.text.components/box/blocks.blocks'
// );
// console.log('Loaded blocks from URL:', loadedBlocks.length);
// Loaded blocks must be parented to appear in the scene
// For demo purposes, we won't add them to avoid duplicates
for (const block of loadedBlocks) {
engine.block.destroy(block);
}
```
Use `saveToString()` for lightweight serialization or `saveToArchive()` to include all referenced assets.
Blocks can be loaded with `loadFromString()`, `loadFromArchiveURL()`, or `loadFromURL()`.
For `loadFromArchiveURL()`, the URL should point to the zipped archive file previously saved with `saveToArchive()`,
whereas for `loadFromURL()`, it should point to a blocks file within an unzipped archive directory.
Loaded blocks are not automatically attached to the scene—you must parent them with `appendChild()` to make them visible.
## Troubleshooting
**Block not visible**: Ensure the block is a child of a page that's a child of the scene.
**Property setter fails**: Verify the property type matches the setter method used. Use `getPropertyType()` to check.
**Block ID invalid after destroy**: Use `isValid()` before operations on potentially destroyed blocks.
**State stuck in Pending**: Check network connectivity for remote resources or use state change events to monitor progress.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Buffers"
description: "Use buffers to store temporary, non-serializable data in CE.SDK via the CreativeEngine API."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/buffers-9c565b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Buffers](https://img.ly/docs/cesdk/node-native/concepts/buffers-9c565b/)
---
Store and manage temporary binary data directly in memory using CE.SDK's buffer API for dynamically generated content.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-buffers-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-buffers-server-js)
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 processing session.
```typescript file=@cesdk_web_examples/guides-concepts-buffers-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables from .env file
config();
// Helper function to create a WAV file from audio samples
function createWavFile(
samples: Float32Array,
sampleRate: number,
numChannels: number
): Uint8Array {
const bytesPerSample = 2; // 16-bit audio
const blockAlign = numChannels * bytesPerSample;
const byteRate = sampleRate * blockAlign;
const dataSize = samples.length * bytesPerSample;
const fileSize = 44 + dataSize; // WAV header is 44 bytes
const buffer = new ArrayBuffer(fileSize);
const view = new DataView(buffer);
// Write WAV header
// "RIFF" chunk descriptor
writeString(view, 0, 'RIFF');
view.setUint32(4, fileSize - 8, true); // File size minus RIFF header
writeString(view, 8, 'WAVE');
// "fmt " sub-chunk
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true); // Subchunk1Size (16 for PCM)
view.setUint16(20, 1, true); // AudioFormat (1 = PCM)
view.setUint16(22, numChannels, true); // NumChannels
view.setUint32(24, sampleRate, true); // SampleRate
view.setUint32(28, byteRate, true); // ByteRate
view.setUint16(32, blockAlign, true); // BlockAlign
view.setUint16(34, bytesPerSample * 8, true); // BitsPerSample
// "data" sub-chunk
writeString(view, 36, 'data');
view.setUint32(40, dataSize, true); // Subchunk2Size
// Write audio samples as 16-bit PCM
let offset = 44;
for (let i = 0; i < samples.length; i++) {
// Convert float (-1 to 1) to 16-bit integer
const sample = Math.max(-1, Math.min(1, samples[i]));
const intSample = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
view.setInt16(offset, intSample, true);
offset += 2;
}
return new Uint8Array(buffer);
}
function writeString(view: DataView, offset: number, str: string): void {
for (let i = 0; i < str.length; i++) {
view.setUint8(offset + i, str.charCodeAt(i));
}
}
async function main(): Promise {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
// 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 to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const bufferData = engine.editor.getBufferData(bufferUri, 0, bufferLength);
const outputPath = `${outputDir}/generated-audio.wav`;
writeFileSync(outputPath, Buffer.from(bufferData));
console.log('Buffer data saved to:', outputPath);
// Update all references from buffer:// to the file path
// In production, use a CDN URL instead of a local file path
const absolutePath = `file://${process.cwd()}/${outputPath}`;
engine.editor.relocateResource(bufferUri, absolutePath);
console.log('Buffer relocated to:', absolutePath);
console.log('Buffers example completed successfully');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
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.
## Initializing the Engine
We start by initializing the headless Creative Engine for server-side processing. The engine is initialized with an optional license key loaded from environment variables.
```typescript highlight-setup
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
```
## Setting Up the Scene
We first create a scene and set up a page for our audio composition.
```typescript highlight-create-scene
// Create a scene with a page
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
```
## Creating and Managing Buffers
We use `engine.editor.createBuffer()` to allocate a new buffer and receive its URI. This URI follows the `buffer://` scheme and uniquely identifies the buffer within the engine instance.
```typescript highlight-create-buffer
// Create a buffer and get its URI
const bufferUri = engine.editor.createBuffer();
console.log('Buffer URI:', bufferUri);
```
Buffers persist in memory until you explicitly destroy them with `engine.editor.destroyBuffer()` or the engine instance is disposed. For large buffers or long processing sessions, you should destroy buffers when they're no longer needed to free memory.
## Writing Data to Buffers
To populate a buffer with binary data, we use `engine.editor.setBufferData()`. This method takes the buffer URI, an offset in bytes, and a `Uint8Array` containing the data to write.
In this example, we generate a 440 Hz sine wave as stereo PCM audio samples. We create a `Float32Array` for the sample values that will be converted to a valid audio format.
```typescript highlight-generate-samples
// Generate sine wave audio samples
const sampleRate = 44100;
const duration = 2; // 2 seconds
const frequency = 440; // A4 note
const numChannels = 2; // Stereo
// Create Float32Array for audio samples (interleaved stereo)
const numSamples = sampleRate * duration * numChannels;
const samples = new Float32Array(numSamples);
// Generate a 440 Hz sine wave
for (let i = 0; i < numSamples; i += numChannels) {
const sampleIndex = i / numChannels;
const time = sampleIndex / sampleRate;
const value = Math.sin(2 * Math.PI * frequency * time) * 0.5; // 50% amplitude
// Write to both left and right channels
samples[i] = value; // Left channel
samples[i + 1] = value; // Right channel
}
```
When using buffers for audio, the data must be in a recognized audio format like WAV. We convert the raw samples to a WAV file by adding the appropriate headers, then write the complete file to the buffer.
```typescript highlight-write-buffer
// Convert samples to WAV format and write to buffer
const wavData = createWavFile(samples, sampleRate, numChannels);
engine.editor.setBufferData(bufferUri, 0, wavData);
// Verify the buffer length
const bufferLength = engine.editor.getBufferLength(bufferUri);
console.log('Buffer length:', bufferLength, 'bytes');
```
## Reading Data from Buffers
To read data back from a buffer, we use `engine.editor.getBufferData()` with the buffer URI, a starting offset, and the number of bytes to read. We first query the buffer length with `engine.editor.getBufferLength()` to determine how much data is available.
```typescript highlight-read-buffer
// Demonstrate reading buffer data back
const readData = engine.editor.getBufferData(bufferUri, 0, 100);
console.log('First 100 bytes of buffer data:', readData);
```
This returns a `Uint8Array` that you can convert back to other typed arrays as needed. Partial reads are supported—you can read any range within the buffer bounds.
## Resizing Buffers
You can change a buffer's size at any time with `engine.editor.setBufferLength()`. Increasing the size allocates additional space, while decreasing it truncates the data. Here we demonstrate resizing with a separate demo buffer to avoid truncating our audio data.
```typescript highlight-resize-buffer
// Demonstrate resizing a buffer with a separate demo buffer
const demoBuffer = engine.editor.createBuffer();
engine.editor.setBufferData(
demoBuffer,
0,
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
);
const demoLength = engine.editor.getBufferLength(demoBuffer);
console.log('Demo buffer length before resize:', demoLength);
engine.editor.setBufferLength(demoBuffer, demoLength / 2);
console.log(
'Demo buffer length after resize:',
engine.editor.getBufferLength(demoBuffer)
);
engine.editor.destroyBuffer(demoBuffer);
```
Keep in mind that truncating a buffer permanently discards data beyond the new length. Always query the current length first if you need to preserve the original size, or create a copy before resizing.
## Assigning Buffers to Blocks
Buffer URIs work like any other resource URI in CE.SDK. We assign them to block properties using `engine.block.setString()`. For audio blocks, we set the `audio/fileURI` property.
```typescript highlight-create-audio-block
// Create an audio block
const audioBlock = engine.block.create('audio');
// Assign the buffer URI to the audio block
engine.block.setString(audioBlock, 'audio/fileURI', bufferUri);
// Set audio duration to match the generated samples
engine.block.setDuration(audioBlock, duration);
// Append the audio block to the page
engine.block.appendChild(page, audioBlock);
```
The same approach works for other resource properties:
- **Audio blocks**: `audio/fileURI`
- **Image fills**: `fill/image/imageFileURI`
- **Video fills**: `fill/video/fileURI`
Any property that accepts a URI can reference a buffer.
## Transient Resources and Scene Serialization
Buffers are transient resources—the URI gets serialized when you save a scene, but the actual binary data does not persist. This means a saved scene will contain references to `buffer://` URIs that won't resolve when the scene is loaded again.
We use `engine.editor.findAllTransientResources()` to discover all transient resources in the current scene, including buffers. Each resource includes its URL and size in bytes.
```typescript highlight-find-transient
// Find all transient resources (including our buffer)
const transientResources = engine.editor.findAllTransientResources();
console.log('Transient resources in scene:');
for (const resource of transientResources) {
console.log(` URL: ${resource.URL}, Size: ${resource.size} bytes`);
}
```
> **Note:** **Limitations**Buffers are intended for temporary data only.* Buffer data is not part of scene serialization
> * Changes to buffers can't be undone using the history system
Note that `engine.scene.saveToString()` does NOT include `buffer://` in its default allowed resource schemes, while `engine.block.saveToString()` does include it. You may need to configure the allowed schemes depending on your serialization needs.
## Persisting Buffer Data
To permanently save buffer content, you must extract the data, save it to persistent storage, then update the block references to point to the new URL. This example demonstrates the pattern by saving to a local file and using `relocateResource()` to update references—in production, you would upload to a CDN or cloud storage and use the CDN URL instead.
```typescript highlight-persist-buffer
// Demonstrate persisting buffer data to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const bufferData = engine.editor.getBufferData(bufferUri, 0, bufferLength);
const outputPath = `${outputDir}/generated-audio.wav`;
writeFileSync(outputPath, Buffer.from(bufferData));
console.log('Buffer data saved to:', outputPath);
// Update all references from buffer:// to the file path
// In production, use a CDN URL instead of a local file path
const absolutePath = `file://${process.cwd()}/${outputPath}`;
engine.editor.relocateResource(bufferUri, absolutePath);
console.log('Buffer relocated to:', absolutePath);
```
We read the buffer data, write it to a local file, then use `engine.editor.relocateResource()` to update all references to the old buffer URI throughout the scene. After relocation, you can save the scene and the new persistent URLs will be serialized.
## Troubleshooting
**Buffer data not appearing in exported scene**
Buffers are transient and don't persist with scene saves. Use `findAllTransientResources()` to identify buffers, then relocate them to persistent storage before exporting.
**Memory usage growing unexpectedly**
Call `engine.editor.destroyBuffer()` when buffers are no longer needed. Unlike external resources that can be garbage collected, buffers remain in memory until explicitly destroyed.
**Data corruption when writing**
Ensure the offset plus data length doesn't exceed the intended buffer bounds. Resize the buffer first with `setBufferLength()` if you need more space.
**Buffer URI not recognized by block**
Verify the buffer was created in the same engine instance. Buffer URIs are not portable between different engine instances or sessions.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Design Units"
description: "Configure design units (pixels, millimeters, inches) and DPI settings for print-ready output in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/design-units-cc6597/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Design Units](https://img.ly/docs/cesdk/node-native/concepts/design-units-cc6597/)
---
Control measurement systems for precise physical dimensions—create print-ready
documents with millimeter or inch units and configurable DPI for export quality.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-design-units-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-design-units-server-js)
Design units determine the coordinate system for all layout values in CE.SDK—positions, sizes, and margins. The engine supports three unit types: **Pixel** for screen-based designs, **Millimeter** for metric print dimensions, and **Inch** for imperial print formats.
```typescript file=@cesdk_web_examples/guides-concepts-design-units-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
// Load environment variables from .env file
config();
/**
* CE.SDK Server Example: 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
*/
async function main(): Promise {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a new scene
const scene = engine.scene.create();
// 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
// Create a page and set A4 dimensions (210 x 297 mm)
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// 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 completed. Scene configured for A4 print output.'
);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers how to get and set design units, configure DPI for export quality, and set up scenes for specific physical dimensions like A4 paper.
## Understanding Design Units
### Supported Unit Types
CE.SDK supports three design unit types, each suited for different output scenarios:
- **Pixel** — Default unit, ideal for screen-based designs, web graphics, and video content. One unit equals one pixel in the design coordinate space.
- **Millimeter** — For print designs targeting metric dimensions (A4, A5, business cards). One unit equals one millimeter at the scene's DPI setting.
- **Inch** — For print designs targeting imperial dimensions (letter, legal, US business cards). One unit equals one inch at the scene's DPI setting.
### Design Unit and DPI Relationship
DPI (dots per inch) determines how physical units convert to pixels during export. At 300 DPI, a 1-inch block exports as 300 pixels wide. Higher DPI values produce higher-resolution exports suitable for professional printing.
For pixel-based scenes, DPI primarily affects font size conversions since font sizes are always specified in points.
## Getting the Current Design Unit
Use `engine.scene.getDesignUnit()` to retrieve the current scene's design unit. This returns one of three values: `'Pixel'`, `'Millimeter'`, or `'Inch'`.
```typescript highlight-get-design-unit
// Create a new scene
const scene = engine.scene.create();
// Get the current design unit
const currentUnit = engine.scene.getDesignUnit();
console.log('Current design unit:', currentUnit); // 'Pixel' by default
```
## Setting the Design Unit
Use `engine.scene.setDesignUnit()` to change the measurement system. When you change the design unit, CE.SDK automatically converts existing layout values to maintain visual appearance.
```typescript highlight-set-design-unit
// Set design unit to Millimeter for print workflow
engine.scene.setDesignUnit('Millimeter');
// Verify the change
const newUnit = engine.scene.getDesignUnit();
console.log('Design unit changed to:', newUnit); // 'Millimeter'
```
## Configuring DPI
Access DPI through the scene's `scene/dpi` property. For print workflows, 300 DPI is the standard for high-quality output.
```typescript highlight-configure-dpi
// Set DPI to 300 for print-quality exports
// Higher DPI produces higher resolution output
engine.block.setFloat(scene, 'scene/dpi', 300);
// Verify the DPI setting
const dpi = engine.block.getFloat(scene, 'scene/dpi');
console.log('DPI set to:', dpi); // 300
```
DPI affects different aspects depending on the design unit:
- **Physical units (mm, in)**: DPI determines the pixel resolution of exported files
- **Pixel units**: DPI only affects the conversion of font sizes from points to pixels
## Setting Up Print-Ready Designs
For print workflows, combine `setDesignUnit()` with appropriate DPI and page dimensions. Here's how to set up an A4 document ready for print export:
```typescript highlight-set-page-dimensions
// Create a page and set A4 dimensions (210 x 297 mm)
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// 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`);
```
## Font Sizes and Design Units
Font sizes are always specified in points (`pt`), regardless of the scene's design unit. The DPI setting affects how points convert to pixels for rendering.
```typescript highlight-create-text-block
// Create a text block with millimeter dimensions
const textBlock = engine.block.create('text');
engine.block.appendChild(page, textBlock);
// Position text at 20mm from left, 30mm from top
engine.block.setPositionX(textBlock, 20);
engine.block.setPositionY(textBlock, 30);
// Set text block size to 170mm x 50mm
engine.block.setWidth(textBlock, 170);
engine.block.setHeight(textBlock, 50);
// Add content to the text block
engine.block.setString(
textBlock,
'text/text',
'This A4 document uses millimeter units with 300 DPI for print-ready output.'
);
```
When DPI changes, text blocks automatically adjust their rendered size to maintain visual consistency.
## Understanding Export Resolution
The relationship between design units and export resolution is important for print workflows:
```typescript highlight-compare-units
// Demonstrate unit comparison
// At 300 DPI: 1 inch = 300 pixels, 1 mm = ~11.81 pixels
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'
);
```
At 300 DPI:
- An A4 page (210 × 297 mm) exports as 2480 × 3508 pixels
- A letter page (8.5 × 11 in) exports as 2550 × 3300 pixels
## Troubleshooting
### Exported Dimensions Don't Match Expected Size
Verify that DPI is set correctly for physical units. At 300 DPI, 1 inch becomes 300 pixels. Check that your design unit matches your target output format.
### Text Appears Wrong Size After Unit Change
Font sizes in points auto-adjust based on DPI. If text looks incorrect, verify the DPI setting matches your workflow requirements.
### Blocks Shift Position After Changing Units
CE.SDK preserves visual appearance during unit conversion. If positions seem unexpected, check the original coordinate values—the numeric values change but visual positions should remain stable.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Editing Workflow"
description: "Control editing access with Creator, Adopter, Viewer, and Presenter roles using global and block-level scopes for tailored permissions."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/editing-workflow-032d27/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Editing Workflow](https://img.ly/docs/cesdk/node-native/concepts/editing-workflow-032d27/)
---
CE.SDK controls editing access through roles and scopes, enabling template workflows where designers create locked layouts and end-users customize only permitted elements.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/github/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.
```typescript file=@cesdk_web_examples/guides-concepts-editing-workflow-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
config();
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
engine.scene.create('VerticalStack');
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'
// Global scopes: 'Allow', 'Deny', or 'Defer' (to block-level)
engine.editor.setGlobalScope('editor/select', 'Defer');
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('lifecycle/destroy', 'Defer');
// Create a block and lock it
const block = engine.block.create('//ly.img.ubq/graphic');
engine.block.setShape(
block,
engine.block.createShape('//ly.img.ubq/shape/rect')
);
engine.block.setWidth(block, 200);
engine.block.setHeight(block, 100);
engine.block.appendChild(page, block);
// Lock the block - Adopters cannot select, move, or delete it
engine.block.setScopeEnabled(block, 'editor/select', false);
engine.block.setScopeEnabled(block, 'layer/move', false);
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
// Check resolved permissions (role + global + block scopes)
const canMoveAsCreator = engine.block.isAllowedByScope(block, 'layer/move');
console.log('Creator can move:', canMoveAsCreator); // true
engine.editor.setRole('Adopter');
const canMoveAsAdopter = engine.block.isAllowedByScope(block, 'layer/move');
console.log('Adopter can move:', canMoveAsAdopter); // false
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers:
- The four user roles and their purposes
- How scopes control editing capabilities
- The permission resolution hierarchy
- Common template workflow patterns
## Roles
Roles define user types with different default permissions:
| Role | Purpose | Default Access |
|------|---------|----------------|
| **Creator** | Designers building templates | Full access to all operations |
| **Adopter** | End-users customizing templates | Limited by block-level scopes |
| **Viewer** | Preview-only users | Read-only access |
| **Presenter** | Slideshow/video presenters | Read-only with playback controls |
Creators set the block-level scopes that constrain what Adopters can do. This separation enables brand consistency while allowing personalization.
```typescript highlight-roles
// Roles define user types: 'Creator', 'Adopter', 'Viewer', 'Presenter'
const role = engine.editor.getRole();
console.log('Current role:', role); // 'Creator'
```
## Scopes
Scopes define specific capabilities organized into categories:
- **Text**: Editing content and character formatting
- **Fill/Stroke**: Changing colors and shapes
- **Layer**: Moving, resizing, rotating, cropping
- **Appearance**: Filters, effects, shadows, animations
- **Lifecycle**: Deleting and duplicating elements
- **Editor**: Adding new elements and selecting
## Global vs Block-Level Scopes
**Global scopes** apply editor-wide and determine whether block-level settings are checked:
- `'Allow'` — Always permit the operation
- `'Deny'` — Always block the operation
- `'Defer'` — Check block-level scope settings
**Block-level scopes** control permissions on individual blocks. These settings only take effect when the corresponding global scope is set to `'Defer'`.
```typescript highlight-global-scopes
// Global scopes: 'Allow', 'Deny', or 'Defer' (to block-level)
engine.editor.setGlobalScope('editor/select', 'Defer');
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('lifecycle/destroy', 'Defer');
```
To lock a specific block, disable its scopes:
```typescript highlight-block-scopes
// Lock the block - Adopters cannot select, move, or delete it
engine.block.setScopeEnabled(block, 'editor/select', false);
engine.block.setScopeEnabled(block, 'layer/move', false);
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
```
## Permission Resolution
Permissions resolve in this order:
1. **Role defaults** — Each role has preset global scope values
2. **Global scope** — If `'Allow'` or `'Deny'`, this is the final answer
3. **Block-level scope** — If global is `'Defer'`, check the block's settings
Use `isAllowedByScope()` to check the final computed permission for any block and scope combination:
```typescript highlight-check-permissions
// Check resolved permissions (role + global + block scopes)
const canMoveAsCreator = engine.block.isAllowedByScope(block, 'layer/move');
console.log('Creator can move:', canMoveAsCreator); // true
engine.editor.setRole('Adopter');
const canMoveAsAdopter = engine.block.isAllowedByScope(block, 'layer/move');
console.log('Adopter can move:', canMoveAsAdopter); // false
```
## Template Workflow Pattern
A typical template workflow:
1. **Designer (Creator)** creates the template layout
2. **Designer** locks brand elements using block scopes
3. **Designer** keeps personalization fields editable
4. **End-user (Adopter)** opens the template
5. **End-user** edits only permitted elements
6. **End-user** exports the personalized result
This pattern ensures brand consistency while enabling personalization.
## Implementation Guides
For detailed implementation, see these guides:
[Lock Design Elements](https://img.ly/docs/cesdk/node-native/create-templates/lock-131489/) — Step-by-step instructions for locking specific elements in templates
[Set Editing Constraints](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) — Configure which properties users can modify
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Events"
description: "Subscribe to block creation, update, and deletion events to track changes in your CE.SDK scene."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/events-353f97/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Events](https://img.ly/docs/cesdk/node-native/concepts/events-353f97/)
---
Monitor and react to block changes in real time by subscribing to creation,
update, and destruction events in your CE.SDK scene.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-events-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-events-server-js)
Events enable real-time monitoring of block changes in CE.SDK. When blocks are created, modified, or destroyed, the engine delivers these changes through callback subscriptions at the end of each update cycle. This push-based notification system eliminates the need for polling and enables efficient reactive architectures.
```typescript file=@cesdk_web_examples/guides-concepts-events-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
// Load environment variables from .env file
config();
/**
* CE.SDK Server Example: 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
*/
async function main(): Promise {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// 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}`
);
}
});
// Create a new scene and page
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// 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');
console.log('Events guide completed successfully.');
console.log(
'Demonstrated: subscribing, event types, processing, and cleanup.'
);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers subscribing to block lifecycle events, processing the three event types (`Created`, `Updated`, `Destroyed`), filtering events to specific blocks, understanding batching and deduplication behavior, and properly cleaning up subscriptions.
## Event Types
CE.SDK provides three event types that capture the block lifecycle:
- **`Created`**: Fires when a new block is added to the scene
- **`Updated`**: Fires when any property of a block changes
- **`Destroyed`**: Fires when a block is removed from the scene
Each event contains a `block` property with the block ID and a `type` property indicating which event occurred.
## Subscribing to All Blocks
Use `engine.event.subscribe()` to register a callback that receives batched events. Pass an empty array to receive events from all blocks in the scene:
```typescript highlight-subscribe-all
// Subscribe to events from all blocks in the scene
// Pass an empty array to receive events from every block
const unsubscribeAll = engine.event.subscribe([], (events) => {
for (const event of events) {
console.log(
`[All Blocks] ${event.type} event for block ${event.block}`
);
}
});
```
The callback receives an array of events at the end of each engine update cycle. The function returns an unsubscribe function you should store for cleanup.
## Subscribing to Specific Blocks
For better performance when you only care about certain blocks, pass an array of block IDs to filter events:
```typescript highlight-subscribe-specific
// Subscribe to events for specific blocks only
// This is more efficient when you only care about certain blocks
const unsubscribeSpecific = engine.event.subscribe([graphic], (events) => {
for (const event of events) {
console.log(
`[Specific Block] ${event.type} event for block ${event.block}`
);
}
});
```
This reduces overhead since the engine only needs to prepare events for the blocks you're tracking.
## Creating Blocks and Handling `Created` Events
When you create a block, the engine fires a `Created` event. You can safely use Block API methods on the block ID since the block is valid:
```typescript highlight-event-created
// Create a graphic block - this triggers a Created event
const graphic = engine.block.create('graphic');
// Set up the graphic with a shape and fill
const rectShape = engine.block.createShape('rect');
engine.block.setShape(graphic, rectShape);
// Position and size the graphic
engine.block.setPositionX(graphic, 200);
engine.block.setPositionY(graphic, 150);
engine.block.setWidth(graphic, 400);
engine.block.setHeight(graphic, 300);
// Add an image fill
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(graphic, imageFill);
engine.block.setEnum(graphic, 'contentFill/mode', 'Cover');
// Append to page to make it visible
engine.block.appendChild(page, graphic);
console.log('Created graphic block:', graphic);
```
Use `Created` events to initialize tracking, update UI state, or set up additional subscriptions for the new block.
## Updating Blocks and Handling `Updated` Events
Modifying any property of a block triggers an `Updated` event. Due to deduplication, you receive at most one `Updated` event per block per engine update cycle, regardless of how many properties changed:
```typescript highlight-event-updated
// Modify the block - this triggers Updated events
// Due to deduplication, multiple rapid changes result in one Updated event
engine.block.setRotation(graphic, 0.1); // Rotate slightly
engine.block.setFloat(graphic, 'opacity', 0.9); // Adjust opacity
console.log('Modified graphic block - rotation and opacity changed');
```
Multiple rapid changes to the same block result in a single `Updated` event, making event handling efficient even during complex operations.
## Processing Events by Type
Handle each event type appropriately by checking the `type` property. For `Created` and `Updated` events, you can safely use Block API methods. For `Destroyed` events, the block ID is no longer valid:
```typescript highlight-process-events
// Process events by checking the type property
const unsubscribeProcess = engine.event.subscribe([], (events) => {
for (const event of events) {
switch (event.type) {
case 'Created': {
// Block was just created - safe to use Block API
const blockType = engine.block.getType(event.block);
console.log(`Block created with type: ${blockType}`);
break;
}
case 'Updated': {
// Block property changed - safe to use Block API
console.log(`Block ${event.block} was updated`);
break;
}
case 'Destroyed': {
// Block was destroyed - must check validity before using Block API
const isValid = engine.block.isValid(event.block);
console.log(
`Block ${event.block} destroyed, still valid: ${isValid}`
);
break;
}
}
}
});
```
## Handling `Destroyed` Events Safely
When a block is destroyed, the block ID becomes invalid. Calling Block API methods on a destroyed block throws an exception. Always check validity with `engine.block.isValid()` before operations:
```typescript highlight-destroyed-safety
// When handling Destroyed events, always check block validity
// The block ID is no longer valid after destruction
const unsubscribeDestroyed = engine.event.subscribe([], (events) => {
for (const event of events) {
if (event.type === 'Destroyed') {
// IMPORTANT: Check validity before any Block API calls
if (engine.block.isValid(event.block)) {
// Block is still valid (this shouldn't happen for Destroyed events)
console.log('Block is unexpectedly still valid');
} else {
// Block is invalid - expected for Destroyed events
// Clean up any references to this block ID
console.log(
`Block ${event.block} has been destroyed and is invalid`
);
}
}
}
});
```
After verifying the block is invalid, you can safely clean up any local references. The destroy operation itself triggers the `Destroyed` event:
```typescript highlight-event-destroyed
// Destroy the text block - this triggers a Destroyed event
engine.block.destroy(textBlock);
console.log('Destroyed text block');
// After destruction, the block ID is no longer valid
const isTextBlockValid = engine.block.isValid(textBlock);
console.log('Text block still valid after destroy:', isTextBlockValid); // false
```
Use `isValid()` to clean up any references to destroyed blocks in your application state.
## Unsubscribing from Events
The `subscribe()` method returns an unsubscribe function. Call it when you no longer need events to prevent memory leaks and reduce engine overhead:
```typescript highlight-unsubscribe
// Clean up subscriptions when no longer needed
// This prevents memory leaks and reduces engine overhead
unsubscribeAll();
unsubscribeSpecific();
unsubscribeProcess();
unsubscribeDestroyed();
console.log('Unsubscribed from all event listeners');
```
Always unsubscribe when your component unmounts, the editor closes, or you no longer need to track changes. Keeping unnecessary subscriptions active forces the engine to prepare event lists for each subscriber at every update.
## Event Batching and Deduplication
Events are collected during an engine update and delivered together at the end. The engine deduplicates events, so you receive at most one `Updated` event per block per update cycle. Event order in the callback does not reflect the actual order of changes within the update.
This batching behavior means:
- Multiple property changes to a single block result in one `Updated` event
- You cannot determine which specific property changed from the event alone
- If you need to track specific property changes, compare against cached values
## Use Cases
Events support various reactive patterns in CE.SDK applications:
- **Syncing external state**: Keep state management systems (Redux, MobX, Zustand) synchronized with scene changes
- **Building reactive UIs**: Update UI components when blocks change without polling
- **Tracking changes for undo/redo**: Monitor all block changes for custom history implementations
- **Validating scene constraints**: React to block creation or property changes to enforce design rules
## Troubleshooting
**Events not firing**: Ensure you haven't unsubscribed prematurely. Verify the blocks you're filtering to still exist.
**Exception on `Destroyed` event**: Never call Block API methods on a destroyed block without first checking `engine.block.isValid()`.
**Missing events**: Events are deduplicated—multiple rapid changes to the same property result in one `Updated` event.
**Memory leaks**: Store the unsubscribe function and call it during cleanup. Forgetting to unsubscribe keeps listeners active.
**Event order confusion**: Don't rely on event array order within a single callback—it doesn't reflect chronological order of changes.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Headless"
description: "Run CE.SDK programmatically without any user interface (UI) using native Node.js bindings."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/headless-mode-24ab98/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Headless Mode](https://img.ly/docs/cesdk/node-native/concepts/headless-mode-24ab98/)
---
CreativeEditor SDK (CE.SDK) **Headless Mode with native Node.js bindings**
lets your backend render scenes and exports with native C++ performance.
The `@cesdk/node-native` package provides CE.SDK's full feature set through native C++ bindings, delivering better performance than the WASM-based `@cesdk/node` package.
You orchestrate the same actions a user can do from the UI using the CreativeEngine, which powers all CE.SDK features.
## Common Use Cases
- **Automated pipelines:** Generate batches of personalized assets from templates.
- **Server-rendered previews:** Produce thumbnails or proofs before a user ever opens the editor.
- **Custom tooling:** Embed CE.SDK logic inside CLI tools, workers, or queue processors.
- **Compliance and governance:** Enforce template rules during backend render jobs without exposing editing controls.
### When to Use Headless Mode
| Scenario | Headless (Node.js Native) | UI-Based Editor |
| ---------------------------------- | :-----------------------: | :-------------: |
| Cron or queue-driven exports | Yes | No |
| Rendering without a browser | Yes | No |
| Letting users tweak the layout | No | Yes |
| Mixing custom UI with CE.SDK tools | Yes | Hybrid |
## How Headless Mode Works with Native Bindings
`@cesdk/node-native` provides the same Engine API as `@cesdk/node`. Without needing a DOM, your script can run server-side to:
- Start the engine.
- Edit and export assets.
You are responsible for:
- Handling file Input/Output.
- Monitoring memory.
- Disposing of the engine when the job ends.
### Available Features
- Create/edit scenes, pages, and blocks programmatically.
- Export compositions (complete edited scenes) in your desired format.
### Requirements
- **Node.js v20+** (or the matching runtime for your serverless provider).
- **@cesdk/node-native** installed locally: `npm install @cesdk/node-native`.
- A valid **CE.SDK license key**.
- A supported platform: **macOS** (ARM or x64) or **Linux** (x64).
> **Tip:** For production, manage your CE.SDK license key using environment variables
> (e.g. `dotenv`) rather than hard-coding it in your scripts.
## Quick Start: Initialize the Engine
To try out the CE.SDK headless mode with native bindings:
1. Open/create a Node.js project.
2. Install the native Node.js package:
```bash
npm install @cesdk/node-native
```
### 1. Create a CE.SDK Helper
Create a reusable script that initializes the engine:
```js title="CesdkHelper.js"
import CreativeEngine from '@cesdk/node-native';
// CE.SDK config
const defaultConfig = {
license: process.env.LICENSE_KEY ?? '',
};
let engine;
// Export the CE.SDK initialization to reuse it.
export async function getCreativeEngine(config = defaultConfig) {
if (!engine) {
engine = await CreativeEngine.init(config);
}
return engine;
}
export function disposeCreativeEngine() {
if (!engine) {
return;
}
engine.dispose();
engine = undefined;
}
export function getCreativeEngineConfig() {
return defaultConfig;
}
```
This pattern is ideal for:
- HTTP handlers
- Queue workers
Each invocation:
1. **Calls** `getCreativeEngine()`.
2. **Reuses** the same instance.
3. **Disposes** of the engine when your process shuts down to release native resources.
> **Note:** * Replace `` with a **valid license key**.
> * Use `dotenv/config` to store your CE.SDK license key in a `.env` file to **avoid hard-coding** it in your scripts.
### 2. Reuse the Engine in a Script
You can now script actions using CE.SDK. In the following example, the script assembles a greeting card entirely on the server.
In your Node.js project:
1. Create a new script.
2. Import your helper along with the needed feature:
```js title="GreetingCard.js"
import { writeFile } from 'node:fs/promises';
import { disposeCreativeEngine, getCreativeEngine } from './CesdkHelper.js';
```
3. Copy-paste the following function:
```js title="GreetingCard.js"
async function buildGreeting() {
// Get the engine
const engine = await getCreativeEngine();
try {
// Create a scene and append it to the 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 from a sample URL
const imageBlock = engine.block.create('graphic');
engine.block.setShape(imageBlock, engine.block.createShape('rect'));
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg',
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setPosition(imageBlock, 100, 100);
engine.block.setWidth(imageBlock, 300);
engine.block.setHeight(imageBlock, 300);
engine.block.appendChild(page, imageBlock);
// Create a text block
const textBlock = engine.block.create('text');
engine.block.setString(textBlock, 'text/text', 'Hello from Headless Mode!');
engine.block.setPosition(textBlock, 100, 450);
engine.block.setWidth(textBlock, 600);
engine.block.appendChild(page, textBlock);
// Export the page as PNG
const pngBuffer = await engine.block.export(page, 'image/png');
// Write the file on disk
await writeFile('greeting-card.png', Buffer.from(pngBuffer));
console.log('Export complete: greeting-card.png');
} catch (error) {
console.error('Failed to export headless scene', error);
} finally {
// Dispose the Engine to free resources
disposeCreativeEngine();
}
}
buildGreeting().catch(error => {
console.error('Unexpected failure', error);
process.exitCode = 1;
});
```
4. Run the script `node .//GreetingCard.js`.
5. Check that your project contains the `greeting-card.png` file.
**The preceding script:**
- Calls the `CesdkHelper.js` helper to start the engine.
- Builds a scene containing:
- An image
- A text block.
- Exports the page to a PNG `Uint8Array`.
- Writes the image to the disk.
- Disposes of the engine.
## Go Further
- [Dig into the Node.js (Native) SDK reference](https://img.ly/docs/cesdk/node-native/what-is-cesdk-2e7acd/)
for every available Engine call.
- [Start a new Node.js (Native) headless project](https://img.ly/docs/cesdk/node-native/get-started/vanilla-nn1a2b/)
with a complete project template.
- Need to mix headless logic with UI-driven editing? [Engine interface guides](https://img.ly/docs/cesdk/node-native/engine-interface-6fb7cf/) show hybrid patterns.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Pages"
description: "Pages structure scenes in CE.SDK and must share the same dimensions to ensure consistent rendering."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/pages-7b6bae/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Pages](https://img.ly/docs/cesdk/node-native/concepts/pages-7b6bae/)
---
Pages define the format of your designs—every graphic block, text element, and media file lives inside a page. This guide covers how pages fit into the scene hierarchy, their properties like margins and title templates, and how to configure page dimensions for different layout modes.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/github/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.
```typescript file=@cesdk_web_examples/guides-concepts-pages-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
config(); // Load .env file
/**
* CE.SDK Server Example: 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
*/
async function main(): Promise {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// 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);
// Alternative: Find pages using block API
const pagesByType = engine.block.findByType('page');
console.log('Pages found by type:', pagesByType);
// Export each page to demonstrate the result
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
for (let i = 0; i < allPages.length; i++) {
const page = allPages[i];
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
const outputPath = `${outputDir}/page-${i + 1}.png`;
writeFileSync(outputPath, buffer);
console.log(`Exported: ${outputPath}`);
}
console.log('Pages guide completed with a 2-page design.');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers:
- Understanding the scene hierarchy: Scene → Pages → Blocks
- Creating and managing multiple pages
- Setting page dimensions at the scene level
- Configuring page properties like margins and title templates
- Navigating between pages programmatically
## Pages in the Scene Hierarchy
In CE.SDK, content follows a strict hierarchy: a **scene** contains **pages**, and pages contain **content blocks**. Only blocks attached to a page are rendered on the canvas.
```typescript highlight=highlight-create-scene
// Create a scene with VerticalStack layout for multi-page designs
engine.scene.create('VerticalStack');
// Get the stack container to configure spacing
const [stack] = engine.block.findByType('stack');
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
```
When you create a scene with a layout mode like `VerticalStack`, pages are automatically arranged according to that mode. Create pages using `engine.block.create('page')`, set their dimensions with `setWidth()` and `setHeight()`, then attach them to the scene (or its stack container) with `engine.block.appendChild()`.
```typescript highlight=highlight-create-pages
// Create the first page and set its dimensions
const firstPage = engine.block.create('page');
engine.block.setWidth(firstPage, 800);
engine.block.setHeight(firstPage, 600);
engine.block.appendChild(stack, firstPage);
// Create the second page with the same dimensions
const secondPage = engine.block.create('page');
engine.block.setWidth(secondPage, 800);
engine.block.setHeight(secondPage, 600);
engine.block.appendChild(stack, secondPage);
```
Content blocks must be added as children of a page to render. For graphic blocks, set both a shape and a fill for content to display. Append blocks to the page before configuring their properties.
```typescript highlight=highlight-add-content
// Add an image block to the first page
const imageBlock = engine.block.create('graphic');
engine.block.appendChild(firstPage, imageBlock);
// Create a rect shape for the graphic block
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
// Configure size and position after appending to the page
engine.block.setWidth(imageBlock, 400);
engine.block.setHeight(imageBlock, 300);
engine.block.setPositionX(imageBlock, 200);
engine.block.setPositionY(imageBlock, 150);
// Create and configure the image fill
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
// Add a text block to the second page
const textBlock = engine.block.create('text');
engine.block.appendChild(secondPage, textBlock);
// Configure text properties after appending to the page
engine.block.replaceText(textBlock, 'Page 2');
engine.block.setTextFontSize(textBlock, 48);
engine.block.setTextColor(textBlock, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 });
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
// Center the text on the page
const textWidth = engine.block.getFrameWidth(textBlock);
const textHeight = engine.block.getFrameHeight(textBlock);
engine.block.setPositionX(textBlock, (800 - textWidth) / 2);
engine.block.setPositionY(textBlock, (600 - textHeight) / 2);
```
## Page Dimensions and Consistency
The CE.SDK engine supports pages with different dimensions. When using stacked layout modes (VerticalStack, HorizontalStack), the Editor UI expects all pages to share the same size. However, with the `Free` layout mode, you can set different dimensions for each page in the UI.
```typescript highlight=highlight-set-dimensions
// Get the scene to set page dimensions
const scene = engine.scene.get();
if (scene === null) {
throw new Error('No scene available');
}
// Set page dimensions at the scene level (all pages share these dimensions)
engine.block.setFloat(scene, 'scene/pageDimensions/width', 800);
engine.block.setFloat(scene, 'scene/pageDimensions/height', 600);
```
You can set default page dimensions at the scene level using `engine.block.setFloat()` with `scene/pageDimensions/width` and `scene/pageDimensions/height`. The `scene/aspectRatioLock` property controls whether changing one dimension automatically adjusts the other. Individual pages can also have their dimensions set directly with `setWidth()` and `setHeight()`.
## Finding and Navigating Pages
CE.SDK provides several methods to locate and navigate between pages in your scene.
```typescript highlight=highlight-find-pages
// Demonstrate finding pages
const allPages = engine.scene.getPages();
console.log('All pages:', allPages);
console.log('Number of pages:', allPages.length);
// Alternative: Find pages using block API
const pagesByType = engine.block.findByType('page');
console.log('Pages found by type:', pagesByType);
```
Use these methods based on your needs:
- `engine.scene.getPages()` returns all pages in sorted order
- `engine.block.findByType('page')` finds all page blocks in the scene
## Page Properties
Each page has its own properties that control its appearance and behavior. These are set on the page block itself, not on the scene.
### Margins
Page margins define bleed areas useful for print designs. Enable margins and configure each side individually:
```typescript highlight=highlight-page-properties
// Configure page properties on the first page
// Enable and set margins for print bleed
engine.block.setBool(firstPage, 'page/marginEnabled', true);
engine.block.setFloat(firstPage, 'page/margin/top', 10);
engine.block.setFloat(firstPage, 'page/margin/bottom', 10);
engine.block.setFloat(firstPage, 'page/margin/left', 10);
engine.block.setFloat(firstPage, 'page/margin/right', 10);
// Set a custom title template for the first page
engine.block.setString(firstPage, 'page/titleTemplate', 'Cover');
// Set a custom title template for the second page
engine.block.setString(secondPage, 'page/titleTemplate', 'Content');
```
Set `page/marginEnabled` to `true` to enable margins, then use `page/margin/top`, `page/margin/bottom`, `page/margin/left`, and `page/margin/right` to configure each side.
### Title Template
The `page/titleTemplate` property defines the display label shown for each page. It supports template variables like `{{ubq.page_index}}` for dynamic numbering.
The default value is `"Page {{ubq.page_index}}"`. You can customize this to show labels like "Slide 1", "Cover", or any custom text.
### Fill and Background
Pages support fills for background colors or images using the standard fill system.
```typescript highlight=highlight-page-background
// Set a background fill on the second page
const colorFill = engine.block.createFill('color');
engine.block.setColor(colorFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 1.0,
a: 1.0
});
engine.block.setFill(secondPage, colorFill);
```
Create a fill using `engine.block.createFill('color')` or `engine.block.createFill('image')`, configure its properties, then apply it to the page with `engine.block.setFill(page, fill)`.
## Page Layout Modes
The scene's layout mode controls how multiple pages are arranged. Set this using `engine.block.setEnum()` on the scene with the `scene/layout` property:
- **VerticalStack** (default): Pages stack vertically, one below the other
- **HorizontalStack**: Pages arrange horizontally, side by side
- **DepthStack**: Pages overlay each other, typically used for video editing
- **Free**: Pages can be positioned freely without automatic arrangement
## Pages for Static Designs vs. Video Editing
Page behavior varies depending on how the scene is used.
### Static Designs
For static designs, pages act like artboards. Each page is a separate canvas ideal for multi-page documents, social media posts, or print layouts. Pages exist side by side and don't have time-based properties.
### Video Editing
For video editing, pages represent time-based compositions that transition sequentially during playback. Each page has playback properties:
- `playback/duration` controls how long the page appears (in seconds)
- `playback/time` tracks the current playback position
## Cleanup
In server environments, always dispose the engine when finished to free resources.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Troubleshooting
### Content Not Visible
If content blocks aren't appearing, check these common causes:
- Verify the block is attached to a page with `engine.block.appendChild(page, block)`
- For graphic blocks, ensure both a shape and fill are set
- Append blocks to the page before setting their size and position
### Dimension Inconsistencies
If pages appear at unexpected sizes when using stacked layouts, ensure all pages have consistent dimensions. With `Free` layout mode, pages can have different sizes. Set dimensions on individual pages using `setWidth()` and `setHeight()`.
### Page Not Found
If `engine.scene.getPages()` returns an empty array, ensure a scene is loaded first. In headless mode, you must create both the scene and pages manually before querying them.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Resources"
description: "Learn how CE.SDK loads and manages external media files, including preloading for performance, handling transient data, and relocating resources when URLs change."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/resources-a58d71/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Resources](https://img.ly/docs/cesdk/node-native/concepts/resources-a58d71/)
---
Manage external media files—images, videos, audio, and fonts—that blocks
reference via URIs in CE.SDK.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-resources-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-resources-server-js)
Resources are external media files that blocks reference through URI properties like `fill/image/imageFileURI` or `fill/video/fileURI`. CE.SDK loads resources automatically when needed, but you can preload them for better performance. When working with temporary data like buffers or blobs, you need to persist them before saving. If resource URLs change (such as during CDN migration), you can update the mappings without modifying scene data.
```typescript file=@cesdk_web_examples/guides-concepts-resources-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
// Load environment variables from .env file
config();
/**
* CE.SDK Server Example: 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
*/
async function main(): Promise {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a new scene and page
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// 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 completed successfully.');
console.log(
'Demonstrated: on-demand loading, preloading, transient resources, and relocation.'
);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers on-demand and preloaded resource loading, identifying and persisting transient resources, relocating resources when URLs change, and discovering all media URIs in a scene.
## On-Demand Loading
The engine fetches resources automatically when rendering blocks or preparing exports. This approach requires no extra code but may delay the initial render while resources download.
```typescript highlight-on-demand-loading
// Create a new scene and page
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// Layout configuration: two blocks with equal margins
const margin = 30;
const gap = 20;
const blockWidth = 300;
const blockHeight = 200;
// Set page dimensions to hug the blocks
const pageWidth = margin + blockWidth + gap + blockWidth + margin;
const pageHeight = margin + blockHeight + margin;
engine.block.setWidth(page, pageWidth);
engine.block.setHeight(page, pageHeight);
// Create a graphic block with an image fill
// Resources are loaded on-demand when the engine renders the block
const imageBlock = engine.block.create('graphic');
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
engine.block.setPositionX(imageBlock, margin);
engine.block.setPositionY(imageBlock, margin);
engine.block.setWidth(imageBlock, blockWidth);
engine.block.setHeight(imageBlock, blockHeight);
// Create an image fill - the image loads when the block is rendered
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_4.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setEnum(imageBlock, 'contentFill/mode', 'Cover');
engine.block.appendChild(page, imageBlock);
console.log('Created image block - resource loads on-demand when rendered');
```
When you create a block with an image fill, the image doesn't load immediately. The engine fetches it when the block first renders on the canvas.
## Preloading Resources
Load resources before they're needed with `forceLoadResources()`. Pass block IDs to load resources for those blocks and their children. Preloading eliminates render delays and is useful when you want the scene fully ready before displaying it.
```typescript highlight-preload-resources
// Preload all resources in the scene before rendering
// This ensures resources are cached and ready for display
console.log('Preloading all resources in the scene...');
await engine.block.forceLoadResources([scene]);
console.log('All resources preloaded successfully');
// Preload specific blocks only (useful for optimizing load order)
await engine.block.forceLoadResources([imageBlock]);
console.log('Image block resources preloaded');
```
Pass the scene to preload all resources in the entire design, or pass specific blocks to load only what you need.
## Preloading Audio and Video
Audio and video resources require `forceLoadAVResource()` for full metadata access. The engine needs to download and parse media files before you can query properties like duration or dimensions.
```typescript highlight-preload-av
// Create a second graphic block for video
const videoBlock = engine.block.create('graphic');
const videoShape = engine.block.createShape('rect');
engine.block.setShape(videoBlock, videoShape);
engine.block.setPositionX(videoBlock, margin + blockWidth + gap);
engine.block.setPositionY(videoBlock, margin);
engine.block.setWidth(videoBlock, blockWidth);
engine.block.setHeight(videoBlock, blockHeight);
// Create a video fill
const videoFill = engine.block.createFill('video');
engine.block.setString(
videoFill,
'fill/video/fileURI',
'https://img.ly/static/ubq_video_samples/bbb.mp4'
);
engine.block.setFill(videoBlock, videoFill);
engine.block.setEnum(videoBlock, 'contentFill/mode', 'Cover');
engine.block.appendChild(page, videoBlock);
// Preload video resource to query its properties
console.log('Preloading video resource...');
await engine.block.forceLoadAVResource(videoFill);
console.log('Video resource preloaded');
// Now we can query video properties
const videoDuration = engine.block.getAVResourceTotalDuration(videoFill);
const videoWidth = engine.block.getVideoWidth(videoFill);
const videoHeight = engine.block.getVideoHeight(videoFill);
console.log(
`Video properties - Duration: ${videoDuration}s, Size: ${videoWidth}x${videoHeight}`
);
```
Without preloading, properties like `getAVResourceTotalDuration()` or `getVideoWidth()` may return zero or incomplete values.
## Finding Transient Resources
Transient resources are temporary data stored in buffers or blobs that won't survive scene serialization. Use `findAllTransientResources()` to discover them before saving.
```typescript highlight-find-transient
// Find all transient resources that need persistence before export
// Transient resources include buffers and blobs that won't survive serialization
const transientResources = engine.editor.findAllTransientResources();
console.log(`Found ${transientResources.length} transient resources`);
for (const resource of transientResources) {
console.log(
`Transient: URL=${resource.URL}, Size=${resource.size} bytes`
);
}
```
Each entry includes the resource URL and its size in bytes. Common transient resources include images from clipboard paste operations, camera captures, or programmatically generated content.
## Finding Media URIs
Get all media file URIs referenced in a scene with `findAllMediaURIs()`. This returns a deduplicated list of URIs from image fills, video fills, audio blocks, and other media sources.
```typescript highlight-find-media-uris
// Get all media URIs referenced in the scene
// Useful for pre-fetching or validating resource availability
const mediaURIs = engine.editor.findAllMediaURIs();
console.log(`Scene contains ${mediaURIs.length} media URIs:`);
for (const uri of mediaURIs) {
console.log(` - ${uri}`);
}
```
Use this for pre-fetching resources, validating availability, or building a manifest of all assets in a design.
## Detecting MIME Types
Determine a resource's content type with `getMimeType()`. The engine downloads the resource if it's not already cached.
```typescript highlight-detect-mime-type
// Detect the MIME type of a resource
// This downloads the resource if not already cached
const imageUri = 'https://img.ly/static/ubq_samples/sample_4.jpg';
const mimeType = await engine.editor.getMimeType(imageUri);
console.log(`MIME type of ${imageUri}: ${mimeType}`);
```
Common return values include `image/jpeg`, `image/png`, `video/mp4`, and `audio/mpeg`. This is useful when you need to verify resource types or make format-dependent decisions.
## Relocating Resources
Update URL mappings when resources move with `relocateResource()`. This modifies the internal cache without changing scene data.
```typescript highlight-relocate-resource
// Relocate a resource when its URL changes
// This updates the internal cache mapping without modifying scene data
const oldUrl = 'https://example.com/old-location/image.jpg';
const newUrl = 'https://cdn.example.com/new-location/image.jpg';
// In a real scenario, you would relocate after uploading to a new location:
// engine.editor.relocateResource(oldUrl, newUrl);
console.log(`Resource relocation example: ${oldUrl} -> ${newUrl}`);
console.log('Use relocateResource() after uploading to a CDN');
```
Use relocation after uploading resources to a CDN or when migrating assets between storage locations. The scene continues to reference the original URL, but the engine fetches from the new location.
## Persisting Transient Resources
Handle transient resources during save with the `onDisallowedResourceScheme` callback in `saveToString()`. The callback receives each resource URL with a disallowed scheme (like `buffer:` or `blob:`) and returns the permanent URL after uploading.
```typescript highlight-persist-transient
// When saving, use onDisallowedResourceScheme to handle transient resources
// This callback is called for each resource with a disallowed scheme (like buffer: or blob:)
const sceneString = await engine.block.saveToString(
[scene],
['http', 'https'], // Only allow http and https URLs
async (url: string) => {
// In a real app, upload the resource and return the permanent URL
// const response = await uploadToCDN(url);
// return response.permanentUrl;
// For this example, we'll just log the URL
console.log(`Would upload transient resource: ${url}`);
// Return the original URL since we're not actually uploading
return url;
}
);
console.log(`Scene saved to string (${sceneString.length} characters)`);
```
This pattern lets you intercept temporary resources, upload them to permanent storage, and save the scene with stable URLs that will work when reloaded.
## Troubleshooting
**Slow initial render**: Preload resources with `forceLoadResources()` before displaying the scene.
**Export fails with missing resources**: Check `findAllTransientResources()` and persist any temporary resources before export.
**Video duration returns 0**: Ensure the video resource is loaded with `forceLoadAVResource()` before querying properties.
**Resources not found after reload**: Transient resources (buffers, blobs) are not serialized—relocate them to persistent URLs before saving.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Scenes"
description: "Create, configure, save, and load scenes—the root container for all design elements in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/scenes-e8596d/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Scenes](https://img.ly/docs/cesdk/node-native/concepts/scenes-e8596d/)
---
Scenes are the root container for all designs in CE.SDK. They hold pages,
blocks, and configuration for how your design is structured—and the engine
manages only one active scene at a time.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-scenes-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-scenes-server-js)
Every design you create starts with a scene. Scenes contain pages, and pages contain the visible design elements—text, images, shapes, and other blocks. Understanding how scenes work is essential for building, saving, and restoring user designs.
```typescript file=@cesdk_web_examples/guides-concepts-scenes-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Example: 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
* - Exporting scenes to files
*/
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// 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);
// Access pages within the scene
const pages = engine.scene.getPages();
console.log('Number of pages:', pages.length);
// 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');
// Export the first page to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const loadedPages = engine.scene.getPages();
const blob = await engine.block.export(loadedPages[0], {
mimeType: 'image/png'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/scenes-page1.png`, buffer);
console.log('Exported to output/scenes-page1.png');
console.log('Scenes guide completed successfully.');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers how to create scenes from scratch, manage pages within scenes, configure scene properties, save and load designs, and export pages to image files.
## 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 when exported. Use `engine.scene.get()` to retrieve the current scene and `engine.scene.getPages()` to access its pages.
## Creating Scenes
### Creating an Empty Scene
Use `engine.scene.create()` to create a new design scene with a configurable page layout. The layout parameter controls how pages are arranged in the canvas.
```typescript highlight-create-scene
// Create a new design scene with VerticalStack layout
// The layout controls how pages are arranged in the canvas
engine.scene.create('VerticalStack');
// Get the stack container and add spacing between pages
const stack = engine.block.findByType('stack')[0];
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
```
Available layouts include:
- `VerticalStack` — Pages stacked vertically
- `HorizontalStack` — Pages arranged horizontally
- `DepthStack` — Pages layered on top of each other
- `Free` — Manual positioning
### Adding Pages
After creating a scene, add pages using `engine.block.create('page')`. Configure the page dimensions and append it to the scene's stack container.
```typescript highlight-create-page
// Create the first page
const page1 = engine.block.create('page');
engine.block.setWidth(page1, 800);
engine.block.setHeight(page1, 600);
engine.block.appendChild(stack, page1);
// Create a second page
const page2 = engine.block.create('page');
engine.block.setWidth(page2, 800);
engine.block.setHeight(page2, 600);
engine.block.appendChild(stack, page2);
```
### Adding Blocks
With pages in place, add design elements like shapes, text, or images. Create a graphic block, configure its shape and fill, then append it to a page.
```typescript highlight-create-block
// Add a shape to the first page
const graphic1 = engine.block.create('graphic');
engine.block.setShape(graphic1, engine.block.createShape('rect'));
const fill1 = engine.block.createFill('color');
engine.block.setColor(fill1, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1
});
engine.block.setFill(graphic1, fill1);
engine.block.setWidth(graphic1, 400);
engine.block.setHeight(graphic1, 300);
engine.block.setPositionX(graphic1, 200);
engine.block.setPositionY(graphic1, 150);
engine.block.appendChild(page1, graphic1);
// Add a different shape to the second page
const graphic2 = engine.block.create('graphic');
engine.block.setShape(graphic2, engine.block.createShape('ellipse'));
const fill2 = engine.block.createFill('color');
engine.block.setColor(fill2, 'fill/color/value', {
r: 0.9,
g: 0.3,
b: 0.2,
a: 1
});
engine.block.setFill(graphic2, fill2);
engine.block.setWidth(graphic2, 350);
engine.block.setHeight(graphic2, 350);
engine.block.setPositionX(graphic2, 225);
engine.block.setPositionY(graphic2, 125);
engine.block.appendChild(page2, graphic2);
```
## Scene Properties
### Design Units
Query or configure how measurements are interpreted using `engine.scene.getDesignUnit()` and `engine.scene.setDesignUnit()`. This is useful for print workflows where precise physical dimensions matter.
```typescript highlight-scene-properties
// Query scene properties
const currentUnit = engine.scene.getDesignUnit();
console.log('Scene design unit:', currentUnit);
// Get the scene layout
const layout = engine.scene.getLayout();
console.log('Scene layout:', layout);
```
Supported units are `'Pixel'`, `'Millimeter'`, and `'Inch'`. For more details, see the [Design Units](https://img.ly/docs/cesdk/node-native/concepts/design-units-cc6597/) guide.
### Scene Layout
Control how pages are arranged using `engine.scene.getLayout()` and `engine.scene.setLayout()`. The layout affects how pages are organized in multi-page designs.
## Page Navigation
Access pages within your scene using these methods:
```typescript highlight-page-navigation
// Access pages within the scene
const pages = engine.scene.getPages();
console.log('Number of pages:', pages.length);
```
Use `engine.scene.getPages()` to get all pages in the scene. In server-side processing, you typically iterate through pages to export or modify each one.
## Saving Scenes
### Saving to String
Use `engine.scene.saveToString()` to serialize the current scene. This captures the complete scene structure—pages, blocks, and their properties—as a string you can store.
```typescript highlight-save-scene
// Save the scene to a string for persistence
const sceneString = await engine.scene.saveToString();
console.log('Scene saved successfully. String length:', sceneString.length);
```
The serialized string references external assets by URL rather than embedding them. For complete portability including assets, use `engine.scene.saveToArchive()`.
## Loading Scenes
### Loading from String
Use `engine.scene.loadFromString()` to restore a scene from a saved string:
```typescript highlight-load-scene
// Demonstrate loading the scene from the saved string
// This replaces the current scene with the saved version
await engine.scene.loadFromString(sceneString);
console.log('Scene loaded from saved string');
```
Loading a new scene replaces any existing scene. The engine only holds one active scene at a time.
### Loading from URL
Use `engine.scene.loadFromURL()` to load a scene directly from a remote location:
```typescript
await engine.scene.loadFromURL('https://example.com/design.scene');
```
## Exporting Scenes
In server-side workflows, you typically export scenes to files for further processing or delivery:
```typescript highlight-export
// Export the first page to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const loadedPages = engine.scene.getPages();
const blob = await engine.block.export(loadedPages[0], {
mimeType: 'image/png'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/scenes-page1.png`, buffer);
console.log('Exported to output/scenes-page1.png');
```
Use `engine.block.export()` to render pages to PNG, JPEG, or PDF format. The exported blob can be saved to the file system or uploaded to cloud storage.
## Engine Cleanup
Always dispose of the engine when finished to free resources:
```typescript highlight-cleanup
engine.dispose();
```
Use a `try/finally` block to ensure cleanup happens even if an error occurs during processing.
## Troubleshooting
### Blocks Not Visible in Export
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 appear in exports.
### 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 exporting.
### Export Fails
Verify the page exists and is part of the active scene. Check that the output directory exists before writing files.
## Scene Type
Represents the scene and its global properties.
This section describes the properties available for the **Scene Type** (`//ly.img.ubq/scene`) block type.
| Property | Type | Default | Description |
| ------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `alwaysOnBottom` | `Bool` | `false` | If true, this element's global sorting order is automatically adjusted to be lower than all other siblings. |
| `alwaysOnTop` | `Bool` | `false` | If true, this element's global sorting order is automatically adjusted to be higher than all other siblings. |
| `blend/mode` | `Enum` | `"Normal"` | The blend mode to use when compositing the block., Possible values: `"PassThrough"`, `"Normal"`, `"Darken"`, `"Multiply"`, `"ColorBurn"`, `"LinearBurn"`, `"DarkenColor"`, `"Lighten"`, `"Screen"`, `"ColorDodge"`, `"LinearDodge"`, `"LightenColor"`, `"Overlay"`, `"SoftLight"`, `"HardLight"`, `"VividLight"`, `"LinearLight"`, `"PinLight"`, `"HardMix"`, `"Difference"`, `"Exclusion"`, `"Subtract"`, `"Divide"`, `"Hue"`, `"Saturation"`, `"Color"`, `"Luminosity"` |
| `clipped` | `Bool` | `false` | This component is used to identify elements whose contents and children should be clipped to their bounds. |
| `contentFill/mode` | `Enum` | `"Cover"` | Defines how content should be resized to fit its container (e.g., Crop, Cover, Contain)., Possible values: `"Crop"`, `"Cover"`, `"Contain"` |
| `flip/horizontal` | `Bool` | `"-"` | Whether the block is flipped horizontally. |
| `flip/vertical` | `Bool` | `"-"` | Whether the block is flipped vertically. |
| `globalBoundingBox/height` | `Float` | `"-"` | The height of the block's axis-aligned bounding box in world coordinates., *(read-only)* |
| `globalBoundingBox/width` | `Float` | `"-"` | The width of the block's axis-aligned bounding box in world coordinates., *(read-only)* |
| `globalBoundingBox/x` | `Float` | `"-"` | The x-coordinate of the block's axis-aligned bounding box in world coordinates., *(read-only)* |
| `globalBoundingBox/y` | `Float` | `"-"` | The y-coordinate of the block's axis-aligned bounding box in world coordinates., *(read-only)* |
| `height` | `Float` | `0` | The height of the block's frame. |
| `height/mode` | `Enum` | `"Auto"` | A mode describing how the height dimension may be interpreted (Absolute, Percent, Auto)., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` |
| `highlightEnabled` | `Bool` | `true` | Show highlighting when selected or hovered |
| `lastFrame/height` | `Float` | `"-"` | The height of the block's frame from the previous layout pass., *(read-only)* |
| `lastFrame/width` | `Float` | `"-"` | The width of the block's frame from the previous layout pass., *(read-only)* |
| `lastFrame/x` | `Float` | `"-"` | The x-coordinate of the block's frame from the previous layout pass., *(read-only)* |
| `lastFrame/y` | `Float` | `"-"` | The y-coordinate of the block's frame from the previous layout pass., *(read-only)* |
| `placeholder/enabled` | `Bool` | `false` | Whether the placeholder behavior is enabled or not. |
| `playback/playing` | `Bool` | `false` | A tag that can be set on elements for their playback time to be progressed. |
| `playback/soloPlaybackEnabled` | `Bool` | `false` | A tag for blocks where playback should progress while the scene is paused. |
| `playback/time` | `Double` | `0` | The current playback time of the block contents in seconds. |
| `position/x` | `Float` | `0` | The x-coordinate of the block's origin. |
| `position/x/mode` | `Enum` | `"Absolute"` | A mode describing how the x-position may be interpreted., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` |
| `position/y` | `Float` | `0` | The y-coordinate of the block's origin. |
| `position/y/mode` | `Enum` | `"Absolute"` | A mode describing how the y-position may be interpreted., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` |
| `rotation` | `Float` | `0` | The rotation of the block in radians. |
| `scene/aspectRatioLock` | `Bool` | `true` | Whether the ratio of the pageDimensions' width and height should remain constant when changing either dimension. |
| `scene/designUnit` | `Enum` | `"Pixel"` | The unit type in which the page values (size, distances, etc.) are defined., Possible values: `"Pixel"`, `"Millimeter"`, `"Inch"` |
| `scene/dpi` | `Float` | `300` | The DPI value to use when exporting and when converting between pixels and inches or millimeter units. |
| `scene/layout` | `Enum` | `"Free"` | A value describing how the scene's children are laid out., Possible values: `"Free"`, `"VerticalStack"`, `"HorizontalStack"`, `"DepthStack"` |
| `scene/mode` | `Enum` | `"Video"` | The mode of this scene and all elements inside of it., *(read-only)*, Possible values: `"Design"`, `"Video"` |
| `scene/pageDimensions/height` | `Float` | `1` | The height of all pages in this scene. |
| `scene/pageDimensions/width` | `Float` | `1` | The width of all pages in this scene. |
| `scene/pageFormatId` | `String` | `""` | The identifier of the page format configuration that was most recently selected for the pages in this scene. |
| `scene/pixelScaleFactor` | `Float` | `1` | A scale factor that is applied to the final export resolution if the design unit is Pixel. |
| `selected` | `Bool` | `false` | Indicates if the block is currently selected. |
| `transformLocked` | `Bool` | `false` | DesignBlocks with this tag can't be transformed (moved, rotated, scaled, cropped, or flipped). |
| `visible` | `Bool` | `true` | If the block is visible in the editor. |
| `width` | `Float` | `0` | The width of the block's frame. |
| `width/mode` | `Enum` | `"Auto"` | A mode describing how the width dimension may be interpreted (Absolute, Percent, Auto)., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Templating"
description: "Templates enable dynamic, reusable designs with text variables and placeholder media. Learn to create, load, and personalize templates programmatically."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/templating-f94385/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Templating](https://img.ly/docs/cesdk/node-native/concepts/templating-f94385/)
---
Templates transform static designs into dynamic, data-driven content. They combine reusable layouts with variable text and placeholder media, enabling personalization at scale.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-templating-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-templating-server-js)
A template is a regular CE.SDK scene that contains **variable tokens** in text and **placeholder blocks** for media. When you load a template, you can populate the variables with data and swap placeholder content—producing personalized designs without modifying the underlying layout.
```typescript file=@cesdk_web_examples/guides-concepts-templating-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Example: Templating Concepts
*
* Demonstrates the core template concepts in CE.SDK:
* - Loading a template from URL
* - Discovering and setting variables
* - Discovering placeholders
* - Exporting personalized designs
*/
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Load a postcard template from URL
// Templates are scenes containing variable tokens and placeholder blocks
const templateUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene';
await engine.scene.loadFromURL(templateUrl);
// 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);
// Export the personalized design to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const pages = engine.scene.getPages();
const blob = await engine.block.export(pages[0], {
mimeType: 'image/png'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/templating-personalized.png`, buffer);
console.log('Exported to output/templating-personalized.png');
console.log('Templating guide completed successfully.');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
This guide explains the core concepts. For implementation details, see the guides linked in each section.
## What Makes a Template
Any CE.SDK scene can become a template by adding dynamic elements:
| Element | Purpose | Example |
|---------|---------|---------|
| **Variables** | Dynamic text replacement | `Hello, {{firstName}}!` |
| **Placeholders** | Swappable media slots | Profile photo, product image |
| **Editing Constraints** | Protected design elements | Locked logo, fixed layout |
Templates separate **design** (created once by designers) from **content** (populated at runtime with data). This enables workflows like batch generation, form-based customization, and user personalization.
## Loading Templates
Load a template from a URL using `engine.scene.loadFromURL()`. This replaces the current scene with the template's structure, including any pages, blocks, variables, and placeholders.
```typescript highlight-load-template
// Load a postcard template from URL
// Templates are scenes containing variable tokens and placeholder blocks
const templateUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene';
await engine.scene.loadFromURL(templateUrl);
```
Templates are standard CE.SDK scene files. You can load them from your own servers, CDNs, or cloud storage.
## Variables
Variables enable dynamic text without modifying the design structure. Text blocks contain `{{variableName}}` tokens that CE.SDK resolves at render time.
### Discovering Variables
Use `engine.variable.findAll()` to discover what variables a template expects:
```typescript highlight-discover-variables
// 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);
```
This returns an array of variable names defined in the template.
### Setting Variable Values
Populate variables with `engine.variable.setString()`:
```typescript highlight-set-variables
// Set variable values to personalize the template
// These values replace {{variableName}} tokens in text blocks
engine.variable.setString('Name', 'Jane');
engine.variable.setString('Greeting', 'Wish you were here!');
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.
[Learn more about text variables →](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/)
## Placeholders
Placeholders mark blocks as content slots that users or automation can replace. When you enable placeholder behavior on an image block, it becomes a designated swap target.
### Discovering Placeholders
Use `engine.block.findAllPlaceholders()` to discover all placeholder blocks in a loaded template:
```typescript highlight-discover-placeholders
// 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);
```
**How placeholders work:**
- Enable with `engine.block.setPlaceholderEnabled(block, true)`
- Placeholder blocks are marked for content replacement
- You can programmatically replace placeholder content with new images or media
[Learn more about placeholders →](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/)
## Template Workflows
Templates support several common workflows:
### Batch Generation
Load a template programmatically, iterate through data records, set variables for each record, and export personalized designs. This powers use cases like certificates, badges, and personalized marketing.
### Design Systems
Create template libraries where designers maintain approved layouts and automation populates them with data at scale.
### Content Personalization
Load templates on the server, populate with user-specific data, and deliver personalized content without exposing the template structure.
## Exporting Personalized Designs
After populating variables, export the personalized design:
```typescript highlight-export
// Export the personalized design to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const pages = engine.scene.getPages();
const blob = await engine.block.export(pages[0], {
mimeType: 'image/png'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/templating-personalized.png`, buffer);
console.log('Exported to output/templating-personalized.png');
```
Use `engine.block.export()` to render pages to PNG, JPEG, or PDF format. The exported blob can be saved to the file system or uploaded to cloud storage.
## Engine Cleanup
Always dispose of the engine when finished to free resources:
```typescript highlight-cleanup
engine.dispose();
```
Use a `try/finally` block to ensure cleanup happens even if an error occurs during processing.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Terminology"
description: "Definitions for the core terms and concepts used throughout CE.SDK documentation, including Engine, Scene, Block, Fill, Shape, Effect, and more."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/terminology-99e82d/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Terminology](https://img.ly/docs/cesdk/node-native/concepts/terminology-99e82d/)
---
A reference guide to the core terms and concepts used throughout CE.SDK documentation.
CE.SDK uses consistent terminology across all platforms. Understanding what we call things helps you navigate the API, read documentation efficiently, and communicate effectively with other developers working on CE.SDK integration.
## Core Architecture
### Engine
All operations—creating scenes, manipulating blocks, rendering, and exporting—go through the *Engine*. Initialize it once and use it throughout your application's lifecycle.
### Scene
The root container for all design content. A *Scene* contains *Pages*, which contain *Blocks*. Only one *Scene* can be active per *Engine* instance. You can create a *Scene* programmatically or load one from a file.
*Scenes* support both static designs (social posts, print materials, graphics) and time-based content (duration, playback time, animation).
See [Scenes](https://img.ly/docs/cesdk/node-native/concepts/scenes-e8596d/) for details.
### Page
*Pages* are containers within a *Scene* that hold content *Blocks* (see below) and define working area dimensions.
For static designs, pages are individual artboards. For video editing, pages are time-based compositions where *Blocks* are arranged across time. See [Pages](https://img.ly/docs/cesdk/node-native/concepts/pages-7b6bae/) for details.
### Block
The fundamental building unit in CE.SDK. Everything visible in a design is a *Block*—images, text, shapes, graphics, audio, video—and even *Pages* themselves. *Blocks* form a parent-child hierarchy.
Each *Block* has two identifiers:
- **DesignBlockId**: A numeric handle (integer) used in API calls
- **UUID**: A stable string identifier that persists across save and load operations
See [Blocks](https://img.ly/docs/cesdk/node-native/concepts/blocks-90241e/) for details.
## Block Anatomy
Modify a *Block's* appearance and behavior by attaching *Fills*, *Shapes*, and *Effects*. Most of these modifiers must be created separately and then attached to a *Block*.
### Fill
*Fills* cover the surface of a *Block's* shape:
- **Color Fill**: Solid color
- **Gradient Fill**: Linear, radial, or conical gradients
- **Image Fill**: Image content
- **Video Fill**: Video content
See the [Color Fills](https://img.ly/docs/cesdk/node-native/fills/color-7129cd/), [Gradient Fills](https://img.ly/docs/cesdk/node-native/filters-and-effects/gradients-0ff079/), [Image Fills](https://img.ly/docs/cesdk/node-native/fills/image-e9cb5c/), and [Video Fills](https://img.ly/docs/cesdk/node-native/fills/video-ec7f9f/) guides.
### Shape
*Shapes* define a *Block's* outline and dimensions, determining the silhouette and how the *Fill* is clipped. *Shape* types include:
- **Rect**: Rectangles and squares
- **Ellipse**: Circles and ovals
- **Polygon**: Multi-sided shapes
- **Star**: Star shapes with configurable points
- **Line**: Straight lines
- **Vector Path**: Custom vector shapes
Like *Fills*, *Shapes* are created separately and attached to *Blocks*. See [Shapes](https://img.ly/docs/cesdk/node-native/shapes-9f1b2c/) for details.
### Effect
*Effects* are non-destructive visual modifications applied to a *Block*. Multiple *Effects* can be stacked. *Effect* categories include:
- **Adjustments**: Brightness, contrast, saturation, and other image corrections
- **Filters**: LUT-based color grading, duotone
- **Stylization**: Pixelize, posterize, half-tone, dot pattern, linocut, outliner
- **Distortion**: Liquid, mirror, shifter, cross-cut, extrude blur
- **Focus**: Tilt-shift, vignette
- **Color**: Recolor, green screen (chroma key)
- **Other**: Glow, TV glitch
The order determines how multiple effects attached to a single block interact. See [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) for details.
### Blur
A modifier that reduces sharpness. *Blur* types include:
- **Uniform Blur**: Even blur across the entire block
- **Radial Blur**: Circular blur from a center point
- **Mirrored Blur**: Blur with reflection
> **Note:** **Blur has a dedicated API because it composites differently than other effects.** While most effects like brightness or saturation operate only on a block's own pixels, blur needs to sample pixels from the surrounding area to calculate the blurred result. This means blur interacts with the scene's layering and transparency in ways other effects don't—when you blur a partially transparent block, the engine must handle how that blur blends with whatever content sits behind it.
See [Blur](https://img.ly/docs/cesdk/node-native/filters-and-effects/blur-71d642/) for details.
### Drop Shadow
A built-in block property (not an *Effect*) that renders a shadow beneath blocks. *Drop Shadow* has dedicated API methods for enabling, color, offset, and blur radius.
> **Warning:** Unlike effects, drop shadow is configured directly on the block rather than created and attached separately.
## Block Handling
These terms describe how *Blocks* are categorized and identified.
### Type
The built-in *Type* defines a *Block's* core behavior and available properties. *Type* is immutable—you choose it when creating the *Block*.
- `//ly.img.ubq/graphic` — Visual block for images, shapes, and graphics
- `//ly.img.ubq/text` — Text content
- `//ly.img.ubq/audio` — Audio content
- `//ly.img.ubq/page` — Page container
- `//ly.img.ubq/scene` — Root scene container
- `//ly.img.ubq/track` — Video timeline track
- `//ly.img.ubq/stack` — Stack container for layering
- `//ly.img.ubq/group` — Group container for organizing blocks
- `//ly.img.ubq/camera` — Camera for scene viewing
- `//ly.img.ubq/cutout` — Cutout/mask block
- `//ly.img.ubq/caption` — Caption/subtitle block
- `//ly.img.ubq/captionTrack` — Track for captions
The *Type* determines which properties and capabilities a *Block* has.
### Kind
A custom string label you assign to categorize *Blocks* for your application. Unlike *Type*, *Kind* is mutable and application-defined. Changing the *Kind* has no effect on appearance or behavior at the engine level. You can query and search for *Blocks* by *Kind*. Common uses:
- Categorizing template elements ("logo", "headline", "background")
- Filtering blocks for custom UI
- Automation workflows that process blocks by purpose
### Property
A configurable attribute of a *Block*. *Properties* have types (`Bool`, `Int`, `Float`, `String`, `Color`, `Enum`) and paths like `text/fontSize` or `fill/image/imageFileURI`.
Access *Properties* using type-specific getter and setter methods. Each *Block* type exposes different properties, which you can discover programmatically. See [Blocks](https://img.ly/docs/cesdk/node-native/concepts/blocks-90241e/) for details.
## Assets and Resources
### Asset
Think of *Assets* as media items that you can provide to your users: images, videos, audio files, fonts, stickers, or templates—anything that can be added to a design. *Assets* have metadata including:
- **ID**: Unique identifier within an asset source
- **Label**: Display name
- **Meta**: Custom metadata (URI, dimensions, format)
- **Thumbnail URI**: Preview image URL
*Assets* are provided by *Asset Sources* and added through the UI or programmatically.
### Asset Source
A provider of *Assets*. *Asset Sources* can be built-in (like the default sticker library) or custom. *Asset Sources* implement a query interface returning paginated results with search and filtering.
- **Local Asset Source**: Assets defined in JSON, loaded at initialization
- **Remote Asset Source**: Custom implementation fetching from external APIs
Register *Asset Sources* with the *Engine* to make *Assets* available throughout your application.
### Resource
Loaded data from an *Asset* URI. When you reference an image or video URL in a *Block*, the *Engine* fetches and caches the *Resource*. *Resources* include binary data and metadata for rendering. See [Resources](https://img.ly/docs/cesdk/node-native/concepts/resources-a58d71/) for details.
### Buffer
A resizable container for arbitrary binary data. *Buffers* are useful for dynamically generated content that doesn't come from a URL, such as synthesized audio or programmatically created images.
Create a *Buffer*, write data to it, and reference it by URI in *Block* properties. *Buffer* data is not serialized with scenes and changes cannot be undone. See [Buffers](https://img.ly/docs/cesdk/node-native/concepts/buffers-9c565b/) for details.
## Templating and Automation
These terms describe dynamic content and reusable designs.
### Template
A reusable design with predefined structure and styling. *Templates* typically contain *Placeholders* and *Variables* that users customize while maintaining overall layout and branding.
*Templates* are scenes saved in a format that can be loaded and modified.
### Placeholder
A *Block* marked for content replacement. When a *Block's* placeholder property is enabled, it signals that the *Block* expects user-provided content—an image drop zone or editable text field.
*Placeholders* indicate which parts of a design should be customized versus fixed. See [Placeholders](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/) for details.
### Variable
A named value referenced in text blocks using `{{variableName}}` syntax. *Variables* enable data-driven design generation by populating templates with dynamic content.
Define *Variables* at the scene level and reference them in text blocks. When a *Variable* value changes, all referencing text blocks update automatically. See [Text Variables](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/) for details.
## Permissions and Scopes
These terms relate to controlling what operations are allowed.
### Scope
A permission setting controlling whether specific operations are allowed on a *Block*. *Scopes* enable fine-grained control over what users can modify—essential for template workflows where some elements should be editable and others locked.
Common scopes:
- `layer/move` — Allow or prevent moving
- `layer/resize` — Allow or prevent resizing
- `layer/rotate` — Allow or prevent rotation
- `layer/visibility` — Allow or prevent hiding
- `lifecycle/destroy` — Allow or prevent deletion
- `editor/select` — Allow or prevent selection
Enable or disable *Scopes* per *Block* to create controlled editing experiences. See [Lock Design Elements](https://img.ly/docs/cesdk/node-native/create-templates/lock-131489/) for details.
### Role
A preset collection of *Scope* settings. CE.SDK defines two built-in *Roles*:
- **Creator**: Full access to all operations, for template authors
- **Adopter**: Restricted access for end-users customizing templates
*Roles* provide a convenient way to apply consistent permission sets.
## Layout and Units
These terms relate to positioning and measurement.
### Design Unit
The measurement unit for dimensions in a *Scene*. The choice affects how positions, sizes, and exports are interpreted. Options:
- **Pixel**: Screen pixels, default for digital designs
- **Millimeter**: Metric measurement for print
- **Inch**: Imperial measurement for print
Set the design unit at the scene level—all dimension values are interpreted in that unit. See [Design Units](https://img.ly/docs/cesdk/node-native/concepts/design-units-cc6597/) for details.
### DPI (Dots Per Inch)
Resolution setting affecting export quality and unit conversion. Higher DPI produces larger exports with more detail. The default is 300 DPI, suitable for print-quality output.
DPI matters when working with physical units (millimeters, inches) as it determines how measurements translate to pixel dimensions during export.
## Operating Modes
These terms describe how CE.SDK runs.
### Scene Capabilities
Every *Scene* supports the full range of features:
- **Static designs**: Content arranged spatially on pages.
- **Video editing**: Blocks can have duration, time offset, playback time, and animation properties.
See [Scenes](https://img.ly/docs/cesdk/node-native/concepts/scenes-e8596d/) for details.
### Headless Mode
Running CE.SDK without the built-in UI. Used for:
- Server-side rendering and export
- Automation pipelines
- Custom UI implementations
- Batch processing
In *Headless Mode*, you work directly with *Engine* APIs without the visual editor. See [Headless Mode](https://img.ly/docs/cesdk/node-native/concepts/headless-mode-24ab98/) for setup.
## Events and State
These terms relate to monitoring changes.
### Event / Subscription
A callback mechanism for reacting to changes in the *Engine*. Subscribe to events and receive notifications when state changes. Common events:
- Selection changes
- Block state changes
- History (undo/redo) changes
Subscriptions return an unsubscribe function to call when you no longer need notifications. See [Events](https://img.ly/docs/cesdk/node-native/concepts/events-353f97/) for details.
### Block State
The current status of a *Block* indicating readiness or issues:
- **Ready**: Normal state, no pending operations
- **Pending**: Operation in progress, with optional progress value (0-1)
- **Error**: Operation failed, with error type (`ImageDecoding`, `VideoDecoding`, `FileFetch`, etc.)
*Block State* reflects the combined status of the *Block* and its attached *Fill*, *Shape*, and *Effects*.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Undo and History"
description: "Manage undo and redo stacks in CE.SDK using multiple histories, callbacks, and API-based controls."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/concepts/undo-and-history-99479d/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Concepts](https://img.ly/docs/cesdk/node-native/concepts-c9ff51/) > [Undo and History](https://img.ly/docs/cesdk/node-native/concepts/undo-and-history-99479d/)
---
Implement undo/redo functionality and manage multiple history stacks to track editing operations.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-concepts-undo-and-history-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-concepts-undo-and-history-server-js)
CE.SDK automatically tracks editing operations, enabling you to undo and redo changes programmatically. 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 different processing pipelines.
```typescript file=@cesdk_web_examples/guides-concepts-undo-and-history-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
// Load environment variables
config();
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Subscribe to history updates to track state changes
const unsubscribe = engine.editor.onHistoryUpdated(() => {
const canUndo = engine.editor.canUndo();
const canRedo = engine.editor.canRedo();
console.log('History updated:', { canUndo, canRedo });
});
// Create a triangle shape and add an undo step to record it in history
const block = engine.block.create('graphic');
engine.block.setPositionX(block, 140);
engine.block.setPositionY(block, 95);
engine.block.setWidth(block, 265);
engine.block.setHeight(block, 265);
const triangleShape = engine.block.createShape('polygon');
engine.block.setInt(triangleShape, 'shape/polygon/sides', 3);
engine.block.setShape(block, triangleShape);
const triangleFill = engine.block.createFill('color');
engine.block.setColor(triangleFill, 'fill/color/value', {
r: 0.2,
g: 0.5,
b: 0.9,
a: 1
});
engine.block.setFill(block, triangleFill);
engine.block.appendChild(page, block);
// Commit the block creation to history so it can be undone
engine.editor.addUndoStep();
// Log current state - canUndo should now be true
console.log('Block created. canUndo:', engine.editor.canUndo());
// Undo the block creation
if (engine.editor.canUndo()) {
engine.editor.undo();
console.log(
'After undo - canUndo:',
engine.editor.canUndo(),
'canRedo:',
engine.editor.canRedo()
);
}
// Redo to restore the block
if (engine.editor.canRedo()) {
engine.editor.redo();
console.log(
'After redo - canUndo:',
engine.editor.canUndo(),
'canRedo:',
engine.editor.canRedo()
);
}
// Create a second history stack for isolated operations
const secondaryHistory = engine.editor.createHistory();
const primaryHistory = engine.editor.getActiveHistory();
console.log(
'Created secondary history. Primary:',
primaryHistory,
'Secondary:',
secondaryHistory
);
// Switch to the secondary history
engine.editor.setActiveHistory(secondaryHistory);
console.log(
'Switched to secondary history. Active:',
engine.editor.getActiveHistory()
);
// Operations in secondary history are isolated from the primary history
const secondBlock = engine.block.create('graphic');
engine.block.setPositionX(secondBlock, 440);
engine.block.setPositionY(secondBlock, 95);
engine.block.setWidth(secondBlock, 220);
engine.block.setHeight(secondBlock, 220);
const circleShape = engine.block.createShape('ellipse');
engine.block.setShape(secondBlock, circleShape);
const circleFill = engine.block.createFill('color');
engine.block.setColor(circleFill, 'fill/color/value', {
r: 0.9,
g: 0.3,
b: 0.3,
a: 1
});
engine.block.setFill(secondBlock, circleFill);
engine.block.appendChild(page, secondBlock);
// Commit changes to the secondary history
engine.editor.addUndoStep();
console.log(
'Block added in secondary history. canUndo:',
engine.editor.canUndo()
);
// Switch back to primary history
engine.editor.setActiveHistory(primaryHistory);
console.log(
'Switched back to primary history. canUndo:',
engine.editor.canUndo()
);
// Clean up the secondary history when no longer needed
engine.editor.destroyHistory(secondaryHistory);
console.log('Secondary history destroyed');
// Manually add an undo step after custom operations
engine.block.setPositionX(block, 190);
engine.editor.addUndoStep();
console.log('Manual undo step added. canUndo:', engine.editor.canUndo());
// Remove the most recent undo step if needed
if (engine.editor.canUndo()) {
engine.editor.removeUndoStep();
console.log('Most recent undo step removed');
}
// Reset block position to its original location
engine.block.setPositionX(block, 140);
// Clean up subscription
unsubscribe();
console.log('Undo and history demo completed successfully');
} finally {
// Always dispose of the engine when done
engine.dispose();
}
```
This guide covers how to perform undo and redo operations programmatically, subscribe to history changes, manually manage undo steps, and work with multiple history stacks.
## Setup
We start by initializing the CE.SDK engine and creating a design scene. The engine automatically creates a history stack when initialized.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
```
## Automatic Undo Step Creation
Most editing operations automatically create undo steps. When we add a shape to the scene, the engine records this operation in the history stack.
```typescript highlight=highlight-create-block
// Create a triangle shape and add an undo step to record it in history
const block = engine.block.create('graphic');
engine.block.setPositionX(block, 140);
engine.block.setPositionY(block, 95);
engine.block.setWidth(block, 265);
engine.block.setHeight(block, 265);
const triangleShape = engine.block.createShape('polygon');
engine.block.setInt(triangleShape, 'shape/polygon/sides', 3);
engine.block.setShape(block, triangleShape);
const triangleFill = engine.block.createFill('color');
engine.block.setColor(triangleFill, 'fill/color/value', {
r: 0.2,
g: 0.5,
b: 0.9,
a: 1
});
engine.block.setFill(block, triangleFill);
engine.block.appendChild(page, block);
// Commit the block creation to history so it can be undone
engine.editor.addUndoStep();
```
After creating the shape, `canUndo()` returns `true` since the operation has been recorded as an undoable step.
## Performing Undo and Redo Operations
We use `engine.editor.undo()` and `engine.editor.redo()` to programmatically revert or restore changes. Before calling these methods, check availability with `engine.editor.canUndo()` and `engine.editor.canRedo()` to prevent errors.
```typescript highlight=highlight-undo
// Undo the block creation
if (engine.editor.canUndo()) {
engine.editor.undo();
console.log(
'After undo - canUndo:',
engine.editor.canUndo(),
'canRedo:',
engine.editor.canRedo()
);
}
```
The undo operation reverts the most recent change. After undoing, `canRedo()` returns `true` since there's now a step available to restore.
```typescript highlight=highlight-redo
// Redo to restore the block
if (engine.editor.canRedo()) {
engine.editor.redo();
console.log(
'After redo - canUndo:',
engine.editor.canUndo(),
'canRedo:',
engine.editor.canRedo()
);
}
```
The redo operation restores the most recently undone change. After redoing, `canRedo()` returns `false` (unless there are more undo steps to restore).
## Subscribing to History Changes
We use `engine.editor.onHistoryUpdated()` to receive notifications when the history state changes. The callback fires after any undo, redo, or new operation. This enables synchronizing application state with the current history state.
```typescript highlight=highlight-subscribe-history
// Subscribe to history updates to track state changes
const unsubscribe = engine.editor.onHistoryUpdated(() => {
const canUndo = engine.editor.canUndo();
const canRedo = engine.editor.canRedo();
console.log('History updated:', { canUndo, canRedo });
});
```
The subscription returns an unsubscribe function. Call it when you no longer need notifications.
## Managing Undo Steps Manually
Most editing operations automatically create undo steps. However, some custom operations may require manual checkpoint creation using `engine.editor.addUndoStep()`. This is useful when you make multiple related changes that should be undone as a single unit.
```typescript highlight=highlight-manual-undo-step
// Manually add an undo step after custom operations
engine.block.setPositionX(block, 190);
engine.editor.addUndoStep();
console.log('Manual undo step added. canUndo:', engine.editor.canUndo());
// Remove the most recent undo step if needed
if (engine.editor.canUndo()) {
engine.editor.removeUndoStep();
console.log('Most recent undo step removed');
}
// Reset block position to its original location
engine.block.setPositionX(block, 140);
```
We use `engine.editor.removeUndoStep()` to remove the most recent undo step. Always check `canUndo()` before calling this method to ensure an undo step is available. This can be useful when you need to discard changes without affecting the redo stack.
## Working with Multiple History Stacks
CE.SDK supports multiple independent history stacks for isolated editing contexts. This is useful when you need separate undo/redo histories for different parts of your application, such as different processing pipelines or batch operations.
### Creating and Switching History Stacks
We create a new history stack using `engine.editor.createHistory()`. Use `engine.editor.setActiveHistory()` to switch between stacks. Only the active history responds to undo/redo operations.
```typescript highlight=highlight-multiple-histories
// Create a second history stack for isolated operations
const secondaryHistory = engine.editor.createHistory();
const primaryHistory = engine.editor.getActiveHistory();
console.log(
'Created secondary history. Primary:',
primaryHistory,
'Secondary:',
secondaryHistory
);
// Switch to the secondary history
engine.editor.setActiveHistory(secondaryHistory);
console.log(
'Switched to secondary history. Active:',
engine.editor.getActiveHistory()
);
// Operations in secondary history are isolated from the primary history
const secondBlock = engine.block.create('graphic');
engine.block.setPositionX(secondBlock, 440);
engine.block.setPositionY(secondBlock, 95);
engine.block.setWidth(secondBlock, 220);
engine.block.setHeight(secondBlock, 220);
const circleShape = engine.block.createShape('ellipse');
engine.block.setShape(secondBlock, circleShape);
const circleFill = engine.block.createFill('color');
engine.block.setColor(circleFill, 'fill/color/value', {
r: 0.9,
g: 0.3,
b: 0.3,
a: 1
});
engine.block.setFill(secondBlock, circleFill);
engine.block.appendChild(page, secondBlock);
// Commit changes to the secondary history
engine.editor.addUndoStep();
console.log(
'Block added in secondary history. canUndo:',
engine.editor.canUndo()
);
// Switch back to primary history
engine.editor.setActiveHistory(primaryHistory);
console.log(
'Switched back to primary history. canUndo:',
engine.editor.canUndo()
);
```
Operations performed while a history is active only affect that history. When you switch back to the primary history, its undo/redo state remains unchanged by operations performed in the secondary history.
### Cleaning Up History Stacks
We destroy unused history stacks with `engine.editor.destroyHistory()` to free resources. Always clean up secondary histories when they're no longer needed.
```typescript highlight=highlight-destroy-history
// Clean up the secondary history when no longer needed
engine.editor.destroyHistory(secondaryHistory);
console.log('Secondary history destroyed');
```
## Troubleshooting
Common issues when working with undo/redo functionality:
- **Undo step not recorded**: Ensure changes occur after the history subscription is active. The engine only tracks operations that happen while the history is being monitored.
- **Redo not available**: Performing any new action after undo clears the redo stack. This is standard behavior to prevent branching history states.
- **Wrong history active**: Always verify the correct history is set with `getActiveHistory()` before performing undo/redo operations when using multiple stacks.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Configuration"
description: "Learn how to configure CE.SDK to match your application's functional, visual, and performance requirements."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/configuration-2c1c3d/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Configuration](https://img.ly/docs/cesdk/node-native/configuration-2c1c3d/)
---
Set up CE.SDK engine with license keys, user IDs, and configuration options
for server-side processing using the native Node.js bindings.
`CreativeEngine.init()` initializes the CE.SDK engine for headless operations in Node.js environments.
```js
import CreativeEngine from '@cesdk/node-native';
const config = {
license: process.env.CESDK_LICENSE || '',
};
const engine = await CreativeEngine.init(config);
```
This guide covers required and optional configuration properties, and runtime APIs for server-side CE.SDK usage with native bindings.
## Required Configuration
The `license` property is the only required configuration. All other properties have sensible defaults.
| Property | Type | Purpose |
| --------- | -------- | --------------------------------------- |
| `license` | `string` | License key to remove export watermarks |
The license key validates your CE.SDK subscription and removes watermarks from exports. Without a valid license, exports include a watermark. Get a free trial license at [https://img.ly/forms/free-trial](https://img.ly/forms/free-trial).
```javascript
const config = {
license: process.env.CESDK_LICENSE || '',
};
const engine = await CreativeEngine.init(config);
```
## Optional Configuration
These properties customize engine behavior and are all optional.
| Property | Type | Default | Purpose |
| -------- | --------------------------------------------------------- | ----------- | -------------------------------- |
| `userId` | `string` | — | User identifier for MAU tracking |
| `logger` | `function` | Console | Custom logging function |
| `role` | `'Creator'` | `'Adopter'` | `'Viewer'` | `'Presenter'` | `'Creator'` | User role for feature access |
> **Tip:** Unlike the WASM-based `@cesdk/node` package, the native bindings do not
> require a `baseURL` for WASM assets. The engine runs natively and assets are
> bundled with the package.
## Configuration Properties
### License Key
The license key validates your CE.SDK subscription and removes watermarks from exports. Without a valid license, exports include a watermark.
```javascript
const config = {
license: 'YOUR_CESDK_LICENSE_KEY',
};
const engine = await CreativeEngine.init(config);
```
### User ID
Provide a unique user identifier for accurate Monthly Active User (MAU) tracking. This helps count users correctly when processing requests from different sources.
```javascript
const config = {
license: 'YOUR_CESDK_LICENSE_KEY',
userId: 'user-123',
};
const engine = await CreativeEngine.init(config);
```
### Custom Logger
Replace the default console logging with a custom logger function. The logger receives a message string and an optional log level (`'Info'`, `'Warning'`, or `'Error'`).
```javascript
const config = {
license: 'YOUR_CESDK_LICENSE_KEY',
logger: (message, level) => {
if (level === 'Error') {
console.error('[CE.SDK]', message);
}
},
};
const engine = await CreativeEngine.init(config);
```
## Runtime Settings
After initialization, configure engine behavior using type-specific setting methods. Settings control features like double-click crop behavior and highlight colors.
```javascript
engine.editor.setSettingBool('page/title/show', false);
const value = engine.editor.getSettingBool('page/title/show');
```
## Exporting Results
After processing a scene, export the result to a file. The engine's `block.export()` method returns a `Uint8Array` that you can write directly to the file system.
```javascript
import fs from 'fs/promises';
const pngBuffer = await engine.block.export(page, 'image/png');
await fs.writeFile('./output.png', Buffer.from(pngBuffer));
```
## Engine Disposal
Clean up engine resources when done processing by calling `engine.dispose()`. Place this in a finally block to ensure cleanup even if errors occur.
```javascript
try {
// ... processing logic
} finally {
engine.dispose();
}
```
## API Reference
| Method | Category | Purpose |
| ---------------------------------- | -------------- | --------------------------------------------------- |
| `CreativeEngine.init()` | Initialization | Initialize headless engine with config |
| `engine.editor.setSettingBool()` | Runtime | Set boolean engine setting |
| `engine.editor.setSettingString()` | Runtime | Set string engine setting |
| `engine.editor.setSettingEnum()` | Runtime | Set enum engine setting |
| `engine.block.export()` | Export | Export block to image format (returns `Uint8Array`) |
| `engine.dispose()` | Lifecycle | Clean up engine resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Conversion"
description: "Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/conversion-c3fbb3/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/node-native/conversion-c3fbb3/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/node-native/conversion/overview-44dc58/) - Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools.
- [To Base64](https://img.ly/docs/cesdk/node-native/conversion/to-base64-39ff25/) - Convert CE.SDK exports to Base64-encoded strings for embedding in URLs, storing in databases, or transmitting via APIs.
- [To PNG](https://img.ly/docs/cesdk/node-native/conversion/to-png-f1660c/) - Export designs and images to PNG format with compression settings and target dimensions using CE.SDK.
- [To PDF](https://img.ly/docs/cesdk/node-native/conversion/to-pdf-eb937f/) - Convert your design or document into a high-quality, print-ready PDF format.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/conversion/overview-44dc58/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/node-native/conversion-c3fbb3/) > [Overview](https://img.ly/docs/cesdk/node-native/conversion/overview-44dc58/)
---
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.
[Launch Web Demo](https://img.ly/showcases/cesdk)
[Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/)
## Supported Input and Output Formats
CE.SDK accepts a range of input formats when working with designs, including:
When it comes to exporting or converting designs, the SDK supports the following output formats:
Each format serves different use cases, giving you the flexibility to adapt designs for your application’s needs.
## 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.
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.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To Base64"
description: "Convert CE.SDK exports to Base64-encoded strings for embedding in URLs, storing in databases, or transmitting via APIs."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/conversion/to-base64-39ff25/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/node-native/conversion-c3fbb3/) > [To Base64](https://img.ly/docs/cesdk/node-native/conversion/to-base64-39ff25/)
---
Convert CE.SDK exports to Base64-encoded strings for storing in databases, transmitting via APIs, or embedding in HTML templates.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-conversion-to-base64-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-conversion-to-base64-server-js)
Base64 encoding transforms binary image data into ASCII text, enabling you to store images in text-only databases, transmit them through JSON APIs, or embed them in HTML email templates.
```typescript file=@cesdk_web_examples/guides-conversion-to-base64-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import * as readline from 'readline';
config();
const OUTPUT_DIR = './output';
function prompt(question: string): Promise {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim().toLowerCase());
});
});
}
function showProgress(msg: string): () => void {
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let i = 0;
process.stdout.write(`${frames[0]} ${msg}`);
const id = setInterval(() => {
i = (i + 1) % frames.length;
process.stdout.write(`\r${frames[i]} ${msg}`);
}, 80);
return () => {
clearInterval(id);
process.stdout.write(`\r✓ ${msg}\n`);
};
}
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
let done = showProgress('Loading scene...');
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
done();
const page = engine.block.findByType('page')[0];
if (!page) throw new Error('No page found');
console.log('\n┌───────────────────────────────────┐');
console.log('│ Convert to Base64 │');
console.log('├───────────────────────────────────┤');
console.log('│ Scene loaded successfully. │');
console.log('│ Ready to export as PNG Base64. │');
console.log('└───────────────────────────────────┘\n');
const confirm = await prompt('Export to Base64? (y/n): ');
if (confirm !== 'y' && confirm !== 'yes') {
console.log('\nExport cancelled.\n');
process.exit(0);
}
if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true });
done = showProgress('Exporting to Base64...');
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
done();
const buffer = Buffer.from(await blob.arrayBuffer());
const base64 = buffer.toString('base64');
const dataUri = `data:${blob.type};base64,${base64}`;
writeFileSync(`${OUTPUT_DIR}/export.png`, buffer);
writeFileSync(`${OUTPUT_DIR}/base64.txt`, dataUri);
console.log(`\n✓ Saved: ${OUTPUT_DIR}/export.png`);
console.log(`✓ Saved: ${OUTPUT_DIR}/base64.txt`);
console.log(` Binary: ${(blob.size / 1024).toFixed(1)} KB`);
console.log(
` Base64: ${(dataUri.length / 1024).toFixed(1)} KB (~33% overhead)\n`
);
} finally {
engine.dispose();
}
```
## Export a Block to Base64
Use `engine.block.export()` to export a design block as a Blob, then convert it to a Base64 string.
```typescript
const page = engine.block.findByType('page')[0];
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
```
The export returns a Blob containing the rendered image. You then convert this Blob to a Base64 string using Node.js Buffer APIs.
## Convert Blob to Base64
Convert the exported Blob into a Base64 data URI using Node.js `Buffer` API.
```typescript highlight=highlight-convert-base64
const buffer = Buffer.from(await blob.arrayBuffer());
const base64 = buffer.toString('base64');
const dataUri = `data:${blob.type};base64,${base64}`;
```
The `toString('base64')` method encodes the buffer contents. Prepending the MIME type prefix (`data:image/png;base64,...`) creates a complete data URI ready for storage or transmission.
## Save to File System
Write both the binary image and Base64 text to the output directory.
```typescript highlight=highlight-save-file
writeFileSync(`${OUTPUT_DIR}/export.png`, buffer);
writeFileSync(`${OUTPUT_DIR}/base64.txt`, dataUri);
```
Saving both formats lets you compare file sizes and verify the Base64 encoding. The binary file is useful for visual inspection.
## When to Use Base64
Base64 encoding works well for:
- Storing images in text-only databases like Redis or document stores
- Transmitting images through JSON APIs that don't support binary data
- Embedding images in HTML email templates
- Caching image data as strings in configuration files
> **Note:** Base64 increases file size by approximately 33%. For images larger than 100KB, consider binary storage or direct URL references instead.
## Troubleshooting
**Base64 string too long** — Use JPEG or WebP formats with lower quality settings. Reduce dimensions with `targetWidth` and `targetHeight` export options.
**Memory issues with large images** — Process images sequentially rather than in parallel. For very large exports, consider streaming approaches.
**Corrupted output** — Ensure the Buffer is created from the complete ArrayBuffer before encoding. Verify the Blob is fully loaded before conversion.
## API Reference
| Method | Description |
|--------|-------------|
| `engine.block.export(block, options)` | Export a block to a Blob with format options (`mimeType`, `jpegQuality`, `webpQuality`, `targetWidth`, `targetHeight`) |
| `engine.block.findByType(type)` | Find all blocks of a specific type |
| `engine.scene.loadFromURL(url)` | Load a scene from a remote URL |
| `Buffer.from(arrayBuffer)` | Create a Buffer from ArrayBuffer (Node.js) |
| `buffer.toString('base64')` | Encode Buffer as Base64 string (Node.js) |
## Next Steps
- [Export Options](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) — Explore all available export formats and configuration
- [Export to PDF](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-pdf-95e04b/) — Generate PDFs for print and document workflows
- [Partial Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export/partial-export-89aaf6/) — Export specific regions or individual elements
- [Size Limits](https://img.ly/docs/cesdk/node-native/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To PDF"
description: "Convert your design or document into a high-quality, print-ready PDF format."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/conversion/to-pdf-eb937f/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/node-native/conversion-c3fbb3/) > [To PDF](https://img.ly/docs/cesdk/node-native/conversion/to-pdf-eb937f/)
---
The CE.SDK allows you to convert JPEG, PNG, WebP, BMP and SVG images into PDFs directly in the browser—no server-side processing required. You can perform this conversion programmatically or through the user interface.
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.
## Convert to PDF Programmatically
You can use the CE.SDK to load an image, apply basic edits, and export it as a PDF programmatically. The following examples demonstrate how to convert a single image and how to merge multiple images into a single PDF.
### Convert a Single Image to PDF
The example below loads an image, applies transformations, and exports it as a PDF:
```ts example=basic-scene marker=cesdk-init-after
// Prepare an image URL
const imageURL = 'https://example.com/image.jpg';
// Create a new scene by loading the image immediately
await instance.createFromImage(image);
// Find the automatically added graphic block with an image fill
const block = engine.block.findByType('graphic')[0];
// Apply crop with a scale ratio of 2.0
engine.block.setCropScaleRatio(block, 2.0);
// Export as PDF Blob
const page = engine.scene.getCurrentPage();
const blob = await engine.block.export(page, { mimeType: 'application/pdf' });
// You can now save it or display it in your application
```
### Combine Multiple Images into a Single PDF
The example below demonstrates how to merge multiple images into a single PDF document:
```ts example=merge-images marker=cesdk-init-after
// Prepare image URLs
const images = [
'https://example.com/image1.jpg',
'https://example.com/image2.png',
'https://example.com/image3.webp',
];
// Create an empty scene with a 'VerticalStack' layout
const scene = await engine.scene.create('VerticalStack');
const [stack] = engine.block.findByType('stack');
// Load all images as pages
for (const image of images) {
// Append the new page to the stack
const page = engine.block.create('page');
engine.block.appendChild(stack, page);
// Set the image as the fill of the page
const imageFill = engine.block.createFill('image');
engine.block.setString(imageFill, 'fill/image/imageFileURI', image);
engine.block.setFill(page, imageFill);
}
// Export all images as a single PDF blob
const blob = await engine.block.export(scene, { mimeType: 'application/pdf' });
// You can now save it or display it in your application
```
## Configuring PDF Output
The SDK provides various options for customizing PDF exports. You can control resolution, compatibility and underlayer.
### 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.
### 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
```javascript
const scene = engine.scene.get();
// For maximum performance (test with your print workflow first)
engine.block.setFloat(scene, 'scene/dpi', 150); // Reduce from default 300
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: false, // Much faster
});
```
**Before using `exportPdfWithHighCompatibility: false` in production:**
- Test generated PDFs with your actual print vendor/equipment
- Verify rendering in Safari and macOS Preview if end-users will view PDFs in those applications
- Check that gradients with transparency render correctly
- Confirm your content renders properly in Adobe Acrobat and Chrome (these typically work fine)
**Safe to use `false` when:**
- PDFs go directly to professional printing (not viewed in Safari/Preview)
- Content is primarily photos and solid colors (minimal gradients with transparency)
- Performance is critical for batch processing workflows
**Keep `true` when:**
- Users view PDFs in Safari or macOS Preview
- Maximum compatibility is required
- Content has complex gradients with transparency
- You cannot test with your print workflow before production
### Customizing PDF Output
You can configure these settings when exporting:
```ts example=basic-scene marker=cesdk-init-after
const scene = engine.scene.get();
// Adjust the DPI to 72
engine.block.setFloat(scene, 'scene/dpi', 72);
// Set spot color to be used as underlayer
engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8);
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
// Set target width and height in pixels
targetWidth: 800,
targetHeight: 600,
// Increase compatibility with different PDF viewers
exportPdfWithHighCompatibility: true,
// Add an underlayer beneath the image content
exportPdfWithUnderlayer: true,
underlayerSpotColorName: 'RDG_WHITE',
underlayerOffset: -2.0,
});
```
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To PNG"
description: "Export designs and images to PNG format with compression settings and target dimensions using CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/conversion/to-png-f1660c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/node-native/conversion-c3fbb3/) > [To PNG](https://img.ly/docs/cesdk/node-native/conversion/to-png-f1660c/)
---
Export designs to PNG format with lossless quality and optional transparency support.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-conversion-to-png-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-conversion-to-png-server-js)
PNG is a lossless image format that preserves image quality and supports transparency. It's ideal for designs requiring pixel-perfect fidelity, logos, graphics with transparent backgrounds, and any content where quality cannot be compromised.
```typescript file=@cesdk_web_examples/guides-conversion-to-png-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import * as readline from 'readline';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Convert to PNG
*
* This example demonstrates:
* - Exporting programmatically with the engine API
* - All available PNG export options
* - Saving exported files to disk
*/
// Interactive menu to select which exports to run
async function selectExports(): Promise {
const options = [
{ key: '1', name: 'basic', label: 'Basic PNG export' },
{ key: '2', name: 'compressed', label: 'Compressed PNG (level 9)' },
{ key: '3', name: 'dimensions', label: 'HD PNG (1920x1080)' },
];
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
console.log(
'\nSelect exports to run (comma-separated, or press Enter for all):'
);
options.forEach((opt) => console.log(` ${opt.key}. ${opt.label}`));
rl.question('\nYour choice: ', (answer) => {
rl.close();
if (!answer.trim()) {
// Default: run all exports
resolve(options.map((opt) => opt.name));
return;
}
const selected = answer
.split(',')
.map((s) => s.trim())
.map((key) => options.find((opt) => opt.key === key)?.name)
.filter((name): name is string => name !== undefined);
resolve(selected.length > 0 ? selected : options.map((opt) => opt.name));
});
});
}
// Initialize CE.SDK engine with baseURL for asset loading
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
// Load a template scene from a remote URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
// Get the first page
const page = engine.block.findByType('page')[0];
if (page == null) {
throw new Error('No page found');
}
// Select which exports to run
const selectedExports = await selectExports();
const blobs: { name: string; blob: Blob }[] = [];
if (selectedExports.includes('basic')) {
console.log('Exporting design.png...');
// Export a block to PNG
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
blobs.push({ name: 'design.png', blob });
}
if (selectedExports.includes('compressed')) {
console.log('Exporting design-compressed.png...');
// Export with compression level (0-9)
// Higher values produce smaller files but take longer
// Quality is not affected since PNG is lossless
const blob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9 // Maximum compression
});
blobs.push({ name: 'design-compressed.png', blob });
}
if (selectedExports.includes('dimensions')) {
console.log('Exporting design-hd.png...');
// Export with target dimensions
// The block scales to fill the target while maintaining aspect ratio
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1920,
targetHeight: 1080
});
blobs.push({ name: 'design-hd.png', blob });
}
// Save exported blobs to disk
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
for (const { name, blob } of blobs) {
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/${name}`, buffer);
console.log(`✓ Exported ${name} (${(blob.size / 1024).toFixed(1)} KB)`);
}
console.log(`\n✓ Exported ${blobs.length} PNG file(s) to ${outputDir}/`);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to export designs to PNG and configure export options.
## Export to PNG
Use `engine.block.export()` to export a design block to PNG. The method returns a Blob containing the image data.
```typescript highlight=highlight-export-programmatic
// Export a block to PNG
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
```
## Compression Level
Control the file size versus export speed tradeoff using `pngCompressionLevel`. Valid values are 0-9, where higher values produce smaller files but take longer to export. Since PNG is lossless, image quality remains unchanged.
```typescript highlight=highlight-options-compression
// Export with compression level (0-9)
// Higher values produce smaller files but take longer
// Quality is not affected since PNG is lossless
const blob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9 // Maximum compression
});
```
The default compression level is 5, providing a good balance between file size and export speed.
## Target Dimensions
Resize the output by setting `targetWidth` and `targetHeight`. The block scales to fill the target dimensions while maintaining its aspect ratio.
```typescript highlight=highlight-options-dimensions
// Export with target dimensions
// The block scales to fill the target while maintaining aspect ratio
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1920,
targetHeight: 1080
});
```
## Save to Disk
Convert the exported Blob to a Buffer and write it to the file system.
```typescript highlight=highlight-save-to-disk
// Save exported blobs to disk
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
for (const { name, blob } of blobs) {
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/${name}`, buffer);
console.log(`✓ Exported ${name} (${(blob.size / 1024).toFixed(1)} KB)`);
}
console.log(`\n✓ Exported ${blobs.length} PNG file(s) to ${outputDir}/`);
```
## Cleanup
Always dispose the engine when finished to free resources.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## API Reference
| API | Description |
| --- | --- |
| `engine.block.export(block, options)` | Exports a block to a Blob with the specified options |
| `engine.scene.getPages()` | Returns an array of all page block IDs |
| `engine.dispose()` | Disposes the engine and frees resources |
## Next Steps
- [Conversion Overview](https://img.ly/docs/cesdk/node-native/conversion/overview-44dc58/) - Learn about other export formats
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Understand the full export workflow
- [To PDF](https://img.ly/docs/cesdk/node-native/conversion/to-pdf-eb937f/) - Export designs to PDF format
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add Music"
description: "Add background music and audio tracks to video projects using CE.SDK's audio block system."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-audio/audio/add-music-5b182c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
---
Add background music and audio tracks to video projects programmatically using
CE.SDK's headless engine for server-side audio processing.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-add-music-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-audio-add-music-server-js)
Audio blocks are standalone time-based blocks that play alongside video content, independent of video fills. In headless server environments, you can create audio blocks, configure time-based position and volume, and manage multiple audio tracks programmatically using the Engine API.
```typescript file=@cesdk_web_examples/guides-create-audio-add-music-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
config();
/**
* CE.SDK Server Guide: Add Music
*
* Demonstrates adding background music to video projects:
* - Creating audio blocks programmatically
* - Setting audio source URIs
* - Configuring timeline position and duration
* - Setting volume levels
* - Managing multiple audio blocks
*/
const engine = await CreativeEngine.init({});
try {
// Create a scene with a page for audio content
engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(engine.scene.get()!, page);
// Set page duration for timeline (30 seconds)
engine.block.setDuration(page, 30);
// Create an audio block for background music
const audioBlock = engine.block.create('audio');
// Set the audio source file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a';
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Append audio to the page (makes it part of the timeline)
engine.block.appendChild(page, audioBlock);
// Wait for audio to load to get duration
await engine.block.forceLoadAVResource(audioBlock);
// Get the total duration of the audio file
const totalDuration = engine.block.getAVResourceTotalDuration(audioBlock);
console.log('Audio total duration:', totalDuration.toFixed(2), 'seconds');
// Set when the audio starts on the timeline (0 = beginning)
engine.block.setTimeOffset(audioBlock, 0);
// Set how long the audio plays (use full duration or page duration)
const playbackDuration = Math.min(totalDuration, 30);
engine.block.setDuration(audioBlock, playbackDuration);
// Set the audio volume (0.0 = mute, 1.0 = full volume)
engine.block.setVolume(audioBlock, 0.8);
// Get current volume
const currentVolume = engine.block.getVolume(audioBlock);
console.log('Audio volume:', `${(currentVolume * 100).toFixed(0)}%`);
// Add a second audio track with different settings
const secondAudioBlock = engine.block.create('audio');
const secondAudioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a';
engine.block.setString(secondAudioBlock, 'audio/fileURI', secondAudioUri);
engine.block.appendChild(page, secondAudioBlock);
// Load and configure the second audio
await engine.block.forceLoadAVResource(secondAudioBlock);
const secondDuration = engine.block.getAVResourceTotalDuration(secondAudioBlock);
// Start second audio after the first one ends, at lower volume
engine.block.setTimeOffset(secondAudioBlock, playbackDuration);
engine.block.setDuration(secondAudioBlock, Math.min(secondDuration, 15));
engine.block.setVolume(secondAudioBlock, 0.5);
// Find all audio blocks in the scene
const allAudioBlocks = engine.block.findByType('audio');
console.log('\nTotal audio blocks:', allAudioBlocks.length);
// Get information about each audio block
allAudioBlocks.forEach((block, index) => {
const uri = engine.block.getString(block, 'audio/fileURI');
const timeOffset = engine.block.getTimeOffset(block);
const duration = engine.block.getDuration(block);
const volume = engine.block.getVolume(block);
console.log(`\nAudio block ${index + 1}:`);
console.log(` File: ${uri.split('/').pop()}`);
console.log(` Time offset: ${timeOffset.toFixed(2)}s`);
console.log(` Duration: ${duration.toFixed(2)}s`);
console.log(` Volume: ${(volume * 100).toFixed(0)}%`);
});
// Demonstrate removing an audio block
if (allAudioBlocks.length > 1) {
const blockToRemove = allAudioBlocks[1];
// Destroy the block to remove it and free resources
engine.block.destroy(blockToRemove);
console.log('\nRemoved second audio block');
console.log(
'Remaining audio blocks:',
engine.block.findByType('audio').length
);
}
// Export the scene to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Save the scene as a .scene file for later use or rendering
const sceneString = await engine.scene.saveToString();
writeFileSync(`${outputDir}/scene-with-audio.scene`, sceneString);
console.log('\nScene saved to output/scene-with-audio.scene');
console.log(
'The scene contains audio configuration that can be rendered using the CE.SDK Renderer.'
);
console.log('\nAdd Music guide complete!');
} finally {
engine.dispose();
}
```
This guide covers how to create and configure audio blocks programmatically using the Block API, manage multiple audio tracks, and export scene configurations for rendering.
## Setting Up the Engine
Initialize the CE.SDK engine in headless mode for server-side audio processing.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({});
```
The headless engine provides full API access to audio functionality without browser dependencies, making it ideal for batch processing, automated workflows, and server-side content generation.
## Creating a Scene
Create a scene with a page to hold audio content. Set the page dimensions and duration to define the composition length.
```typescript highlight=highlight-create-scene
// Create a scene with a page for audio content
engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(engine.scene.get()!, page);
// Set page duration for timeline (30 seconds)
engine.block.setDuration(page, 30);
```
The page duration determines the maximum playback length for the composition. Audio blocks attached to this page participate in the composition.
## Programmatic Audio Creation
### Create Audio Block
We create audio blocks using `engine.block.create('audio')` and set the source file using the `audio/fileURI` property. The audio block must be appended to a page to become part of the composition.
```typescript highlight=highlight-create-audio-block
// Create an audio block for background music
const audioBlock = engine.block.create('audio');
// Set the audio source file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a';
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Append audio to the page (makes it part of the timeline)
engine.block.appendChild(page, audioBlock);
```
Audio blocks support common formats including M4A, MP3, and WAV. The source URI can point to any accessible URL.
### Configure Time Position
Audio blocks have time-based properties that control when and how long they play. We use `setTimeOffset()` to set the start time and `setDuration()` to control playback length.
```typescript highlight=highlight-configure-timeline
// Wait for audio to load to get duration
await engine.block.forceLoadAVResource(audioBlock);
// Get the total duration of the audio file
const totalDuration = engine.block.getAVResourceTotalDuration(audioBlock);
console.log('Audio total duration:', totalDuration.toFixed(2), 'seconds');
// Set when the audio starts on the timeline (0 = beginning)
engine.block.setTimeOffset(audioBlock, 0);
// Set how long the audio plays (use full duration or page duration)
const playbackDuration = Math.min(totalDuration, 30);
engine.block.setDuration(audioBlock, playbackDuration);
```
The `forceLoadAVResource()` method ensures the audio file is loaded before we access its duration. This is important when you need to know the total length of the audio file for timing calculations.
### Configure Volume
Volume is set using `setVolume()` with values from 0.0 (mute) to 1.0 (full volume). This volume level is applied during export and affects the final rendered output.
```typescript highlight=highlight-configure-volume
// Set the audio volume (0.0 = mute, 1.0 = full volume)
engine.block.setVolume(audioBlock, 0.8);
// Get current volume
const currentVolume = engine.block.getVolume(audioBlock);
console.log('Audio volume:', `${(currentVolume * 100).toFixed(0)}%`);
```
## Adding Multiple Audio Tracks
Add multiple audio tracks to create layered soundscapes. Each track can have independent timing, duration, and volume settings.
```typescript highlight=highlight-add-second-audio
// Add a second audio track with different settings
const secondAudioBlock = engine.block.create('audio');
const secondAudioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a';
engine.block.setString(secondAudioBlock, 'audio/fileURI', secondAudioUri);
engine.block.appendChild(page, secondAudioBlock);
// Load and configure the second audio
await engine.block.forceLoadAVResource(secondAudioBlock);
const secondDuration = engine.block.getAVResourceTotalDuration(secondAudioBlock);
// Start second audio after the first one ends, at lower volume
engine.block.setTimeOffset(secondAudioBlock, playbackDuration);
engine.block.setDuration(secondAudioBlock, Math.min(secondDuration, 15));
engine.block.setVolume(secondAudioBlock, 0.5);
```
When working with multiple audio sources, use different volume levels to create a balanced mix. A common approach is to keep primary audio at higher levels and background music at lower levels.
## Managing Audio Blocks
### List Audio Blocks
Use `findByType('audio')` to retrieve all audio blocks in the scene. This is useful for building audio management interfaces or batch operations.
```typescript highlight=highlight-list-audio-blocks
// Find all audio blocks in the scene
const allAudioBlocks = engine.block.findByType('audio');
console.log('\nTotal audio blocks:', allAudioBlocks.length);
// Get information about each audio block
allAudioBlocks.forEach((block, index) => {
const uri = engine.block.getString(block, 'audio/fileURI');
const timeOffset = engine.block.getTimeOffset(block);
const duration = engine.block.getDuration(block);
const volume = engine.block.getVolume(block);
console.log(`\nAudio block ${index + 1}:`);
console.log(` File: ${uri.split('/').pop()}`);
console.log(` Time offset: ${timeOffset.toFixed(2)}s`);
console.log(` Duration: ${duration.toFixed(2)}s`);
console.log(` Volume: ${(volume * 100).toFixed(0)}%`);
});
```
### Remove Audio
To remove an audio block, call `destroy()` which removes it from the scene and frees its resources.
```typescript highlight=highlight-remove-audio
// Demonstrate removing an audio block
if (allAudioBlocks.length > 1) {
const blockToRemove = allAudioBlocks[1];
// Destroy the block to remove it and free resources
engine.block.destroy(blockToRemove);
console.log('\nRemoved second audio block');
console.log(
'Remaining audio blocks:',
engine.block.findByType('audio').length
);
}
```
Always destroy blocks that are no longer needed to prevent memory leaks, especially when processing multiple audio files in batch workflows.
## Exporting Results
Save the scene configuration for later use or rendering. In headless mode, export the scene as a `.scene` file that can be loaded and rendered using the CE.SDK Renderer.
```typescript highlight=highlight-export
// Export the scene to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Save the scene as a .scene file for later use or rendering
const sceneString = await engine.scene.saveToString();
writeFileSync(`${outputDir}/scene-with-audio.scene`, sceneString);
console.log('\nScene saved to output/scene-with-audio.scene');
console.log(
'The scene contains audio configuration that can be rendered using the CE.SDK Renderer.'
);
```
The exported scene contains all audio configuration including source URIs, time positions, durations, and volume levels. Use the CE.SDK Renderer for server-side video rendering with audio.
## Troubleshooting
**Audio Resource Loading Fails**: Verify the audio URI is accessible from the server environment. Check network connectivity and URL validity. Ensure the audio format is supported (M4A, MP3, WAV).
**Duration Returns Zero**: Call `forceLoadAVResource()` before accessing duration. The audio metadata must be loaded first.
**Volume Not Applied**: Ensure volume is set before exporting the scene. Volume settings are stored in the scene and applied during rendering.
**Memory Issues with Multiple Files**: Destroy audio blocks when they are no longer needed. For batch processing, consider processing files in smaller batches.
## API Reference
| Method | Description |
| ------------------------------------------- | --------------------------------- |
| `block.create('audio')` | Create a new audio block |
| `block.setString(id, 'audio/fileURI', uri)` | Set the audio source file |
| `block.appendChild(parent, child)` | Append audio to page |
| `block.setTimeOffset(id, seconds)` | Set when audio starts playing |
| `block.setDuration(id, seconds)` | Set audio playback duration |
| `block.setVolume(id, volume)` | Set volume (0.0 to 1.0) |
| `block.getVolume(id)` | Get current volume level |
| `block.getAVResourceTotalDuration(id)` | Get total audio file duration |
| `block.forceLoadAVResource(id)` | Force load audio resource |
| `block.findByType('audio')` | Find all audio blocks in scene |
| `block.destroy(id)` | Remove audio block |
| `scene.saveToString()` | Export scene as .scene file |
## Audio Type
A block for playing audio content.
This section describes the properties available for the **Audio Type** (`//ly.img.ubq/audio`) block type.
| Property | Type | Default | Description |
| ------------------------------ | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `audio/fileURI` | `String` | `""` | A URI referencing an audio file. |
| `audio/totalDuration` | `Double` | `"-"` | The total duration of the audio file., *(read-only)* |
| `contentFill/mode` | `Enum` | `"Cover"` | Defines how content should be resized to fit its container (e.g., Crop, Cover, Contain)., Possible values: `"Crop"`, `"Cover"`, `"Contain"` |
| `playback/duration` | `Double` | `null` | The duration in seconds for which this block should be visible. |
| `playback/looping` | `Bool` | `false` | Whether the medium should start from the beginning again or should stop. |
| `playback/muted` | `Bool` | `false` | Whether the audio is muted. |
| `playback/playing` | `Bool` | `false` | A tag that can be set on elements for their playback time to be progressed. |
| `playback/soloPlaybackEnabled` | `Bool` | `false` | A tag for blocks where playback should progress while the scene is paused. |
| `playback/speed` | `Float` | `1` | The playback speed multiplier. |
| `playback/time` | `Double` | `0` | The current playback time of the block contents in seconds. |
| `playback/timeOffset` | `Double` | `0` | The time in seconds relative to its parent at which this block should first appear. |
| `playback/trimLength` | `Double` | `"-"` | The relative duration of the clip for playback. |
| `playback/trimOffset` | `Double` | `"-"` | The time within the clip at which playback should begin, in seconds. |
| `playback/volume` | `Float` | `1` | Audio volume with a range of \[0, 1]. |
| `selected` | `Bool` | `false` | Indicates if the block is currently selected. |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add Sound Effects"
description: "Learn how to use buffers with arbitrary data to generate sound effects programmatically"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-audio/audio/add-sound-effects-9e984e/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
---
Generate sound effects programmatically using buffers with arbitrary audio data. Create notification chimes, alert tones, and melodies without external files in headless server environments.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-add-sound-effects-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-audio-add-sound-effects-server-js)
CE.SDK's headless engine lets you create audio from code using buffers. This approach generates sound effects dynamically without external files—useful for notification tones, procedural audio, or any scenario where you need to synthesize audio programmatically on the server.
```typescript file=@cesdk_web_examples/guides-create-audio-add-sound-effects-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
config();
/**
* Creates a WAV file buffer from audio parameters and a sample generator function.
*
* @param sampleRate - Sample rate in Hz (e.g., 48000)
* @param durationSeconds - Duration of the audio in seconds
* @param generator - Function that generates sample values (-1.0 to 1.0) for each time point
* @returns Uint8Array containing a stereo WAV file
*/
function createWavBuffer(
sampleRate: number,
durationSeconds: number,
generator: (time: number) => number
): Uint8Array {
const bitsPerSample = 16;
const channels = 2; // Stereo output
const numSamples = Math.floor(durationSeconds * sampleRate);
const dataSize = numSamples * channels * (bitsPerSample / 8);
// Create WAV file buffer (44-byte header + audio data)
const wavBuffer = new ArrayBuffer(44 + dataSize);
const view = new DataView(wavBuffer);
// RIFF chunk descriptor
view.setUint32(0, 0x52494646, false); // "RIFF"
view.setUint32(4, 36 + dataSize, true); // File size - 8
view.setUint32(8, 0x57415645, false); // "WAVE"
// fmt sub-chunk
view.setUint32(12, 0x666d7420, false); // "fmt "
view.setUint32(16, 16, true); // Sub-chunk size (16 for PCM)
view.setUint16(20, 1, true); // Audio format (1 = PCM)
view.setUint16(22, channels, true); // Number of channels
view.setUint32(24, sampleRate, true); // Sample rate
view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true);
view.setUint16(32, channels * (bitsPerSample / 8), true); // Block align
view.setUint16(34, bitsPerSample, true); // Bits per sample
// data sub-chunk
view.setUint32(36, 0x64617461, false); // "data"
view.setUint32(40, dataSize, true); // Data size
// Generate audio samples
let offset = 44;
for (let i = 0; i < numSamples; i++) {
const time = i / sampleRate;
// Generate mono sample and duplicate to both channels
const value = generator(time);
const sample = Math.max(-32768, Math.min(32767, Math.round(value * 32767)));
view.setInt16(offset, sample, true); // Left channel
view.setInt16(offset + 2, sample, true); // Right channel
offset += 4;
}
return new Uint8Array(wavBuffer);
}
/**
* Calculates an ADSR (Attack-Decay-Sustain-Release) envelope value for a note.
* The envelope shapes the volume over time, creating natural-sounding tones.
*
* @param time - Current time in seconds
* @param noteStart - When the note starts (seconds)
* @param noteDuration - Total note duration including release (seconds)
* @param attack - Time to reach peak volume (seconds)
* @param decay - Time to fall from peak to sustain level (seconds)
* @param sustain - Held volume level (0.0 to 1.0)
* @param release - Time to fade to silence (seconds)
* @returns Envelope amplitude (0.0 to 1.0)
*/
function adsr(
time: number,
noteStart: number,
noteDuration: number,
attack: number,
decay: number,
sustain: number,
release: number
): number {
const t = time - noteStart;
if (t < 0) return 0;
const noteEnd = noteDuration - release;
if (t < attack) {
// Attack phase: ramp up from 0 to 1
return t / attack;
} else if (t < attack + decay) {
// Decay phase: ramp down from 1 to sustain level
return 1 - ((t - attack) / decay) * (1 - sustain);
} else if (t < noteEnd) {
// Sustain phase: hold at sustain level
return sustain;
} else if (t < noteDuration) {
// Release phase: ramp down from sustain to 0
return sustain * (1 - (t - noteEnd) / release);
}
return 0;
}
// Musical note frequencies (Hz) for the 4th and 5th octaves
const NOTE_FREQUENCIES = {
C4: 261.63,
D4: 293.66,
E4: 329.63,
F4: 349.23,
G4: 392.0,
A4: 440.0,
B4: 493.88,
C5: 523.25,
D5: 587.33,
E5: 659.25,
F5: 698.46,
G5: 783.99,
A5: 880.0,
B5: 987.77,
C6: 1046.5
};
// Sound effect 1: Ascending "success" fanfare (2 seconds)
// Creates a triumphant feeling with overlapping notes building to a chord
const SUCCESS_CHIME = {
notes: [
// Quick ascending arpeggio
{ freq: NOTE_FREQUENCIES.C4, start: 0.0, duration: 0.3 },
{ freq: NOTE_FREQUENCIES.E4, start: 0.1, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.G4, start: 0.2, duration: 0.5 },
// Sustained major chord
{ freq: NOTE_FREQUENCIES.C5, start: 0.35, duration: 1.65 },
{ freq: NOTE_FREQUENCIES.E5, start: 0.4, duration: 1.6 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.45, duration: 1.55 }
],
totalDuration: 2.0
};
// Sound effect 2: Gentle notification melody (2 seconds)
// A musical phrase that resolves pleasantly
const NOTIFICATION_MELODY = {
notes: [
{ freq: NOTE_FREQUENCIES.E5, start: 0.0, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.25, duration: 0.5 },
{ freq: NOTE_FREQUENCIES.A5, start: 0.6, duration: 0.3 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.85, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.E5, start: 1.15, duration: 0.85 }
],
totalDuration: 2.0
};
// Sound effect 3: Alert/warning tone (2 seconds)
// Descending pattern that grabs attention
const ALERT_TONE = {
notes: [
// Attention-grabbing high notes
{ freq: NOTE_FREQUENCIES.A5, start: 0.0, duration: 0.25 },
{ freq: NOTE_FREQUENCIES.A5, start: 0.3, duration: 0.25 },
// Descending resolution
{ freq: NOTE_FREQUENCIES.F5, start: 0.6, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.D5, start: 0.9, duration: 0.5 },
{ freq: NOTE_FREQUENCIES.A4, start: 1.3, duration: 0.7 }
],
totalDuration: 2.0
};
/**
* CE.SDK Server Guide: Add Sound Effects
*
* This example demonstrates:
* - Creating audio buffers with arbitrary data
* - Generating sound effects programmatically (chimes, melodies, alerts)
* - Using WAV format for in-memory audio
* - Positioning sound effects on the timeline
* - Exporting the scene for rendering
*/
const engine = await CreativeEngine.init({});
try {
// Create a scene with a page for audio content
engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(engine.scene.get()!, page);
// Calculate total duration: 3 effects x 2s + 2 gaps x 0.5s = 7s
const effectDuration = 2.0;
const gapDuration = 0.5;
const totalDuration = 3 * effectDuration + 2 * gapDuration; // 7 seconds
// Set page duration to match total effects length
engine.block.setDuration(page, totalDuration);
const sampleRate = 48000;
// Create the "success chime" sound effect
const chimeBuffer = engine.editor.createBuffer();
// Generate the chime using our helper function
const chimeWav = createWavBuffer(
sampleRate,
SUCCESS_CHIME.totalDuration,
(time) => {
let sample = 0;
// Mix all notes together
for (const note of SUCCESS_CHIME.notes) {
// Calculate envelope for this note
const envelope = adsr(
time,
note.start,
note.duration,
0.02, // Soft attack (20ms)
0.08, // Gentle decay (80ms)
0.7, // Sustain at 70%
0.25 // Smooth release (250ms)
);
if (envelope > 0) {
// Generate sine wave with slight harmonics for richness
const fundamental = Math.sin(2 * Math.PI * note.freq * time);
const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.25;
const harmonic3 = Math.sin(6 * Math.PI * note.freq * time) * 0.1;
sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.3;
}
}
return sample;
}
);
// Write WAV data to the buffer
engine.editor.setBufferData(chimeBuffer, 0, chimeWav);
// Create audio block for the chime (starts at 0s)
const chimeBlock = engine.block.create('audio');
engine.block.appendChild(page, chimeBlock);
engine.block.setString(chimeBlock, 'audio/fileURI', chimeBuffer);
engine.block.setTimeOffset(chimeBlock, 0);
engine.block.setDuration(chimeBlock, SUCCESS_CHIME.totalDuration);
engine.block.setVolume(chimeBlock, 0.8);
console.log('Created success chime at 0s');
// Create the "notification melody" sound effect
const melodyBuffer = engine.editor.createBuffer();
const melodyWav = createWavBuffer(
sampleRate,
NOTIFICATION_MELODY.totalDuration,
(time) => {
let sample = 0;
for (const note of NOTIFICATION_MELODY.notes) {
const envelope = adsr(
time,
note.start,
note.duration,
0.01, // Soft attack (10ms)
0.06, // Gentle decay (60ms)
0.6, // Sustain at 60%
0.2 // Smooth release (200ms)
);
if (envelope > 0) {
// Pure sine wave with light 2nd harmonic for gentle tone
const fundamental = Math.sin(2 * Math.PI * note.freq * time);
const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.15;
sample += (fundamental + harmonic2) * envelope * 0.4;
}
}
return sample;
}
);
engine.editor.setBufferData(melodyBuffer, 0, melodyWav);
// Starts at 2.5s (after 2s effect + 0.5s gap)
const melodyBlock = engine.block.create('audio');
engine.block.appendChild(page, melodyBlock);
engine.block.setString(melodyBlock, 'audio/fileURI', melodyBuffer);
engine.block.setTimeOffset(melodyBlock, effectDuration + gapDuration); // 2.5s
engine.block.setDuration(melodyBlock, NOTIFICATION_MELODY.totalDuration);
engine.block.setVolume(melodyBlock, 0.8);
console.log('Created notification melody at 2.5s');
// Create the "alert" sound effect
const alertBuffer = engine.editor.createBuffer();
const alertWav = createWavBuffer(
sampleRate,
ALERT_TONE.totalDuration,
(time) => {
let sample = 0;
for (const note of ALERT_TONE.notes) {
const envelope = adsr(
time,
note.start,
note.duration,
0.005, // Sharp attack (5ms)
0.05, // Quick decay (50ms)
0.5, // Sustain at 50%
0.15 // Medium release (150ms)
);
if (envelope > 0) {
// Slightly brighter tone for alert
const fundamental = Math.sin(2 * Math.PI * note.freq * time);
const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.2;
const harmonic3 = Math.sin(6 * Math.PI * note.freq * time) * 0.15;
sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.35;
}
}
return sample;
}
);
engine.editor.setBufferData(alertBuffer, 0, alertWav);
// Starts at 5s (after 2 effects + 2 gaps)
const alertBlock = engine.block.create('audio');
engine.block.appendChild(page, alertBlock);
engine.block.setString(alertBlock, 'audio/fileURI', alertBuffer);
engine.block.setTimeOffset(alertBlock, 2 * (effectDuration + gapDuration)); // 5s
engine.block.setDuration(alertBlock, ALERT_TONE.totalDuration);
engine.block.setVolume(alertBlock, 0.75);
console.log('Created alert tone at 5s');
// Export the scene as an archive containing all audio data
// saveToArchive() packages the scene with all embedded resources (including buffers)
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene archive with all audio buffers embedded
const archiveBlob = await engine.scene.saveToArchive();
const archiveBuffer = Buffer.from(await archiveBlob.arrayBuffer());
writeFileSync('output/sound-effects.zip', archiveBuffer);
console.log('');
console.log('Exported scene archive to output/sound-effects.zip');
console.log('Scene contains:');
console.log(' - Scene structure with audio timeline');
console.log(' - 3 embedded sound effect audio files');
console.log('');
console.log('Sound effects guide completed successfully.');
console.log(
`Timeline: Success (0s), Melody (2.5s), Alert (5s) - each 2s, total ${totalDuration}s`
);
console.log('');
console.log('Audio blocks created with:');
console.log(' - 3 programmatically generated sound effects');
console.log(' - Each effect with unique tonal character');
console.log(' - Scene exported with embedded audio for later use');
} finally {
engine.dispose();
}
```
This guide covers working with buffers to create audio data and position it in the composition in a headless Node.js environment.
## Setting Up the Engine
Initialize CE.SDK's headless engine for server-side audio generation:
```typescript highlight-setup
const engine = await CreativeEngine.init({});
```
The headless engine provides full API access for buffer creation and audio synthesis without browser dependencies.
## Working with Buffers
CE.SDK provides a buffer API for creating and managing arbitrary binary data in memory. Use buffers when you need to generate content programmatically rather than loading from files.
### Creating a Buffer
Create a buffer with `engine.editor.createBuffer()`, which returns a URI you can use to reference the buffer:
```typescript highlight-buffer-create
// Create the "success chime" sound effect
const chimeBuffer = engine.editor.createBuffer();
```
### Writing Data
Write data to a buffer using `engine.editor.setBufferData()`. The offset parameter specifies where to start writing:
```typescript highlight-buffer-write
// Write WAV data to the buffer
engine.editor.setBufferData(chimeBuffer, 0, chimeWav);
```
### Reading Data
Read data back from a buffer:
```typescript
const length = engine.editor.getBufferLength(buffer);
const data = engine.editor.getBufferData(buffer, 0, length);
```
### Adding an Audio Track
Create an audio block and assign the buffer URI to its `audio/fileURI` property. Append it to the page to add it to the composition:
```typescript highlight-audio-track
// Create audio block for the chime (starts at 0s)
const chimeBlock = engine.block.create('audio');
engine.block.appendChild(page, chimeBlock);
engine.block.setString(chimeBlock, 'audio/fileURI', chimeBuffer);
```
### Cleanup
Destroy buffers when no longer needed (buffers are also cleaned up automatically with the scene):
```typescript
engine.editor.destroyBuffer(buffer);
```
## Generating Audio Data
To use buffers for audio, you need valid audio data. The WAV format is straightforward to generate: a 44-byte header followed by raw PCM samples.
```typescript highlight-wav-body
const bitsPerSample = 16;
const channels = 2; // Stereo output
const numSamples = Math.floor(durationSeconds * sampleRate);
const dataSize = numSamples * channels * (bitsPerSample / 8);
// Create WAV file buffer (44-byte header + audio data)
const wavBuffer = new ArrayBuffer(44 + dataSize);
const view = new DataView(wavBuffer);
// RIFF chunk descriptor
view.setUint32(0, 0x52494646, false); // "RIFF"
view.setUint32(4, 36 + dataSize, true); // File size - 8
view.setUint32(8, 0x57415645, false); // "WAVE"
// fmt sub-chunk
view.setUint32(12, 0x666d7420, false); // "fmt "
view.setUint32(16, 16, true); // Sub-chunk size (16 for PCM)
view.setUint16(20, 1, true); // Audio format (1 = PCM)
view.setUint16(22, channels, true); // Number of channels
view.setUint32(24, sampleRate, true); // Sample rate
view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true);
view.setUint16(32, channels * (bitsPerSample / 8), true); // Block align
view.setUint16(34, bitsPerSample, true); // Bits per sample
// data sub-chunk
view.setUint32(36, 0x64617461, false); // "data"
view.setUint32(40, dataSize, true); // Data size
// Generate audio samples
let offset = 44;
for (let i = 0; i < numSamples; i++) {
const time = i / sampleRate;
// Generate mono sample and duplicate to both channels
const value = generator(time);
const sample = Math.max(-32768, Math.min(32767, Math.round(value * 32767)));
view.setInt16(offset, sample, true); // Left channel
view.setInt16(offset + 2, sample, true); // Right channel
offset += 4;
}
return new Uint8Array(wavBuffer);
```
This code builds a stereo WAV file by writing the RIFF header, format chunk, and data chunk, then iterating through time to generate samples from a generator function that returns values between -1.0 and 1.0.
## Creating Sound Effect Generators
### ADSR Envelope
Shape notes with ADSR envelopes (attack, decay, sustain, release) to avoid clicks and create natural-sounding tones:
```typescript highlight-envelope-helper
/**
* Calculates an ADSR (Attack-Decay-Sustain-Release) envelope value for a note.
* The envelope shapes the volume over time, creating natural-sounding tones.
*
* @param time - Current time in seconds
* @param noteStart - When the note starts (seconds)
* @param noteDuration - Total note duration including release (seconds)
* @param attack - Time to reach peak volume (seconds)
* @param decay - Time to fall from peak to sustain level (seconds)
* @param sustain - Held volume level (0.0 to 1.0)
* @param release - Time to fade to silence (seconds)
* @returns Envelope amplitude (0.0 to 1.0)
*/
function adsr(
time: number,
noteStart: number,
noteDuration: number,
attack: number,
decay: number,
sustain: number,
release: number
): number {
const t = time - noteStart;
if (t < 0) return 0;
const noteEnd = noteDuration - release;
if (t < attack) {
// Attack phase: ramp up from 0 to 1
return t / attack;
} else if (t < attack + decay) {
// Decay phase: ramp down from 1 to sustain level
return 1 - ((t - attack) / decay) * (1 - sustain);
} else if (t < noteEnd) {
// Sustain phase: hold at sustain level
return sustain;
} else if (t < noteDuration) {
// Release phase: ramp down from sustain to 0
return sustain * (1 - (t - noteEnd) / release);
}
return 0;
}
```
The envelope function shapes volume over time—quickly ramping up during attack, gradually falling during decay, holding steady during sustain, and fading out during release.
### Sound Effect Definitions
Define sound effects as note sequences with frequencies, start times, and durations:
```typescript highlight-sound-definitions
// Musical note frequencies (Hz) for the 4th and 5th octaves
const NOTE_FREQUENCIES = {
C4: 261.63,
D4: 293.66,
E4: 329.63,
F4: 349.23,
G4: 392.0,
A4: 440.0,
B4: 493.88,
C5: 523.25,
D5: 587.33,
E5: 659.25,
F5: 698.46,
G5: 783.99,
A5: 880.0,
B5: 987.77,
C6: 1046.5
};
// Sound effect 1: Ascending "success" fanfare (2 seconds)
// Creates a triumphant feeling with overlapping notes building to a chord
const SUCCESS_CHIME = {
notes: [
// Quick ascending arpeggio
{ freq: NOTE_FREQUENCIES.C4, start: 0.0, duration: 0.3 },
{ freq: NOTE_FREQUENCIES.E4, start: 0.1, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.G4, start: 0.2, duration: 0.5 },
// Sustained major chord
{ freq: NOTE_FREQUENCIES.C5, start: 0.35, duration: 1.65 },
{ freq: NOTE_FREQUENCIES.E5, start: 0.4, duration: 1.6 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.45, duration: 1.55 }
],
totalDuration: 2.0
};
// Sound effect 2: Gentle notification melody (2 seconds)
// A musical phrase that resolves pleasantly
const NOTIFICATION_MELODY = {
notes: [
{ freq: NOTE_FREQUENCIES.E5, start: 0.0, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.25, duration: 0.5 },
{ freq: NOTE_FREQUENCIES.A5, start: 0.6, duration: 0.3 },
{ freq: NOTE_FREQUENCIES.G5, start: 0.85, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.E5, start: 1.15, duration: 0.85 }
],
totalDuration: 2.0
};
// Sound effect 3: Alert/warning tone (2 seconds)
// Descending pattern that grabs attention
const ALERT_TONE = {
notes: [
// Attention-grabbing high notes
{ freq: NOTE_FREQUENCIES.A5, start: 0.0, duration: 0.25 },
{ freq: NOTE_FREQUENCIES.A5, start: 0.3, duration: 0.25 },
// Descending resolution
{ freq: NOTE_FREQUENCIES.F5, start: 0.6, duration: 0.4 },
{ freq: NOTE_FREQUENCIES.D5, start: 0.9, duration: 0.5 },
{ freq: NOTE_FREQUENCIES.A4, start: 1.3, duration: 0.7 }
],
totalDuration: 2.0
};
```
Each sound effect specifies a series of notes with their musical frequencies, when they start, and how long they play. Overlapping notes create chords and harmonic textures.
## Creating a Sound Effect
Combine the buffer API with the WAV helper to create a complete sound effect. This example generates a notification melody by mixing multiple notes with harmonics:
```typescript highlight-generate-melody
// Create the "notification melody" sound effect
const melodyBuffer = engine.editor.createBuffer();
const melodyWav = createWavBuffer(
sampleRate,
NOTIFICATION_MELODY.totalDuration,
(time) => {
let sample = 0;
for (const note of NOTIFICATION_MELODY.notes) {
const envelope = adsr(
time,
note.start,
note.duration,
0.01, // Soft attack (10ms)
0.06, // Gentle decay (60ms)
0.6, // Sustain at 60%
0.2 // Smooth release (200ms)
);
if (envelope > 0) {
// Pure sine wave with light 2nd harmonic for gentle tone
const fundamental = Math.sin(2 * Math.PI * note.freq * time);
const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.15;
sample += (fundamental + harmonic2) * envelope * 0.4;
}
}
return sample;
}
);
engine.editor.setBufferData(melodyBuffer, 0, melodyWav);
```
The generator function mixes overlapping notes, each with its own start time and duration. Adding harmonics at lower amplitudes creates a warmer tone than a pure sine wave.
## Positioning in Time
Audio blocks exist in the composition and can be positioned precisely. Set when audio starts and how long it plays:
```typescript highlight-timeline-position
engine.block.setTimeOffset(chimeBlock, 0);
engine.block.setDuration(chimeBlock, SUCCESS_CHIME.totalDuration);
```
### Timeline Layout Example
The example spaces three sound effects with 0.5-second gaps:
```
Timeline: |----|----|----|----|----|----|----|
0s 1s 2s 3s 4s 5s 6s 7s
Success: |====|
^ 0s (2s)
Melody: |====|
^ 2.5s (2s)
Alert: |====|
^ 5s (2s)
```
Each effect is 2 seconds with 0.5-second gaps between them, for a total duration of 7 seconds.
## Exporting the Scene
Export the scene as an archive containing all audio data. The `saveToArchive()` method packages the scene with all embedded resources, including buffer data:
```typescript highlight-export
// Export the scene as an archive containing all audio data
// saveToArchive() packages the scene with all embedded resources (including buffers)
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene archive with all audio buffers embedded
const archiveBlob = await engine.scene.saveToArchive();
const archiveBuffer = Buffer.from(await archiveBlob.arrayBuffer());
writeFileSync('output/sound-effects.zip', archiveBuffer);
console.log('');
console.log('Exported scene archive to output/sound-effects.zip');
console.log('Scene contains:');
console.log(' - Scene structure with audio timeline');
console.log(' - 3 embedded sound effect audio files');
```
> **Note:** Buffer URIs are in-memory resources and cannot be serialized with `saveToString()`. Use `saveToArchive()` to export the complete scene with embedded audio buffers.
## Troubleshooting
### No Sound
- **Check WAV format** - Ensure the 44-byte header matches the data size and parameters
- **Verify duration** - Audio blocks need duration greater than 0
- **Check buffer data** - The buffer must contain valid WAV data
### Audio Sounds Wrong
- **Clipping** - Reduce sample values if they exceed -1.0 to 1.0
- **Clicking** - Add attack/release envelope to avoid pops
- **Wrong pitch** - Verify frequency calculations and sample rate (48 kHz)
### Buffer Errors
- **Invalid WAV** - Ensure header size fields match actual data size
- **Format mismatch** - Use 16-bit PCM, stereo, 48 kHz for best compatibility
## API Reference
| Method | Description | Parameters |
| --------------------------------- | ----------------------------------------- | ------------------------------------- |
| `engine.editor.createBuffer()` | Create a new buffer resource | None |
| `engine.editor.setBufferData()` | Write data to a buffer | `uri: string, offset: number, data: Uint8Array` |
| `engine.editor.getBufferLength()` | Get the length of buffer data | `uri: string` |
| `engine.editor.getBufferData()` | Read data from a buffer | `uri: string, offset: number, length: number` |
| `engine.editor.destroyBuffer()` | Clean up buffer resources | `uri: string` |
| `engine.block.create('audio')` | Create a new audio block | `type: 'audio'` |
| `engine.block.setString()` | Set string properties (e.g., audio/fileURI) | `id: number, key: string, value: string` |
| `engine.block.setTimeOffset()` | Set when the audio block starts | `id: number, offset: number` |
| `engine.block.setDuration()` | Set the duration of the audio block | `id: number, duration: number` |
| `engine.block.setVolume()` | Set the volume level (0.0 to 1.0) | `id: number, volume: number` |
| `engine.block.appendChild()` | Add block to parent (page) | `parent: number, child: number` |
| `engine.scene.saveToArchive()` | Export scene with embedded resources | None (returns `Promise`) |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Loop Audio"
description: "Create seamless repeating audio playback for background music and sound effects using CE.SDK's audio looping system."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-audio/audio/loop-937be7/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
---
Control audio looping behavior programmatically using CE.SDK's headless engine
for server-side audio processing and automated content workflows.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-loop-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-audio-audio-loop-server-js)
Audio looping allows media to play continuously by restarting from the beginning when it reaches the end. When you set a block's duration longer than the audio length and enable looping, CE.SDK automatically repeats the audio to fill the entire duration. Server-side looping configuration is useful for batch processing, automated content generation, and preparing audio compositions for later export.
```typescript file=@cesdk_web_examples/guides-create-audio-audio-loop-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
config();
/**
* CE.SDK Server Guide: Loop Audio
*
* Demonstrates audio looping in CE.SDK:
* - Enabling looping with setLooping
* - Querying looping state with isLooping
* - Disabling looping for one-time playback
* - Combining looping with trim settings
*/
const engine = await CreativeEngine.init({});
try {
// Create a scene with a page for audio content
engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(engine.scene.get()!, page);
// Set page duration for timeline content
engine.block.setDuration(page, 30);
// Use a sample audio file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a';
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Load the audio resource to access metadata
await engine.block.forceLoadAVResource(audioBlock);
// Get the total audio duration
const audioDuration = engine.block.getDouble(
audioBlock,
'audio/totalDuration'
);
console.log(`Audio duration: ${audioDuration.toFixed(2)} seconds`);
// Enable looping for seamless repeating playback
const loopingAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, loopingAudio);
engine.block.setTimeOffset(loopingAudio, 0);
engine.block.setLooping(loopingAudio, true);
engine.block.setDuration(loopingAudio, 15);
console.log('Looping audio: enabled, duration 15 seconds');
// Check if audio block is set to loop
const isLooping = engine.block.isLooping(loopingAudio);
console.log(`Is looping: ${isLooping}`);
// Create non-looping audio for one-time playback
const nonLoopingAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, nonLoopingAudio);
engine.block.setTimeOffset(nonLoopingAudio, 16);
engine.block.setLooping(nonLoopingAudio, false);
engine.block.setDuration(nonLoopingAudio, 12);
console.log('Non-looping audio: plays once and stops');
// Combine looping with trim settings for short repeating segments
const trimmedLoopAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, trimmedLoopAudio);
engine.block.setTimeOffset(trimmedLoopAudio, 29);
// Trim to a 2-second segment starting at 1 second
engine.block.setTrimOffset(trimmedLoopAudio, 1.0);
engine.block.setTrimLength(trimmedLoopAudio, 2.0);
// Enable looping and set duration longer than trim length
engine.block.setLooping(trimmedLoopAudio, true);
engine.block.setDuration(trimmedLoopAudio, 8.0);
console.log('Trimmed loop: 2s segment loops 4 times to fill 8s duration');
// Remove the original audio block (we only need the duplicates)
engine.block.destroy(audioBlock);
// Display summary
console.log('\n--- Audio Looping Summary ---');
console.log(
`Looping audio block: looping=${engine.block.isLooping(loopingAudio)}`
);
console.log(
`Non-looping audio block: looping=${engine.block.isLooping(nonLoopingAudio)}`
);
console.log(
`Trimmed looping audio block: looping=${engine.block.isLooping(trimmedLoopAudio)}`
);
// Save the scene as a .scene file for later use or rendering
console.log('\nSaving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/audio-looping.scene', sceneString);
console.log('Exported to output/audio-looping.scene');
console.log('\nAudio looping example complete');
} finally {
engine.dispose();
}
```
This guide covers how to enable and disable audio looping, control looping behavior with duration settings, and loop trimmed audio segments using the headless Node.js engine.
## Setting Up the Engine
First, we initialize the CE.SDK engine in headless mode for server-side processing.
```typescript highlight-setup
const engine = await CreativeEngine.init({});
```
The headless engine provides full API access to audio looping operations without browser dependencies.
## Understanding Audio Looping
When looping is enabled on an audio block, CE.SDK repeats the audio content from the beginning each time it reaches the end. This continues until the block's duration is filled. For example, a 5-second audio clip with looping enabled and a 15-second duration plays three complete times.
The loop transitions are seamless—CE.SDK jumps immediately from the end back to the beginning without gaps or clicks. The audio content itself determines how smooth the loop sounds. Audio files designed for looping (with matching start and end points) create perfectly seamless loops.
## Creating Audio Blocks
### Adding Audio Content
Audio blocks use file URIs to reference audio sources. We create the block and set the audio source.
```typescript highlight-create-audio-block
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
```
The `audio/fileURI` property points to the audio file. CE.SDK supports common audio formats including MP3, M4A, WAV, and AAC.
## Enabling Audio Looping
### Loading Audio Resources
Before working with audio properties like duration or trim, we load the audio resource to ensure metadata is available.
```typescript highlight-load-audio-resource
// Load the audio resource to access metadata
await engine.block.forceLoadAVResource(audioBlock);
// Get the total audio duration
const audioDuration = engine.block.getDouble(
audioBlock,
'audio/totalDuration'
);
console.log(`Audio duration: ${audioDuration.toFixed(2)} seconds`);
```
Loading the resource provides access to the total audio duration, which helps calculate how many times the audio will loop given a specific block duration.
### Setting Looping State
We enable looping by calling `engine.block.setLooping()` with `true`. When combined with a block duration longer than the audio length, the audio repeats to fill the full duration.
```typescript highlight-enable-looping
// Enable looping for seamless repeating playback
const loopingAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, loopingAudio);
engine.block.setTimeOffset(loopingAudio, 0);
engine.block.setLooping(loopingAudio, true);
engine.block.setDuration(loopingAudio, 15);
console.log('Looping audio: enabled, duration 15 seconds');
```
In this example, if the audio is 5 seconds long and the block duration is 15 seconds, the audio loops three times (5 seconds × 3 = 15 seconds total).
## Querying and Controlling Looping
### Checking Looping State
We can check whether an audio block has looping enabled at any time.
```typescript highlight-query-looping-state
// Check if audio block is set to loop
const isLooping = engine.block.isLooping(loopingAudio);
console.log(`Is looping: ${isLooping}`);
```
This is useful when managing batch operations with multiple audio tracks, allowing us to query and update looping states dynamically.
### Disabling Looping
To play audio once without repeating, we set looping to `false`.
```typescript highlight-non-looping-audio
// Create non-looping audio for one-time playback
const nonLoopingAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, nonLoopingAudio);
engine.block.setTimeOffset(nonLoopingAudio, 16);
engine.block.setLooping(nonLoopingAudio, false);
engine.block.setDuration(nonLoopingAudio, 12);
console.log('Non-looping audio: plays once and stops');
```
With looping disabled and a duration longer than the audio length, the audio plays once and then stops, leaving silence for the remaining duration.
## Looping with Trim Settings
### Trimming Looped Audio
We can combine trimming with looping to create short repeating segments from longer audio files.
```typescript highlight-looping-with-trim
// Combine looping with trim settings for short repeating segments
const trimmedLoopAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, trimmedLoopAudio);
engine.block.setTimeOffset(trimmedLoopAudio, 29);
// Trim to a 2-second segment starting at 1 second
engine.block.setTrimOffset(trimmedLoopAudio, 1.0);
engine.block.setTrimLength(trimmedLoopAudio, 2.0);
// Enable looping and set duration longer than trim length
engine.block.setLooping(trimmedLoopAudio, true);
engine.block.setDuration(trimmedLoopAudio, 8.0);
console.log('Trimmed loop: 2s segment loops 4 times to fill 8s duration');
```
This trims the audio to a 2-second segment (from 1.0s to 3.0s of the source), then loops that segment four times to fill an 8-second duration. This technique is useful for creating rhythmic loops or extracting repeatable portions from longer audio files.
### Choosing Loop Points
For seamless loops, choose trim points where the audio content flows naturally from end to beginning. Audio with consistent rhythm, tone, and volume at trim boundaries creates the smoothest loops. Abrupt changes in content or volume at loop boundaries create noticeable transitions.
## Exporting the Scene
After configuring audio looping, we save the scene for later use or rendering. The scene file preserves all looping settings and can be loaded in any CE.SDK environment.
```typescript highlight-export
// Save the scene as a .scene file for later use or rendering
console.log('\nSaving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/audio-looping.scene', sceneString);
console.log('Exported to output/audio-looping.scene');
```
The exported `.scene` file contains all audio blocks with their looping configurations, ready for rendering with CE.SDK Renderer or further editing.
## Troubleshooting
**Audio not looping**: Verify looping is enabled with `engine.block.isLooping()` and that the block duration exceeds the audio length. Looping only takes effect when the duration allows multiple repetitions.
**Audible gaps at loop points**: Choose trim points where the audio naturally transitions from end to beginning. Audio with matching start and end volume levels creates smoother loops.
**Resource not loaded**: Always call `engine.block.forceLoadAVResource()` before accessing duration properties. Without loading the resource first, metadata like total duration won't be available.
## API Reference
| Method | Description | Parameters | Returns |
| ------------------------------------- | ---------------------------------- | ---------------------------------------------------- | --------------- |
| `engine.block.create(type)` | Create an audio block | `type: 'audio'` | `DesignBlockId` |
| `engine.block.setString(id, property, value)` | Set audio source URI | `id: DesignBlockId, property: string, value: string` | `void` |
| `engine.block.setLooping(id, enabled)` | Enable or disable audio looping | `id: DesignBlockId, enabled: boolean` | `void` |
| `engine.block.isLooping(id)` | Check if audio is set to loop | `id: DesignBlockId` | `boolean` |
| `engine.block.setDuration(id, duration)` | Set block playback duration | `id: DesignBlockId, duration: number` | `void` |
| `engine.block.getDuration(id)` | Get block duration | `id: DesignBlockId` | `number` |
| `engine.block.setTrimOffset(id, offset)` | Set trim start point | `id: DesignBlockId, offset: number` | `void` |
| `engine.block.setTrimLength(id, length)` | Set trim length | `id: DesignBlockId, length: number` | `void` |
| `engine.block.forceLoadAVResource(id)` | Load audio resource with metadata | `id: DesignBlockId` | `Promise` |
| `engine.block.getDouble(id, property)` | Get audio property value | `id: DesignBlockId, property: string` | `number` |
| `engine.scene.saveToString()` | Export scene as string | none | `Promise` |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Compositions"
description: "Combine and arrange multiple elements to create complex, multi-page, or layered design compositions."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition-db709c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/node-native/create-composition/overview-5b19c5/) - Combine and arrange multiple elements to create complex, multi-page, or layered design compositions.
- [Multi-Page Layouts](https://img.ly/docs/cesdk/node-native/create-composition/multi-page-4d2b50/) - Create and manage multi-page designs in CE.SDK for documents like brochures, presentations, and catalogs with multiple pages in a single scene.
- [Create a Collage](https://img.ly/docs/cesdk/node-native/create-composition/collage-f7d28d/) - Create collages programmatically by applying layout templates and transferring content between scenes.
- [Design a Layout](https://img.ly/docs/cesdk/node-native/create-composition/layout-b66311/) - Create structured compositions using scene layouts, positioning systems, and hierarchical block organization for collages, magazines, and multi-page documents.
- [Add a Background](https://img.ly/docs/cesdk/node-native/create-composition/add-background-375a47/) - Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks.
- [Positioning and Alignment](https://img.ly/docs/cesdk/node-native/insert-media/position-and-align-cc6b6a/) - Precisely position, align, and distribute objects using guides, snapping, and alignment tools.
- [Group and Ungroup Objects](https://img.ly/docs/cesdk/node-native/create-composition/group-and-ungroup-62565a/) - Group multiple design elements together so they move, scale, and transform as a single unit; ungroup to edit them individually.
- [Layer Management](https://img.ly/docs/cesdk/node-native/create-composition/layer-management-18f07a/) - Organize design elements using a layer stack for precise control over stacking and visibility.
- [Lock Design](https://img.ly/docs/cesdk/node-native/create-composition/lock-design-0a81de/) - Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. Control which properties users can edit at both global and block levels.
- [Blend Modes](https://img.ly/docs/cesdk/node-native/create-composition/blend-modes-ad3519/) - Apply blend modes to elements to control how colors and layers interact visually.
- [Programmatic Creation](https://img.ly/docs/cesdk/node-native/create-composition/programmatic-a688bf/) - Documentation for Programmatic Creation
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add a Background"
description: "Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/add-background-375a47/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Add a Background](https://img.ly/docs/cesdk/node-native/create-composition/add-background-375a47/)
---
Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-add-background-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-add-background-server-js)
CE.SDK provides two distinct approaches for adding backgrounds to design elements. Understanding when to use each approach ensures your designs render correctly and efficiently.
```typescript file=@cesdk_web_examples/guides-create-composition-add-background-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Add a Background
*
* Demonstrates:
* - Applying gradient fills to pages
* - Adding background colors to text blocks
* - Applying image fills to shapes
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } },
});
const page = engine.block.findByType('page')[0];
const scene = engine.scene.get()!;
engine.block.setFloat(scene, 'scene/dpi', 300);
// 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', 12);
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', 8);
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, 310);
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);
// eslint-disable-next-line no-console
console.log('Page supports fill:', pageSupportsFill);
// eslint-disable-next-line no-console
console.log('Text supports backgroundColor:', textSupportsBackground);
// eslint-disable-next-line no-console
console.log('Image supports fill:', imageSupportsFill);
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/add-background-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('Exported result to output/add-background-result.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
## Setup
Initialize the CE.SDK engine in headless mode and create a scene with a page.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Fills
Fills are visual content applied to pages and graphic blocks. Supported fill types include solid colors, linear gradients, radial gradients, and images.
### Check Fill Support
Before applying a fill, verify the block supports it with `supportsFill()`. Pages and graphic blocks typically support fills, while text blocks handle their content differently.
```typescript highlight=highlight-check-support
// Check feature support on different blocks
const pageSupportsFill = engine.block.supportsFill(page);
const textSupportsBackground =
engine.block.supportsBackgroundColor(featuredText);
const imageSupportsFill = engine.block.supportsFill(imageBlock);
// eslint-disable-next-line no-console
console.log('Page supports fill:', pageSupportsFill);
// eslint-disable-next-line no-console
console.log('Text supports backgroundColor:', textSupportsBackground);
// eslint-disable-next-line no-console
console.log('Image supports fill:', imageSupportsFill);
```
### Apply a Gradient Fill
Create a fill with `createFill()` specifying the type, configure its properties, then apply it with `setFill()`. The example below creates a linear gradient with two color stops.
```typescript highlight=highlight-page-fill
// Check if the page supports fill, then apply a pastel gradient
if (engine.block.supportsFill(page)) {
const gradientFill = engine.block.createFill('gradient/linear');
engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [
{ color: { r: 0.85, g: 0.75, b: 0.95, a: 1.0 }, stop: 0 },
{ color: { r: 0.7, g: 0.9, b: 0.95, a: 1.0 }, stop: 1 },
]);
engine.block.setFill(page, gradientFill);
}
```
The gradient transitions from a pastel purple at the start to a light cyan at the end.
### Apply an Image Fill
Image fills display images within the block's shape bounds. Create an image fill, set its URI, and apply it to a graphic block.
```typescript highlight=highlight-shape-fill
// Check if the block supports fill, then apply an image fill
if (engine.block.supportsFill(imageBlock)) {
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
}
```
The shape's corner radius creates rounded corners on the image. Image fills automatically scale to cover the shape area.
## Background Color
Background color is a dedicated property available specifically on text blocks. Unlike fills, background colors include configurable padding and corner radius, creating highlighted text effects without additional graphic blocks.
### Check Background Color Support
Use `supportsBackgroundColor()` to verify a block supports this feature. Currently, only text blocks support background colors.
```typescript highlight=highlight-check-support
// Check feature support on different blocks
const pageSupportsFill = engine.block.supportsFill(page);
const textSupportsBackground =
engine.block.supportsBackgroundColor(featuredText);
const imageSupportsFill = engine.block.supportsFill(imageBlock);
// eslint-disable-next-line no-console
console.log('Page supports fill:', pageSupportsFill);
// eslint-disable-next-line no-console
console.log('Text supports backgroundColor:', textSupportsBackground);
// eslint-disable-next-line no-console
console.log('Image supports fill:', imageSupportsFill);
```
### Apply Background Color
Enable the background color with `setBackgroundColorEnabled()`, then configure its appearance using property paths for color, padding, and corner radius.
```typescript highlight=highlight-background-color
// Add white background color to the featured text block
if (engine.block.supportsBackgroundColor(featuredText)) {
engine.block.setBackgroundColorEnabled(featuredText, true);
engine.block.setColor(featuredText, 'backgroundColor/color', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
});
engine.block.setFloat(featuredText, 'backgroundColor/paddingLeft', 16);
engine.block.setFloat(featuredText, 'backgroundColor/paddingRight', 16);
engine.block.setFloat(featuredText, 'backgroundColor/paddingTop', 10);
engine.block.setFloat(featuredText, 'backgroundColor/paddingBottom', 10);
engine.block.setFloat(featuredText, 'backgroundColor/cornerRadius', 8);
}
```
The padding properties (`backgroundColor/paddingLeft`, `backgroundColor/paddingRight`, `backgroundColor/paddingTop`, `backgroundColor/paddingBottom`) control the space between the text and the background edge. The `backgroundColor/cornerRadius` property rounds the corners.
## Export the Result
After creating the composition, export the page to a PNG file. The engine supports various export formats including PNG, JPEG, and PDF.
```typescript highlight=highlight-export
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/add-background-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('Exported result to output/add-background-result.png');
```
## Cleanup
Always dispose the engine when finished to free system resources. Using a try/finally block ensures cleanup happens even if errors occur.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Troubleshooting
### Fill Not Visible
If a fill doesn't appear:
- Ensure all color components (r, g, b) are between 0 and 1
- Check that the alpha component is greater than 0
- Verify the block supports fills with `supportsFill()`
### Background Color Not Appearing
If a background color doesn't appear:
- Confirm the block supports it with `supportsBackgroundColor()`
- Verify `setBackgroundColorEnabled(block, true)` was called
- Check that the color's alpha value is greater than 0
### Image Not Loading
If an image fill doesn't display:
- Verify the image URI is accessible
- Check server logs for network errors
- Ensure the image format is supported (PNG, JPEG, WebP)
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.supportsFill(block)` | Check if a block supports fills |
| `engine.block.createFill(type)` | Create a fill (color, gradient/linear, gradient/radial, image) |
| `engine.block.setFill(block, fill)` | Apply a fill to a block |
| `engine.block.getFill(block)` | Get the fill applied to a block |
| `engine.block.setGradientColorStops(fill, property, stops)` | Set gradient color stops |
| `engine.block.supportsBackgroundColor(block)` | Check if a block supports background color |
| `engine.block.setBackgroundColorEnabled(block, enabled)` | Enable or disable background color |
| `engine.block.isBackgroundColorEnabled(block)` | Check if background color is enabled |
| `engine.block.setColor(block, property, color)` | Set color properties |
| `engine.block.setFloat(block, property, value)` | Set float properties (padding, radius) |
| `engine.block.export(block, options)` | Export a block to an image or document |
| `engine.dispose()` | Free engine resources |
## Next Steps
Explore related topics:
- [Apply Colors](https://img.ly/docs/cesdk/node-native/colors/apply-2211e3/) - Work with RGB, CMYK, and spot colors
- [Fills Overview](https://img.ly/docs/cesdk/node-native/fills/overview-3895ee/) - Learn about all fill types in depth
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Blend Modes"
description: "Apply blend modes to elements to control how colors and layers interact visually."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/blend-modes-ad3519/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Blend Modes](https://img.ly/docs/cesdk/node-native/create-composition/blend-modes-ad3519/)
---
Control how design blocks visually blend with underlying layers using CE.SDK's
blend mode system for professional layered compositions.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-blend-modes-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-blend-modes-server-js)
Blend modes control how a block's colors combine with underlying layers, similar to blend modes in Photoshop or other design tools. CE.SDK provides 27 blend modes organized into categories: Normal, Darken, Lighten, Contrast, Inversion, and Component. Each category serves different compositing needs—darken modes make images darker, lighten modes make them brighter, and contrast modes increase midtone contrast.
```typescript file=@cesdk_web_examples/guides-create-composition-blend-modes-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import * as fs from 'fs';
import * as dotenv from 'dotenv';
dotenv.config();
async function main() {
// Initialize the Creative Engine in headless mode
const engine = await CreativeEngine.init({
license: process.env.CESDK_LICENSE || 'YOUR_CESDK_LICENSE_KEY'
});
try {
// Create a new scene with a design 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 a background image block as the base layer
const backgroundBlock = engine.block.create('graphic');
engine.block.setWidth(backgroundBlock, 800);
engine.block.setHeight(backgroundBlock, 600);
engine.block.setPositionX(backgroundBlock, 0);
engine.block.setPositionY(backgroundBlock, 0);
// Set the image fill for the background
const backgroundFill = engine.block.createFill('image');
engine.block.setString(
backgroundFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(backgroundBlock, backgroundFill);
engine.block.appendChild(page, backgroundBlock);
// Create a second image block on top for blending
const overlayBlock = engine.block.create('graphic');
engine.block.setWidth(overlayBlock, 800);
engine.block.setHeight(overlayBlock, 600);
engine.block.setPositionX(overlayBlock, 0);
engine.block.setPositionY(overlayBlock, 0);
// Set a different image fill for the overlay
const overlayFill = engine.block.createFill('image');
engine.block.setString(
overlayFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_2.jpg'
);
engine.block.setFill(overlayBlock, overlayFill);
engine.block.appendChild(page, overlayBlock);
// Check if the block supports blend modes before applying
if (engine.block.supportsBlendMode(overlayBlock)) {
// Apply the Multiply blend mode to the overlay
engine.block.setBlendMode(overlayBlock, 'Multiply');
// Retrieve and log the current blend mode
const currentMode = engine.block.getBlendMode(overlayBlock);
console.log('Current blend mode:', currentMode);
}
// Check if the block supports opacity
if (engine.block.supportsOpacity(overlayBlock)) {
// Set the opacity to 70%
engine.block.setOpacity(overlayBlock, 0.7);
}
// Retrieve and log the opacity value
const opacity = engine.block.getOpacity(overlayBlock);
console.log('Current opacity:', opacity);
// Export the blended composition to a PNG file
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
fs.writeFileSync('output.png', buffer);
console.log('Exported blended composition to output.png');
} finally {
// Always dispose of the engine when done
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers how to check blend mode support, apply blend modes programmatically, understand the available blend mode options, and combine blend modes with opacity for fine control over layer compositing.
## Checking Blend Mode Support
Before applying a blend mode, verify that the block supports it using `supportsBlendMode()`. Most graphic blocks support blend modes, but always check to avoid errors.
```typescript highlight-check-support
// Check if the block supports blend modes before applying
if (engine.block.supportsBlendMode(overlayBlock)) {
```
Blend mode support is available for graphic blocks with image or video fills, shape blocks, and text blocks. Page blocks and scene blocks typically do not support blend modes directly.
## Setting and Getting Blend Modes
Apply a blend mode with `setBlendMode()` and retrieve the current mode with `getBlendMode()`. The default blend mode is `'Normal'`, which displays the block without any blending effect.
```typescript highlight-set-blend-mode
// Apply the Multiply blend mode to the overlay
engine.block.setBlendMode(overlayBlock, 'Multiply');
```
After setting a blend mode, you can confirm the change by retrieving the current value:
```typescript highlight-get-blend-mode
// Retrieve and log the current blend mode
const currentMode = engine.block.getBlendMode(overlayBlock);
console.log('Current blend mode:', currentMode);
```
## Available Blend Modes
CE.SDK provides 27 blend modes organized into categories, each producing different visual results:
### Normal Modes
- **`PassThrough`** - Allows children of a group to blend with layers below the group
- **`Normal`** - Default mode with no blending effect
### Darken Modes
These modes darken the result by comparing the base and blend colors:
- **`Darken`** - Selects the darker of the base and blend colors
- **`Multiply`** - Multiplies colors, producing darker results (great for shadows)
- **`ColorBurn`** - Darkens base color by increasing contrast
- **`LinearBurn`** - Darkens base color by decreasing brightness
- **`DarkenColor`** - Selects the darker color based on luminosity
### Lighten Modes
These modes lighten the result by comparing colors:
- **`Lighten`** - Selects the lighter of the base and blend colors
- **`Screen`** - Multiplies the inverse of colors, producing lighter results (great for highlights)
- **`ColorDodge`** - Lightens base color by decreasing contrast
- **`LinearDodge`** - Lightens base color by increasing brightness
- **`LightenColor`** - Selects the lighter color based on luminosity
### Contrast Modes
These modes increase midtone contrast:
- **`Overlay`** - Combines Multiply and Screen based on the base color
- **`SoftLight`** - Similar to Overlay but with a softer effect
- **`HardLight`** - Similar to Overlay but based on the blend color
- **`VividLight`** - Burns or dodges colors based on the blend color
- **`LinearLight`** - Increases or decreases brightness based on blend color
- **`PinLight`** - Replaces colors based on the blend color
- **`HardMix`** - Reduces colors to white, black, or primary colors
### Inversion Modes
These modes create inverted or subtracted effects:
- **`Difference`** - Subtracts the darker from the lighter color
- **`Exclusion`** - Similar to Difference with lower contrast
- **`Subtract`** - Subtracts blend color from base color
- **`Divide`** - Divides base color by blend color
### Component Modes
These modes affect specific color components:
- **`Hue`** - Uses the hue of the blend color with base saturation and luminosity
- **`Saturation`** - Uses the saturation of the blend color
- **`Color`** - Uses the hue and saturation of the blend color
- **`Luminosity`** - Uses the luminosity of the blend color
## Combining Blend Modes with Opacity
For finer control over compositing, combine blend modes with opacity. Opacity reduces overall visibility while the blend mode affects color interaction with underlying layers.
```typescript highlight-set-opacity
// Check if the block supports opacity
if (engine.block.supportsOpacity(overlayBlock)) {
// Set the opacity to 70%
engine.block.setOpacity(overlayBlock, 0.7);
}
```
You can retrieve the current opacity value to confirm changes or read existing state:
```typescript highlight-get-opacity
// Retrieve and log the opacity value
const opacity = engine.block.getOpacity(overlayBlock);
console.log('Current opacity:', opacity);
```
## Exporting the Result
After applying blend modes and opacity, export the composed result to a file. The blended composition is rendered and saved as an image.
```typescript highlight-export
// Export the blended composition to a PNG file
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
fs.writeFileSync('output.png', buffer);
console.log('Exported blended composition to output.png');
```
## Troubleshooting
### Blend Mode Has No Visible Effect
If a blend mode doesn't produce visible changes:
- Ensure there are underlying layers for the block to blend with. Blend modes only affect compositing with content below.
- Verify the blend mode is applied to the correct block using `getBlendMode()`.
- Check that the block has visible content (fill or image) to blend.
### Cannot Set Blend Mode
If `setBlendMode()` throws an error:
- Check that `supportsBlendMode()` returns `true` for the block.
- Verify the block ID is valid and the block exists in the scene.
- Ensure you're passing a valid blend mode string from the available options.
### Unexpected Blending Results
If the visual result doesn't match expectations:
- Verify the blend mode category matches your intent (darken vs lighten vs contrast).
- Check the stacking order of blocks—blend modes affect content below the block.
- Experiment with different blend modes from the same category to find the best visual match.
## API Reference
| Method | Description |
| ---------------------------------------- | ------------------------------------------------- |
| `engine.block.supportsBlendMode(id)` | Check if a block supports blend modes |
| `engine.block.setBlendMode(id, mode)` | Set the blend mode for a block |
| `engine.block.getBlendMode(id)` | Get the current blend mode of a block |
| `engine.block.supportsOpacity(id)` | Check if a block supports opacity |
| `engine.block.setOpacity(id, opacity)` | Set the opacity for a block (0-1) |
| `engine.block.getOpacity(id)` | Get the current opacity of a block |
| `engine.block.export(id, mimeType)` | Export block content to specified format |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create a Collage"
description: "Create collages programmatically by applying layout templates and transferring content between scenes."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/collage-f7d28d/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Create a Collage](https://img.ly/docs/cesdk/node-native/create-composition/collage-f7d28d/)
---
Create collages in a headless Node.js environment by loading layout templates and transferring image content between scenes.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-collage-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-collage-server-js)
In server-side workflows, collages are created by loading a layout scene file that defines the visual structure, then transferring content from an existing scene into those positions. This approach enables batch processing of images into various collage formats.
```typescript file=@cesdk_web_examples/guides-create-composition-collage-server-js/server-js.ts reference-only
import CreativeEngine, { DesignBlockId } from '@cesdk/node';
import { writeFileSync } from 'fs';
import { config as loadEnv } from 'dotenv';
// Load environment variables
loadEnv();
/**
* Create a Collage (Server/Node.js)
*
* This example demonstrates how to create collages programmatically:
* 1. Creating a scene with images
* 2. Loading a layout scene file
* 3. Transferring content from the original scene to the layout
* 4. Exporting the final collage
*/
// Sort blocks by visual position (top-to-bottom, left-to-right)
// This ensures consistent content mapping between layouts
function visuallySortBlocks(
engine: CreativeEngine,
blocks: DesignBlockId[]
): DesignBlockId[] {
return blocks
.map((block) => ({
block,
x: Math.round(engine.block.getPositionX(block)),
y: Math.round(engine.block.getPositionY(block))
}))
.sort((a, b) => {
if (a.y === b.y) return a.x - b.x;
return a.y - b.y;
})
.map((item) => item.block);
}
// Recursively collect all descendant blocks
function getChildrenTree(
engine: CreativeEngine,
blockId: DesignBlockId
): DesignBlockId[] {
const children = engine.block.getChildren(blockId);
const result: DesignBlockId[] = [];
for (const child of children) {
result.push(child);
result.push(...getChildrenTree(engine, child));
}
return result;
}
async function run() {
let engine: CreativeEngine | undefined;
try {
const config = {
// license: process.env.CESDK_LICENSE,
logger: (message: string, logLevel?: string) => {
if (logLevel === 'ERROR' || logLevel === 'WARN') {
console.log(`[${logLevel}]`, message);
}
}
};
engine = await CreativeEngine.init(config);
console.log('✓ Engine initialized');
// Create a scene with images to arrange in a collage
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// Set page dimensions for the collage
engine.block.setWidth(page, 1080);
engine.block.setHeight(page, 1080);
// Add sample images to the page
const imageUrls = [
'https://img.ly/static/ubq_samples/imgly_logo.jpg',
'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'
];
for (let i = 0; i < imageUrls.length; i++) {
const graphic = engine.block.create('graphic');
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
imageUrls[i]
);
engine.block.setFill(graphic, imageFill);
// Position images in a simple grid (will be rearranged by layout)
engine.block.setPositionX(graphic, (i % 2) * 540);
engine.block.setPositionY(graphic, Math.floor(i / 2) * 540);
engine.block.setWidth(graphic, 540);
engine.block.setHeight(graphic, 540);
engine.block.appendChild(page, graphic);
}
console.log(`✓ Scene created with ${imageUrls.length} images`);
// Load a layout template that defines the collage structure
// The layout contains positioned placeholder blocks
const layoutUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_collage_1.scene';
const layoutSceneString = await fetch(layoutUrl).then((res) => res.text());
const layoutBlocks = await engine.block.loadFromString(layoutSceneString);
const layoutPage = layoutBlocks[0];
console.log('✓ Layout template loaded');
// Collect image blocks from both pages
const sourceBlocks = getChildrenTree(engine, page);
const sourceImages = sourceBlocks.filter(
(id) => engine!.block.getKind(id) === 'image'
);
const sortedSourceImages = visuallySortBlocks(engine, sourceImages);
const layoutChildren = getChildrenTree(engine, layoutPage);
const layoutImages = layoutChildren.filter(
(id) => engine!.block.getKind(id) === 'image'
);
const sortedLayoutImages = visuallySortBlocks(engine, layoutImages);
console.log(
`✓ Found ${sortedSourceImages.length} source images, ${sortedLayoutImages.length} layout slots`
);
// Transfer image content from source to layout positions
const transferCount = Math.min(
sortedSourceImages.length,
sortedLayoutImages.length
);
for (let i = 0; i < transferCount; i++) {
const sourceBlock = sortedSourceImages[i];
const targetBlock = sortedLayoutImages[i];
// Get the source image fill
const sourceFill = engine.block.getFill(sourceBlock);
const targetFill = engine.block.getFill(targetBlock);
// Transfer the image URI
const imageUri = engine.block.getString(
sourceFill,
'fill/image/imageFileURI'
);
engine.block.setString(targetFill, 'fill/image/imageFileURI', imageUri);
// Transfer source sets if present
try {
const sourceSet = engine.block.getSourceSet(
sourceFill,
'fill/image/sourceSet'
);
if (sourceSet.length > 0) {
engine.block.setSourceSet(
targetFill,
'fill/image/sourceSet',
sourceSet
);
}
} catch {
// Source set not available, skip
}
// Reset crop to fill the new frame dimensions
engine.block.resetCrop(targetBlock);
// Transfer placeholder behavior if supported
if (engine.block.supportsPlaceholderBehavior(sourceBlock)) {
engine.block.setPlaceholderBehaviorEnabled(
targetBlock,
engine.block.isPlaceholderBehaviorEnabled(sourceBlock)
);
}
}
console.log(`✓ Transferred ${transferCount} images to layout`);
// Transfer text content (if both scenes have text blocks)
const sourceTexts = sourceBlocks.filter(
(id) => engine!.block.getType(id) === '//ly.img.ubq/text'
);
const layoutTexts = layoutChildren.filter(
(id) => engine!.block.getType(id) === '//ly.img.ubq/text'
);
const sortedSourceTexts = visuallySortBlocks(engine, sourceTexts);
const sortedLayoutTexts = visuallySortBlocks(engine, layoutTexts);
const textTransferCount = Math.min(
sortedSourceTexts.length,
sortedLayoutTexts.length
);
for (let i = 0; i < textTransferCount; i++) {
const sourceText = sortedSourceTexts[i];
const targetText = sortedLayoutTexts[i];
// Transfer text content
const text = engine.block.getString(sourceText, 'text/text');
engine.block.setString(targetText, 'text/text', text);
// Transfer font
const fontUri = engine.block.getString(sourceText, 'text/fontFileUri');
const typeface = engine.block.getTypeface(sourceText);
engine.block.setFont(targetText, fontUri, typeface);
// Transfer text color
const textColor = engine.block.getColor(sourceText, 'fill/solid/color');
engine.block.setColor(targetText, 'fill/solid/color', textColor);
}
if (textTransferCount > 0) {
console.log(`✓ Transferred ${textTransferCount} text blocks`);
}
// Export the final collage
// Use the layout page dimensions for the export
const layoutWidth = engine.block.getWidth(layoutPage);
const layoutHeight = engine.block.getHeight(layoutPage);
const blob = await engine.block.export(layoutPage, {
mimeType: 'image/png',
targetWidth: layoutWidth,
targetHeight: layoutHeight
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('collage-output.png', buffer);
console.log(
`✓ Exported collage to collage-output.png (${layoutWidth}x${layoutHeight})`
);
// Clean up temporary blocks
engine.block.destroy(page);
console.log('✓ Cleanup completed');
console.log('\n✓ Collage created successfully!');
} catch (error) {
console.error('Error:', error);
process.exit(1);
} finally {
// Always dispose the engine
engine?.dispose();
console.log('\n✓ Engine disposed');
}
}
// Run the example
run();
```
This guide covers how to load layout templates, implement visual sorting to map content between layouts, transfer images and text while preserving properties, and export the final collage.
## How Server-Side Collages Work
Server-side collage creation follows this workflow:
1. **Create a scene with images** — Load or create blocks containing your source images
2. **Load a layout template** — Fetch a scene file that defines the collage structure
3. **Sort blocks visually** — Order blocks by position for consistent mapping
4. **Transfer content** — Copy image URIs and text from source to layout positions
5. **Export the result** — Generate the final collage image
The layout template defines where images appear in the final output. By sorting blocks visually (top-to-bottom, left-to-right), content maps predictably between different layouts.
## Initialize the Engine
Start by initializing the headless engine.
```typescript highlight=highlight-setup
const config = {
// license: process.env.CESDK_LICENSE,
logger: (message: string, logLevel?: string) => {
if (logLevel === 'ERROR' || logLevel === 'WARN') {
console.log(`[${logLevel}]`, message);
}
}
};
engine = await CreativeEngine.init(config);
console.log('✓ Engine initialized');
```
## Create a Scene with Images
Create a scene containing the images you want to arrange in the collage. In production, you might load these from a database or API.
```typescript highlight=highlight-create-scene
// Create a scene with images to arrange in a collage
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// Set page dimensions for the collage
engine.block.setWidth(page, 1080);
engine.block.setHeight(page, 1080);
// Add sample images to the page
const imageUrls = [
'https://img.ly/static/ubq_samples/imgly_logo.jpg',
'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'
];
for (let i = 0; i < imageUrls.length; i++) {
const graphic = engine.block.create('graphic');
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
imageUrls[i]
);
engine.block.setFill(graphic, imageFill);
// Position images in a simple grid (will be rearranged by layout)
engine.block.setPositionX(graphic, (i % 2) * 540);
engine.block.setPositionY(graphic, Math.floor(i / 2) * 540);
engine.block.setWidth(graphic, 540);
engine.block.setHeight(graphic, 540);
engine.block.appendChild(page, graphic);
}
console.log(`✓ Scene created with ${imageUrls.length} images`);
```
## Load a Layout Template
Load a layout scene file that defines the collage structure. Layout templates contain positioned image blocks that serve as placeholders.
```typescript highlight=highlight-load-layout
// Load a layout template that defines the collage structure
// The layout contains positioned placeholder blocks
const layoutUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_collage_1.scene';
const layoutSceneString = await fetch(layoutUrl).then((res) => res.text());
const layoutBlocks = await engine.block.loadFromString(layoutSceneString);
const layoutPage = layoutBlocks[0];
console.log('✓ Layout template loaded');
```
## Sort Blocks by Visual Position
Visual sorting ensures consistent content mapping regardless of the order blocks were created. Blocks are sorted top-to-bottom, then left-to-right.
```typescript highlight=highlight-visual-sort
// Sort blocks by visual position (top-to-bottom, left-to-right)
// This ensures consistent content mapping between layouts
function visuallySortBlocks(
engine: CreativeEngine,
blocks: DesignBlockId[]
): DesignBlockId[] {
return blocks
.map((block) => ({
block,
x: Math.round(engine.block.getPositionX(block)),
y: Math.round(engine.block.getPositionY(block))
}))
.sort((a, b) => {
if (a.y === b.y) return a.x - b.x;
return a.y - b.y;
})
.map((item) => item.block);
}
```
## Collect Blocks from Both Scenes
Recursively collect all descendant blocks, then filter by type to separate images from other content.
```typescript highlight=highlight-collect-blocks
// Collect image blocks from both pages
const sourceBlocks = getChildrenTree(engine, page);
const sourceImages = sourceBlocks.filter(
(id) => engine!.block.getKind(id) === 'image'
);
const sortedSourceImages = visuallySortBlocks(engine, sourceImages);
const layoutChildren = getChildrenTree(engine, layoutPage);
const layoutImages = layoutChildren.filter(
(id) => engine!.block.getKind(id) === 'image'
);
const sortedLayoutImages = visuallySortBlocks(engine, layoutImages);
console.log(
`✓ Found ${sortedSourceImages.length} source images, ${sortedLayoutImages.length} layout slots`
);
```
## Transfer Image Content
Copy image URIs and source sets from source blocks to layout positions. Reset the crop on each target block so images fill their new frames.
```typescript highlight=highlight-transfer-images
// Transfer image content from source to layout positions
const transferCount = Math.min(
sortedSourceImages.length,
sortedLayoutImages.length
);
for (let i = 0; i < transferCount; i++) {
const sourceBlock = sortedSourceImages[i];
const targetBlock = sortedLayoutImages[i];
// Get the source image fill
const sourceFill = engine.block.getFill(sourceBlock);
const targetFill = engine.block.getFill(targetBlock);
// Transfer the image URI
const imageUri = engine.block.getString(
sourceFill,
'fill/image/imageFileURI'
);
engine.block.setString(targetFill, 'fill/image/imageFileURI', imageUri);
// Transfer source sets if present
try {
const sourceSet = engine.block.getSourceSet(
sourceFill,
'fill/image/sourceSet'
);
if (sourceSet.length > 0) {
engine.block.setSourceSet(
targetFill,
'fill/image/sourceSet',
sourceSet
);
}
} catch {
// Source set not available, skip
}
// Reset crop to fill the new frame dimensions
engine.block.resetCrop(targetBlock);
// Transfer placeholder behavior if supported
if (engine.block.supportsPlaceholderBehavior(sourceBlock)) {
engine.block.setPlaceholderBehaviorEnabled(
targetBlock,
engine.block.isPlaceholderBehaviorEnabled(sourceBlock)
);
}
}
console.log(`✓ Transferred ${transferCount} images to layout`);
```
**Key methods:**
- `getFill()` and `setString()` — Transfer the image URI between fills
- `getSourceSet()` and `setSourceSet()` — Preserve responsive image variants
- `resetCrop()` — Adjust the image crop to fill the new frame dimensions
- `supportsPlaceholderBehavior()` — Check and transfer placeholder settings
## Transfer Text Content
If both scenes contain text blocks, transfer text content, fonts, and colors in visual order.
```typescript highlight=highlight-transfer-text
// Transfer text content (if both scenes have text blocks)
const sourceTexts = sourceBlocks.filter(
(id) => engine!.block.getType(id) === '//ly.img.ubq/text'
);
const layoutTexts = layoutChildren.filter(
(id) => engine!.block.getType(id) === '//ly.img.ubq/text'
);
const sortedSourceTexts = visuallySortBlocks(engine, sourceTexts);
const sortedLayoutTexts = visuallySortBlocks(engine, layoutTexts);
const textTransferCount = Math.min(
sortedSourceTexts.length,
sortedLayoutTexts.length
);
for (let i = 0; i < textTransferCount; i++) {
const sourceText = sortedSourceTexts[i];
const targetText = sortedLayoutTexts[i];
// Transfer text content
const text = engine.block.getString(sourceText, 'text/text');
engine.block.setString(targetText, 'text/text', text);
// Transfer font
const fontUri = engine.block.getString(sourceText, 'text/fontFileUri');
const typeface = engine.block.getTypeface(sourceText);
engine.block.setFont(targetText, fontUri, typeface);
// Transfer text color
const textColor = engine.block.getColor(sourceText, 'fill/solid/color');
engine.block.setColor(targetText, 'fill/solid/color', textColor);
}
if (textTransferCount > 0) {
console.log(`✓ Transferred ${textTransferCount} text blocks`);
}
```
## Export the Collage
Export the layout page with the transferred content. The output dimensions match the layout template.
```typescript highlight=highlight-export
// Export the final collage
// Use the layout page dimensions for the export
const layoutWidth = engine.block.getWidth(layoutPage);
const layoutHeight = engine.block.getHeight(layoutPage);
const blob = await engine.block.export(layoutPage, {
mimeType: 'image/png',
targetWidth: layoutWidth,
targetHeight: layoutHeight
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('collage-output.png', buffer);
console.log(
`✓ Exported collage to collage-output.png (${layoutWidth}x${layoutHeight})`
);
```
## Clean Up
Destroy temporary blocks and dispose of the engine when finished.
```typescript highlight=highlight-cleanup
// Clean up temporary blocks
engine.block.destroy(page);
console.log('✓ Cleanup completed');
```
## Handling Mismatched Slot Counts
When the source has more images than the layout has slots, extra images are ignored. When the layout has more slots, some remain empty or keep their placeholder content.
```typescript
// Transfer only as many images as both sides support
const transferCount = Math.min(
sortedSourceImages.length,
sortedLayoutImages.length
);
```
For production use, consider:
- Selecting layouts based on image count
- Filling empty slots with placeholder images
- Prioritizing images by metadata or user selection
## Troubleshooting
### Images Not Appearing in Layout
Verify the layout template contains image blocks with the correct kind. Check that `engine.block.getKind(id)` returns `'image'` for your target blocks.
### Content in Wrong Positions
Visual sorting depends on block coordinates. If blocks have identical Y positions, they sort by X. Ensure layout templates have distinct positions for each slot.
### Source Sets Not Transferring
Not all image fills have source sets. Wrap the transfer in a try-catch to handle blocks without this property.
### Layout Template Not Loading
Check that the layout URL is accessible from your server environment. In production, host layout templates on your own infrastructure or use a CDN.
## API Reference
| Method | Category | Purpose |
|--------|----------|---------|
| `engine.scene.create()` | Scene | Create an empty scene |
| `engine.block.create()` | Block | Create blocks of various types |
| `engine.block.createFill()` | Fill | Create fill objects for blocks |
| `engine.block.setFill()` | Fill | Assign a fill to a block |
| `engine.block.getFill()` | Fill | Get the fill assigned to a block |
| `engine.block.loadFromString()` | Import | Load blocks from a scene string |
| `engine.block.getChildren()` | Hierarchy | Get direct children of a block |
| `engine.block.appendChild()` | Hierarchy | Add a child to a block |
| `engine.block.getKind()` | Property | Get the semantic kind of a block |
| `engine.block.getType()` | Property | Get the type of a block |
| `engine.block.getPositionX()` | Layout | Get X position of a block |
| `engine.block.getPositionY()` | Layout | Get Y position of a block |
| `engine.block.getString()` | Property | Get string property value |
| `engine.block.setString()` | Property | Set string property value |
| `engine.block.getSourceSet()` | Fill | Get image source set |
| `engine.block.setSourceSet()` | Fill | Set image source set |
| `engine.block.resetCrop()` | Crop | Reset crop to fill frame |
| `engine.block.supportsPlaceholderBehavior()` | Placeholder | Check placeholder support |
| `engine.block.isPlaceholderBehaviorEnabled()` | Placeholder | Check if placeholder is enabled |
| `engine.block.setPlaceholderBehaviorEnabled()` | Placeholder | Enable/disable placeholder |
| `engine.block.getTypeface()` | Text | Get typeface of a text block |
| `engine.block.setFont()` | Text | Set font for a text block |
| `engine.block.getColor()` | Property | Get color property value |
| `engine.block.setColor()` | Property | Set color property value |
| `engine.block.getWidth()` | Layout | Get width of a block |
| `engine.block.getHeight()` | Layout | Get height of a block |
| `engine.block.export()` | Export | Export a block to an image |
| `engine.block.destroy()` | Lifecycle | Destroy a block |
| `engine.dispose()` | Lifecycle | Clean up engine resources |
## Related Guides
- [Apply Templates](https://img.ly/docs/cesdk/node-native/use-templates/apply-template-35c73e/) — Loading complete templates instead of transferring content
- [Headless Mode](https://img.ly/docs/cesdk/node-native/concepts/headless-mode-24ab98/) — Server-side processing fundamentals
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) — Export options and formats
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Group and Ungroup Objects"
description: "Group multiple design elements together so they move, scale, and transform as a single unit; ungroup to edit them individually."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/group-and-ungroup-62565a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Group and Ungroup Objects](https://img.ly/docs/cesdk/node-native/create-composition/group-and-ungroup-62565a/)
---
Group multiple blocks to move, scale, and transform them as a single unit; ungroup to edit them individually.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-grouping-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-grouping-server-js)
Groups let you treat multiple blocks as a cohesive unit. Grouped blocks move, scale, and rotate together while maintaining their relative positions. Groups can contain other groups, enabling hierarchical compositions.
> **Note:** Groups are not currently available when editing videos.
```typescript file=@cesdk_web_examples/guides-create-composition-grouping-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Group and Ungroup Objects
*
* Demonstrates:
* - Creating multiple graphic blocks
* - Checking if blocks can be grouped
* - Grouping blocks together
* - Finding and inspecting groups
* - Ungrouping blocks
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } },
});
const page = engine.block.findByType('page')[0];
const scene = engine.scene.get()!;
engine.block.setFloat(scene, 'scene/dpi', 300);
// 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]);
// eslint-disable-next-line no-console
console.log('Blocks can be grouped:', canGroup);
// Group the blocks together
let groupId: number | null = null;
if (canGroup) {
groupId = engine.block.group([block1, block2, block3]);
// eslint-disable-next-line no-console
console.log('Created group with ID:', groupId);
// Find all groups in the scene
const allGroups = engine.block.findByType('group');
// eslint-disable-next-line no-console
console.log('Number of groups in scene:', allGroups.length);
// Check the type of the group block
const groupType = engine.block.getType(groupId);
// eslint-disable-next-line no-console
console.log('Group block type:', groupType);
// Get the members of the group
const members = engine.block.getChildren(groupId);
// eslint-disable-next-line no-console
console.log('Group has', members.length, 'members');
// Ungroup the blocks to make them independent again
engine.block.ungroup(groupId);
// eslint-disable-next-line no-console
console.log('Ungrouped blocks');
// Verify blocks are no longer in a group
const groupsAfterUngroup = engine.block.findByType('group');
// eslint-disable-next-line no-console
console.log('Groups after ungrouping:', groupsAfterUngroup.length);
// Re-group for the final export
groupId = engine.block.group([block1, block2, block3]);
}
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/grouping-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('Exported result to output/grouping-result.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to check if blocks can be grouped, create and dissolve groups, find existing groups in a scene, and export the result.
## Understanding Groups
Groups are blocks with type `'group'` that contain child blocks as members. Transformations applied to a group affect all members proportionally—position, scale, and rotation cascade to all children.
Groups can be nested, meaning a group can contain other groups. This enables complex hierarchical structures where multiple logical units can be combined and manipulated together.
> **Note:** **What cannot be grouped*** Scene blocks cannot be grouped
> * Blocks already part of a group cannot be grouped again until ungrouped
## Create the Blocks
Create several graphic blocks that we'll group together. Each block has a different color fill to make them visually distinct.
```typescript highlight=highlight-create-blocks
// Create a graphic block with a colored rectangle shape
const block1 = engine.block.create('graphic');
const shape1 = engine.block.createShape('rect');
engine.block.setShape(block1, shape1);
engine.block.setWidth(block1, 120);
engine.block.setHeight(block1, 120);
engine.block.setPositionX(block1, 200);
engine.block.setPositionY(block1, 240);
const fill1 = engine.block.createFill('color');
engine.block.setColor(fill1, 'fill/color/value', {
r: 0.4,
g: 0.6,
b: 0.9,
a: 1.0,
});
engine.block.setFill(block1, fill1);
engine.block.appendChild(page, block1);
```
## Check If Blocks Can Be Grouped
Before grouping, verify that the selected blocks can be grouped using `engine.block.isGroupable()`. This method returns `true` if all blocks can be grouped together, or `false` if any block is a scene or already belongs to a group.
```typescript highlight=highlight-check-groupable
// Check if the blocks can be grouped together
const canGroup = engine.block.isGroupable([block1, block2, block3]);
// eslint-disable-next-line no-console
console.log('Blocks can be grouped:', canGroup);
```
## Create a Group
Use `engine.block.group()` to combine multiple blocks into a new group. The method returns the ID of the newly created group block. The group inherits the combined bounding box of its members.
```typescript highlight=highlight-create-group
// Group the blocks together
let groupId: number | null = null;
if (canGroup) {
groupId = engine.block.group([block1, block2, block3]);
// eslint-disable-next-line no-console
console.log('Created group with ID:', groupId);
```
## Find and Inspect Groups
Discover groups in a scene and inspect their contents using `engine.block.findByType()`, `engine.block.getType()`, and `engine.block.getChildren()`.
```typescript highlight=highlight-find-groups
// Find all groups in the scene
const allGroups = engine.block.findByType('group');
// eslint-disable-next-line no-console
console.log('Number of groups in scene:', allGroups.length);
// Check the type of the group block
const groupType = engine.block.getType(groupId);
// eslint-disable-next-line no-console
console.log('Group block type:', groupType);
// Get the members of the group
const members = engine.block.getChildren(groupId);
// eslint-disable-next-line no-console
console.log('Group has', members.length, 'members');
```
Use `engine.block.findByType('group')` to get all group blocks in the current scene. Use `engine.block.getType()` to check if a specific block is a group (returns `'//ly.img.ubq/group'`). Use `engine.block.getChildren()` to get the member blocks of a group.
## Ungroup Blocks
Use `engine.block.ungroup()` to dissolve a group and release its children back to the parent container. The children maintain their current positions in the scene.
```typescript highlight=highlight-ungroup
// Ungroup the blocks to make them independent again
engine.block.ungroup(groupId);
// eslint-disable-next-line no-console
console.log('Ungrouped blocks');
// Verify blocks are no longer in a group
const groupsAfterUngroup = engine.block.findByType('group');
// eslint-disable-next-line no-console
console.log('Groups after ungrouping:', groupsAfterUngroup.length);
```
## Export the Result
After creating the composition, export the page to a PNG file. The engine supports various export formats including PNG, JPEG, and PDF.
```typescript highlight=highlight-export
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/grouping-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('Exported result to output/grouping-result.png');
```
## Cleanup
Always dispose the engine when finished to free system resources. Using a try/finally block ensures cleanup happens even if errors occur.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Troubleshooting
### Blocks Cannot Be Grouped
If `engine.block.isGroupable()` returns `false`:
- Check if any of the blocks is a scene block (scenes cannot be grouped)
- Check if any block is already part of a group (use `engine.block.getParent()` to verify)
- Ensure all block IDs are valid
### Group Not Visible After Creation
If a newly created group is not visible:
- Check that the member blocks were visible before grouping
- Verify the group's opacity using `engine.block.getOpacity()`
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.isGroupable(ids)` | Check if blocks can be grouped together |
| `engine.block.group(ids)` | Create a group from multiple blocks |
| `engine.block.ungroup(id)` | Dissolve a group and release its children |
| `engine.block.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 |
| `engine.block.export(block, options)` | Export a block to an image or document |
| `engine.dispose()` | Free engine resources |
## Next Steps
Explore related topics:
- [Layer Management](https://img.ly/docs/cesdk/node-native/create-composition/layer-management-18f07a/) - Control z-order and visibility of blocks
- [Position and Align](https://img.ly/docs/cesdk/node-native/insert-media/position-and-align-cc6b6a/) - Arrange blocks precisely on the canvas
- [Lock Design](https://img.ly/docs/cesdk/node-native/create-composition/lock-design-0a81de/) - Prevent modifications to specific elements
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Layer Management"
description: "Organize design elements using a layer stack for precise control over stacking and visibility."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/layer-management-18f07a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Layers](https://img.ly/docs/cesdk/node-native/create-composition/layer-management-18f07a/)
---
Organize design elements in CE.SDK using a hierarchical layer stack to control stacking order, visibility, and element relationships.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-layer-management-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-layer-management-server-js)
Design elements in CE.SDK are organized in a hierarchical parent-child structure. Children of a block are rendered in order, with the last child appearing on top. This layer stack model gives you precise control over how elements overlap and interact visually.
```typescript file=@cesdk_web_examples/guides-create-composition-layer-management-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { mkdirSync, writeFileSync } from 'fs';
config();
/**
* CE.SDK Server Guide: Layer Management
*
* Demonstrates programmatic layer management:
* - 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
* - Exporting the result
*/
const engine = await CreativeEngine.init({});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0]!;
// Create a colored rectangle
const redRect = engine.block.create('graphic');
engine.block.setShape(redRect, engine.block.createShape('rect'));
const redFill = engine.block.createFill('color');
engine.block.setFill(redRect, redFill);
engine.block.setColor(redFill, 'fill/color/value', {
r: 0.9,
g: 0.2,
b: 0.2,
a: 1
});
engine.block.setWidth(redRect, 180);
engine.block.setHeight(redRect, 180);
engine.block.setPositionX(redRect, 220);
engine.block.setPositionY(redRect, 120);
// Create additional rectangles to demonstrate layer ordering
const greenRect = engine.block.create('graphic');
engine.block.setShape(greenRect, engine.block.createShape('rect'));
const greenFill = engine.block.createFill('color');
engine.block.setFill(greenRect, greenFill);
engine.block.setColor(greenFill, 'fill/color/value', {
r: 0.2,
g: 0.8,
b: 0.2,
a: 1
});
engine.block.setWidth(greenRect, 180);
engine.block.setHeight(greenRect, 180);
engine.block.setPositionX(greenRect, 280);
engine.block.setPositionY(greenRect, 180);
const blueRect = engine.block.create('graphic');
engine.block.setShape(blueRect, engine.block.createShape('rect'));
const blueFill = engine.block.createFill('color');
engine.block.setFill(blueRect, blueFill);
engine.block.setColor(blueFill, 'fill/color/value', {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1
});
engine.block.setWidth(blueRect, 180);
engine.block.setHeight(blueRect, 180);
engine.block.setPositionX(blueRect, 340);
engine.block.setPositionY(blueRect, 240);
// Add blocks to the page - last appended is on top
engine.block.appendChild(page, redRect);
engine.block.appendChild(page, greenRect);
engine.block.appendChild(page, blueRect);
// Get the parent of a block
const parent = engine.block.getParent(redRect);
console.log('Parent of red rectangle:', parent);
// Get all children of the page
const children = engine.block.getChildren(page);
console.log('Page children (in render order):', children);
// Insert a new block at a specific position (index 0 = back)
const yellowRect = engine.block.create('graphic');
engine.block.setShape(yellowRect, engine.block.createShape('rect'));
const yellowFill = engine.block.createFill('color');
engine.block.setFill(yellowRect, yellowFill);
engine.block.setColor(yellowFill, 'fill/color/value', {
r: 0.95,
g: 0.85,
b: 0.2,
a: 1
});
engine.block.setWidth(yellowRect, 180);
engine.block.setHeight(yellowRect, 180);
engine.block.setPositionX(yellowRect, 160);
engine.block.setPositionY(yellowRect, 60);
engine.block.insertChild(page, yellowRect, 0);
// Bring the red rectangle to the front
engine.block.bringToFront(redRect);
console.log('Red rectangle brought to front');
// Send the blue rectangle to the back
engine.block.sendToBack(blueRect);
console.log('Blue rectangle sent to back');
// Move the green rectangle forward one layer
engine.block.bringForward(greenRect);
console.log('Green rectangle moved forward');
// Move the yellow rectangle backward one layer
engine.block.sendBackward(yellowRect);
console.log('Yellow rectangle moved backward');
// Check and toggle visibility
const isVisible = engine.block.isVisible(blueRect);
console.log('Blue rectangle visible:', isVisible);
// Hide the blue rectangle temporarily
engine.block.setVisible(blueRect, false);
console.log('Blue rectangle hidden');
// Show it again for the export
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);
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);
// Log final layer order
const finalChildren = engine.block.getChildren(page);
console.log('Final page children:', finalChildren);
// Export the page as an image
mkdirSync('output', { recursive: true });
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output/layer-management-result.png', buffer);
console.log('Exported: output/layer-management-result.png');
console.log('\nLayer management example complete');
} finally {
engine.dispose();
}
```
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 in a server environment.
## Setting Up the Engine
We initialize the headless CE.SDK engine for server-side processing.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({});
```
## Creating Visual Blocks
To demonstrate layer ordering, we create colored rectangles that overlap on the canvas. Each block is created using `engine.block.create()` and configured with a shape, fill color, dimensions, and position.
```typescript highlight=highlight-create-block
// Create a colored rectangle
const redRect = engine.block.create('graphic');
engine.block.setShape(redRect, engine.block.createShape('rect'));
const redFill = engine.block.createFill('color');
engine.block.setFill(redRect, redFill);
engine.block.setColor(redFill, 'fill/color/value', {
r: 0.9,
g: 0.2,
b: 0.2,
a: 1
});
engine.block.setWidth(redRect, 180);
engine.block.setHeight(redRect, 180);
engine.block.setPositionX(redRect, 220);
engine.block.setPositionY(redRect, 120);
```
## Navigating the Block Hierarchy
CE.SDK organizes blocks in a parent-child tree structure. Every block can have one parent and multiple children. Understanding this hierarchy is essential for programmatic layer management.
### Getting a Block's Parent
We retrieve the parent of any block using `engine.block.getParent()`. This returns the parent's block ID, or null if the block has no parent (such as the scene root).
```typescript highlight=highlight-get-parent
// Get the parent of a block
const parent = engine.block.getParent(redRect);
console.log('Parent of red rectangle:', parent);
```
Knowing a block's parent helps you understand where it sits in the hierarchy and enables operations like reparenting or finding sibling blocks.
### Listing Child Blocks
We get all direct children of a block using `engine.block.getChildren()`. Children are returned sorted in their rendering order, where the last child renders in front of other children.
```typescript highlight=highlight-get-children
// Get all children of the page
const children = engine.block.getChildren(page);
console.log('Page children (in render order):', children);
```
This method is useful for iterating through all elements on a page or within a group.
## Adding and Positioning Blocks
When you create a new block, it exists independently until you add it to the hierarchy. There are two ways to attach blocks to a parent: appending to the end or inserting at a specific position.
### Appending Blocks
We add a block as the last child of a parent using `engine.block.appendChild()`. Since the last child renders on top, the appended block becomes the topmost element.
```typescript highlight=highlight-append-child
// Add blocks to the page - last appended is on top
engine.block.appendChild(page, redRect);
engine.block.appendChild(page, greenRect);
engine.block.appendChild(page, blueRect);
```
When you append multiple blocks in sequence, each new block appears in front of the previous ones.
### Inserting at a Specific Position
We insert a block at a specific index in the layer stack using `engine.block.insertChild()`. Index 0 places the block at the back, behind all other children.
```typescript highlight=highlight-insert-child
// Insert a new block at a specific position (index 0 = back)
const yellowRect = engine.block.create('graphic');
engine.block.setShape(yellowRect, engine.block.createShape('rect'));
const yellowFill = engine.block.createFill('color');
engine.block.setFill(yellowRect, yellowFill);
engine.block.setColor(yellowFill, 'fill/color/value', {
r: 0.95,
g: 0.85,
b: 0.2,
a: 1
});
engine.block.setWidth(yellowRect, 180);
engine.block.setHeight(yellowRect, 180);
engine.block.setPositionX(yellowRect, 160);
engine.block.setPositionY(yellowRect, 60);
engine.block.insertChild(page, yellowRect, 0);
```
This gives you precise control over where new elements appear in the stacking order.
### Reparenting Blocks
When you add a block to a new parent using `appendChild()` or `insertChild()`, it is automatically removed from its previous parent. This makes reparenting operations straightforward without needing to manually detach blocks first.
## Changing Z-Order
Once blocks are in the hierarchy, you can change their stacking order without removing and re-adding them. CE.SDK provides four methods for z-order manipulation.
### Bring to Front
We move an element to the top of its siblings using `engine.block.bringToFront()`. This gives the block the highest stacking order among its siblings.
```typescript highlight=highlight-bring-to-front
// Bring the red rectangle to the front
engine.block.bringToFront(redRect);
console.log('Red rectangle brought to front');
```
### Send to Back
We move an element behind all its siblings using `engine.block.sendToBack()`. This gives the block the lowest stacking order among its siblings.
```typescript highlight=highlight-send-to-back
// Send the blue rectangle to the back
engine.block.sendToBack(blueRect);
console.log('Blue rectangle sent to back');
```
### Move Forward One Layer
We move an element one position forward using `engine.block.bringForward()`. This swaps the block with its immediate sibling in front.
```typescript highlight=highlight-bring-forward
// Move the green rectangle forward one layer
engine.block.bringForward(greenRect);
console.log('Green rectangle moved forward');
```
### Move Backward One Layer
We move an element one position backward using `engine.block.sendBackward()`. This swaps the block with its immediate sibling behind.
```typescript highlight=highlight-send-backward
// Move the yellow rectangle backward one layer
engine.block.sendBackward(yellowRect);
console.log('Yellow rectangle moved backward');
```
These incremental operations are useful for fine-tuning the layer order without jumping to extremes.
## Controlling Visibility
Visibility allows you to temporarily hide elements without removing them from the scene. Hidden elements remain in the hierarchy and preserve their properties, but are not rendered.
### Checking and Toggling Visibility
We query the current visibility state using `engine.block.isVisible()` and change it using `engine.block.setVisible()`.
```typescript highlight=highlight-visibility
// Check and toggle visibility
const isVisible = engine.block.isVisible(blueRect);
console.log('Blue rectangle visible:', isVisible);
// Hide the blue rectangle temporarily
engine.block.setVisible(blueRect, false);
console.log('Blue rectangle hidden');
// Show it again for the export
engine.block.setVisible(blueRect, true);
console.log('Blue rectangle shown again');
```
Visibility is useful for creating before/after comparisons, hiding elements during processing, or implementing conditional rendering in your application.
## Managing Block Lifecycle
CE.SDK provides methods for duplicating blocks to create copies and destroying blocks to remove them permanently.
### Duplicating Blocks
We create a copy of a block and all its children using `engine.block.duplicate()`. By default, the duplicate is attached to the same parent as the original.
```typescript highlight=highlight-duplicate
// Duplicate a block
const duplicateGreen = engine.block.duplicate(greenRect);
engine.block.setPositionX(duplicateGreen, 400);
engine.block.setPositionY(duplicateGreen, 300);
console.log('Green rectangle duplicated');
```
The duplicated block is positioned at the same location as the original. You typically want to reposition it to make it visible as a separate element.
### Checking Block Validity
Before performing operations on a block, we can verify it still exists using `engine.block.isValid()`. A block becomes invalid after it has been destroyed.
```typescript highlight=highlight-is-valid
// Check if a block is valid before operations
const isValidBefore = engine.block.isValid(yellowRect);
console.log('Yellow rectangle valid before destroy:', isValidBefore);
```
### Removing Blocks
We permanently remove a block and all its children from the scene using `engine.block.destroy()`. This operation cannot be undone programmatically.
```typescript highlight=highlight-destroy
// Remove a block from the scene
engine.block.destroy(yellowRect);
console.log('Yellow rectangle destroyed');
// Check validity after destruction
const isValidAfter = engine.block.isValid(yellowRect);
console.log('Yellow rectangle valid after destroy:', isValidAfter);
```
After destruction, any references to the block become invalid. Attempting to use an invalid block ID will result in errors.
## Exporting the Result
After making layer changes, we export the page as an image to verify the final composition.
```typescript highlight=highlight-export
// Export the page as an image
mkdirSync('output', { recursive: true });
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output/layer-management-result.png', buffer);
console.log('Exported: output/layer-management-result.png');
```
## Troubleshooting
**Block not visible after appendChild**: The block may be behind other elements. Use `engine.block.bringToFront()` or adjust the insert index to control stacking order.
**getParent returns null**: The block is not attached to any parent. Use `engine.block.appendChild()` or `engine.block.insertChild()` to attach it to a page or container.
**Changes not reflected**: The block handle may be invalid. Check with `engine.block.isValid()` before performing operations.
**Z-order not updating**: Verify you're operating on the correct block ID and that the block is in the expected parent context.
**Duplicate not appearing**: If `attachToParent` is set to false, the duplicate won't be attached automatically. Set it to true or manually attach the duplicate to a parent.
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.getParent(id)` | Get the parent block of a given block |
| `engine.block.getChildren(id)` | Get all child blocks in rendering order |
| `engine.block.appendChild(parent, child)` | Append a block as the last child |
| `engine.block.insertChild(parent, child, index)` | Insert a block at a specific position |
| `engine.block.bringToFront(id)` | Bring a block to the front of its siblings |
| `engine.block.sendToBack(id)` | Send a block to the back of its siblings |
| `engine.block.bringForward(id)` | Move a block one position forward |
| `engine.block.sendBackward(id)` | Move a block one position backward |
| `engine.block.isVisible(id)` | Check if a block is visible |
| `engine.block.setVisible(id, visible)` | Set the visibility of a block |
| `engine.block.duplicate(id, attachToParent?)` | Duplicate a block and its children |
| `engine.block.destroy(id)` | Remove a block and its children |
| `engine.block.isValid(id)` | Check if a block handle is valid |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Design a Layout"
description: "Create structured compositions using scene layouts, positioning systems, and hierarchical block organization for collages, magazines, and multi-page documents."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/layout-b66311/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Design a Layout](https://img.ly/docs/cesdk/node-native/create-composition/layout-b66311/)
---
Create automatic stack layouts in CE.SDK that arrange pages vertically or horizontally with consistent spacing. Build structured compositions like collages, catalogs, or carousels without manually positioning each element.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-layout-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-layout-server-js)
CE.SDK provides stack layouts that automatically arrange pages with consistent spacing. Vertical stacks arrange pages top-to-bottom, horizontal stacks arrange left-to-right. This eliminates manual positioning for compositions like photo collages, product catalogs, social media carousels, and magazine layouts.
```typescript file=@cesdk_web_examples/guides-create-composition-layout-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Design a Layout
*
* Demonstrates:
* - Creating vertical stack layouts (top-to-bottom arrangement)
* - Creating horizontal stack layouts (left-to-right arrangement)
* - Adding blocks to stacks for automatic positioning
* - Configuring spacing between stacked blocks
* - Reordering blocks within stacks
* - Switching between stack and free layouts
* - Building a practical photo collage example
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// ===== VERTICAL STACK LAYOUT =====
console.log('--- Vertical Stack Layout ---');
// Create a scene with vertical stack layout
// Pages and blocks will arrange top-to-bottom automatically
const verticalScene = engine.scene.create('VerticalStack');
console.log('Created VerticalStack scene:', verticalScene);
// Get the stack container to add pages
const [stack] = engine.block.findByType('stack');
// Create first page
const page1 = engine.block.create('page');
engine.block.setWidth(page1, 400);
engine.block.setHeight(page1, 300);
engine.block.appendChild(stack, page1);
// Create second page - appears below 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 (in screen space pixels)
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
const spacing = engine.block.getFloat(stack, 'stack/spacing');
console.log(`Stack spacing: ${spacing}px`);
// ===== HORIZONTAL STACK LAYOUT =====
console.log('\n--- Horizontal Stack Layout ---');
// Switch to horizontal stack layout
// Pages will now arrange left-to-right
engine.scene.setLayout('HorizontalStack');
console.log('Changed to HorizontalStack layout');
// Verify the layout change
const currentLayout = engine.scene.getLayout();
console.log('Current layout:', currentLayout);
// Pages are automatically rearranged horizontally
// Useful for carousels, timelines, or horizontal galleries
// ===== ADD BLOCKS TO PAGES =====
console.log('\n--- Adding Blocks to Pages ---');
// Add graphic blocks to each page
// Blocks must have a shape and fill to be visible
// Sample image URL for demonstrations
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Add an image block to page 1
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);
console.log('Added image block to page 1');
// 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);
console.log('Added colored block to page 2');
// ===== ADD NEW PAGE TO EXISTING STACK =====
console.log('\n--- Adding Page to Existing Stack ---');
// Add a new page to the existing stack layout
// The page automatically appears at the end of the stack
const page3 = engine.block.create('page');
engine.block.setWidth(page3, 400);
engine.block.setHeight(page3, 300);
engine.block.appendChild(stack, page3);
console.log('Added page 3 to stack');
// Add content to page 3
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);
// Count pages in stack
const pages = engine.block.getChildren(stack);
console.log(`Stack now has ${pages.length} pages`);
// ===== REORDER PAGES IN STACK =====
console.log('\n--- Reordering Pages ---');
// Reorder pages using insertChild
// Move page3 to the first position (index 0)
engine.block.insertChild(stack, page3, 0);
console.log('Moved page 3 to first position');
// Verify the new order by listing children
const reorderedPages = engine.block.getChildren(stack);
console.log('Page order after reordering:', reorderedPages);
// ===== CHANGE STACK SPACING =====
console.log('\n--- Changing Stack Spacing ---');
// Update spacing between stacked pages
engine.block.setFloat(stack, 'stack/spacing', 40);
const newSpacing = engine.block.getFloat(stack, 'stack/spacing');
console.log(`Updated stack spacing to: ${newSpacing}px`);
// Spacing updates immediately - pages reposition automatically
// ===== SWITCH TO FREE LAYOUT =====
console.log('\n--- Switching to Free Layout ---');
// Check current layout type
const layoutBefore = engine.scene.getLayout();
console.log('Current layout:', layoutBefore);
// Convert to free layout for manual positioning
engine.scene.setLayout('Free');
console.log('Switched to Free layout');
// In free layout, pages keep their positions but stop auto-arranging
// Now you can position pages manually
const [firstPage] = engine.block.findByType('page');
engine.block.setPositionX(firstPage, 50);
engine.block.setPositionY(firstPage, 50);
console.log('Manually positioned first page');
// Verify layout change
const layoutAfter = engine.scene.getLayout();
console.log('Layout after switch:', layoutAfter);
// ===== PHOTO COLLAGE EXAMPLE =====
console.log('\n--- Photo Collage Example ---');
// Create a new vertical stack scene for a photo collage
engine.scene.create('VerticalStack', {
page: { size: { width: 600, height: 200 } }
});
// Get the new stack
const [collageStack] = engine.block.findByType('stack');
// Set tight spacing for collage effect
engine.block.setFloat(collageStack, 'stack/spacing', 10);
engine.block.setBool(collageStack, 'stack/spacingInScreenspace', true);
// Get the first page created with the scene
const [collagePage1] = engine.block.findByType('page');
// Create additional pages for the collage
const collagePage2 = engine.block.create('page');
engine.block.setWidth(collagePage2, 600);
engine.block.setHeight(collagePage2, 200);
engine.block.appendChild(collageStack, collagePage2);
const collagePage3 = engine.block.create('page');
engine.block.setWidth(collagePage3, 600);
engine.block.setHeight(collagePage3, 200);
engine.block.appendChild(collageStack, collagePage3);
// Add images to each collage page
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'
];
const collagePages = [collagePage1, collagePage2, collagePage3];
for (let i = 0; i < collagePages.length; i++) {
const photoBlock = await engine.block.addImage(imageUrls[i], {
size: { width: 580, height: 180 }
});
engine.block.setPositionX(photoBlock, 10);
engine.block.setPositionY(photoBlock, 10);
engine.block.appendChild(collagePages[i], photoBlock);
}
console.log('Created photo collage with 3 images');
// Export the collage result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(collagePage1, {
mimeType: 'image/png'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/layout-collage.png`, buffer);
console.log('\nExported collage to output/layout-collage.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to create vertical and horizontal stack layouts, add pages to stacks, configure spacing, reorder pages, and switch between automatic and manual positioning modes.
## Setting Up the Engine
We initialize the headless CE.SDK engine for server-side layout operations.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Create a Vertical Stack Layout
Vertical stack layouts arrange pages from top to bottom. When you add pages to the stack, they automatically position below each other with the configured spacing.
```typescript highlight=highlight-vertical-stack
// Create a scene with vertical stack layout
// Pages and blocks will arrange top-to-bottom automatically
const verticalScene = engine.scene.create('VerticalStack');
console.log('Created VerticalStack scene:', verticalScene);
// Get the stack container to add pages
const [stack] = engine.block.findByType('stack');
// Create first page
const page1 = engine.block.create('page');
engine.block.setWidth(page1, 400);
engine.block.setHeight(page1, 300);
engine.block.appendChild(stack, page1);
// Create second page - appears below 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 (in screen space pixels)
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
const spacing = engine.block.getFloat(stack, 'stack/spacing');
console.log(`Stack spacing: ${spacing}px`);
```
Pages added with `appendChild` appear at the bottom of the stack. The `stack/spacing` property controls the gap between pages, and `stack/spacingInScreenspace` determines whether spacing is in screen pixels or design units.
## Create a Horizontal Stack Layout
Horizontal stack layouts arrange pages from left to right. Switch between layout types using `engine.scene.setLayout()`.
```typescript highlight=highlight-horizontal-stack
// Switch to horizontal stack layout
// Pages will now arrange left-to-right
engine.scene.setLayout('HorizontalStack');
console.log('Changed to HorizontalStack layout');
// Verify the layout change
const currentLayout = engine.scene.getLayout();
console.log('Current layout:', currentLayout);
// Pages are automatically rearranged horizontally
// Useful for carousels, timelines, or horizontal galleries
```
Horizontal layouts are useful for carousels, timelines, or horizontal galleries. The same spacing configuration applies—pages automatically reposition when you change the layout type.
## Add Blocks to Pages
Once you have pages in your stack, add graphic blocks to display content. Each block needs a shape and fill to be visible.
```typescript highlight=highlight-add-blocks
// Add graphic blocks to each page
// Blocks must have a shape and fill to be visible
// Sample image URL for demonstrations
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Add an image block to page 1
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);
console.log('Added image block to page 1');
// 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);
console.log('Added colored block to page 2');
```
Position blocks within their parent page using `setPositionX()` and `setPositionY()`. The block's position is relative to the page's top-left corner.
## Add Pages to an Existing Stack
After creating a composition, you can add more pages to the stack. New pages automatically appear at the end.
```typescript highlight=highlight-add-page
// Add a new page to the existing stack layout
// The page automatically appears at the end of the stack
const page3 = engine.block.create('page');
engine.block.setWidth(page3, 400);
engine.block.setHeight(page3, 300);
engine.block.appendChild(stack, page3);
console.log('Added page 3 to stack');
// Add content to page 3
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);
// Count pages in stack
const pages = engine.block.getChildren(stack);
console.log(`Stack now has ${pages.length} pages`);
```
Use `getChildren()` to see all pages in the stack. The insertion order determines the visual order in the layout.
## Reorder Pages in the Stack
Change the position of pages using `insertChild()`. This lets you move pages to any position in the stack.
```typescript highlight=highlight-reorder
// Reorder pages using insertChild
// Move page3 to the first position (index 0)
engine.block.insertChild(stack, page3, 0);
console.log('Moved page 3 to first position');
// Verify the new order by listing children
const reorderedPages = engine.block.getChildren(stack);
console.log('Page order after reordering:', reorderedPages);
```
The second parameter specifies the target index. Index 0 places the page first, and higher indices move it further down (or right in horizontal layouts).
## Change Stack Spacing
Adjust the spacing between pages at any time. Changes apply immediately and pages reposition automatically.
```typescript highlight=highlight-spacing
// Update spacing between stacked pages
engine.block.setFloat(stack, 'stack/spacing', 40);
const newSpacing = engine.block.getFloat(stack, 'stack/spacing');
console.log(`Updated stack spacing to: ${newSpacing}px`);
// Spacing updates immediately - pages reposition automatically
```
Use `getFloat()` to read the current spacing value. Larger spacing creates more separation between pages; zero spacing creates a seamless grid.
## Switch to Free Layout
Stack layouts automatically position pages. For manual control, switch to Free layout.
```typescript highlight=highlight-free-layout
// Check current layout type
const layoutBefore = engine.scene.getLayout();
console.log('Current layout:', layoutBefore);
// Convert to free layout for manual positioning
engine.scene.setLayout('Free');
console.log('Switched to Free layout');
// In free layout, pages keep their positions but stop auto-arranging
// Now you can position pages manually
const [firstPage] = engine.block.findByType('page');
engine.block.setPositionX(firstPage, 50);
engine.block.setPositionY(firstPage, 50);
console.log('Manually positioned first page');
// Verify layout change
const layoutAfter = engine.scene.getLayout();
console.log('Layout after switch:', layoutAfter);
```
In Free layout, pages keep their current positions but stop auto-arranging. You can then use `setPositionX()` and `setPositionY()` to position pages manually.
## Build a Photo Collage
This practical example creates a vertical photo collage with three images and tight spacing.
```typescript highlight=highlight-collage
// Create a new vertical stack scene for a photo collage
engine.scene.create('VerticalStack', {
page: { size: { width: 600, height: 200 } }
});
// Get the new stack
const [collageStack] = engine.block.findByType('stack');
// Set tight spacing for collage effect
engine.block.setFloat(collageStack, 'stack/spacing', 10);
engine.block.setBool(collageStack, 'stack/spacingInScreenspace', true);
// Get the first page created with the scene
const [collagePage1] = engine.block.findByType('page');
// Create additional pages for the collage
const collagePage2 = engine.block.create('page');
engine.block.setWidth(collagePage2, 600);
engine.block.setHeight(collagePage2, 200);
engine.block.appendChild(collageStack, collagePage2);
const collagePage3 = engine.block.create('page');
engine.block.setWidth(collagePage3, 600);
engine.block.setHeight(collagePage3, 200);
engine.block.appendChild(collageStack, collagePage3);
// Add images to each collage page
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'
];
const collagePages = [collagePage1, collagePage2, collagePage3];
for (let i = 0; i < collagePages.length; i++) {
const photoBlock = await engine.block.addImage(imageUrls[i], {
size: { width: 580, height: 180 }
});
engine.block.setPositionX(photoBlock, 10);
engine.block.setPositionY(photoBlock, 10);
engine.block.appendChild(collagePages[i], photoBlock);
}
console.log('Created photo collage with 3 images');
```
The collage uses a vertical stack with 10px spacing. Each page contains an image that fills most of the page area, creating a cohesive photo strip effect.
## Exporting the Result
After building the layout, export pages as images to verify the composition.
```typescript highlight=highlight-export
// Export the collage result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(collagePage1, {
mimeType: 'image/png'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/layout-collage.png`, buffer);
console.log('\nExported collage to output/layout-collage.png');
```
## Troubleshooting
**Pages not arranging automatically**: Verify the scene layout type is `VerticalStack` or `HorizontalStack` using `engine.scene.getLayout()`.
**Spacing not applying**: Check the spacing value with `engine.block.getFloat(stack, 'stack/spacing')`. Ensure you're setting spacing on the stack block, not the scene.
**Pages overlapping**: Ensure pages are direct children of the stack block. Use `engine.block.findByType('stack')` to get the correct parent.
**Can't position pages manually**: Stack layouts override manual positions. Switch to `Free` layout with `engine.scene.setLayout('Free')`.
**Wrong page order**: Child order determines visual position. Use `insertChild(stack, page, index)` to move pages to specific positions.
## API Reference
| Method | Description |
| --- | --- |
| `engine.scene.create(layout, options)` | Create a scene with specified layout type |
| `engine.scene.getLayout()` | Get the current scene layout type |
| `engine.scene.setLayout(layout)` | Change the scene 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.setBool(id, 'stack/spacingInScreenspace', value)` | Set whether spacing is in screen pixels |
| `engine.block.appendChild(parent, child)` | Add page to stack (positions automatically) |
| `engine.block.insertChild(parent, child, index)` | Insert page at specific position in stack |
| `engine.block.getChildren(id)` | Get all child pages of a stack |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Lock Design"
description: "Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. Control which properties users can edit at both global and block levels."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/lock-design-0a81de/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Lock Design](https://img.ly/docs/cesdk/node-native/create-composition/lock-design-0a81de/)
---
Protect design elements from unwanted modifications using CE.SDK's scope-based permission system.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-lock-design-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-lock-design-server-js)
CE.SDK uses a two-layer scope system to control editing permissions. Global scopes set defaults for the entire scene, while block-level scopes override when the global setting is `Defer`. This enables flexible permission models from fully locked to selectively editable designs.
```typescript file=@cesdk_web_examples/guides-create-composition-lock-design-server-js/server-js.ts reference-only
import CreativeEngine, { type Scope } from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
config(); // Load .env file
async function lockDesignExample() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create scene with page
const scene = engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0]!;
// Create sample content to demonstrate different locking techniques
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Column 1: Fully Locked
const caption1 = engine.block.create('text');
engine.block.setString(caption1, 'text/text', 'Fully Locked');
engine.block.setFloat(caption1, 'text/fontSize', 32);
engine.block.setWidth(caption1, 220);
engine.block.setHeight(caption1, 50);
engine.block.setPositionX(caption1, 30);
engine.block.setPositionY(caption1, 30);
engine.block.appendChild(page, caption1);
const imageBlock = await engine.block.addImage(imageUri, {
x: 30,
y: 100,
size: { width: 220, height: 165 }
});
engine.block.appendChild(page, imageBlock);
// Column 2: Text Editing Only
const caption2 = engine.block.create('text');
engine.block.setString(caption2, 'text/text', 'Text Editing');
engine.block.setFloat(caption2, 'text/fontSize', 32);
engine.block.setWidth(caption2, 220);
engine.block.setHeight(caption2, 50);
engine.block.setPositionX(caption2, 290);
engine.block.setPositionY(caption2, 30);
engine.block.appendChild(page, caption2);
const textBlock = engine.block.create('text');
engine.block.setString(textBlock, 'text/text', 'Edit Me');
engine.block.setFloat(textBlock, 'text/fontSize', 72);
engine.block.setWidth(textBlock, 220);
engine.block.setHeight(textBlock, 165);
engine.block.setPositionX(textBlock, 290);
engine.block.setPositionY(textBlock, 100);
engine.block.appendChild(page, textBlock);
// Column 3: Image Replace Only
const caption3 = engine.block.create('text');
engine.block.setString(caption3, 'text/text', 'Image Replace');
engine.block.setFloat(caption3, 'text/fontSize', 32);
engine.block.setWidth(caption3, 220);
engine.block.setHeight(caption3, 50);
engine.block.setPositionX(caption3, 550);
engine.block.setPositionY(caption3, 30);
engine.block.appendChild(page, caption3);
const placeholderBlock = await engine.block.addImage(imageUri, {
x: 550,
y: 100,
size: { width: 220, height: 165 }
});
engine.block.appendChild(page, placeholderBlock);
// Lock the entire design by setting all scopes to Deny
const scopes = engine.editor.findAllScopes();
for (const scope of scopes) {
engine.editor.setGlobalScope(scope, 'Deny');
}
// Enable selection for specific blocks
engine.editor.setGlobalScope('editor/select', 'Defer');
engine.block.setScopeEnabled(textBlock, 'editor/select', true);
engine.block.setScopeEnabled(placeholderBlock, 'editor/select', true);
// Enable text editing on the text block
engine.editor.setGlobalScope('text/edit', 'Defer');
engine.editor.setGlobalScope('text/character', 'Defer');
engine.block.setScopeEnabled(textBlock, 'text/edit', true);
engine.block.setScopeEnabled(textBlock, 'text/character', true);
// Enable image replacement on the placeholder block
engine.editor.setGlobalScope('fill/change', 'Defer');
engine.block.setScopeEnabled(placeholderBlock, 'fill/change', true);
// Check if operations are permitted on blocks
const canEditText = engine.block.isAllowedByScope(textBlock, 'text/edit');
const canMoveImage = engine.block.isAllowedByScope(imageBlock, 'layer/move');
const canReplacePlaceholder = engine.block.isAllowedByScope(
placeholderBlock,
'fill/change'
);
console.log('Permission status:');
console.log('- Can edit text:', canEditText); // true
console.log('- Can move locked image:', canMoveImage); // false
console.log('- Can replace placeholder:', canReplacePlaceholder); // true
// Discover all available scopes
const allScopes: Scope[] = engine.editor.findAllScopes();
console.log('Available scopes:', allScopes);
// Check global scope settings
const textEditGlobal = engine.editor.getGlobalScope('text/edit');
const layerMoveGlobal = engine.editor.getGlobalScope('layer/move');
console.log('Global text/edit:', textEditGlobal); // 'Defer'
console.log('Global layer/move:', layerMoveGlobal); // 'Deny'
// Check block-level scope settings
const textEditEnabled = engine.block.isScopeEnabled(textBlock, 'text/edit');
console.log('Text block text/edit enabled:', textEditEnabled); // true
// Export the scene to demonstrate the design
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('./output')) {
mkdirSync('./output', { recursive: true });
}
writeFileSync('./output/locked-design.png', buffer);
console.log('Design exported to ./output/locked-design.png');
} finally {
engine.dispose();
}
}
lockDesignExample().catch(console.error);
```
This guide covers how to lock entire designs, selectively enable specific editing capabilities, and check permissions programmatically in a server environment.
## Understanding the Scope Permission Model
Scopes control what operations can be performed on design elements. CE.SDK combines global scope settings with block-level settings to determine the final permission.
| Global Scope | Block Scope | Result |
| ------------ | ----------- | --------- |
| `Allow` | any | Permitted |
| `Deny` | any | Blocked |
| `Defer` | enabled | Permitted |
| `Defer` | disabled | Blocked |
Global scopes have three possible values:
- **`Allow`**: The operation is always permitted, regardless of block-level settings
- **`Deny`**: The operation is always blocked, regardless of block-level settings
- **`Defer`**: The permission depends on the block-level scope setting
Block-level scopes are binary: enabled or disabled. They only take effect when the global scope is set to `Defer`.
## Locking an Entire Design
To lock all editing operations, iterate through all available scopes and set each to `Deny`. We use `engine.editor.findAllScopes()` to discover all scope names dynamically.
```typescript highlight=highlight-lock-entire-design
// Lock the entire design by setting all scopes to Deny
const scopes = engine.editor.findAllScopes();
for (const scope of scopes) {
engine.editor.setGlobalScope(scope, 'Deny');
}
```
When all scopes are set to `Deny`, any operations on the design would be blocked. This is useful for creating protected templates that users cannot modify.
## Enabling Selection for Interactive Blocks
Before users can interact with any block through an application, you must enable the `editor/select` scope. Without selection enabled, blocks cannot be accessed programmatically for editing.
```typescript highlight=highlight-enable-selection
// Enable selection for specific blocks
engine.editor.setGlobalScope('editor/select', 'Defer');
engine.block.setScopeEnabled(textBlock, 'editor/select', true);
engine.block.setScopeEnabled(placeholderBlock, 'editor/select', true);
```
Setting the global `editor/select` scope to `Defer` delegates the decision to each block. We then enable selection only on the specific blocks that should be modifiable.
## Selective Locking Patterns
Lock everything first, then selectively enable specific capabilities on chosen blocks. This pattern provides fine-grained control over what can be modified.
### Text-Only Editing
To allow text content editing while protecting everything else, enable the `text/edit` scope. For text styling changes like font, size, and color, also enable `text/character`.
```typescript highlight=highlight-text-editing
// Enable text editing on the text block
engine.editor.setGlobalScope('text/edit', 'Defer');
engine.editor.setGlobalScope('text/character', 'Defer');
engine.block.setScopeEnabled(textBlock, 'text/edit', true);
engine.block.setScopeEnabled(textBlock, 'text/character', true);
```
Only the designated text block can have its content modified while other properties remain locked.
### Image Replacement
To allow image replacement while protecting layout and position, enable the `fill/change` scope on placeholder blocks.
```typescript highlight=highlight-image-replacement
// Enable image replacement on the placeholder block
engine.editor.setGlobalScope('fill/change', 'Defer');
engine.block.setScopeEnabled(placeholderBlock, 'fill/change', true);
```
The image content can be replaced but the block's position, dimensions, and other properties remain locked.
## Checking Permissions
Verify whether operations are permitted using `engine.block.isAllowedByScope()`. This method evaluates both global and block-level settings to return the effective permission state.
```typescript highlight=highlight-check-permissions
// Check if operations are permitted on blocks
const canEditText = engine.block.isAllowedByScope(textBlock, 'text/edit');
const canMoveImage = engine.block.isAllowedByScope(imageBlock, 'layer/move');
const canReplacePlaceholder = engine.block.isAllowedByScope(
placeholderBlock,
'fill/change'
);
console.log('Permission status:');
console.log('- Can edit text:', canEditText); // true
console.log('- Can move locked image:', canMoveImage); // false
console.log('- Can replace placeholder:', canReplacePlaceholder); // true
```
The distinction between checking methods is:
- `isAllowedByScope()` returns the **effective permission** after evaluating all scope levels
- `isScopeEnabled()` returns only the **block-level setting**
- `getGlobalScope()` returns only the **global setting**
## Discovering Available Scopes
To work with scopes programmatically, you can discover all available scope names and check their current settings.
```typescript highlight=highlight-get-scopes
// Discover all available scopes
const allScopes: Scope[] = engine.editor.findAllScopes();
console.log('Available scopes:', allScopes);
// Check global scope settings
const textEditGlobal = engine.editor.getGlobalScope('text/edit');
const layerMoveGlobal = engine.editor.getGlobalScope('layer/move');
console.log('Global text/edit:', textEditGlobal); // 'Defer'
console.log('Global layer/move:', layerMoveGlobal); // 'Deny'
// Check block-level scope settings
const textEditEnabled = engine.block.isScopeEnabled(textBlock, 'text/edit');
console.log('Text block text/edit enabled:', textEditEnabled); // true
```
## Cleanup
Always dispose the engine when finished to release resources.
```typescript highlight=highlight-cleanup
} finally {
engine.dispose();
}
```
## Available Scopes Reference
| Scope | Description |
| ------------------------ | --------------------------------------- |
| `layer/move` | Move block position |
| `layer/resize` | Resize block dimensions |
| `layer/rotate` | Rotate block |
| `layer/flip` | Flip block horizontally or vertically |
| `layer/crop` | Crop block content |
| `layer/opacity` | Change block opacity |
| `layer/blendMode` | Change blend mode |
| `layer/visibility` | Toggle block visibility |
| `layer/clipping` | Change clipping behavior |
| `fill/change` | Change fill content |
| `fill/changeType` | Change fill type |
| `stroke/change` | Change stroke properties |
| `shape/change` | Change shape type |
| `text/edit` | Edit text content |
| `text/character` | Change text styling (font, size, color) |
| `appearance/adjustments` | Change color adjustments |
| `appearance/filter` | Apply or change filters |
| `appearance/effect` | Apply or change effects |
| `appearance/blur` | Apply or change blur |
| `appearance/shadow` | Apply or change shadows |
| `appearance/animation` | Apply or change animations |
| `lifecycle/destroy` | Delete the block |
| `lifecycle/duplicate` | Duplicate the block |
| `editor/add` | Add new blocks |
| `editor/select` | Select blocks |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Multi-Page Layouts"
description: "Create and manage multi-page designs in CE.SDK for documents like brochures, presentations, and catalogs with multiple pages in a single scene."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/multi-page-4d2b50/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Multi-Page Layouts](https://img.ly/docs/cesdk/node-native/create-composition/multi-page-4d2b50/)
---
Create and manage multi-page designs programmatically using CE.SDK's headless engine for documents like brochures, presentations, and catalogs.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-multi-page-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-multi-page-server-js)
Multi-page layouts allow you to create documents with multiple artboards within a single scene. Each page operates as an independent canvas that can contain different content while sharing the same scene context. CE.SDK provides scene layout modes that automatically arrange pages vertically, horizontally, or in a free-form canvas.
```typescript file=@cesdk_web_examples/guides-create-composition-multi-page-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { mkdirSync, writeFileSync } from 'fs';
config();
/**
* CE.SDK Server Guide: Multi-Page Layouts
*
* Demonstrates programmatic multi-page layout management:
* - Creating scenes with multiple pages
* - Adding and configuring pages
* - Scene layout types (HorizontalStack)
* - Stack spacing between pages
* - Exporting multi-page designs
*/
const engine = await CreativeEngine.init({});
try {
// 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);
// Export each page as a separate image
mkdirSync('output', { recursive: true });
const pages = engine.block.findByType('page');
for (let i = 0; i < pages.length; i++) {
const blob = await engine.block.export(pages[i], { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`output/page-${i + 1}.png`, buffer);
console.log(`Exported page ${i + 1}`);
}
console.log('Multi-page layout example complete');
} finally {
engine.dispose();
}
```
This guide covers how to create multi-page scenes, add pages, configure spacing, and export pages as individual images.
## Setting Up the Engine
First, initialize the CE.SDK engine in headless mode for server-side processing.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({});
```
## Creating Multi-Page Scenes
We can create scenes with multiple pages using the engine API. The scene acts as a container for pages, and each page can hold any number of content blocks.
### Creating a Scene with Pages
We create a new scene using `engine.scene.create()` and specify the layout type. The layout type determines how pages are arranged. After creating the scene, we get the stack container and create pages within it.
```typescript highlight=highlight-create-scene
// Create a scene with HorizontalStack layout
engine.scene.create('HorizontalStack');
// Get the stack container
const [stack] = engine.block.findByType('stack');
// Add spacing between pages (20 pixels in screen space)
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
// Create the first page
const firstPage = engine.block.create('page');
engine.block.setWidth(firstPage, 800);
engine.block.setHeight(firstPage, 600);
engine.block.appendChild(stack, firstPage);
```
The scene is created with a `HorizontalStack` layout, meaning pages are arranged side by side from left to right. We then create a page, set its dimensions, and append it to the stack container.
### Configuring Page Spacing
We can add spacing between pages in a stack layout using the `stack/spacing` property. This creates visual separation between pages.
```typescript highlight=highlight-stack-spacing
// Add spacing between pages (20 pixels in screen space)
engine.block.setFloat(stack, 'stack/spacing', 20);
engine.block.setBool(stack, 'stack/spacingInScreenspace', true);
```
Setting `stack/spacingInScreenspace` to `true` means the spacing value is interpreted as screen pixels, maintaining consistent visual spacing regardless of zoom level.
### Adding More Pages
To add additional pages, we create new page blocks, set their dimensions, and append them to the stack container.
```typescript highlight=highlight-add-page
// Create a second page with different content
const secondPage = engine.block.create('page');
engine.block.setWidth(secondPage, 800);
engine.block.setHeight(secondPage, 600);
engine.block.appendChild(stack, secondPage);
// Add a different image to the second page
const imageBlock2 = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_2.jpg',
{ size: { width: 300, height: 200 } }
);
engine.block.setPositionX(imageBlock2, 250);
engine.block.setPositionY(imageBlock2, 200);
engine.block.appendChild(secondPage, imageBlock2);
```
Each page can contain different content. Here we add different images to each page to demonstrate independent page content.
## Scene Layout Types
CE.SDK supports different layout modes that control how pages are arranged. You specify the layout type when creating the scene with `engine.scene.create()`.
**Free Layout** is the default where pages can be positioned anywhere. This provides complete control over page placement.
**VerticalStack Layout** arranges pages automatically in a vertical stack from top to bottom.
**HorizontalStack Layout** arranges pages side by side from left to right.
## Exporting Pages
We can export each page individually as an image file. This is useful for generating thumbnails, previews, or final output files.
```typescript highlight=highlight-export
// Export each page as a separate image
mkdirSync('output', { recursive: true });
const pages = engine.block.findByType('page');
for (let i = 0; i < pages.length; i++) {
const blob = await engine.block.export(pages[i], { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`output/page-${i + 1}.png`, buffer);
console.log(`Exported page ${i + 1}`);
}
```
The export loop iterates through all pages and saves each one as a PNG file. You can also export to other formats like JPEG or PDF.
## Troubleshooting
**Page not visible after creation**: Ensure the page is attached to the stack with `appendChild()` and has valid dimensions set with `setWidth()` and `setHeight()`.
**Cannot add content to page**: Verify you're appending blocks to the page block, not the scene directly. Content blocks should be children of pages.
**Pages overlapping**: When using stack layouts, make sure pages are appended to the stack container (found via `findByType('stack')`), not directly to the scene.
**Spacing not visible**: Check that `stack/spacing` is set to a positive value and that you're using a stack layout (HorizontalStack or VerticalStack).
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Combine and arrange multiple elements to create complex, multi-page, or layered design compositions."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/overview-5b19c5/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Overview](https://img.ly/docs/cesdk/node-native/create-composition/overview-5b19c5/)
---
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.
[Launch Web Demo](https://img.ly/showcases/cesdk)
[Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/)
## Exporting Compositions
CE.SDK compositions can be exported in several formats:
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Programmatic Creation"
description: "Documentation for Programmatic Creation"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-composition/programmatic-a688bf/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) > [Programmatic Creation](https://img.ly/docs/cesdk/node-native/create-composition/programmatic-a688bf/)
---
Build compositions entirely through code using CE.SDK's APIs for automation, batch processing, and headless rendering.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-composition-programmatic-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-composition-programmatic-server-js)
CE.SDK provides a complete API for building designs through code. In headless environments, you can create scenes, add blocks like text, images, and shapes, and position them programmatically. This enables server-side rendering, batch processing, and automated content generation without a browser.
```typescript file=@cesdk_web_examples/guides-create-composition-programmatic-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
/**
* CE.SDK Server Guide: Programmatic Creation
*
* 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
* - Exporting the composition
*/
// Roboto typeface with all variants for mixed styling
const ROBOTO_TYPEFACE = {
name: 'Roboto',
fonts: [
{
uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf',
subFamily: 'Regular',
},
{
uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf',
subFamily: 'Bold',
weight: 'bold' as const,
},
{
uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf',
subFamily: 'Italic',
style: 'italic' as const,
},
{
uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf',
subFamily: 'Bold Italic',
weight: 'bold' as const,
style: 'italic' as const,
},
],
};
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
license: process.env.CESDK_LICENSE
});
try {
// Create a new scene with social media dimensions (1080x1080)
engine.scene.create('VerticalStack', {
page: { size: { width: 1080, height: 1080 } },
});
const scene = engine.scene.get()!;
engine.block.setFloat(scene, 'scene/dpi', 300);
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// 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', 12);
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', 10);
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,
});
// Ensure output directory exists
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Write the exported file
const buffer = Buffer.from(await blob.arrayBuffer());
const outputPath = `${outputDir}/composition.png`;
writeFileSync(outputPath, buffer);
console.log(`Export complete: ${outputPath}`);
} finally {
// Always dispose the engine when done
engine.dispose();
console.log('Engine disposed');
}
```
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 the Engine
We start by initializing the CE.SDK engine in headless mode using `@cesdk/node`:
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
license: process.env.CESDK_LICENSE
});
```
## Create Scene Structure
We create the foundation of our composition with social media dimensions (1080x1080 pixels for Instagram). The `VerticalStack` layout option automatically handles page positioning.
```typescript highlight=highlight-create-scene
// Create a new scene with social media dimensions (1080x1080)
engine.scene.create('VerticalStack', {
page: { size: { width: 1080, height: 1080 } },
});
const scene = engine.scene.get()!;
engine.block.setFloat(scene, 'scene/dpi', 300);
```
We pass configuration options to set the page dimensions, then retrieve the page using `engine.block.findByType('page')`.
## Set Page Background
We set the page background using a color fill. This demonstrates how to create and assign fills to blocks.
```typescript highlight=highlight-add-background
// Set page background to light lavender color
const backgroundFill = engine.block.createFill('color');
engine.block.setColor(backgroundFill, 'fill/color/value', {
r: 0.94,
g: 0.93,
b: 0.98,
a: 1.0,
});
engine.block.setFill(page, backgroundFill);
```
We create a color fill using `createFill('color')`, set the color via `setColor()` with the `fill/color/value` property, then assign the fill to the page.
## Add Text Blocks
Text blocks allow you to add and style text content. We demonstrate three different approaches to text sizing and styling.
### Create Text and Set Content
Create a text block and set its content with `replaceText()`:
```typescript highlight=highlight-text-create
// Add main headline text with bold Roboto font
const headline = engine.block.create('text');
engine.block.replaceText(
headline,
'Integrate\nCreative Editing\ninto your App'
);
engine.block.setFont(headline, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE);
engine.block.setFloat(headline, 'text/lineHeight', 0.78);
```
### Style Entire Text Block
Apply styling to the entire text block using `toggleBoldFont()` and `setTextColor()`:
```typescript highlight=highlight-text-style-block
// Make headline bold
if (engine.block.canToggleBoldFont(headline)) {
engine.block.toggleBoldFont(headline);
}
engine.block.setTextColor(headline, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 });
```
### Enable Automatic Font Sizing
Configure the text block to automatically scale its font size to fit within fixed dimensions:
```typescript highlight=highlight-text-auto-size
// Set fixed container size and enable automatic font sizing
engine.block.setWidthMode(headline, 'Absolute');
engine.block.setHeightMode(headline, 'Absolute');
engine.block.setWidth(headline, 960);
engine.block.setHeight(headline, 300);
engine.block.setBool(headline, 'text/automaticFontSizeEnabled', true);
```
### Range-based Text Styling
Apply different styles to specific character ranges within a single text block:
```typescript highlight=highlight-text-range-style
// Style "in hours," - purple and italic (characters 0-9)
engine.block.setTextColor(tagline, { r: 0.2, g: 0.2, b: 0.8, a: 1.0 }, 0, 9);
if (engine.block.canToggleItalicFont(tagline, 0, 9)) {
engine.block.toggleItalicFont(tagline, 0, 9);
}
// Style "not months." - black and bold (characters 10-21)
engine.block.setTextColor(
tagline,
{ r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
10,
21
);
if (engine.block.canToggleBoldFont(tagline, 10, 21)) {
engine.block.toggleBoldFont(tagline, 10, 21);
}
```
The range-based APIs accept start and end character indices:
- `setTextColor(id, color, from, to)` - Apply color to a specific character range
- `toggleBoldFont(id, from, to)` - Toggle bold styling for a range
- `toggleItalicFont(id, from, to)` - Toggle italic styling for a range
### Fixed Font Size
Set an explicit font size instead of using auto-sizing:
```typescript highlight=highlight-text-fixed-size
// Add CTA text "Start a Free Trial" with bold font
const ctaTitle = engine.block.create('text');
engine.block.replaceText(ctaTitle, 'Start a Free Trial');
engine.block.setFont(ctaTitle, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE);
engine.block.setFloat(ctaTitle, 'text/fontSize', 12);
engine.block.setFloat(ctaTitle, 'text/lineHeight', 1.0);
```
## Add Shapes
We create shapes using graphic blocks. CE.SDK supports `rect`, `line`, `ellipse`, `polygon`, `star`, and `vector_path` shapes.
### Create a Shape Block
Create a graphic block and assign a shape to it:
```typescript highlight=highlight-shape-create
// Add horizontal divider line
const dividerLine = engine.block.create('graphic');
const lineShape = engine.block.createShape('line');
engine.block.setShape(dividerLine, lineShape);
```
### Apply Fill to Shape
Create a color fill and apply it to the shape:
```typescript highlight=highlight-shape-fill
const lineFill = engine.block.createFill('color');
engine.block.setColor(lineFill, 'fill/color/value', {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
});
engine.block.setFill(dividerLine, lineFill);
```
## Add Images
We add images using graphic blocks with image fills.
### Create an Image Block
Create a graphic block with a rect shape and an image fill:
```typescript highlight=highlight-image-create
// Add IMG.LY logo image
const logo = engine.block.create('graphic');
const logoShape = engine.block.createShape('rect');
engine.block.setShape(logo, logoShape);
const logoFill = engine.block.createFill('image');
engine.block.setString(
logoFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/imgly_logo.jpg'
);
engine.block.setFill(logo, logoFill);
```
We set the image URL via `setString()` with the `fill/image/imageFileURI` property.
## Position and Size Blocks
All blocks use the same positioning and sizing APIs:
```typescript highlight=highlight-block-position
engine.block.setContentFillMode(logo, 'Contain');
engine.block.setWidth(logo, 200);
engine.block.setHeight(logo, 65);
engine.block.setPositionX(logo, 820);
engine.block.setPositionY(logo, 960);
engine.block.appendChild(page, logo);
```
- `setWidth()` / `setHeight()` - Set block dimensions
- `setPositionX()` / `setPositionY()` - Set block position
- `setContentFillMode()` - Control how content fills the block (`Contain`, `Cover`, `Crop`)
- `appendChild()` - Add the block to the page hierarchy
## Export the Composition
We export the finished composition using the engine API.
### Export Using the Engine API
The `engine.block.export()` method exports a block as a blob:
```typescript highlight=highlight-export-api
// Export the composition to PNG
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1080,
targetHeight: 1080,
});
```
### Write to File System
In headless environments, convert the blob to a buffer and write it to disk:
```typescript highlight=highlight-export-file
// Ensure output directory exists
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Write the exported file
const buffer = Buffer.from(await blob.arrayBuffer());
const outputPath = `${outputDir}/composition.png`;
writeFileSync(outputPath, buffer);
console.log(`Export complete: ${outputPath}`);
```
## Clean Up Resources
Always dispose the engine when done to release resources:
```typescript highlight=highlight-cleanup
// Always dispose the engine when done
engine.dispose();
console.log('Engine disposed');
```
Using a `try/finally` block ensures the engine is disposed even if an error occurs during processing.
## 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.
- **Memory leaks**: Always call `engine.dispose()` when processing is complete, especially in long-running server processes.
## Next Steps
- [Layer Management](https://img.ly/docs/cesdk/node-native/create-composition/layer-management-18f07a/) - Control block stacking and organization
- [Positioning and Alignment](https://img.ly/docs/cesdk/node-native/insert-media/position-and-align-cc6b6a/) - Precise block placement
- [Group and Ungroup](https://img.ly/docs/cesdk/node-native/create-composition/group-and-ungroup-62565a/) - Group blocks for unified transforms
- [Blend Modes](https://img.ly/docs/cesdk/node-native/create-composition/blend-modes-ad3519/) - Control how blocks interact visually
- [Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) - Export options and formats
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Templates"
description: "Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates-3aef79/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/node-native/create-templates/overview-4ebe30/) - Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK.
- [Create From Scratch](https://img.ly/docs/cesdk/node-native/create-templates/from-scratch-663cda/) - Build reusable design templates programmatically using CE.SDK's APIs. Create scenes, add text and graphic blocks, configure placeholders and variables, apply editing constraints, and save templates for reuse.
- [Import Templates](https://img.ly/docs/cesdk/node-native/create-templates/import-e50084/) - Load and import design templates into CE.SDK from URLs, archives, and serialized strings.
- [Dynamic Content](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content-53fad7/) - Use variables and placeholders to inject dynamic data into templates at design or runtime.
- [Lock the Template](https://img.ly/docs/cesdk/node-native/create-templates/lock-131489/) - Restrict editing access to specific elements or properties in a template to enforce design rules.
- [Edit or Remove Templates](https://img.ly/docs/cesdk/node-native/create-templates/edit-or-remove-38a8be/) - Modify existing templates and manage template lifecycle by loading, editing, saving, and removing templates from asset sources.
- [Add to Template Library](https://img.ly/docs/cesdk/node-native/create-templates/add-to-template-library-8bfbc7/) - Save and organize templates in an asset source for users to browse and apply from the template library.
- [Overview](https://img.ly/docs/cesdk/node-native/use-templates/overview-ae74e1/) - Learn how to browse, apply, and dynamically populate templates in CE.SDK to streamline design workflows.
- [Apply a Template](https://img.ly/docs/cesdk/node-native/use-templates/apply-template-35c73e/) - Learn how to apply template scenes via API in the CreativeEditor SDK.
- [Generate From Template](https://img.ly/docs/cesdk/node-native/use-templates/generate-334e15/) - Learn how to generate finished designs from templates by loading, populating variables, and exporting to images, PDFs, or videos.
- [Replace Content](https://img.ly/docs/cesdk/node-native/use-templates/replace-content-4c482b/) - Learn how to dynamically replace content within templates using CE.SDK's placeholder and variable systems.
- [Use Templates Programmatically](https://img.ly/docs/cesdk/node-native/use-templates/programmatic-9349f3/) - Work with templates programmatically through CE.SDK's engine APIs to load existing templates, build new templates from scratch, modify template structures, and populate templates with dynamic data for batch processing and automation.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Dynamic Content"
description: "Use variables and placeholders to inject dynamic data into templates at design or runtime."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content-53fad7/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content-53fad7/)
---
Dynamic content transforms static designs into flexible, data-driven templates. CE.SDK provides three complementary capabilities—text variables, placeholders, and editing constraints—that work together to enable personalization while maintaining design integrity in headless Node.js environments.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-add-dynamic-content-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-templates-add-dynamic-content-server-js)
```typescript file=@cesdk_web_examples/guides-create-templates-add-dynamic-content-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { createInterface } from 'readline';
// Load environment variables
config();
/**
* CE.SDK Server Guide: 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
* - Exporting personalized designs to PNG
*/
// Helper function to prompt for user input
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
// Gather user input for personalization
console.log('=== Dynamic Content Generator ===\n');
const firstName =
(await prompt('Enter first name (default: Jane): ')) || 'Jane';
const lastName = (await prompt('Enter last name (default: Doe): ')) || 'Doe';
const companyName =
(await prompt('Enter company name (default: IMG.LY): ')) || 'IMG.LY';
const heroImageUrl =
(await prompt('Enter hero image URL (default: sample image): ')) ||
'https://img.ly/static/ubq_samples/sample_1.jpg';
console.log('\nGenerating design with:');
console.log(` First Name: ${firstName}`);
console.log(` Last Name: ${lastName}`);
console.log(` Company: ${companyName}`);
console.log(` Hero Image: ${heroImageUrl}\n`);
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with free positioning
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.setFloat(scene, 'scene/dpi', 30);
engine.block.appendChild(scene, page);
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 from user input
engine.variable.setString('firstName', firstName);
engine.variable.setString('lastName', lastName);
engine.variable.setString('companyName', companyName);
// Create heading with company variable
const headingText = engine.block.create('text');
engine.block.replaceText(
headingText,
'Welcome to {{companyName}}, {{firstName}} {{lastName}}.'
);
engine.block.appendChild(page, headingText);
engine.block.setPositionX(headingText, contentX);
engine.block.setPositionY(headingText, 200);
engine.block.setWidth(headingText, contentWidth);
engine.block.setHeightMode(headingText, 'Auto');
engine.block.setTextColor(headingText, { r: 0.1, g: 0.1, b: 0.1, a: 1.0 });
engine.block.setFloat(headingText, 'text/fontSize', 64);
// Create description with bullet points
const descriptionText = engine.block.create('text');
engine.block.appendChild(page, descriptionText);
engine.block.setPositionX(descriptionText, contentX);
engine.block.setPositionY(descriptionText, 300);
engine.block.setWidth(descriptionText, contentWidth);
engine.block.setHeightMode(descriptionText, 'Auto');
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.setTextColor(descriptionText, {
r: 0.2,
g: 0.2,
b: 0.2,
a: 1.0
});
engine.block.setFloat(descriptionText, 'text/fontSize', 44);
// Discover all variables in the scene
const allVariables = engine.variable.findAll();
console.log('Variables in scene:', allVariables);
// PLACEHOLDERS: Create hero image from user-provided URL
const heroImage = await engine.block.addImage(heroImageUrl, {
size: { width: contentWidth, height: 140 }
});
engine.block.appendChild(page, heroImage);
engine.block.setPositionX(heroImage, contentX);
engine.block.setPositionY(heroImage, 40);
engine.block.setWidth(heroImage, contentWidth);
engine.block.setHeight(heroImage, 140);
// 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);
engine.block.setWidth(logo, 100);
engine.block.setHeight(logo, 25);
// 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);
// Export the personalized design to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
const outputPath = `${outputDir}/welcome-${firstName.toLowerCase()}-${lastName.toLowerCase()}.png`;
writeFileSync(outputPath, buffer);
console.log(`\n✓ Exported result to ${outputPath}`);
console.log('\nDynamic Content generation completed.');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
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 hero image from a user-provided URL, and a protected logo. The server prompts for input values and exports a personalized PNG.
## Dynamic Content Capabilities
CE.SDK offers three ways to make templates dynamic:
- **Text Variables** — Insert `{{tokens}}` in text that resolve to dynamic values at runtime
- **Placeholders** — Mark blocks as drop zones where users can swap images or videos
- **Editing Constraints** — Lock specific properties to protect brand elements while allowing controlled changes
## Text Variables
Text variables enable data-driven text personalization. Define variables using `engine.variable.setString()`, then reference them in text blocks with `{{variableName}}` tokens.
```typescript highlight-text-variables
engine.variable.setString('firstName', firstName);
engine.variable.setString('lastName', lastName);
engine.variable.setString('companyName', companyName);
// Create heading with company variable
const headingText = engine.block.create('text');
engine.block.replaceText(
headingText,
'Welcome to {{companyName}}, {{firstName}} {{lastName}}.'
);
```
Variables are defined globally and can be referenced in any text block. The `findAll()` method returns all variable keys in the scene, useful for validating that all required variables are set before export.
> **Note:** Variable keys are case-sensitive. `{{Name}}` and `{{name}}` are different variables.
## Placeholders
Placeholders turn design blocks into drop zones for swappable media. In server environments, you can programmatically set the placeholder image URL from user input or database values.
```typescript highlight-placeholders
// Enable placeholder behavior for the hero image
if (engine.block.supportsPlaceholderBehavior(heroImage)) {
engine.block.setPlaceholderBehaviorEnabled(heroImage, true);
engine.block.setPlaceholderEnabled(heroImage, true);
if (engine.block.supportsPlaceholderControls(heroImage)) {
engine.block.setPlaceholderControlsOverlayEnabled(heroImage, true);
engine.block.setPlaceholderControlsButtonEnabled(heroImage, true);
}
}
```
Enable placeholder behavior with `setPlaceholderBehaviorEnabled()`, then enable user interaction with `setPlaceholderEnabled()`. The placeholder configuration is preserved when saving templates, allowing future editing sessions to replace the content.
## Editing Constraints
Editing constraints protect design integrity by limiting what users can modify. Use scope-based APIs to lock specific properties while keeping others editable.
```typescript highlight-editing-constraints
// Lock the logo: prevent moving, resizing, and selection
engine.block.setScopeEnabled(logo, 'layer/move', false);
engine.block.setScopeEnabled(logo, 'layer/resize', false);
engine.block.setScopeEnabled(logo, 'editor/select', false);
// Verify constraints are applied
const canSelect = engine.block.isScopeEnabled(logo, 'editor/select');
const canMove = engine.block.isScopeEnabled(logo, 'layer/move');
console.log('Logo - canSelect:', canSelect, 'canMove:', canMove);
```
The `setScopeEnabled()` method controls individual properties. Setting `'editor/select'` to `false` prevents users from selecting the block entirely, making it completely non-interactive. Combined with `'layer/move'` and `'layer/resize'`, this creates a fully protected element.
## Choosing the Right Capability
| Need | Capability |
| --- | --- |
| Dynamic text content | Text Variables |
| Swappable images/videos | Placeholders |
| Lock specific properties | Editing Constraints |
## API Reference
| Method | Description |
| --- | --- |
| `engine.variable.findAll()` | Get all variable keys in the scene |
| `engine.variable.setString()` | Create or update a text variable |
| `engine.variable.getString()` | Read a variable's current value |
| `engine.block.supportsPlaceholderBehavior()` | Check placeholder support |
| `engine.block.setPlaceholderBehaviorEnabled()` | Enable placeholder behavior |
| `engine.block.setPlaceholderEnabled()` | Enable user interaction |
| `engine.block.findAllPlaceholders()` | Find all placeholder blocks |
| `engine.block.setScopeEnabled()` | Enable or disable editing scope |
| `engine.block.isScopeEnabled()` | Query scope state |
---
## Related Pages
- [Text Variables](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/) - Define dynamic text elements that can be populated with custom values during design generation in Node.js environments.
- [Placeholders](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/) - Use placeholders to mark editable image, video, or text areas within a locked template layout.
- [Set Editing Constraints](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) - Control editing capabilities in CE.SDK templates using the Scope system to lock positions, prevent transformations, and create guided editing experiences in Node.js/server environments
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Placeholders"
description: "Use placeholders to mark editable image, video, or text areas within a locked template layout."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content-53fad7/) > [Placeholders](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/)
---
Placeholders turn design blocks into drop-zones that can be programmatically populated with content while maintaining layout and styling control.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-dynamic-content-placeholders-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-templates-dynamic-content-placeholders-server-js)
Placeholders enable content replacement while preserving design constraints. They support both graphic blocks (images/videos) and text blocks, with different configuration approaches for each type.
```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-placeholders-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
/**
* CE.SDK Server Guide: Dynamic Content Placeholders
*
* This example demonstrates three different placeholder configurations:
* 1. All placeholder controls enabled (all scopes + behavior)
* 2. Fill properties only (fill scopes + behavior)
* 3. No placeholder features (default state)
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init();
try {
// Create a design scene
engine.scene.create('VerticalStack', {
page: { size: { width: 1200, height: 800 } },
});
const pages = engine.block.findByType('page');
const page = pages[0];
if (!page) {
throw new Error('No page found');
}
// Get page dimensions
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Layout configuration for 3 blocks horizontally
const blockWidth = 300;
const blockHeight = 300;
const spacing = 50;
const startX = (pageWidth - blockWidth * 3 - spacing * 2) / 2;
const blockY = (pageHeight - blockHeight) / 2 + 40;
const labelY = blockY - 50;
// Sample images
const imageUri1 = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const imageUri2 = 'https://img.ly/static/ubq_samples/sample_2.jpg';
const imageUri3 = 'https://img.ly/static/ubq_samples/sample_3.jpg';
// Define ALL available scopes for reference
const allScopes: Array<
| 'text/edit'
| 'text/character'
| 'fill/change'
| 'fill/changeType'
| 'stroke/change'
| 'shape/change'
| 'layer/move'
| 'layer/resize'
| 'layer/rotate'
| 'layer/flip'
| 'layer/crop'
| 'layer/opacity'
| 'layer/blendMode'
| 'layer/visibility'
| 'layer/clipping'
| 'appearance/adjustments'
| 'appearance/filter'
| 'appearance/effect'
| 'appearance/blur'
| 'appearance/shadow'
| 'appearance/animation'
| 'lifecycle/destroy'
| 'lifecycle/duplicate'
| 'editor/add'
| 'editor/select'
> = [
'text/edit',
'text/character',
'fill/change',
'fill/changeType',
'stroke/change',
'shape/change',
'layer/move',
'layer/resize',
'layer/rotate',
'layer/flip',
'layer/crop',
'layer/opacity',
'layer/blendMode',
'layer/visibility',
'layer/clipping',
'appearance/adjustments',
'appearance/filter',
'appearance/effect',
'appearance/blur',
'appearance/shadow',
'appearance/animation',
'lifecycle/destroy',
'lifecycle/duplicate',
'editor/add',
'editor/select',
];
// Block 1: All Placeholder Controls Enabled
const block1 = await engine.block.addImage(imageUri1, {
size: { width: blockWidth, height: blockHeight },
});
engine.block.appendChild(page, block1);
engine.block.setPositionX(block1, startX);
engine.block.setPositionY(block1, blockY);
// Step 1: Explicitly disable ALL scopes first
allScopes.forEach((scope) => {
engine.block.setScopeEnabled(block1, scope, false);
});
// Step 2: Enable specific scopes for full placeholder functionality
// General/Layer options
engine.block.setScopeEnabled(block1, 'layer/opacity', true);
engine.block.setScopeEnabled(block1, 'layer/blendMode', true);
engine.block.setScopeEnabled(block1, 'lifecycle/duplicate', true);
engine.block.setScopeEnabled(block1, 'lifecycle/destroy', true);
// Arrange scopes
engine.block.setScopeEnabled(block1, 'layer/move', true);
engine.block.setScopeEnabled(block1, 'layer/resize', true);
engine.block.setScopeEnabled(block1, 'layer/rotate', true);
engine.block.setScopeEnabled(block1, 'layer/flip', true);
// Fill scopes (for image replacement and cropping)
engine.block.setScopeEnabled(block1, 'fill/change', true);
engine.block.setScopeEnabled(block1, 'fill/changeType', true);
engine.block.setScopeEnabled(block1, 'layer/crop', true);
// Appearance scopes
engine.block.setScopeEnabled(block1, 'appearance/adjustments', true);
engine.block.setScopeEnabled(block1, 'appearance/filter', true);
engine.block.setScopeEnabled(block1, 'appearance/effect', true);
engine.block.setScopeEnabled(block1, 'appearance/blur', true);
engine.block.setScopeEnabled(block1, 'appearance/shadow', true);
engine.block.setScopeEnabled(block1, 'appearance/animation', true);
engine.block.setScopeEnabled(block1, 'editor/select', true);
// Step 3: Enable placeholder behavior ("Act as a placeholder")
// This makes the block interactive in Adopter mode
engine.block.setPlaceholderEnabled(block1, true);
// Step 4: Check if block/fill supports placeholder features
const fill1 = engine.block.getFill(block1);
const supportsBehavior = engine.block.supportsPlaceholderBehavior(fill1);
// Enable placeholder behavior on the fill (for graphic blocks)
if (supportsBehavior) {
engine.block.setPlaceholderBehaviorEnabled(fill1, true);
}
// Block 2: Fill Properties Only
const block2 = await engine.block.addImage(imageUri2, {
size: { width: blockWidth, height: blockHeight },
});
engine.block.appendChild(page, block2);
engine.block.setPositionX(block2, startX + blockWidth + spacing);
engine.block.setPositionY(block2, blockY);
// Batch operation: Apply settings to multiple blocks
const graphicBlocks = [block1, block2];
graphicBlocks.forEach((block) => {
// Enable placeholder for each block
engine.block.setPlaceholderEnabled(block, true);
const fill = engine.block.getFill(block);
if (engine.block.supportsPlaceholderBehavior(fill)) {
engine.block.setPlaceholderBehaviorEnabled(fill, true);
}
});
// Step 1: Explicitly disable ALL scopes first
allScopes.forEach((scope) => {
engine.block.setScopeEnabled(block2, scope, false);
});
// Step 2: Enable ONLY fill-related scopes
engine.block.setScopeEnabled(block2, 'fill/change', true);
engine.block.setScopeEnabled(block2, 'fill/changeType', true);
engine.block.setScopeEnabled(block2, 'layer/crop', true);
engine.block.setScopeEnabled(block2, 'editor/select', true);
// Step 3: Enable placeholder behavior ("Act as a placeholder")
engine.block.setPlaceholderEnabled(block2, true);
// Step 4: Enable fill-based placeholder behavior
const fill2 = engine.block.getFill(block2);
if (engine.block.supportsPlaceholderBehavior(fill2)) {
engine.block.setPlaceholderBehaviorEnabled(fill2, true);
}
// Block 3: No Placeholder Features (Default State)
const block3 = await engine.block.addImage(imageUri3, {
size: { width: blockWidth, height: blockHeight },
});
engine.block.appendChild(page, block3);
engine.block.setPositionX(block3, startX + (blockWidth + spacing) * 2);
engine.block.setPositionY(block3, blockY);
// Explicitly disable ALL scopes to ensure default state
allScopes.forEach((scope) => {
engine.block.setScopeEnabled(block3, scope, false);
});
// No placeholder behavior enabled - this block remains non-interactive
// Add descriptive labels above each block
const labelConfig = {
height: 40,
fontSize: 34,
fontUri:
'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Bold.ttf',
fontFamily: 'Roboto',
};
// Label for Block 1
const label1 = engine.block.create('text');
engine.block.appendChild(page, label1);
engine.block.setPositionX(label1, startX);
engine.block.setPositionY(label1, labelY);
engine.block.setWidth(label1, blockWidth);
engine.block.setHeight(label1, labelConfig.height);
engine.block.replaceText(label1, 'All Controls');
engine.block.setTextColor(label1, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 });
engine.block.setFont(label1, labelConfig.fontUri, {
name: labelConfig.fontFamily,
fonts: [{ uri: labelConfig.fontUri, subFamily: 'Bold' }],
});
engine.block.setFloat(label1, 'text/fontSize', labelConfig.fontSize);
engine.block.setEnum(label1, 'text/horizontalAlignment', 'Center');
// Label for Block 2
const label2 = engine.block.create('text');
engine.block.appendChild(page, label2);
engine.block.setPositionX(label2, startX + blockWidth + spacing);
engine.block.setPositionY(label2, labelY);
engine.block.setWidth(label2, blockWidth);
engine.block.setHeight(label2, labelConfig.height);
engine.block.replaceText(label2, 'Fill Only');
engine.block.setTextColor(label2, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 });
engine.block.setFont(label2, labelConfig.fontUri, {
name: labelConfig.fontFamily,
fonts: [{ uri: labelConfig.fontUri, subFamily: 'Bold' }],
});
engine.block.setFloat(label2, 'text/fontSize', labelConfig.fontSize);
engine.block.setEnum(label2, 'text/horizontalAlignment', 'Center');
// Label for Block 3
const label3 = engine.block.create('text');
engine.block.appendChild(page, label3);
engine.block.setPositionX(label3, startX + (blockWidth + spacing) * 2);
engine.block.setPositionY(label3, labelY);
engine.block.setWidth(label3, blockWidth);
engine.block.setHeight(label3, labelConfig.height);
engine.block.replaceText(label3, 'Disabled');
engine.block.setTextColor(label3, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 });
engine.block.setFont(label3, labelConfig.fontUri, {
name: labelConfig.fontFamily,
fonts: [{ uri: labelConfig.fontUri, subFamily: 'Bold' }],
});
engine.block.setFloat(label3, 'text/fontSize', labelConfig.fontSize);
engine.block.setEnum(label3, 'text/horizontalAlignment', 'Center');
// Verify configurations
// eslint-disable-next-line no-console
console.log('Block 1 - All Controls:');
// eslint-disable-next-line no-console
console.log(
' Placeholder enabled:',
engine.block.isPlaceholderEnabled(block1)
);
// eslint-disable-next-line no-console
console.log(' Scopes enabled:');
// eslint-disable-next-line no-console
console.log(
' - layer/move:',
engine.block.isScopeEnabled(block1, 'layer/move')
);
// eslint-disable-next-line no-console
console.log(
' - layer/resize:',
engine.block.isScopeEnabled(block1, 'layer/resize')
);
// eslint-disable-next-line no-console
console.log(
' - fill/change:',
engine.block.isScopeEnabled(block1, 'fill/change')
);
// eslint-disable-next-line no-console
console.log(
' - layer/crop:',
engine.block.isScopeEnabled(block1, 'layer/crop')
);
// eslint-disable-next-line no-console
console.log(
' - appearance/adjustments:',
engine.block.isScopeEnabled(block1, 'appearance/adjustments')
);
// eslint-disable-next-line no-console
console.log('\nBlock 2 - Fill Only:');
// eslint-disable-next-line no-console
console.log(
' Placeholder enabled:',
engine.block.isPlaceholderEnabled(block2)
);
// eslint-disable-next-line no-console
console.log(' Scopes enabled:');
// eslint-disable-next-line no-console
console.log(
' - layer/move:',
engine.block.isScopeEnabled(block2, 'layer/move')
);
// eslint-disable-next-line no-console
console.log(
' - fill/change:',
engine.block.isScopeEnabled(block2, 'fill/change')
);
// eslint-disable-next-line no-console
console.log(
' - fill/changeType:',
engine.block.isScopeEnabled(block2, 'fill/changeType')
);
// eslint-disable-next-line no-console
console.log(
' - layer/crop:',
engine.block.isScopeEnabled(block2, 'layer/crop')
);
// eslint-disable-next-line no-console
console.log('\nBlock 3 - Disabled:');
// eslint-disable-next-line no-console
console.log(
' Placeholder enabled:',
engine.block.isPlaceholderEnabled(block3)
);
// eslint-disable-next-line no-console
console.log(' Scopes enabled:');
// eslint-disable-next-line no-console
console.log(
' - layer/move:',
engine.block.isScopeEnabled(block3, 'layer/move')
);
// eslint-disable-next-line no-console
console.log(
' - fill/change:',
engine.block.isScopeEnabled(block3, 'fill/change')
);
// Export the configured template
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Export the template as a scene file for later use
const sceneBlob = await engine.scene.saveToArchive();
const sceneBuffer = Buffer.from(await sceneBlob.arrayBuffer());
writeFileSync(`${outputDir}/placeholders-template.scene`, sceneBuffer);
// Also export a visual preview
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/placeholders-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('\n✓ Exported template to output/placeholders-template.scene');
// eslint-disable-next-line no-console
console.log('✓ Exported preview to output/placeholders-result.png');
// eslint-disable-next-line no-console
console.log('Placeholder configurations initialized successfully');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers placeholder fundamentals, checking support, enabling behavior, and configuring placeholders for graphic and text blocks programmatically.
## Placeholder Fundamentals
Placeholders convert design blocks into interactive drop-zones where content can be programmatically replaced while maintaining layout and styling control.
### Block-Level vs Fill-Level Behavior
The key distinction in placeholder implementation depends on block type:
**For graphic blocks** (images/videos): Placeholder behavior is enabled on the **fill**, not the block itself. This pattern reflects how graphic blocks contain fills that can be replaced.
**For text blocks**: Placeholder behavior is enabled directly on the **block**.
We can check support with `supportsPlaceholderBehavior()` for both blocks and fills.
## Checking Placeholder Support
Before enabling placeholder features, check if a block supports them:
```typescript highlight-check-support
// Step 4: Check if block/fill supports placeholder features
const fill1 = engine.block.getFill(block1);
const supportsBehavior = engine.block.supportsPlaceholderBehavior(fill1);
```
The `supportsPlaceholderBehavior()` method indicates whether a block or fill can become a drop-zone. For graphic blocks, we check the fill rather than the block itself.
## Enabling Placeholder Behavior
To convert a block into a placeholder drop-zone, enable placeholder behavior. The approach differs based on block type.
### For Graphic Blocks (Images/Videos)
For graphic blocks, placeholder behavior must be enabled on the **fill**, not the block itself:
```typescript highlight-enable-behavior
// Enable placeholder behavior on the fill (for graphic blocks)
if (supportsBehavior) {
engine.block.setPlaceholderBehaviorEnabled(fill1, true);
}
```
This pattern is critical: `setPlaceholderBehaviorEnabled()` is called on the fill obtained from `block.getFill()`. This reflects the underlying architecture where graphic blocks contain replaceable fills.
### For Text Blocks
For text blocks, placeholder behavior is enabled directly on the block. Text blocks don't use fills in the same way as graphics, so placeholder behavior is configured on the block itself using the same methods (`supportsPlaceholderBehavior()` and `setPlaceholderBehaviorEnabled()`) but applied to the block rather than a fill.
We can verify placeholder behavior state with `isPlaceholderBehaviorEnabled()` on the appropriate target (fill for graphics, block for text).
## Enabling Adopter Mode Interaction
Placeholder behavior alone isn't enough - blocks must also be enabled for interaction in Adopter mode:
```typescript highlight-enable-adopter-mode
// Step 3: Enable placeholder behavior ("Act as a placeholder")
// This makes the block interactive in Adopter mode
engine.block.setPlaceholderEnabled(block1, true);
```
The `setPlaceholderEnabled()` method controls whether the placeholder is interactive for programmatic content replacement. CE.SDK distinguishes Creator (full access) and Adopter (replace-only) roles, and this setting enables the Adopter mode functionality.
### Automatic Management
In practice, `setPlaceholderEnabled()` is typically managed automatically by the editor: when you enable relevant scopes (like `fill/change` for graphics or `text/edit` for text), the placeholder interaction is automatically enabled. When all scopes are disabled, placeholder interaction is automatically disabled.
## Scope Requirements and Dependencies
Placeholders depend on specific scopes being enabled to function correctly. For graphic blocks (images/videos), the `fill/change` scope must be enabled before placeholder behavior will work. When you disable `fill/change`, placeholder behavior and interaction are automatically disabled to maintain consistency.
For text blocks, the `text/edit` scope must be enabled before placeholder behavior can function.
**Optional related scopes** that enhance placeholder functionality:
- `fill/changeType` - Allows changing between image, video, and solid color fills
- `layer/crop` - Enables cropping replacement images
- `text/character` - Allows font and character formatting for text placeholders
## Working with Multiple Placeholders
When creating templates with multiple placeholders, apply settings systematically:
```typescript highlight-batch-operation
// Batch operation: Apply settings to multiple blocks
const graphicBlocks = [block1, block2];
graphicBlocks.forEach((block) => {
// Enable placeholder for each block
engine.block.setPlaceholderEnabled(block, true);
const fill = engine.block.getFill(block);
if (engine.block.supportsPlaceholderBehavior(fill)) {
engine.block.setPlaceholderBehaviorEnabled(fill, true);
}
});
```
This pattern works well for collage templates, product showcases, or any layout requiring multiple content slots.
## Exporting Templates
After configuring placeholders, we export the template for later use:
```typescript highlight-export
// Export the template as a scene file for later use
const sceneBlob = await engine.scene.saveToArchive();
const sceneBuffer = Buffer.from(await sceneBlob.arrayBuffer());
writeFileSync(`${outputDir}/placeholders-template.scene`, sceneBuffer);
// Also export a visual preview
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/placeholders-result.png`, buffer);
```
We save both the template as a scene file and a visual preview as PNG. The scene file preserves all placeholder settings for later use.
## Cleanup
We must dispose the engine to free system resources when processing is complete:
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
Always use a try-finally block to ensure the engine is disposed even if errors occur during processing.
## API Reference
| Method | Description |
|--------|-------------|
| `CreativeEngine.init()` | Initializes the headless engine for programmatic creation |
| `engine.scene.create()` | Creates a new scene programmatically |
| `engine.block.supportsPlaceholderBehavior()` | Checks whether the block or fill supports placeholder behavior |
| `engine.block.setPlaceholderBehaviorEnabled()` | Enables or disables placeholder behavior for a block or fill |
| `engine.block.isPlaceholderBehaviorEnabled()` | Queries whether placeholder behavior is enabled |
| `engine.block.setPlaceholderEnabled()` | Enables or disables placeholder interaction in Adopter mode |
| `engine.block.isPlaceholderEnabled()` | Queries whether placeholder interaction is enabled |
| `engine.block.setScopeEnabled()` | Enables or disables editing scopes required for placeholder functionality |
| `engine.block.getFill()` | Gets the fill from a graphic block |
| `engine.block.addImage()` | Creates and adds an image block to the scene |
| `engine.block.findByType()` | Finds all blocks of a specific type in the scene |
| `engine.block.export()` | Exports a block to an image format |
| `engine.scene.saveToArchive()` | Saves the scene to a .scene archive file |
| `engine.dispose()` | Disposes the engine and frees resources |
## Next Steps
- [Lock the Template](https://img.ly/docs/cesdk/node-native/create-templates/lock-131489/) - Restrict editing access to specific elements or properties to enforce design rules
- [Text Variables](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/) - Define dynamic text elements that can be populated with custom values
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Set Editing Constraints"
description: "Control editing capabilities in CE.SDK templates using the Scope system to lock positions, prevent transformations, and create guided editing experiences in Node.js/server environments"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/set-editing-constraints-c892c0/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content-53fad7/) > [Set Editing Constraints](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/set-editing-constraints-c892c0/)
---
Control what users can edit in templates by setting fine-grained permissions on individual blocks or globally across your scene using CE.SDK's Scope system in headless Node.js environments.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples)
Editing constraints in CE.SDK allow you to lock specific properties of design elements programmatically from your server or Node.js application. The Scope system provides granular control over 20+ editing capabilities including movement, resizing, rotation, fill changes, text editing, and lifecycle operations.
```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-set-editing-constraints-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Set Editing Constraints
*
* Demonstrates how to use CE.SDK's Scope system to control editing capabilities:
* - Setting global scopes to respect block-level settings
* - Disabling move scope to lock position
* - Disabling lifecycle scopes to prevent deletion
* - Checking scope states
* - Saving constrained scenes
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 1920, height: 1080 } },
});
const page = engine.block.findByType('page')[0];
if (!page) {
throw new Error('No page found');
}
// Set page background color
const pageFill = engine.block.getFill(page);
engine.block.setColor(pageFill, 'fill/color/value', {
r: 0.95,
g: 0.95,
b: 0.95,
a: 1.0,
});
// Set global scopes to 'Defer' to respect block-level scope settings
// Without this, global 'Allow' settings might override block-level restrictions
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('layer/resize', 'Defer');
engine.editor.setGlobalScope('lifecycle/destroy', 'Defer');
engine.editor.setGlobalScope('lifecycle/duplicate', 'Defer');
// Global scope modes:
// - 'Allow': Always allow (overrides block-level settings)
// - 'Deny': Always deny (overrides block-level settings)
// - 'Defer': Use block-level settings (respects setScopeEnabled)
// Calculate layout for 4 examples (2x2 grid)
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const margin = 40;
const spacing = 20;
const blockWidth = (pageWidth - margin * 2 - spacing) / 2;
const blockHeight = (pageHeight - margin * 2 - spacing) / 2;
const getPosition = (index: number) => {
const col = index % 2;
const row = Math.floor(index / 2);
return {
x: margin + col * (blockWidth + spacing),
y: margin + row * (blockHeight + spacing),
};
};
// Helper function to create a labeled example block
const createExampleBlock = (
labelText: string,
backgroundColor: { r: number; g: number; b: number },
applyScopesCallback?: (blockId: number) => void
): number => {
// Create container block
const block = engine.block.create('graphic');
const shape = engine.block.createShape('rect');
engine.block.setShape(block, shape);
engine.block.setWidth(block, blockWidth);
engine.block.setHeight(block, blockHeight);
// Set background color
const fill = engine.block.createFill('color');
engine.block.setFill(block, fill);
engine.block.setColor(fill, 'fill/color/value', {
...backgroundColor,
a: 1.0,
});
// Add label text
const textBlock = engine.block.create('text');
engine.block.setWidth(textBlock, blockWidth * 0.85);
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setString(textBlock, 'text/text', labelText);
engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center');
engine.block.setFloat(textBlock, 'text/fontSize', 16);
// Append text to get dimensions
engine.block.appendChild(block, textBlock);
// Center text in container
const textWidth = engine.block.getWidth(textBlock);
const textHeight = engine.block.getHeight(textBlock);
engine.block.setPositionX(textBlock, (blockWidth - textWidth) / 2);
engine.block.setPositionY(textBlock, (blockHeight - textHeight) / 2);
// Set text color to white
const textFill = engine.block.createFill('color');
engine.block.setFill(textBlock, textFill);
engine.block.setColor(textFill, 'fill/color/value', {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
});
// Apply scope configuration to both blocks
if (applyScopesCallback) {
applyScopesCallback(block);
applyScopesCallback(textBlock);
}
// Append container to page
engine.block.appendChild(page, block);
return block;
};
// ===== Example 1: All Scopes Enabled =====
const enableAllScopes = (block: number) => {
// Explicitly enable all transform scopes
engine.block.setScopeEnabled(block, 'layer/move', true);
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
// Explicitly enable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', true);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true);
// Explicitly enable fill scopes
engine.block.setScopeEnabled(block, 'fill/change', true);
engine.block.setScopeEnabled(block, 'fill/changeType', true);
// Explicitly enable text scopes
engine.block.setScopeEnabled(block, 'text/edit', true);
engine.block.setScopeEnabled(block, 'text/character', true);
};
const fullyEditableBlock = createExampleBlock(
'Fully\neditable',
{
r: 0.5,
g: 0.85,
b: 0.5,
},
enableAllScopes
);
// All scopes are explicitly enabled - users have full editing capabilities
// This is the default behavior, but explicitly enabling shows clear intent
// ===== Example 2: Lock Position (Disable Move Scope) =====
const disableMoveScope = (block: number) => {
// Disable move scope
engine.block.setScopeEnabled(block, 'layer/move', false);
// Explicitly enable other transform scopes
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
// Explicitly enable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', true);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true);
};
const moveLockedBlock = createExampleBlock(
'Locked\nposition',
{
r: 0.5,
g: 0.75,
b: 0.9,
},
disableMoveScope
);
// Block position is locked - users cannot move or reposition it
// Other scopes are explicitly enabled: resizing, rotation, flipping, deletion, duplication
// ===== Example 3: Prevent Deletion (Disable Lifecycle Scopes) =====
const disableLifecycleScopes = (block: number) => {
// Disable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false);
// Explicitly enable transform scopes
engine.block.setScopeEnabled(block, 'layer/move', true);
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
};
const lifecycleLockedBlock = createExampleBlock(
'Cannot\ndelete',
{
r: 0.75,
g: 0.75,
b: 0.75,
},
disableLifecycleScopes
);
// Block cannot be deleted or duplicated
// Other scopes are explicitly enabled: moving, resizing, rotation, flipping
// ===== Example 4: All Scopes Disabled =====
const disableAllScopes = (block: number) => {
// Disable all transform scopes
engine.block.setScopeEnabled(block, 'layer/move', false);
engine.block.setScopeEnabled(block, 'layer/resize', false);
engine.block.setScopeEnabled(block, 'layer/rotate', false);
engine.block.setScopeEnabled(block, 'layer/flip', false);
engine.block.setScopeEnabled(block, 'layer/crop', false);
// Disable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false);
// Disable fill scopes
engine.block.setScopeEnabled(block, 'fill/change', false);
engine.block.setScopeEnabled(block, 'fill/changeType', false);
engine.block.setScopeEnabled(block, 'stroke/change', false);
// Disable text scopes
engine.block.setScopeEnabled(block, 'text/edit', false);
engine.block.setScopeEnabled(block, 'text/character', false);
// Disable shape scopes
engine.block.setScopeEnabled(block, 'shape/change', false);
// Disable editor scopes
engine.block.setScopeEnabled(block, 'editor/select', false);
// Disable appearance scopes
engine.block.setScopeEnabled(block, 'layer/opacity', false);
engine.block.setScopeEnabled(block, 'layer/blendMode', false);
engine.block.setScopeEnabled(block, 'layer/visibility', false);
};
const fullyLockedBlock = createExampleBlock(
'Fully\nlocked',
{
r: 0.9,
g: 0.5,
b: 0.5,
},
disableAllScopes
);
// All scopes are disabled - block is completely locked and cannot be edited
// Useful for watermarks, logos, or legal disclaimers
// ===== Block-Level Scope Setting Example =====
// Check if a scope is enabled for a specific block
const canMove = engine.block.isScopeEnabled(moveLockedBlock, 'layer/move');
const canDelete = engine.block.isScopeEnabled(
lifecycleLockedBlock,
'lifecycle/destroy'
);
const canEditFully = engine.block.isScopeEnabled(
fullyEditableBlock,
'layer/move'
);
const canEditLocked = engine.block.isScopeEnabled(
fullyLockedBlock,
'layer/move'
);
// eslint-disable-next-line no-console
console.log('Move-locked block - layer/move enabled:', canMove); // false
// eslint-disable-next-line no-console
console.log('Lifecycle-locked block - lifecycle/destroy enabled:', canDelete); // false
// eslint-disable-next-line no-console
console.log('Fully editable block - layer/move enabled:', canEditFully); // true
// eslint-disable-next-line no-console
console.log('Fully locked block - layer/move enabled:', canEditLocked); // false
// Position blocks in 2x2 grid
const blocks = [
fullyEditableBlock,
moveLockedBlock,
lifecycleLockedBlock,
fullyLockedBlock,
];
blocks.forEach((block, index) => {
const pos = getPosition(index);
engine.block.setPositionX(block, pos.x);
engine.block.setPositionY(block, pos.y);
});
// Save the scene with all scope constraints preserved
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Save scene to file
const sceneData = await engine.scene.saveToString();
writeFileSync(`${outputDir}/constrained-scene.scene`, sceneData);
// eslint-disable-next-line no-console
console.log('✓ Scene saved to output/constrained-scene.scene');
// eslint-disable-next-line no-console
console.log(' All scope constraints are preserved in the scene file');
// Export the result to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/editing-constraints-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported result to output/editing-constraints-result.png');
// Log instructions
// eslint-disable-next-line no-console
console.log(`
=== Editing Constraints Demo ===
Created 4 examples demonstrating scope constraints (arranged in 2x2 grid):
Top row:
1. "Fully editable" (green): All scopes enabled - complete editing freedom
2. "Locked position" (light blue): Cannot move, but can resize/edit/delete
Bottom row:
3. "Cannot delete" (light grey): Cannot delete/duplicate, but can move/resize/edit
4. "Fully locked" (red): All scopes disabled - completely locked
Note: Global scopes are set to 'Defer' to respect block-level settings.
Files created:
- output/constrained-scene.scene (scene with constraints)
- output/editing-constraints-result.png (visual result)
`);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide demonstrates how to apply editing constraints in a headless environment to create templates that can be adopted and used with predefined limitations, ensuring brand consistency and design integrity.
## Understanding Scopes
### What are Scopes?
A scope is a permission key that controls a specific editing capability in CE.SDK. Each scope represents a distinct action users can perform, such as moving blocks (`'layer/move'`), changing fills (`'fill/change'`), or editing text content (`'text/edit'`). By enabling or disabling scopes, you control exactly what users can and cannot do with each design element.
Scopes exist at two levels:
- **Block-level scopes**: Per-block permissions set using `setScopeEnabled()`
- **Global scopes**: Default behavior for all blocks set using `setGlobalScope()`
### Available Scope Categories
CE.SDK provides scopes organized into logical categories:
| Category | Purpose | Example Scopes |
| --- | --- | --- |
| **Text Editing** | Control text content and formatting | `text/edit`, `text/character` |
| **Fill & Stroke** | Manage colors and gradients | `fill/change`, `fill/changeType`, `stroke/change` |
| **Shape** | Modify shape properties | `shape/change` |
| **Layer Transform** | Control position and dimensions | `layer/move`, `layer/resize`, `layer/rotate`, `layer/flip`, `layer/crop` |
| **Layer Appearance** | Manage visual properties | `layer/opacity`, `layer/blendMode`, `layer/visibility` |
| **Effects & Filters** | Apply visual effects | `appearance/adjustments`, `appearance/filter`, `appearance/effect`, `appearance/blur`, `appearance/shadow` |
| **Lifecycle** | Control creation and deletion | `lifecycle/destroy`, `lifecycle/duplicate` |
| **Editor** | Manage scene-level actions | `editor/add`, `editor/select` |
## Scope Configuration
### Global Scope Modes
Global scopes set the default behavior for all blocks in the scene. They have three modes:
- **Allow**: Always allow the action, overriding block-level settings
- **Deny**: Always deny the action, overriding block-level settings
- **Defer**: Use block-level settings (default mode)
To ensure block-level scope settings are respected, set relevant global scopes to 'Defer'. Without setting global scopes to 'Defer', default 'Allow' settings might override your block-level restrictions. This is essential when applying fine-grained constraints.
### Scope Resolution Priority
When both global and block-level scopes are set, they resolve in this order:
1. **Global Deny** takes highest priority (action always denied)
2. **Global Allow** takes second priority (action always allowed)
3. **Global Defer** defers to block-level settings (default behavior)
## Setting Block-Level Constraints
### Locking Position
Prevent users from moving or repositioning a block while allowing other edits:
```typescript highlight=highlight-disable-move-scope
const disableMoveScope = (block: number) => {
// Disable move scope
engine.block.setScopeEnabled(block, 'layer/move', false);
// Explicitly enable other transform scopes
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
// Explicitly enable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', true);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true);
};
const moveLockedBlock = createExampleBlock(
'Locked\nposition',
{
r: 0.5,
g: 0.75,
b: 0.9,
},
disableMoveScope
);
// Block position is locked - users cannot move or reposition it
// Other scopes are explicitly enabled: resizing, rotation, flipping, deletion, duplication
```
The block position is locked—users cannot move or reposition it. Other scopes remain enabled, allowing resizing, editing, and deletion. This pattern maintains layout integrity while allowing content updates.
### Preventing Deletion
Protect blocks from being deleted or duplicated:
```typescript highlight=highlight-disable-lifecycle-scopes
const disableLifecycleScopes = (block: number) => {
// Disable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false);
// Explicitly enable transform scopes
engine.block.setScopeEnabled(block, 'layer/move', true);
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
};
const lifecycleLockedBlock = createExampleBlock(
'Cannot\ndelete',
{
r: 0.75,
g: 0.75,
b: 0.75,
},
disableLifecycleScopes
);
// Block cannot be deleted or duplicated
// Other scopes are explicitly enabled: moving, resizing, rotation, flipping
```
Users cannot delete or duplicate the block but can still move, resize, and edit it. Use this for essential template elements that must remain present.
### Enabling All Scopes
Allow complete editing freedom by explicitly enabling all scopes:
```typescript highlight=highlight-enable-all-scopes
const enableAllScopes = (block: number) => {
// Explicitly enable all transform scopes
engine.block.setScopeEnabled(block, 'layer/move', true);
engine.block.setScopeEnabled(block, 'layer/resize', true);
engine.block.setScopeEnabled(block, 'layer/rotate', true);
engine.block.setScopeEnabled(block, 'layer/flip', true);
// Explicitly enable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', true);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true);
// Explicitly enable fill scopes
engine.block.setScopeEnabled(block, 'fill/change', true);
engine.block.setScopeEnabled(block, 'fill/changeType', true);
// Explicitly enable text scopes
engine.block.setScopeEnabled(block, 'text/edit', true);
engine.block.setScopeEnabled(block, 'text/character', true);
};
const fullyEditableBlock = createExampleBlock(
'Fully\neditable',
{
r: 0.5,
g: 0.85,
b: 0.5,
},
enableAllScopes
);
// All scopes are explicitly enabled - users have full editing capabilities
// This is the default behavior, but explicitly enabling shows clear intent
```
All scopes are explicitly enabled, providing users with full editing capabilities. While this is the default behavior, explicitly enabling shows clear intent.
### Disabling All Scopes
Create completely locked blocks by disabling all editing capabilities:
```typescript highlight=highlight-disable-all-scopes
const disableAllScopes = (block: number) => {
// Disable all transform scopes
engine.block.setScopeEnabled(block, 'layer/move', false);
engine.block.setScopeEnabled(block, 'layer/resize', false);
engine.block.setScopeEnabled(block, 'layer/rotate', false);
engine.block.setScopeEnabled(block, 'layer/flip', false);
engine.block.setScopeEnabled(block, 'layer/crop', false);
// Disable lifecycle scopes
engine.block.setScopeEnabled(block, 'lifecycle/destroy', false);
engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false);
// Disable fill scopes
engine.block.setScopeEnabled(block, 'fill/change', false);
engine.block.setScopeEnabled(block, 'fill/changeType', false);
engine.block.setScopeEnabled(block, 'stroke/change', false);
// Disable text scopes
engine.block.setScopeEnabled(block, 'text/edit', false);
engine.block.setScopeEnabled(block, 'text/character', false);
// Disable shape scopes
engine.block.setScopeEnabled(block, 'shape/change', false);
// Disable editor scopes
engine.block.setScopeEnabled(block, 'editor/select', false);
// Disable appearance scopes
engine.block.setScopeEnabled(block, 'layer/opacity', false);
engine.block.setScopeEnabled(block, 'layer/blendMode', false);
engine.block.setScopeEnabled(block, 'layer/visibility', false);
};
const fullyLockedBlock = createExampleBlock(
'Fully\nlocked',
{
r: 0.9,
g: 0.5,
b: 0.5,
},
disableAllScopes
);
// All scopes are disabled - block is completely locked and cannot be edited
// Useful for watermarks, logos, or legal disclaimers
```
All scopes are disabled, making the block completely locked and uneditable. Useful for watermarks, logos, or legal disclaimers that must remain unchanged.
## Configuring Global Scopes
### Setting Global Scope Modes
Configure global scopes to control default behavior across all blocks:
```typescript highlight=highlight-global-scopes
// Set global scopes to 'Defer' to respect block-level scope settings
// Without this, global 'Allow' settings might override block-level restrictions
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('layer/resize', 'Defer');
engine.editor.setGlobalScope('lifecycle/destroy', 'Defer');
engine.editor.setGlobalScope('lifecycle/duplicate', 'Defer');
// Global scope modes:
// - 'Allow': Always allow (overrides block-level settings)
// - 'Deny': Always deny (overrides block-level settings)
// - 'Defer': Use block-level settings (respects setScopeEnabled)
```
Setting global scopes to 'Defer' ensures block-level scope settings are respected. Without this, default 'Allow' settings might override your block-level restrictions.
## Checking Scope State
### Checking Block-Level Scopes
Query the current state of any scope for a block:
```typescript highlight=highlight-block-level-scope-check
// Check if a scope is enabled for a specific block
const canMove = engine.block.isScopeEnabled(moveLockedBlock, 'layer/move');
const canDelete = engine.block.isScopeEnabled(
lifecycleLockedBlock,
'lifecycle/destroy'
);
const canEditFully = engine.block.isScopeEnabled(
fullyEditableBlock,
'layer/move'
);
const canEditLocked = engine.block.isScopeEnabled(
fullyLockedBlock,
'layer/move'
);
// eslint-disable-next-line no-console
console.log('Move-locked block - layer/move enabled:', canMove); // false
// eslint-disable-next-line no-console
console.log('Lifecycle-locked block - lifecycle/destroy enabled:', canDelete); // false
// eslint-disable-next-line no-console
console.log('Fully editable block - layer/move enabled:', canEditFully); // true
// eslint-disable-next-line no-console
console.log('Fully locked block - layer/move enabled:', canEditLocked); // false
```
Use `isScopeEnabled()` to check the block-level setting. This returns whether the scope is enabled at the block level, but doesn't consider global scope settings.
### Checking Effective Permissions
Check the effective permission considering both block and global settings:
```typescript
// Check if scope is allowed (considers global + block settings)
const moveAllowed = engine.block.isAllowedByScope(block, 'layer/move');
```
`isAllowedByScope()` returns the final permission after resolving block-level and global scope settings. Use this when you need to know if an action is actually permitted.
## Saving Constrained Scenes
Scope constraints are preserved when saving scenes:
```typescript highlight=highlight-save-constrained-scene
// Save the scene with all scope constraints preserved
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Save scene to file
const sceneData = await engine.scene.saveToString();
writeFileSync(`${outputDir}/constrained-scene.scene`, sceneData);
// eslint-disable-next-line no-console
console.log('✓ Scene saved to output/constrained-scene.scene');
// eslint-disable-next-line no-console
console.log(' All scope constraints are preserved in the scene file');
```
When this scene is loaded in a browser or client application, all scope constraints will be maintained. This allows you to:
1. Create templates server-side with predefined constraints
2. Save them to your storage/database
3. Load them client-side with constraints already applied
4. Ensure users can only edit allowed properties
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.setScopeEnabled()` | Enable or disable a scope for a specific block |
| `engine.block.isScopeEnabled()` | Check if a scope is enabled at the block level |
| `engine.block.isAllowedByScope()` | Check if a scope is allowed considering both block and global settings |
| `engine.editor.setGlobalScope()` | Set global scope policy (`'Allow'`, `'Deny'`, or `'Defer'`) |
| `engine.editor.findAllScopes()` | List all available scope keys |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Text Variables"
description: "Define dynamic text elements that can be populated with custom values during design generation in Node.js environments."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content-53fad7/) > [Text Variables](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/)
---
Text variables enable data-driven template personalization in headless Node.js environments. Insert placeholder tokens like `{{firstName}}` into text blocks, then populate them with actual values programmatically. This separates design from content, enabling automated document generation, batch processing, and server-side mass personalization workflows.
> **Reading time:** 12 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-dynamic-content-text-variables-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-templates-dynamic-content-text-variables-server-js)
```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-text-variables-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Text Variables
*
* Demonstrates text variable management in headless Node.js environment:
* - Discovering variables with findAll()
* - Creating and updating variables with setString()
* - Reading variable values with getString()
* - Binding variables to text blocks with {{variable}} tokens
* - Detecting variable references with referencesAnyVariables()
* - Removing variables with remove()
* - Exporting personalized designs to PNG
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 1920, height: 1080 } },
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Discover all existing variables in the scene
// This is useful when loading templates to see what variables need values
const existingVariables = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log('Existing variables:', existingVariables); // []
// Create and update text variables
// If a variable doesn't exist, setString() creates it
// If it already exists, setString() updates its value
engine.variable.setString('firstName', 'Alex');
engine.variable.setString('lastName', 'Smith');
engine.variable.setString('email', 'alex.smith@example.com');
engine.variable.setString('company', 'IMG.LY');
engine.variable.setString('title', 'Creative Developer');
// Read variable values at runtime
const firstName = engine.variable.getString('firstName');
// eslint-disable-next-line no-console
console.log('First name variable:', firstName); // 'Alex'
// Create a text block demonstrating variable binding patterns
const textBlock = engine.block.create('text');
// Multi-line text combining:
// - Single variable ({{firstName}})
// - Multiple variables ({{firstName}} {{lastName}})
// - Variables in context (Email: {{email}})
const textContent = `Hello, {{firstName}}!
Full Name: {{firstName}} {{lastName}}
Email: {{email}}
Position: {{title}}
Company: {{company}}`;
engine.block.replaceText(textBlock, textContent);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setFloat(textBlock, 'text/fontSize', 24);
engine.block.appendChild(page, textBlock);
// Center the text block on the page
const frameX = engine.block.getFrameX(textBlock);
const frameY = engine.block.getFrameY(textBlock);
const frameWidth = engine.block.getFrameWidth(textBlock);
const frameHeight = engine.block.getFrameHeight(textBlock);
engine.block.setPositionX(textBlock, (pageWidth - frameWidth) / 2 - frameX);
engine.block.setPositionY(textBlock, (pageHeight - frameHeight) / 2 - frameY);
// Check if the block contains variable references
const hasVariables = engine.block.referencesAnyVariables(textBlock);
// eslint-disable-next-line no-console
console.log('Text block has variables:', hasVariables); // true
// Create and then remove a temporary variable to demonstrate removal
engine.variable.setString('tempVariable', 'Temporary Value');
// eslint-disable-next-line no-console
console.log('Variables before removal:', engine.variable.findAll());
// Remove the temporary variable
engine.variable.remove('tempVariable');
// eslint-disable-next-line no-console
console.log('Variables after removal:', engine.variable.findAll());
// Final check: List all variables in the scene
const finalVariables = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log('Final variables in scene:', finalVariables);
// Expected: ['firstName', 'lastName', 'email', 'company', 'title']
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/text-variables-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported result to output/text-variables-result.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to programmatically manage text variables for automated document generation and mass personalization in headless Node.js environments.
## Introduction
Text variables allow you to design templates once and personalize them with different content for each use. In server-side environments, this enables powerful automation workflows where CE.SDK Engine runs headlessly to generate personalized designs at scale.
### Key Use Cases
- **Automated Document Generation** - Certificates, invoices, reports with dynamic data
- **Mass Personalization** - Marketing materials populated from databases or CSV files
- **Server-Side Rendering** - Cloud functions that generate personalized graphics on demand
- **Batch Processing** - Generate thousands of unique designs from a single template
## Discovering Variables
When working with templates that already contain variables, discover what variables exist before populating them with values.
```typescript highlight-discover-variables
// Discover all existing variables in the scene
// This is useful when loading templates to see what variables need values
const existingVariables = engine.variable.findAll();
// eslint-disable-next-line no-console
console.log('Existing variables:', existingVariables); // []
```
The `findAll()` method returns an array of all variable keys defined in the scene. This is essential when loading templates to understand what data needs to be provided.
## Creating and Updating Variables
Create or update variables using `setString()`. If the variable doesn't exist, it will be created. If it already exists, its value will be updated.
```typescript highlight-create-update-variables
// Create and update text variables
// If a variable doesn't exist, setString() creates it
// If it already exists, setString() updates its value
engine.variable.setString('firstName', 'Alex');
engine.variable.setString('lastName', 'Smith');
engine.variable.setString('email', 'alex.smith@example.com');
engine.variable.setString('company', 'IMG.LY');
engine.variable.setString('title', 'Creative Developer');
```
> **Note:** Variable keys are case-sensitive. `{{Name}}` and `{{name}}` are different variables. Use consistent naming conventions across your templates.
## Reading Variable Values
Retrieve the current value of a variable at runtime using `getString()`. This is useful for validation or logging current values in automation pipelines.
```typescript highlight-read-variable-value
// Read variable values at runtime
const firstName = engine.variable.getString('firstName');
// eslint-disable-next-line no-console
console.log('First name variable:', firstName); // 'Alex'
```
## Binding Variables to Text Blocks
Insert variable tokens directly into text block content using the `{{variableName}}` syntax. CE.SDK Engine automatically detects and resolves these tokens at render time.
### Single Variable
```typescript highlight-single-variable-binding
// Create a text block demonstrating variable binding patterns
const textBlock = engine.block.create('text');
// Multi-line text combining:
// - Single variable ({{firstName}})
// - Multiple variables ({{firstName}} {{lastName}})
// - Variables in context (Email: {{email}})
const textContent = `Hello, {{firstName}}!
Full Name: {{firstName}} {{lastName}}
Email: {{email}}
Position: {{title}}
Company: {{company}}`;
engine.block.replaceText(textBlock, textContent);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setFloat(textBlock, 'text/fontSize', 24);
engine.block.appendChild(page, textBlock);
```
### Multiple Variables
Combine multiple variables in a single text block for complex text templates:
```typescript highlight-multiple-variable-binding
// Create a text block demonstrating variable binding patterns
const textBlock = engine.block.create('text');
// Multi-line text combining:
// - Single variable ({{firstName}})
// - Multiple variables ({{firstName}} {{lastName}})
// - Variables in context (Email: {{email}})
const textContent = `Hello, {{firstName}}!
Full Name: {{firstName}} {{lastName}}
Email: {{email}}
Position: {{title}}
Company: {{company}}`;
engine.block.replaceText(textBlock, textContent);
engine.block.setWidthMode(textBlock, 'Auto');
engine.block.setHeightMode(textBlock, 'Auto');
engine.block.setFloat(textBlock, 'text/fontSize', 24);
engine.block.appendChild(page, textBlock);
```
The variables resolve in place, maintaining the surrounding text and formatting. Use this for business cards, certificates, or any template requiring multiple personalization points.
## Detecting Variable References
Check if a block contains variable references using `referencesAnyVariables()`. This returns `true` if the block's text contains any `{{variable}}` tokens.
```typescript highlight-detect-variable-references
// Check if the block contains variable references
const hasVariables = engine.block.referencesAnyVariables(textBlock);
// eslint-disable-next-line no-console
console.log('Text block has variables:', hasVariables); // true
```
This is useful for:
- Identifying which blocks need variable values before export
- Implementing validation logic in automation workflows
- Filtering blocks that require data population
## Removing Variables
Remove unused variables from the scene with `remove()`. This cleans up the variable store when certain variables are no longer needed.
```typescript highlight-remove-variable
// Create and then remove a temporary variable to demonstrate removal
engine.variable.setString('tempVariable', 'Temporary Value');
// eslint-disable-next-line no-console
console.log('Variables before removal:', engine.variable.findAll());
// Remove the temporary variable
engine.variable.remove('tempVariable');
// eslint-disable-next-line no-console
console.log('Variables after removal:', engine.variable.findAll());
```
> **Warning:** After removal, text blocks that reference removed variables will display the token literally (e.g., `{{removedVar}}`). Only remove variables after ensuring no blocks reference them.
## API Reference
| Method | Description |
| --- | --- |
| `engine.variable.findAll()` | Get array of all variable keys in the scene |
| `engine.variable.setString()` | Create or update a text variable |
| `engine.variable.getString()` | Read the current value of a variable |
| `engine.variable.remove()` | Delete a variable from the scene |
| `engine.block.referencesAnyVariables()` | Check if a block contains variable tokens |
| `engine.block.replaceText()` | Set text content (supports variable tokens) |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add to Template Library"
description: "Save and organize templates in an asset source for users to browse and apply from the template library."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/add-to-template-library-8bfbc7/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Add to Template Library](https://img.ly/docs/cesdk/node-native/create-templates/add-to-template-library-8bfbc7/)
---
Create a template library where templates can be stored, managed, and applied programmatically in a headless environment.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-add-to-template-library-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-templates-add-to-template-library-server-js)
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, enabling server-side workflows to manage templates programmatically.
```typescript file=@cesdk_web_examples/guides-create-templates-add-to-template-library-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import * as readline from 'readline';
import { mkdirSync, writeFileSync } from 'fs';
/**
* CE.SDK Server Guide: 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. Saving scenes as templates
* 4. Managing templates programmatically
*/
// Helper function to prompt user for input
function prompt(question: string): Promise {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init();
try {
// 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);
return undefined;
});
// Add a template to the source with metadata
engine.asset.addAssetToSource('my-templates', {
id: 'template-postcard',
label: { en: 'Postcard' },
meta: {
uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene',
thumbUri:
'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_postcard_1.jpg'
}
});
// Add more templates
engine.asset.addAssetToSource('my-templates', {
id: 'template-business-card',
label: { en: 'Business Card' },
meta: {
uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_business_card_1.scene',
thumbUri:
'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_business_card_1.jpg'
}
});
engine.asset.addAssetToSource('my-templates', {
id: 'template-social-media',
label: { en: 'Social Media Post' },
meta: {
uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_post_1.scene',
thumbUri:
'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_instagram_post_1.jpg'
}
});
// Load the first template
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
// Ask user how to save the template
console.log('\nHow would you like to save the template?');
console.log('1. String format (lightweight, references remote assets)');
console.log('2. Archive format (self-contained with bundled assets)');
console.log('3. Cancel');
const choice = await prompt('\nEnter your choice (1-3): ');
if (choice === '1') {
mkdirSync('output', { recursive: true });
const templateString = await engine.scene.saveToString();
writeFileSync('output/saved-scene.scene', templateString);
console.log('Template saved to output/saved-scene.scene');
} else if (choice === '2') {
mkdirSync('output', { recursive: true });
const templateBlob = await engine.scene.saveToArchive();
const buffer = Buffer.from(await templateBlob.arrayBuffer());
writeFileSync('output/saved-scene.zip', buffer);
console.log('Template saved to output/saved-scene.zip');
} else {
console.log('Save operation cancelled.');
}
// List all registered asset sources
const sources = engine.asset.findAllSources();
console.log('Registered sources:', sources);
// Notify that source contents have changed (useful after dynamic updates)
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');
console.log('Template library operations completed successfully');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to save scenes as templates, create a template asset source, add templates with metadata, and manage templates in headless server environments.
## Saving Templates
Scenes can be exported in two formats for use as templates:
- **String format**: Use `engine.scene.saveToString()` to serialize the scene to a base64 string. This lightweight format references remote assets by URL and is ideal for templates where assets are hosted on a CDN.
- **Archive format**: Use `engine.scene.saveToArchive()` to bundle all assets together. This returns a Blob containing a self-contained template, making it portable without external dependencies.
```typescript highlight=highlight-save-template
// Ask user how to save the template
console.log('\nHow would you like to save the template?');
console.log('1. String format (lightweight, references remote assets)');
console.log('2. Archive format (self-contained with bundled assets)');
console.log('3. Cancel');
const choice = await prompt('\nEnter your choice (1-3): ');
if (choice === '1') {
mkdirSync('output', { recursive: true });
const templateString = await engine.scene.saveToString();
writeFileSync('output/saved-scene.scene', templateString);
console.log('Template saved to output/saved-scene.scene');
} else if (choice === '2') {
mkdirSync('output', { recursive: true });
const templateBlob = await engine.scene.saveToArchive();
const buffer = Buffer.from(await templateBlob.arrayBuffer());
writeFileSync('output/saved-scene.zip', buffer);
console.log('Template saved to output/saved-scene.zip');
} else {
console.log('Save operation cancelled.');
}
```
## Creating a Template Asset Source
Register a local asset source using `engine.asset.addLocalSource()` with an ID and `applyAsset` callback.
```typescript highlight=highlight-create-source
// Create a local asset source for templates
engine.asset.addLocalSource('my-templates', undefined, async (asset) => {
// Apply the selected template to the current scene
await engine.scene.applyTemplateFromURL(asset.meta!.uri as string);
return undefined;
});
```
The `applyAsset` callback receives the selected asset and determines how to apply it. We use `engine.scene.applyTemplateFromURL()` to load the template from the asset's `meta.uri` property. The template is applied to the current scene, adjusting content to fit the existing page dimensions.
## Adding Templates to the Source
Register templates using `engine.asset.addAssetToSource()` with an asset definition that includes metadata for display and loading.
```typescript highlight=highlight-add-templates
// Add a template to the source with metadata
engine.asset.addAssetToSource('my-templates', {
id: 'template-postcard',
label: { en: 'Postcard' },
meta: {
uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene',
thumbUri:
'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_postcard_1.jpg'
}
});
```
Each template asset requires:
- `id` - Unique identifier for the template
- `label` - Localized display name shown in the template library
- `meta.uri` - URL to the `.scene` file that will be loaded when the template is applied
- `meta.thumbUri` - URL to a preview image for client-side display
## Managing Templates
After the initial setup, you can manage templates programmatically.
```typescript highlight=highlight-manage-templates
// List all registered asset sources
const sources = engine.asset.findAllSources();
console.log('Registered sources:', sources);
// Notify that source contents have changed (useful after dynamic updates)
engine.asset.assetSourceContentsChanged('my-templates');
// Query templates from the source
const queryResult = await engine.asset.findAssets('my-templates', {
page: 0,
perPage: 10
});
console.log('Templates in library:', queryResult.total);
// Remove a template from the source
engine.asset.removeAssetFromSource('my-templates', 'template-social-media');
console.log('Removed template-social-media from library');
```
Use `engine.asset.findAllSources()` to list registered sources. Query templates with `engine.asset.findAssets()` to retrieve template metadata. When you add or remove templates from a source, call `engine.asset.assetSourceContentsChanged()` to notify any connected clients. To remove a template, use `engine.asset.removeAssetFromSource()`.
## Cleanup
Always dispose the engine when finished to free system resources.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| Template fails to load | Incorrect URI in asset meta | Verify the `uri` points to a valid `.scene` file |
| Apply callback not triggered | `applyAsset` not defined in `addLocalSource` | Provide the callback when creating the source |
| Empty query results | Templates not added before querying | Ensure `addAssetToSource` is called before `findAssets` |
## 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 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 |
| `engine.dispose()` | Release engine resources when finished |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Edit or Remove Templates"
description: "Modify existing templates and manage template lifecycle by loading, editing, saving, and removing templates from asset sources."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/edit-or-remove-38a8be/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Edit or Remove Templates](https://img.ly/docs/cesdk/node-native/create-templates/edit-or-remove-38a8be/)
---
Modify existing templates and manage template lifecycle in headless Node.js environments using CE.SDK Engine.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-edit-or-remove-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-templates-edit-or-remove-server-js)
Templates evolve as designs change. You might need to update branding, fix content errors, or remove outdated templates from your library. CE.SDK Engine provides APIs for adding, editing, and removing templates from asset sources in server-side automation workflows.
```typescript file=@cesdk_web_examples/guides-create-templates-edit-or-remove-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { createInterface } from 'readline';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Edit or Remove Templates
*
* Demonstrates template management workflows in headless Node.js:
* - 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 prompt user for input
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
// Helper function to generate SVG thumbnail with text label
function generateThumbnail(label: string): string {
const svg = ``;
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
}
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 1200, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Create a local asset source for managing templates
engine.asset.addLocalSource('my-templates');
// Create the template with text blocks
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Original Template');
engine.block.setFloat(titleBlock, 'text/fontSize', 14);
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, 'Server-side template management');
engine.block.setFloat(subtitleBlock, 'text/fontSize', 10);
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
// Position text blocks centered on the page
const pageWidth = 1200;
const pageHeight = 600;
const titleWidth = engine.block.getFrameWidth(titleBlock);
const titleHeight = engine.block.getFrameHeight(titleBlock);
engine.block.setPositionX(titleBlock, (pageWidth - titleWidth) / 2);
engine.block.setPositionY(titleBlock, pageHeight / 2 - titleHeight - 20);
const subtitleWidth = engine.block.getFrameWidth(subtitleBlock);
engine.block.setPositionX(subtitleBlock, (pageWidth - subtitleWidth) / 2);
engine.block.setPositionY(subtitleBlock, pageHeight / 2 + 20);
// Save template content and add to asset source
const originalContent = await engine.scene.saveToString();
engine.asset.addAssetToSource('my-templates', {
id: 'template-original',
label: { en: 'Original Template' },
meta: {
uri: `data:application/octet-stream;base64,${originalContent}`,
thumbUri: generateThumbnail('Original Template')
}
});
// eslint-disable-next-line no-console
console.log('Original template added to asset source');
// Edit the template content and save as a new version
engine.block.replaceText(titleBlock, 'Updated Template');
engine.block.replaceText(subtitleBlock, 'This template was edited and saved');
const updatedContent = await engine.scene.saveToString();
engine.asset.addAssetToSource('my-templates', {
id: 'template-updated',
label: { en: 'Updated Template' },
meta: {
uri: `data:application/octet-stream;base64,${updatedContent}`,
thumbUri: generateThumbnail('Updated Template')
}
});
// Re-center after modification
const newTitleWidth = engine.block.getFrameWidth(titleBlock);
const newTitleHeight = engine.block.getFrameHeight(titleBlock);
engine.block.setPositionX(titleBlock, (pageWidth - newTitleWidth) / 2);
engine.block.setPositionY(titleBlock, pageHeight / 2 - newTitleHeight - 20);
const newSubtitleWidth = engine.block.getFrameWidth(subtitleBlock);
engine.block.setPositionX(subtitleBlock, (pageWidth - newSubtitleWidth) / 2);
// eslint-disable-next-line no-console
console.log('Updated template added to asset source');
// Add a temporary template to demonstrate removal
engine.asset.addAssetToSource('my-templates', {
id: 'template-temporary',
label: { en: 'Temporary Template' },
meta: {
uri: `data:application/octet-stream;base64,${originalContent}`,
thumbUri: generateThumbnail('Temporary Template')
}
});
// Remove the temporary template from the asset source
engine.asset.removeAssetFromSource('my-templates', 'template-temporary');
// eslint-disable-next-line no-console
console.log('Temporary template removed from asset source');
// Update an existing template by removing and re-adding with same ID
engine.block.replaceText(subtitleBlock, 'Updated again with new content');
const reUpdatedContent = await engine.scene.saveToString();
engine.asset.removeAssetFromSource('my-templates', 'template-updated');
engine.asset.addAssetToSource('my-templates', {
id: 'template-updated',
label: { en: 'Updated Template' },
meta: {
uri: `data:application/octet-stream;base64,${reUpdatedContent}`,
thumbUri: generateThumbnail('Updated Template')
}
});
// Notify that the asset source contents have changed
engine.asset.assetSourceContentsChanged('my-templates');
// Re-center subtitle after final update
const reUpdatedSubtitleWidth = engine.block.getFrameWidth(subtitleBlock);
engine.block.setPositionX(subtitleBlock, (pageWidth - reUpdatedSubtitleWidth) / 2);
// eslint-disable-next-line no-console
console.log('Template updated in asset source');
// Export templates based on user choice
// eslint-disable-next-line no-console
console.log('\n--- Template Export ---');
// eslint-disable-next-line no-console
console.log('1. Original Template');
// eslint-disable-next-line no-console
console.log('2. Updated Template');
// eslint-disable-next-line no-console
console.log('3. Both Templates');
const choice = await prompt(
'\nWhich template would you like to export? (1/2/3): '
);
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Load and export based on user choice
if (choice === '1' || choice === '3') {
await engine.scene.loadFromString(originalContent);
const originalBlob = await engine.block.export(
engine.block.findByType('page')[0],
{ mimeType: 'image/png' }
);
const originalBuffer = Buffer.from(await originalBlob.arrayBuffer());
writeFileSync(`${outputDir}/template-original.png`, originalBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported: output/template-original.png');
}
if (choice === '2' || choice === '3') {
await engine.scene.loadFromString(reUpdatedContent);
const updatedBlob = await engine.block.export(
engine.block.findByType('page')[0],
{ mimeType: 'image/png' }
);
const updatedBuffer = Buffer.from(await updatedBlob.arrayBuffer());
writeFileSync(`${outputDir}/template-updated.png`, updatedBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported: output/template-updated.png');
}
if (choice !== '1' && choice !== '2' && choice !== '3') {
// eslint-disable-next-line no-console
console.log('Invalid choice. Exporting both templates by default.');
await engine.scene.loadFromString(originalContent);
const originalBlob = await engine.block.export(
engine.block.findByType('page')[0],
{ mimeType: 'image/png' }
);
writeFileSync(
`${outputDir}/template-original.png`,
Buffer.from(await originalBlob.arrayBuffer())
);
await engine.scene.loadFromString(reUpdatedContent);
const updatedBlob = await engine.block.export(
engine.block.findByType('page')[0],
{ mimeType: 'image/png' }
);
writeFileSync(
`${outputDir}/template-updated.png`,
Buffer.from(await updatedBlob.arrayBuffer())
);
// eslint-disable-next-line no-console
console.log('✓ Exported both templates to output/');
}
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to add templates to asset sources, edit template content, remove templates, and save updated versions.
## Adding Templates
First, create a local asset source to store your templates:
```typescript highlight-create-source
// Create a local asset source for managing templates
engine.asset.addLocalSource('my-templates');
```
Next, create your template content using block APIs:
```typescript highlight-create-template
// Create the template with text blocks
const titleBlock = engine.block.create('text');
engine.block.replaceText(titleBlock, 'Original Template');
engine.block.setFloat(titleBlock, 'text/fontSize', 14);
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, 'Server-side template management');
engine.block.setFloat(subtitleBlock, 'text/fontSize', 10);
engine.block.setWidthMode(subtitleBlock, 'Auto');
engine.block.setHeightMode(subtitleBlock, 'Auto');
engine.block.appendChild(page, subtitleBlock);
```
Then save the template and add it to the asset source using `addAssetToSource()`. Each template needs a unique ID, a label, and metadata containing the template URI and thumbnail:
```typescript highlight-add-to-source
// Save template content and add to asset source
const originalContent = await engine.scene.saveToString();
engine.asset.addAssetToSource('my-templates', {
id: 'template-original',
label: { en: 'Original Template' },
meta: {
uri: `data:application/octet-stream;base64,${originalContent}`,
thumbUri: generateThumbnail('Original Template')
}
});
```
The `meta.uri` field contains the template content as a data URI. The `meta.thumbUri` provides a thumbnail image for reference.
## Editing Templates
Modify template content using block APIs. You can update text, change images, adjust positions, and reconfigure any block properties.
```typescript highlight-modify-template
// Edit the template content and save as a new version
engine.block.replaceText(titleBlock, 'Updated Template');
engine.block.replaceText(subtitleBlock, 'This template was edited and saved');
const updatedContent = await engine.scene.saveToString();
engine.asset.addAssetToSource('my-templates', {
id: 'template-updated',
label: { en: 'Updated Template' },
meta: {
uri: `data:application/octet-stream;base64,${updatedContent}`,
thumbUri: generateThumbnail('Updated Template')
}
});
```
After editing, save the modified template as a new asset or update an existing one.
## Removing Templates
Remove templates from asset sources using `removeAssetFromSource()`. This permanently deletes the template entry from the source.
```typescript highlight-remove-template
// Add a temporary template to demonstrate removal
engine.asset.addAssetToSource('my-templates', {
id: 'template-temporary',
label: { en: 'Temporary Template' },
meta: {
uri: `data:application/octet-stream;base64,${originalContent}`,
thumbUri: generateThumbnail('Temporary Template')
}
});
// Remove the temporary template from the asset source
engine.asset.removeAssetFromSource('my-templates', 'template-temporary');
```
> **Warning:** Removal is permanent. The template is no longer accessible from the asset source after removal. If you need to restore templates, maintain backups or implement a soft-delete mechanism.
## Saving Updated Templates
To update an existing template, first remove it using `removeAssetFromSource()`, then add the updated version with `addAssetToSource()` using the same asset ID.
```typescript highlight-update-in-source
// Update an existing template by removing and re-adding with same ID
engine.block.replaceText(subtitleBlock, 'Updated again with new content');
const reUpdatedContent = await engine.scene.saveToString();
engine.asset.removeAssetFromSource('my-templates', 'template-updated');
engine.asset.addAssetToSource('my-templates', {
id: 'template-updated',
label: { en: 'Updated Template' },
meta: {
uri: `data:application/octet-stream;base64,${reUpdatedContent}`,
thumbUri: generateThumbnail('Updated Template')
}
});
// Notify that the asset source contents have changed
engine.asset.assetSourceContentsChanged('my-templates');
```
After updating templates, call `assetSourceContentsChanged()` to notify that the asset source contents have changed.
## Best Practices
### Versioning Strategies
When managing template updates in server-side automation, consider these approaches:
- **Replace in place**: Use the same asset ID to update templates without changing references. Existing designs using the template won't break.
- **Version suffixes**: Create new entries with version identifiers (e.g., `template-v2`). This preserves old versions while introducing new ones.
- **Archive old versions**: Move deprecated templates to a separate source before removal. This maintains a history without cluttering the main library.
### Batch Operations
When adding, updating, or removing multiple templates, call `assetSourceContentsChanged()` once after all operations complete rather than after each individual change. This minimizes notification overhead in automated workflows.
### Template IDs
Use descriptive, unique IDs that reflect the template's purpose (e.g., `marketing-banner-2024`, `social-post-square`). Consistent naming conventions make templates easier to find and manage programmatically.
### Thumbnails
Generate meaningful thumbnails that accurately represent template content. Even in server-side workflows, thumbnails are useful when templates are later displayed in browser-based asset libraries.
### Memory Considerations
Templates stored as base64 data URIs remain in memory. For server-side batch processing with many templates, consider storing template content externally and using URLs in the `meta.uri` field instead of inline data URIs.
## Cleanup
Always dispose the engine when done to free resources.
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## 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 of asset source changes |
| `engine.scene.saveToString()` | Save scene as base64 string |
| `engine.scene.loadFromString()` | Load scene from base64 string |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create From Scratch"
description: "Build reusable design templates programmatically using CE.SDK's APIs. Create scenes, add text and graphic blocks, configure placeholders and variables, apply editing constraints, and save templates for reuse."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/from-scratch-663cda/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Create From Scratch](https://img.ly/docs/cesdk/node-native/create-templates/from-scratch-663cda/)
---
Build reusable design templates entirely through code using CE.SDK's programmatic APIs for automation, batch generation, and custom template creation tools.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-from-scratch-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-templates-from-scratch-server-js)
CE.SDK provides a complete API for building design templates through code. Instead of starting from an existing template, you can create a blank scene, define page dimensions, add text and graphic blocks, configure placeholders for swappable media, add text variables for dynamic content, apply editing constraints to protect layout integrity, and save the template for reuse. This approach enables automation workflows, batch template generation, and integration with custom template creation tools.
```typescript file=@cesdk_web_examples/guides-create-templates-from-scratch-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { createInterface } from 'readline';
import { config } from 'dotenv';
// Load environment variables
config();
// Prompt utility for interactive save options
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
async function main() {
// Display save options menu
console.log('=== Template Save Options ===\n');
console.log('1. Save as string (for CDN-hosted assets)');
console.log('2. Save as archive (self-contained ZIP)');
console.log('3. Export as PNG image');
console.log('4. Save all formats and export png\n');
const choice = (await prompt('Select save option (1-4): ')) || '4';
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// 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);
// Ensure output directory exists
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Save template based on user choice
if (choice === '1' || choice === '4') {
// Save the template as a string (for CDN-hosted assets)
const templateString = await engine.scene.saveToString();
writeFileSync(`${outputDir}/template.scene`, templateString);
console.log('Template saved to output/template.scene');
}
if (choice === '2' || choice === '4') {
// Save the template as a self-contained archive
const templateArchive = await engine.scene.saveToArchive();
const archiveBuffer = Buffer.from(await templateArchive.arrayBuffer());
writeFileSync(`${outputDir}/template.zip`, archiveBuffer);
console.log('Template archive saved to output/template.zip');
}
if (choice === '3' || choice === '4') {
// Export the template as a PNG image
const pngBlob = await engine.block.export(page, { mimeType: 'image/png' });
const pngBuffer = Buffer.from(await pngBlob.arrayBuffer());
writeFileSync(`${outputDir}/template.png`, pngBuffer);
console.log('Template exported to output/template.png');
}
console.log('Template creation completed successfully');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
This guide covers how to create a blank scene, add text blocks with variables, add image placeholders, apply editing constraints, and save the template.
## Initialize CE.SDK
We start by initializing CE.SDK's Node.js engine for server-side rendering and scene manipulation.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
```
## Create a Blank Scene
We create the foundation of our template with custom page dimensions. The `engine.scene.create()` method accepts page options to set width, height, and background color.
```typescript highlight=highlight-create-scene
// Template layout constants for a promotional card
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 1000;
const PADDING = 40;
const CONTENT_WIDTH = CANVAS_WIDTH - PADDING * 2;
// Create a blank scene with custom dimensions
engine.scene.create('Free', {
page: { size: { width: CANVAS_WIDTH, height: CANVAS_HEIGHT } }
});
// Set design unit to Pixel for precise coordinate mapping
engine.scene.setDesignUnit('Pixel');
```
The scene creation method accepts a layout mode and optional page configuration. When options are provided, the scene automatically includes a page with the specified dimensions.
## Set Page Background
We set a light background color to give the template a consistent base appearance.
```typescript highlight=highlight-add-background
// Set a gradient background for the template
const backgroundFill = engine.block.createFill('gradient/linear');
engine.block.setGradientColorStops(backgroundFill, 'fill/gradient/colors', [
{ color: { r: 0.4, g: 0.2, b: 0.6, a: 1.0 }, stop: 0 }, // Purple
{ color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } // Blue
]);
engine.block.setFill(page, backgroundFill);
```
We create a color fill using `engine.block.createFill('color')`, set the color via `engine.block.setColor()` with the `fill/color/value` property, then assign the fill to the page using `engine.block.setFill()`.
## Add Text Blocks
Text blocks allow you to add styled text content. We create a headline that includes a variable token for dynamic content.
```typescript highlight=highlight-add-text
// Create headline text block with {{title}} variable
const headline = engine.block.create('text');
engine.block.replaceText(headline, '{{title}}');
// Set font with proper typeface for consistent rendering
engine.block.setFont(headline, FONT_BOLD, {
name: 'Roboto',
fonts: [{ uri: FONT_BOLD, subFamily: 'Bold', weight: 'bold' }]
});
engine.block.setFloat(headline, 'text/fontSize', 28);
engine.block.setTextColor(headline, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
// Position and size the headline
engine.block.setWidthMode(headline, 'Absolute');
engine.block.setHeightMode(headline, 'Auto');
engine.block.setWidth(headline, CONTENT_WIDTH);
engine.block.setPositionX(headline, PADDING);
engine.block.setPositionY(headline, 50);
engine.block.setEnum(headline, 'text/horizontalAlignment', 'Center');
engine.block.appendChild(page, headline);
```
We create a text block using `engine.block.create('text')`, set its content with `engine.block.replaceText()`, configure dimensions and position, and append it to the page using `engine.block.appendChild()`.
## Add Text Variables
Text variables enable data-driven personalization. By using `{{variableName}}` tokens in text blocks, you can populate content programmatically.
```typescript highlight=highlight-add-variable
// Set default value for the title variable
engine.variable.setString('title', 'Summer Sale');
```
The `engine.variable.setString()` method sets the default value for the variable. When the template is used, this value can be changed to personalize the content.
## Add Graphic Blocks
Graphic blocks serve as containers for images. We create an image block that will become a placeholder for swappable media.
```typescript highlight=highlight-add-graphic
// Create image placeholder in the center of the card
const imageBlock = engine.block.create('graphic');
const imageShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, imageShape);
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setWidth(imageBlock, CONTENT_WIDTH);
engine.block.setHeight(imageBlock, 420);
engine.block.setPositionX(imageBlock, PADDING);
engine.block.setPositionY(imageBlock, 295);
engine.block.appendChild(page, imageBlock);
```
We create a graphic block with `engine.block.create('graphic')`, assign a rectangle shape using `engine.block.createShape('rect')` and `engine.block.setShape()`, create an image fill with `engine.block.createFill('image')`, set the image URI via `engine.block.setString()`, and position it on the page.
## Configure Placeholders
Placeholders turn design blocks into drop-zones where users can swap content while maintaining layout integrity. We enable placeholder behavior on the image fill and configure visual controls.
```typescript highlight=highlight-configure-placeholder
// Enable placeholder behavior on the image fill
const fill = engine.block.getFill(imageBlock);
if (fill !== null && engine.block.supportsPlaceholderBehavior(fill)) {
engine.block.setPlaceholderBehaviorEnabled(fill, true);
}
engine.block.setPlaceholderEnabled(imageBlock, true);
// Enable visual controls for the placeholder
engine.block.setPlaceholderControlsOverlayEnabled(imageBlock, true);
engine.block.setPlaceholderControlsButtonEnabled(imageBlock, true);
```
Placeholder behavior is enabled on the fill (not the block) for graphic blocks. We also enable the overlay pattern and replace button for visual guidance.
## Apply Editing Constraints
Editing constraints protect template elements by restricting what users can modify. We use scopes to lock position and size while allowing content changes.
```typescript highlight=highlight-apply-constraints
// Set global scope to 'Defer' for per-block control
engine.editor.setGlobalScope('layer/move', 'Defer');
engine.editor.setGlobalScope('layer/resize', 'Defer');
// Lock all text block positions but allow text editing
const textBlocks = [headline, subheadline, cta];
textBlocks.forEach((block) => {
engine.block.setScopeEnabled(block, 'layer/move', false);
engine.block.setScopeEnabled(block, 'layer/resize', false);
});
// Lock image position but allow fill replacement
engine.block.setScopeEnabled(imageBlock, 'layer/move', false);
engine.block.setScopeEnabled(imageBlock, 'layer/resize', false);
engine.block.setScopeEnabled(imageBlock, 'fill/change', true);
```
Setting global scope to `'Defer'` enables per-block control. We then disable movement and resizing for both blocks while enabling fill changes for the image placeholder.
## Save the Template
We persist the template in two formats: a lightweight string for CDN-hosted assets and a self-contained archive with embedded assets.
```typescript highlight=highlight-save-template
// Prompt utility for interactive save options
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
async function main() {
// Display save options menu
console.log('=== Template Save Options ===\n');
console.log('1. Save as string (for CDN-hosted assets)');
console.log('2. Save as archive (self-contained ZIP)');
console.log('3. Export as PNG image');
console.log('4. Save all formats and export png\n');
const choice = (await prompt('Select save option (1-4): ')) || '4';
```
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.
## Cleanup
We dispose of the engine to release resources when processing is complete.
```typescript highlight=highlight-cleanup
engine.dispose();
```
## Troubleshooting
- **Blocks not appearing**: Verify that `engine.block.appendChild()` attaches blocks to the page. Blocks must be part of the scene hierarchy to render.
- **Variables not resolving**: Verify the variable name in the text matches exactly, including curly braces syntax `{{variableName}}`.
- **Placeholder not interactive**: Ensure `engine.block.setPlaceholderEnabled()` is called on the block and the appropriate scope (`fill/change`) is enabled.
- **Constraints not enforced**: Verify `engine.editor.setGlobalScope()` is set to `'Defer'` before setting per-block scopes.
## API Reference
| Method | Description |
| --- | --- |
| `engine.scene.create()` | Create a new design scene with optional page size |
| `engine.scene.setDesignUnit()` | Set the design unit (Pixel, Millimeter, Inch) |
| `engine.scene.saveToString()` | Save scene to string format |
| `engine.scene.saveToArchive()` | Save scene to ZIP archive |
| `engine.block.create()` | Create a design block (page, text, graphic) |
| `engine.block.appendChild()` | Append a child block to a parent |
| `engine.block.findByType()` | Find blocks by their type |
| `engine.block.createFill()` | Create a fill (color, image, etc.) |
| `engine.block.setFill()` | Assign a fill to a block |
| `engine.block.getFill()` | Get the fill of a block |
| `engine.block.createShape()` | Create a shape (rect, ellipse, etc.) |
| `engine.block.setShape()` | Assign a shape to a graphic block |
| `engine.block.setString()` | Set a string property on a block |
| `engine.block.setColor()` | Set a color property |
| `engine.block.replaceText()` | Set text content |
| `engine.block.setFont()` | Set font with typeface |
| `engine.block.setPlaceholderBehaviorEnabled()` | Enable placeholder behavior on fill |
| `engine.block.setPlaceholderEnabled()` | Enable placeholder interaction on block |
| `engine.block.setPlaceholderControlsOverlayEnabled()` | Enable overlay visual control |
| `engine.block.setPlaceholderControlsButtonEnabled()` | Enable button visual control |
| `engine.variable.setString()` | Set a text variable value |
| `engine.editor.setGlobalScope()` | Set global scope permission |
| `engine.block.setScopeEnabled()` | Enable/disable scope on a block |
## Next Steps
- [Placeholders](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/) - Configure placeholder behavior and visual controls in depth
- [Text Variables](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/) - Implement dynamic text personalization with variables
- [Set Editing Constraints](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) - Lock layout properties to protect design integrity
- [Add to Template Library](https://img.ly/docs/cesdk/node-native/create-templates/add-to-template-library-8bfbc7/) - Register templates in the asset library for users to discover
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Import Templates"
description: "Load and import design templates into CE.SDK from URLs, archives, and serialized strings."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/import-e50084/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Import Templates](https://img.ly/docs/cesdk/node-native/create-templates/import-e50084/)
---
Load design templates into CE.SDK from archive URLs, scene URLs, and serialized strings.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-import-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-templates-import-server-js)
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 files.
```typescript file=@cesdk_web_examples/guides-create-templates-import-server-js/server-js.ts reference-only
/**
* CE.SDK Node.js Example: Import Templates
*
* Demonstrates loading templates from different sources:
* - Archive URLs (.zip files with bundled assets)
* - Scene URLs (.scene files)
* - Serialized strings (file content)
*/
import CreativeEngine from '@cesdk/node';
import { readFileSync, writeFileSync } from 'fs';
import { config } from 'dotenv';
import * as readline from 'readline';
// Load environment variables from .env file
config();
// Template sources
const fashionAdArchiveUrl =
'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip';
const postcardSceneUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene';
// For loadFromString: read the scene file content
const businessCardSceneString = readFileSync(
'./assets/business-card.scene',
'utf-8'
);
// Prompt user to select import method
async function selectImportMethod(): Promise {
const methods = [
{ key: '1', label: 'Import Archive (fashion-ad.zip)', value: 'archive' },
{ key: '2', label: 'Import URL (postcard.scene)', value: 'url' },
{ key: '3', label: 'Import String (business-card.scene)', value: 'string' }
];
console.log('\nSelect import method:');
methods.forEach((m) => console.log(` ${m.key}) ${m.label}`));
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question('\nEnter choice (1-3): ', (answer) => {
rl.close();
const selected = methods.find((m) => m.key === answer.trim());
if (!selected) {
console.error(
`\n✗ Invalid choice: "${answer.trim()}". Please enter 1-3.`
);
process.exit(1);
}
resolve(selected.value);
});
});
}
const configuration = {
userId: 'guides-user'
};
async function main() {
// Get user's import method selection
const method = await selectImportMethod();
console.log('');
const engine = await CreativeEngine.init(configuration);
try {
let templateName = '';
if (method === 'archive') {
// Load template from archive URL (bundled assets)
await engine.scene.loadFromArchiveURL(fashionAdArchiveUrl);
templateName = 'fashion-ad';
}
if (method === 'url') {
// Load template from a scene URL
await engine.scene.loadFromURL(postcardSceneUrl);
templateName = 'postcard';
}
if (method === 'string') {
// Load template from serialized string
await engine.scene.loadFromString(businessCardSceneString);
templateName = 'business-card';
}
// Verify the loaded scene
const scene = engine.scene.get();
if (scene == null) {
throw new Error('Failed to load scene');
}
const pages = engine.scene.getPages();
// eslint-disable-next-line no-console
console.log(`Loaded ${templateName} template with ${pages.length} page(s)`);
// Export the loaded template to a PNG file
const blob = await engine.block.export(scene, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
const outputPath = `output-${templateName}.png`;
writeFileSync(outputPath, buffer);
// eslint-disable-next-line no-console
console.log(`Exported template to ${outputPath}`);
// eslint-disable-next-line no-console
console.log('Example completed successfully!');
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error:', error);
throw error;
} finally {
// Always dispose the engine when done
engine.dispose();
}
}
// Run the example
main().catch((error) => {
// eslint-disable-next-line no-console
console.error('Failed to run example:', error);
process.exit(1);
});
```
This guide covers how to load templates from archives, URLs, and strings, and export the result.
## Load from Archive
Load a template from an archive URL using `loadFromArchiveURL()`. Archives are `.zip` files that bundle the scene with all its assets, making them portable and self-contained.
```typescript highlight=highlight-load-from-archive
// Load template from archive URL (bundled assets)
await engine.scene.loadFromArchiveURL(fashionAdArchiveUrl);
```
## Load from URL
Load a template from a remote `.scene` file URL using `loadFromURL()`. The scene file is a JSON-based format that references assets via URLs.
```typescript highlight=highlight-load-from-url
// Load template from a scene URL
await engine.scene.loadFromURL(postcardSceneUrl);
```
## Load from String
For templates stored in databases or files, load from a serialized string using `loadFromString()`.
```typescript highlight=highlight-load-from-string
// Load template from serialized string
await engine.scene.loadFromString(businessCardSceneString);
```
This method works with content previously saved using `engine.scene.saveToString()`.
## Working with the Loaded Scene
After loading a template, verify the scene loaded correctly and inspect its contents.
### Verify the Scene
Use `engine.scene.get()` to retrieve the scene block and `engine.scene.getPages()` to inspect its pages.
```typescript highlight=highlight-get-scene
// Verify the loaded scene
const scene = engine.scene.get();
if (scene == null) {
throw new Error('Failed to load scene');
}
const pages = engine.scene.getPages();
```
## Cleanup
Always dispose the engine when finished to release resources.
```typescript highlight=highlight-cleanup
// Always dispose the engine when done
engine.dispose();
```
---
## Related Pages
- [Import Templates from Scene Files](https://img.ly/docs/cesdk/node-native/create-templates/import/from-scene-file-52a01e/) - Load and import templates from scene files programmatically using CE.SDK in Node.js for headless automation
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Import Templates from Scene Files"
description: "Load and import templates from scene files programmatically using CE.SDK in Node.js for headless automation"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/import/from-scene-file-52a01e/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Import Templates](https://img.ly/docs/cesdk/node-native/create-templates/import-e50084/) > [From Scene File](https://img.ly/docs/cesdk/node-native/create-templates/import/from-scene-file-52a01e/)
---
CE.SDK lets you load complete design templates from scene files programmatically in a headless environment for automation, batch processing, and server-side template management.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-templates-import-from-scene-file-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-templates-import-from-scene-file-server-js)
Scene files are portable design templates that preserve the entire design structure including blocks, assets, styles, and layout. In a headless environment, we can load and process these templates programmatically.
```typescript file=@cesdk_web_examples/guides-create-templates-import-from-scene-file-server-js/server-js.ts reference-only
/**
* CE.SDK Node.js Example: Import Templates from Scene Files
*
* This example demonstrates:
* - Loading scenes from .scene file URLs
* - Loading scenes from .archive (ZIP) URLs
* - Applying templates while preserving page dimensions
* - Understanding the difference between loading and applying templates
* - Working with scene files programmatically in a headless environment
*/
import CreativeEngine from '@cesdk/node';
import { writeFileSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables from .env file
config();
const configuration = {
userId: 'guides-user'
};
async function main() {
// Initialize the headless creative engine
const engine = await CreativeEngine.init(configuration);
try {
// ===== Example: Load Scene from Archive URL =====
// This is the recommended approach for loading complete templates
// with all their assets embedded in a ZIP file
// Load a complete template from an archive (ZIP) file
// This loads both the scene structure and all embedded assets
await engine.scene.loadFromArchiveURL(
'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip'
);
// Get information about the loaded scene
const scene = engine.scene.get();
console.log(scene);
if (scene == null) {
throw new Error('Failed to get scene after loading');
}
console.log('Scene loaded successfully:', scene);
// Get all pages in the scene
const pages = engine.scene.getPages();
console.log(`Scene has ${pages.length} page(s)`);
// Get design unit (Pixel, Millimeter, Inch)
const designUnit = engine.scene.getDesignUnit();
console.log('Design unit:', designUnit);
// Export the scene to a PNG file
const blob = await engine.block.export(scene, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output-from-archive.png', buffer);
console.log('Exported scene to output-from-archive.png');
// Alternative: Load scene from URL (.scene file)
// This loads only the scene structure - assets must be accessible via URLs
// Uncomment to try:
// await engine.scene.loadFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
// );
//
// // Export the loaded scene
// const sceneFromUrl = engine.scene.get();
// if (sceneFromUrl) {
// const blob2 = await engine.block.export(sceneFromUrl, 'image/png');
// const buffer2 = Buffer.from(await blob2.arrayBuffer());
// writeFileSync('output-from-url.png', buffer2);
// console.log('Exported scene to output-from-url.png');
// }
// Alternative: Apply template while preserving current page dimensions
// This is useful when you want to load template content into an existing scene
// with specific dimensions
// Uncomment to try:
// // First create a scene with specific dimensions
// engine.scene.create();
// const page = engine.block.findByType('page')[0];
// engine.block.setWidth(page, 1920);
// engine.block.setHeight(page, 1080);
//
// // Now apply template - content will be adjusted to fit
// await engine.scene.applyTemplateFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene'
// );
//
// // Export the scene with applied template
// const appliedScene = engine.scene.get();
// if (appliedScene) {
// const blob3 = await engine.block.export(appliedScene, 'image/png');
// const buffer3 = Buffer.from(await blob3.arrayBuffer());
// writeFileSync('output-applied-template.png', buffer3);
// console.log('Exported scene to output-applied-template.png');
// }
console.log('Example completed successfully!');
} catch (error) {
console.error('Error:', error);
throw error;
} finally {
// Always dispose the engine when done
engine.dispose();
}
}
// Run the example
main().catch((error) => {
console.error('Failed to run example:', error);
process.exit(1);
});
```
This guide covers loading scenes from archives, loading from URLs, applying templates while preserving dimensions, and exporting the results.
## Scene File Formats
CE.SDK supports two scene file formats for importing templates:
### Scene Format (.scene)
Scene files are JSON-based representations of design structures. They reference external assets via URLs, making them lightweight and suitable for database storage. However, the referenced assets must remain accessible at their URLs.
**When to use:**
- Templates stored in databases
- Templates with hosted assets
- Lightweight transmission
### Archive Format (.archive or .zip)
Archive files are self-contained packages that bundle the scene structure with all referenced assets in a ZIP file. This makes them portable and suitable for offline use.
**When to use:**
- Template distribution
- Offline-capable templates
- Complete portability
- **Recommended for most use cases**
## Load Scene from Archive
The most common way to load templates is from archive URLs. This method loads both the scene structure and all embedded assets:
```typescript highlight-load-from-archive
// Load a complete template from an archive (ZIP) file
// This loads both the scene structure and all embedded assets
await engine.scene.loadFromArchiveURL(
'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip'
);
```
When you load from an archive:
- The ZIP file is fetched and extracted
- All assets are registered with CE.SDK
- The scene structure is loaded
- Asset paths are automatically resolved
## Load Scene from URL
You can also load scenes directly from .scene file URLs. This approach requires that all referenced assets remain accessible at their original URLs:
```typescript highlight-load-from-url
// await engine.scene.loadFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
// );
//
// // Export the loaded scene
// const sceneFromUrl = engine.scene.get();
// if (sceneFromUrl) {
// const blob2 = await engine.block.export(sceneFromUrl, 'image/png');
// const buffer2 = Buffer.from(await blob2.arrayBuffer());
// writeFileSync('output-from-url.png', buffer2);
// console.log('Exported scene to output-from-url.png');
// }
```
**Important:** With this method, if asset URLs become unavailable (404 errors, CORS issues, etc.), those assets won't load and your template may appear incomplete.
## Apply Template vs Load Scene
CE.SDK provides two approaches for working with templates, each serving different purposes:
### Load Scene
When you use `loadFromURL()` or `loadFromArchiveURL()`, CE.SDK:
- Replaces the entire current scene
- Adopts the template's page dimensions
- Loads all content as-is
This is appropriate when starting a new project from a template.
### Apply Template
When you use `applyTemplateFromURL()` or `applyTemplateFromString()`, CE.SDK:
- Keeps your current page dimensions
- Adjusts template content to fit
- Preserves your scene structure
This is useful when you want to load template content into an existing scene with specific dimensions:
```typescript highlight-apply-template
// // First create a scene with specific dimensions
// engine.scene.create();
// const page = engine.block.findByType('page')[0];
// engine.block.setWidth(page, 1920);
// engine.block.setHeight(page, 1080);
//
// // Now apply template - content will be adjusted to fit
// await engine.scene.applyTemplateFromURL(
// 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene'
// );
//
// // Export the scene with applied template
// const appliedScene = engine.scene.get();
// if (appliedScene) {
// const blob3 = await engine.block.export(appliedScene, 'image/png');
// const buffer3 = Buffer.from(await blob3.arrayBuffer());
// writeFileSync('output-applied-template.png', buffer3);
// console.log('Exported scene to output-applied-template.png');
// }
```
## Get Scene Information
After loading a template, we can retrieve information about the scene:
```typescript highlight-get-scene-info
// Get information about the loaded scene
const scene = engine.scene.get();
console.log(scene);
if (scene == null) {
throw new Error('Failed to get scene after loading');
}
console.log('Scene loaded successfully:', scene);
// Get all pages in the scene
const pages = engine.scene.getPages();
console.log(`Scene has ${pages.length} page(s)`);
// Get design unit (Pixel, Millimeter, Inch)
const designUnit = engine.scene.getDesignUnit();
console.log('Design unit:', designUnit);
```
## Export Scene
We can export the loaded scene to various formats:
```typescript highlight-export-scene
// Export the scene to a PNG file
const blob = await engine.block.export(scene, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output-from-archive.png', buffer);
console.log('Exported scene to output-from-archive.png');
```
## Error Handling
When loading templates, several issues can occur:
### Network Errors
Template URLs might be unreachable:
```typescript
try {
await engine.scene.loadFromArchiveURL(templateUrl);
} catch (error) {
console.error('Failed to load template:', error);
// Fall back to default template or handle error
}
```
### Invalid Scene Format
The file might not be a valid scene:
```typescript
try {
await engine.scene.loadFromURL(sceneUrl);
} catch (error) {
if (error.message.includes('parse')) {
console.error('Invalid scene file format');
}
}
```
### Missing Assets
For .scene files, referenced assets might be unavailable. The scene loads but assets appear missing. Consider using archives to avoid this issue.
## Performance Considerations
### Loading Time
Archive size directly impacts loading time:
- Small archives (\< 1MB): Nearly instant
- Medium archives (1-5MB): 1-2 seconds
- Large archives (> 5MB): Several seconds
For batch processing, consider parallel processing of independent templates.
## CORS Considerations
When loading templates from external URLs in Node.js, most CORS restrictions don't apply. However, ensure the URLs are accessible from your server environment and check for any firewall or network restrictions.
## API Reference
| Method | Description |
| ---------------------------------------- | --------------------------------------------------------- |
| `engine.scene.loadFromArchiveURL()` | Loads a complete scene from an archive (ZIP) file |
| `engine.scene.loadFromURL()` | Loads a scene from a .scene file URL |
| `engine.scene.applyTemplateFromURL()` | Applies a template while preserving page dimensions |
| `engine.scene.get()` | Returns the current scene block ID |
| `engine.scene.getPages()` | Returns all page IDs in the scene |
| `engine.scene.getDesignUnit()` | Returns the measurement unit |
| `engine.block.export()` | Exports a block to various formats |
| `CreativeEngine.init()` | Initializes the headless engine |
| `engine.dispose()` | Disposes the engine and frees resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Lock the Template"
description: "Restrict editing access to specific elements or properties in a template to enforce design rules."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/lock-131489/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Lock the Template](https://img.ly/docs/cesdk/node-native/create-templates/lock-131489/)
---
Understand why template locking is designed for browser-based workflows, and when you might configure scopes server-side for template preparation.
## Browser-Focused Feature
Template locking uses a two-surface pattern with two separate editor instances: one for designers (the Creator role) who build templates with full access, and another for end users (the Adopter role) who can only modify unlocked elements. This separation requires:
- **User interaction**: Different people editing in different modes
- **Multiple editor instances**: Separate surfaces for creators and adopters
- **Visual feedback**: Users see what they can and cannot edit
Server-side automation processes templates in a single execution context without user interaction. The two-surface pattern does not apply—when processing templates on the server, you work with pre-configured templates where scopes are already set.
## When You Might Use Scopes Server-Side
If you need to **prepare templates** for the two-surface workflow, you can configure scopes programmatically before distributing templates:
- Set `editor/select` and editing scopes on blocks that adopters should modify
- Leave scopes disabled on protected elements like logos and brand assets
- Save the configured template for use in browser-based adopter surfaces
For scope configuration APIs, see [Lock Content](https://img.ly/docs/cesdk/node-native/rules/lock-content-9fa727/).
## API Reference
### Role Management
| Method | Description |
|--------|-------------|
| `engine.editor.setRole(role)` | Set the editing role (`'Creator'`, `'Adopter'`, or `'Viewer'`) |
| `engine.editor.getRole()` | Get the current editing role |
### Scope Configuration
| Method | Description |
|--------|-------------|
| `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 |
### 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 |
## See the Browser Guide
For the complete two-surface pattern implementation with interactive role switching, see the [browser guide](https://img.ly/docs/cesdk/node-native/create-templates/lock-131489/).
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-templates/overview-4ebe30/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) > [Overview](https://img.ly/docs/cesdk/node-native/create-templates/overview-4ebe30/)
---
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 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 static output formats (like PNG, PDF) and can be created or applied using the 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](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/)
These imported designs can then be adapted into editable, structured templates inside CE.SDK.
## 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.
This makes it easy to generate hundreds or thousands of personalized variations from a single design.
## Working with Templates Programmatically
You can manage templates using the API:
- **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.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Videos"
description: "Learn how to create and customize videos in CE.SDK using scenes, assets, and time-based editing."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-video-c41a08/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/)
---
---
## Related Pages
- [Control Audio and Video](https://img.ly/docs/cesdk/node-native/create-video/control-daba54/) - Learn to play, pause, seek, and preview audio and video content in CE.SDK using playback controls and solo mode.
- [Trim Video Clips](https://img.ly/docs/cesdk/node-native/edit-video/trim-4f688b/) - Learn how to trim video clips programmatically using CE.SDK's Engine API in headless mode.
- [Split Video and Audio](https://img.ly/docs/cesdk/node-native/edit-video/split-464167/) - Learn how to split video and audio clips at specific time points in CE.SDK, creating two independent segments from a single clip.
- [Join and Arrange Video Clips](https://img.ly/docs/cesdk/node-native/edit-video/join-and-arrange-3bbc30/) - Combine multiple video clips into sequences and organize them on the timeline using tracks and time offsets in CE.SDK.
- [Transform](https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/) - Documentation for Transform
- [Add Captions](https://img.ly/docs/cesdk/node-native/edit-video/add-captions-f67565/) - Documentation for adding captions to videos
- [Add Watermark](https://img.ly/docs/cesdk/node-native/edit-video/add-watermark-762ce6/) - Add text and image watermarks to videos using CE.SDK for copyright protection, branding, and content attribution with timeline management and visibility controls.
- [Redact Sensitive Content in Videos](https://img.ly/docs/cesdk/node-native/edit-video/redaction-cf6d03/) - Redact sensitive video content using blur, pixelization, or solid overlays. Essential for privacy protection when obscuring faces, license plates, or personal information.
- [Node.js Video Editor SDK](https://img.ly/docs/cesdk/node-native/overview-7d12d5/) - Explore video editing features in CE.SDK including trimming, splitting, captions, and programmatic editing.
- [Limitations](https://img.ly/docs/cesdk/node-native/create-video/limitations-6a740d/) - Understand video resolution limits, duration constraints, codec support, and browser-specific restrictions in CE.SDK.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Adjust Audio Playback Speed"
description: "Learn how to adjust audio playback speed in CE.SDK to create slow-motion, time-stretched, and fast-forward audio effects."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-video/audio/adjust-speed-908d57/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
---
Control audio playback speed programmatically using CE.SDK's headless engine
for server-side audio processing, from quarter-speed (0.25x) to triple-speed
(3.0x).
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-adjust-speed-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-audio-audio-adjust-speed-server-js)
Playback speed adjustment changes how fast or slow audio plays without changing its pitch (though pitch shifting may occur depending on the audio processing implementation). A speed multiplier of 1.0 represents normal speed, values below 1.0 slow down playback, and values above 1.0 speed it up. This technique is commonly used for podcast speed controls, time-compressed narration, slow-motion audio effects, and accessibility features.
```typescript file=@cesdk_web_examples/guides-create-audio-audio-adjust-speed-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFile, mkdir } from 'fs/promises';
import { config } from 'dotenv';
config();
/**
* CE.SDK Server Guide: Adjust Audio Playback Speed
*
* Demonstrates audio playback speed adjustment in CE.SDK:
* - Loading audio files
* - Adjusting playback speed with setPlaybackSpeed
* - Three speed presets: slow-motion (0.5x), normal (1.0x), and maximum (3.0x)
* - Understanding how speed affects duration
*/
const engine = await CreativeEngine.init({});
try {
// Create a scene with a page for audio content
engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(engine.scene.get()!, page);
// Use a sample audio file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a';
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Wait for audio resource to load
await engine.block.forceLoadAVResource(audioBlock);
// Slow Motion Audio (0.5x - half speed, doubles duration)
const slowAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, slowAudioBlock);
engine.block.setTimeOffset(slowAudioBlock, 0);
engine.block.setPlaybackSpeed(slowAudioBlock, 0.5);
// Normal Speed Audio (1.0x - original playback rate)
const normalAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, normalAudioBlock);
engine.block.setTimeOffset(normalAudioBlock, 5);
engine.block.setPlaybackSpeed(normalAudioBlock, 1.0);
// Query current speed to verify the change
const currentSpeed = engine.block.getPlaybackSpeed(normalAudioBlock);
console.log(`Normal speed block set to: ${currentSpeed}x`);
// Maximum Speed Audio (3.0x - triple speed, reduces duration to 1/3)
const maxSpeedAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, maxSpeedAudioBlock);
engine.block.setTimeOffset(maxSpeedAudioBlock, 10);
engine.block.setPlaybackSpeed(maxSpeedAudioBlock, 3.0);
// Log duration changes to demonstrate speed-duration relationship
const slowDuration = engine.block.getDuration(slowAudioBlock);
const normalDuration = engine.block.getDuration(normalAudioBlock);
const maxDuration = engine.block.getDuration(maxSpeedAudioBlock);
console.log(`Slow motion (0.5x) duration: ${slowDuration.toFixed(2)}s`);
console.log(`Normal speed (1.0x) duration: ${normalDuration.toFixed(2)}s`);
console.log(`Maximum speed (3.0x) duration: ${maxDuration.toFixed(2)}s`);
// Remove the original audio block (we only need the duplicates)
engine.block.destroy(audioBlock);
// Export the scene to a .scene file
const sceneContent = await engine.scene.saveToString();
await mkdir('output', { recursive: true });
await writeFile('output/audio-speed-adjustment.scene', sceneContent);
console.log('Scene exported to output/audio-speed-adjustment.scene');
console.log('Audio playback speed adjustment example complete');
} finally {
engine.dispose();
}
```
This guide covers how to adjust audio playback speed programmatically using the Engine API, understand speed constraints, and manage how speed changes affect block duration.
## Understanding Speed Concepts
CE.SDK supports playback speeds from **0.25x** (quarter speed) to **3.0x** (triple speed), with **1.0x** as the default normal speed. Values below 1.0 slow down playback, values above 1.0 speed it up.
**Speed and Duration**: Adjusting speed automatically changes the block's duration following an inverse relationship: `perceived_duration = original_duration / speed_multiplier`. A 10-second clip at 2.0x speed plays in 5 seconds; at 0.5x speed it takes 20 seconds. This automatic adjustment maintains synchronization when coordinating audio with other elements.
**Common use cases**: Podcast playback controls (1.5x-2.0x), accessibility features (0.75x for easier comprehension), time-compressed narration, dramatic slow-motion effects (0.25x-0.5x), transcription work, and music tempo adjustments.
## Setting Up the Engine
For headless audio processing, initialize CE.SDK's Node.js engine. This provides full API access without browser dependencies, ideal for server-side automation and batch processing.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({});
```
## Setting Up Audio for Speed Adjustment
### Loading Audio Files
We create an audio block and load an audio file by setting its `fileURI` property.
```typescript highlight=highlight-create-audio
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Wait for audio resource to load
await engine.block.forceLoadAVResource(audioBlock);
```
Unlike video or image blocks which use fills, audio blocks store the file URI directly on the block itself using the `audio/fileURI` property. The `forceLoadAVResource` call ensures CE.SDK has downloaded the audio file and loaded its metadata, which is essential for accurate duration information and playback speed control.
## Adjusting Playback Speed
### Setting Normal Speed
By default, audio plays at normal speed (1.0x). We can explicitly set this to ensure consistent baseline behavior.
```typescript highlight=highlight-set-normal-speed
// Normal Speed Audio (1.0x - original playback rate)
const normalAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, normalAudioBlock);
engine.block.setTimeOffset(normalAudioBlock, 5);
engine.block.setPlaybackSpeed(normalAudioBlock, 1.0);
// Query current speed to verify the change
const currentSpeed = engine.block.getPlaybackSpeed(normalAudioBlock);
console.log(`Normal speed block set to: ${currentSpeed}x`);
```
Setting speed to 1.0 ensures the audio plays at its original recorded rate. This is useful after experimenting with different speeds and wanting to return to normal, or when initializing audio blocks programmatically to ensure consistent starting states.
## Common Speed Presets
### Slow Motion Audio (0.5x)
Slowing audio to half speed creates a slow-motion effect that's useful for careful listening or transcription.
```typescript highlight=highlight-set-slow-motion
// Slow Motion Audio (0.5x - half speed, doubles duration)
const slowAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, slowAudioBlock);
engine.block.setTimeOffset(slowAudioBlock, 0);
engine.block.setPlaybackSpeed(slowAudioBlock, 0.5);
```
At 0.5x speed, a 10-second audio clip will take 20 seconds to play. This slower pace makes it easier to catch details, transcribe speech accurately, or create dramatic slow-motion audio effects in creative projects.
### Maximum Speed (3.0x)
The maximum supported speed is 3.0x, three times normal playback rate.
```typescript highlight=highlight-set-maximum-speed
// Maximum Speed Audio (3.0x - triple speed, reduces duration to 1/3)
const maxSpeedAudioBlock = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, maxSpeedAudioBlock);
engine.block.setTimeOffset(maxSpeedAudioBlock, 10);
engine.block.setPlaybackSpeed(maxSpeedAudioBlock, 3.0);
```
At maximum speed, audio plays very quickly—a 10-second clip finishes in just 3.33 seconds. This extreme speed is useful for rapidly skimming through content to find specific moments, though comprehension becomes challenging at this rate.
## Speed and Block Duration
### Understanding Duration Changes
When we change playback speed, CE.SDK automatically adjusts the block's duration to reflect the new playback time.
```typescript highlight=highlight-speed-and-duration
// Log duration changes to demonstrate speed-duration relationship
const slowDuration = engine.block.getDuration(slowAudioBlock);
const normalDuration = engine.block.getDuration(normalAudioBlock);
const maxDuration = engine.block.getDuration(maxSpeedAudioBlock);
console.log(`Slow motion (0.5x) duration: ${slowDuration.toFixed(2)}s`);
console.log(`Normal speed (1.0x) duration: ${normalDuration.toFixed(2)}s`);
console.log(`Maximum speed (3.0x) duration: ${maxDuration.toFixed(2)}s`);
```
The original duration represents how long the audio takes to play at normal speed. When we double the speed to 2.0x, the duration is automatically halved. The audio content is the same, but it plays through in half the time, so the block duration shrinks accordingly.
This automatic adjustment keeps your composition synchronized. If you have multiple audio tracks or need to coordinate audio with video, the block durations will accurately reflect the new playback duration after speed changes.
## Exporting Results
After adjusting audio speeds, export the scene to preserve your work. The `engine.scene.saveToString()` method serializes the entire scene, including all audio blocks with their speed settings.
```typescript highlight=highlight-export
// Export the scene to a .scene file
const sceneContent = await engine.scene.saveToString();
await mkdir('output', { recursive: true });
await writeFile('output/audio-speed-adjustment.scene', sceneContent);
console.log('Scene exported to output/audio-speed-adjustment.scene');
```
The exported `.scene` file can be loaded later for further editing or used as a template for batch processing workflows.
## API Reference
| Method | Description | Parameters | Returns |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ----------------- |
| `engine.block.create(type)` | Creates a new design block of the specified type | `type: DesignBlockType` | `DesignBlockId` |
| `engine.block.setString(id, property, value)` | Sets a string property on a block | `id: DesignBlockId, property: string, value: string` | `void` |
| `engine.block.forceLoadAVResource(id)` | Forces loading of audio/video resource metadata | `id: DesignBlockId` | `Promise` |
| `engine.block.setPlaybackSpeed(id, speed)` | Sets the playback speed multiplier. Valid range is \[0.25, 3.0] for audio blocks. Also adjusts trim and duration | `id: DesignBlockId, speed: number` | `void` |
| `engine.block.getPlaybackSpeed(id)` | Gets the current playback speed multiplier | `id: DesignBlockId` | `number` |
| `engine.block.getDuration(id)` | Gets the playback duration of a block in seconds | `id: DesignBlockId` | `number` |
| `engine.scene.saveToString()` | Serializes the current scene into a string | `allowedResourceSchemes?: string[]` | `Promise` |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Adjust Audio Volume"
description: "Learn how to adjust audio volume in CE.SDK to control playback levels, mute audio, and balance multiple audio sources in video projects."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-video/audio/adjust-volume-7ecc4a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
---
Control audio playback volume programmatically using CE.SDK's headless engine for server-side audio processing, from silent (0.0) to full volume (1.0).
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-audio-audio-adjust-volume-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-audio-audio-adjust-volume-server-js)
Volume control adjusts how loud or quiet audio plays during playback. CE.SDK uses a normalized 0.0-1.0 range where 0.0 is completely silent and 1.0 is full volume. This applies to both audio blocks and video fills with embedded audio. Server-side volume control is useful for batch audio processing, automated video production, and generating content with pre-configured volume levels.
```typescript file=@cesdk_web_examples/guides-create-audio-audio-adjust-volume-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
config();
/**
* CE.SDK Server Guide: Adjust Audio Volume
*
* Demonstrates audio volume control in CE.SDK:
* - Setting volume levels with setVolume
* - Muting and unmuting with setMuted
* - Querying volume and mute states
* - Volume levels for multiple audio sources
*/
const engine = await CreativeEngine.init({});
try {
// Create a scene with a page for audio content
engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(engine.scene.get()!, page);
// Use a sample audio file
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a';
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Wait for audio resource to load
await engine.block.forceLoadAVResource(audioBlock);
// Set volume to 80% (0.8 on a 0.0-1.0 scale)
const fullVolumeAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, fullVolumeAudio);
engine.block.setTimeOffset(fullVolumeAudio, 0);
engine.block.setVolume(fullVolumeAudio, 0.8);
// Set volume to 30% for background music
const lowVolumeAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, lowVolumeAudio);
engine.block.setTimeOffset(lowVolumeAudio, 5);
engine.block.setVolume(lowVolumeAudio, 0.3);
// Mute an audio block (preserves volume setting)
const mutedAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, mutedAudio);
engine.block.setTimeOffset(mutedAudio, 10);
engine.block.setVolume(mutedAudio, 1.0);
engine.block.setMuted(mutedAudio, true);
// Query current volume and mute states
const currentVolume = engine.block.getVolume(fullVolumeAudio);
const isMuted = engine.block.isMuted(mutedAudio);
const isForceMuted = engine.block.isForceMuted(mutedAudio);
console.log(`Full volume audio: ${(currentVolume * 100).toFixed(0)}%`);
console.log(
`Low volume audio: ${(engine.block.getVolume(lowVolumeAudio) * 100).toFixed(0)}%`
);
console.log(
`Muted audio - isMuted: ${isMuted}, isForceMuted: ${isForceMuted}`
);
// Remove the original audio block (we only need the duplicates)
engine.block.destroy(audioBlock);
console.log('Audio volume adjustment example complete');
} finally {
engine.dispose();
}
```
This guide covers how to adjust audio volume programmatically using the Engine API, mute and unmute audio, and query volume and mute states.
## Setting Up the Engine
First, initialize the CE.SDK engine in headless mode for server-side processing.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({});
```
## Understanding Volume Concepts
CE.SDK supports volume levels from **0.0** (silent) to **1.0** (full volume), with **1.0** as the default for new audio blocks. Values in between represent proportional volume levels—0.5 is half volume, 0.25 is quarter volume.
**Volume vs Muting**: Setting volume to 0.0 makes audio silent, but muting with `setMuted()` is preferred when you want to temporarily silence audio without losing the volume setting. Unmuting restores the previous volume level.
**Common use cases**: Batch audio processing, automated video production with pre-set volume levels, normalizing volume across multiple clips, and creating audio mixes programmatically.
## Setting Up Audio for Volume Control
### Loading Audio Files
We create an audio block and load an audio file by setting its `fileURI` property.
```typescript highlight=highlight-create-audio
// Create an audio block and load the audio file
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
// Wait for audio resource to load
await engine.block.forceLoadAVResource(audioBlock);
```
Unlike video or image blocks which use fills, audio blocks store the file URI directly on the block itself using the `audio/fileURI` property. The `forceLoadAVResource` call ensures CE.SDK has downloaded the audio file and loaded its metadata before we manipulate it.
## Adjusting Volume
### Setting Volume
We can set volume using `setVolume()` with a value between 0.0 and 1.0.
```typescript highlight=highlight-set-volume
// Set volume to 80% (0.8 on a 0.0-1.0 scale)
const fullVolumeAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, fullVolumeAudio);
engine.block.setTimeOffset(fullVolumeAudio, 0);
engine.block.setVolume(fullVolumeAudio, 0.8);
```
Setting volume to 0.8 (80%) is useful when you want prominent audio that isn't at maximum level, leaving headroom for other audio sources or preventing distortion.
### Setting Low Volume for Background Audio
For background music that should be audible but not prominent, use lower volume levels.
```typescript highlight=highlight-set-low-volume
// Set volume to 30% for background music
const lowVolumeAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, lowVolumeAudio);
engine.block.setTimeOffset(lowVolumeAudio, 5);
engine.block.setVolume(lowVolumeAudio, 0.3);
```
At 0.3 (30%) volume, the audio is clearly audible but stays in the background. This is a common level for background music under voiceover or dialogue.
## Muting Audio
### Mute and Unmute
Use `setMuted()` to mute audio without changing its volume setting. This is useful for toggle controls.
```typescript highlight=highlight-mute-audio
// Mute an audio block (preserves volume setting)
const mutedAudio = engine.block.duplicate(audioBlock);
engine.block.appendChild(page, mutedAudio);
engine.block.setTimeOffset(mutedAudio, 10);
engine.block.setVolume(mutedAudio, 1.0);
engine.block.setMuted(mutedAudio, true);
```
When you mute an audio block, the volume setting (1.0 in this case) is preserved. Unmuting later with `setMuted(block, false)` restores playback at the same volume level.
### Querying Volume and Mute States
You can query the current volume and mute states at any time.
```typescript highlight=highlight-query-volume
// Query current volume and mute states
const currentVolume = engine.block.getVolume(fullVolumeAudio);
const isMuted = engine.block.isMuted(mutedAudio);
const isForceMuted = engine.block.isForceMuted(mutedAudio);
console.log(`Full volume audio: ${(currentVolume * 100).toFixed(0)}%`);
console.log(
`Low volume audio: ${(engine.block.getVolume(lowVolumeAudio) * 100).toFixed(0)}%`
);
console.log(
`Muted audio - isMuted: ${isMuted}, isForceMuted: ${isForceMuted}`
);
```
Use `getVolume()` to read the current volume level, `isMuted()` to check if the block is muted by the user, and `isForceMuted()` to check if the engine has automatically muted the block due to playback rules.
## Mixing Multiple Audio Sources
### Balancing Tracks
When working with multiple audio sources, use different volume levels to create a balanced mix. A common approach is to keep voiceover or dialogue at higher levels (0.8-1.0) and background music at lower levels (0.3-0.5).
### Common Mixing Patterns
**Voiceover prominent**: Set background music to 0.3 and voiceover to 1.0 for clear narration with musical accompaniment.
**Balanced dialogue and music**: Set both to 0.6-0.7 when both elements are equally important.
**Sound effects as accents**: Set sound effects to 0.5-0.8 depending on how prominent they should be in the mix.
## Troubleshooting
**Volume Changes Not Audible**: Check if the block is muted with `isMuted()` or force muted with `isForceMuted()`. Also verify the audio resource has loaded successfully.
**Force Muted State**: Video fills at playback speeds above 3.0x are automatically force muted by the engine. Reduce the playback speed to restore audio output.
**Volume Not Persisting**: Ensure you're setting volume on the correct block ID. Volume settings are block-specific and don't propagate to duplicates or other instances.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Control Audio and Video"
description: "Learn to play, pause, seek, and preview audio and video content in CE.SDK using playback controls and solo mode."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-video/control-daba54/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Control Audio and Video](https://img.ly/docs/cesdk/node-native/create-video/control-daba54/)
---
Query metadata, seek to specific positions, and check block visibility programmatically using CE.SDK's playback control APIs in headless mode.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-control-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-control-server-js)
CE.SDK provides playback control for audio and video through the Block API. In headless mode, playback time, seeking, and visibility checks are controlled programmatically. Resources must be loaded before accessing metadata like duration and dimensions.
```typescript file=@cesdk_web_examples/guides-create-video-control-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Control Audio and Video
*
* Demonstrates audio and video control in headless mode:
* - Force loading video resources with forceLoadAVResource
* - Querying video metadata (dimensions, duration)
* - Setting and getting playback time for timeline positioning
* - Checking visibility at current playback time
* - Checking playback control support
* - Saving the scene for later rendering
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
// Set page duration to accommodate our video content
engine.block.setDuration(page, 15);
// Create a track for video blocks
const track = engine.block.create('track');
engine.block.appendChild(page, track);
// Sample video URL
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
// Create a video block and add it to the track
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);
// Force load the video resource to access metadata
// This is required before accessing duration, dimensions, or other properties
console.log('Loading video resource...');
await engine.block.forceLoadAVResource(videoFill);
console.log('Video resource loaded successfully');
// Query video dimensions from the loaded resource
const videoWidth = engine.block.getVideoWidth(videoFill);
const videoHeight = engine.block.getVideoHeight(videoFill);
console.log(`Video dimensions: ${videoWidth}x${videoHeight} pixels`);
// Get the total duration of the source media
const totalDuration = engine.block.getAVResourceTotalDuration(videoFill);
console.log(`Source media duration: ${totalDuration} seconds`);
// Check if a block supports playback control
// Pages and scenes support playback control for timeline-based playback
const pageSupportsPlayback = engine.block.supportsPlaybackControl(page);
console.log(`Page supports playback control: ${pageSupportsPlayback}`);
// Video fills support playback for individual block preview
const fillSupportsPlayback = engine.block.supportsPlaybackControl(videoFill);
console.log(`Video fill supports playback control: ${fillSupportsPlayback}`);
// Check if the page is currently playing
// In headless mode, this will be false as there is no active playback
const isPlaying = engine.block.isPlaying(page);
console.log(`Is playing: ${isPlaying}`);
// Check if the page supports playback time operations
if (engine.block.supportsPlaybackTime(page)) {
// Set the playback time to position the timeline
// This is useful for rendering frames at specific times
engine.block.setPlaybackTime(page, 3.0);
console.log('Playback time set to 3.0 seconds');
// Get the current playback time
const currentTime = engine.block.getPlaybackTime(page);
console.log(`Current playback time: ${currentTime} seconds`);
}
// Check if the video block is visible at the current playback time
// This is useful when working with multiple blocks that have different time offsets
const isVisible = engine.block.isVisibleAtCurrentPlaybackTime(videoBlock);
console.log(`Video block visible at current time: ${isVisible}`);
// Time offset controls when a block becomes active in the timeline
// Duration controls how long the block appears
const blockDuration = engine.block.getDuration(videoBlock);
const blockTimeOffset = engine.block.getTimeOffset(videoBlock);
console.log(`Video block duration: ${blockDuration} seconds`);
console.log(`Video block time offset: ${blockTimeOffset} seconds`);
// Modify time offset to start the block at 1 second
engine.block.setTimeOffset(videoBlock, 1.0);
console.log(
`Updated time offset to: ${engine.block.getTimeOffset(videoBlock)} seconds`
);
// Trim controls which portion of the source media plays
// Check if the video fill supports trimming
if (engine.block.supportsTrim(videoFill)) {
// Set trim offset to start playback 2 seconds into the source video
engine.block.setTrimOffset(videoFill, 2.0);
// Set trim length to play 5 seconds of the source video
engine.block.setTrimLength(videoFill, 5.0);
const trimOffset = engine.block.getTrimOffset(videoFill);
const trimLength = engine.block.getTrimLength(videoFill);
console.log(
`Trim offset: ${trimOffset} seconds (starts at this point in source)`
);
console.log(
`Trim length: ${trimLength} seconds (plays this much of source)`
);
}
// Demonstrate visibility with multiple blocks at different time offsets
// Create a second video block that starts later in the timeline
const videoBlock2 = engine.block.create('graphic');
engine.block.setShape(videoBlock2, engine.block.createShape('rect'));
engine.block.setWidth(videoBlock2, 960);
engine.block.setHeight(videoBlock2, 540);
const videoFill2 = engine.block.createFill('video');
engine.block.setString(videoFill2, 'fill/video/fileURI', videoUri);
engine.block.setFill(videoBlock2, videoFill2);
engine.block.appendChild(track, videoBlock2);
engine.block.setTimeOffset(videoBlock2, 5.0); // Starts at 5 seconds
engine.block.setDuration(videoBlock2, 8);
// Load resources for the second video
console.log('Loading second video resource...');
await engine.block.forceLoadAVResource(videoFill2);
console.log('Second video resource loaded');
// At playback time 3.0s: first video visible, second not yet
engine.block.setPlaybackTime(page, 3.0);
console.log(
`At 3.0s - Video 1 visible: ${engine.block.isVisibleAtCurrentPlaybackTime(videoBlock)}`
);
console.log(
`At 3.0s - Video 2 visible: ${engine.block.isVisibleAtCurrentPlaybackTime(videoBlock2)}`
);
// At playback time 7.0s: both videos visible (if first extends to this time)
engine.block.setPlaybackTime(page, 7.0);
console.log(
`At 7.0s - Video 1 visible: ${engine.block.isVisibleAtCurrentPlaybackTime(videoBlock)}`
);
console.log(
`At 7.0s - Video 2 visible: ${engine.block.isVisibleAtCurrentPlaybackTime(videoBlock2)}`
);
// Add an audio block to demonstrate audio resource handling
const audioUri =
'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a';
const audioBlock = engine.block.create('audio');
engine.block.setString(audioBlock, 'audio/fileURI', audioUri);
engine.block.appendChild(page, audioBlock);
engine.block.setDuration(audioBlock, 5);
engine.block.setTimeOffset(audioBlock, 2.0);
// Force load the audio resource
console.log('Loading audio resource...');
await engine.block.forceLoadAVResource(audioBlock);
const audioDuration = engine.block.getAVResourceTotalDuration(audioBlock);
console.log(`Audio resource loaded - duration: ${audioDuration} seconds`);
// Check audio visibility at different times
engine.block.setPlaybackTime(page, 1.0);
console.log(
`At 1.0s - Audio visible: ${engine.block.isVisibleAtCurrentPlaybackTime(audioBlock)}`
);
engine.block.setPlaybackTime(page, 3.0);
console.log(
`At 3.0s - Audio visible: ${engine.block.isVisibleAtCurrentPlaybackTime(audioBlock)}`
);
// Save the scene for later rendering with CE.SDK Renderer
// Video export is not supported in Node.js - use CE.SDK Renderer instead
console.log('');
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/video-control.scene', sceneString);
console.log('Exported to output/video-control.scene');
console.log('');
console.log('Audio and video control guide completed successfully.');
console.log('Scene saved with:');
console.log(' - 2 video blocks with different time offsets');
console.log(' - 1 audio block');
console.log(' - Ready for rendering with CE.SDK Renderer');
} finally {
engine.dispose();
}
```
This guide covers how to load media resources, seek to specific positions, check visibility at playback time, and access video resource metadata.
## Setup
We start by initializing the engine in headless mode and creating a scene with a page and track.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
The video block is created with specified dimensions and duration, then added to the track.
```typescript highlight=highlight-add-video
// Create a video block and add it to the track
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);
```
## Force Loading Resources
Media resource metadata is unavailable until the resource is loaded. We call `engine.block.forceLoadAVResource()` on the video fill to ensure dimensions and duration are accessible.
```typescript highlight=highlight-force-load
// Force load the video resource to access metadata
// This is required before accessing duration, dimensions, or other properties
console.log('Loading video resource...');
await engine.block.forceLoadAVResource(videoFill);
console.log('Video resource loaded successfully');
```
Without loading the resource first, accessing properties like duration or dimensions throws an error.
## Getting Video Metadata
Once the resource is loaded, we can query the video dimensions and total duration.
```typescript highlight=highlight-get-dimensions
// Query video dimensions from the loaded resource
const videoWidth = engine.block.getVideoWidth(videoFill);
const videoHeight = engine.block.getVideoHeight(videoFill);
console.log(`Video dimensions: ${videoWidth}x${videoHeight} pixels`);
// Get the total duration of the source media
const totalDuration = engine.block.getAVResourceTotalDuration(videoFill);
console.log(`Source media duration: ${totalDuration} seconds`);
```
The `engine.block.getVideoWidth()` and `engine.block.getVideoHeight()` methods return the original video dimensions in pixels. The `engine.block.getAVResourceTotalDuration()` method returns the full duration of the source media in seconds.
## Playback Control
We check if the block supports playback control using `engine.block.supportsPlaybackControl()`. In headless mode, this determines whether the block can be positioned programmatically in the composition. The `engine.block.isPlaying()` method returns whether the block is currently playing, which will be `false` in headless mode since there is no active playback.
```typescript highlight=highlight-playback-control-support
// Check if a block supports playback control
// Pages and scenes support playback control for timeline-based playback
const pageSupportsPlayback = engine.block.supportsPlaybackControl(page);
console.log(`Page supports playback control: ${pageSupportsPlayback}`);
// Video fills support playback for individual block preview
const fillSupportsPlayback = engine.block.supportsPlaybackControl(videoFill);
console.log(`Video fill supports playback control: ${fillSupportsPlayback}`);
// Check if the page is currently playing
// In headless mode, this will be false as there is no active playback
const isPlaying = engine.block.isPlaying(page);
console.log(`Is playing: ${isPlaying}`);
```
## Seeking
To set the playback position, we use `engine.block.setPlaybackTime()`. First, check if the block supports playback time with `engine.block.supportsPlaybackTime()`.
```typescript highlight=highlight-playback-time
// Check if the page supports playback time operations
if (engine.block.supportsPlaybackTime(page)) {
// Set the playback time to position the timeline
// This is useful for rendering frames at specific times
engine.block.setPlaybackTime(page, 3.0);
console.log('Playback time set to 3.0 seconds');
// Get the current playback time
const currentTime = engine.block.getPlaybackTime(page);
console.log(`Current playback time: ${currentTime} seconds`);
}
```
Playback time is specified in seconds. The `engine.block.getPlaybackTime()` method returns the current position.
## Visibility at Current Time
We can check if a block is visible at the current playback position using `engine.block.isVisibleAtCurrentPlaybackTime()`. This is useful when blocks have different time offsets or durations.
```typescript highlight=highlight-visibility
// Check if the video block is visible at the current playback time
// This is useful when working with multiple blocks that have different time offsets
const isVisible = engine.block.isVisibleAtCurrentPlaybackTime(videoBlock);
console.log(`Video block visible at current time: ${isVisible}`);
```
## Time Offset and Duration
Time offset controls when a block becomes active in the composition, while duration controls how long the block appears. Use `engine.block.getTimeOffset()` and `engine.block.getDuration()` to query these values, and their corresponding setters to modify them.
```typescript highlight=highlight-time-offset-duration
// Time offset controls when a block becomes active in the timeline
// Duration controls how long the block appears
const blockDuration = engine.block.getDuration(videoBlock);
const blockTimeOffset = engine.block.getTimeOffset(videoBlock);
console.log(`Video block duration: ${blockDuration} seconds`);
console.log(`Video block time offset: ${blockTimeOffset} seconds`);
// Modify time offset to start the block at 1 second
engine.block.setTimeOffset(videoBlock, 1.0);
console.log(
`Updated time offset to: ${engine.block.getTimeOffset(videoBlock)} seconds`
);
```
## Trim
Trim controls which portion of the source media plays. The trim offset specifies where playback starts within the source file, and trim length defines how much of the source to play. Check `engine.block.supportsTrim()` before applying trim operations.
```typescript highlight=highlight-trim
// Trim controls which portion of the source media plays
// Check if the video fill supports trimming
if (engine.block.supportsTrim(videoFill)) {
// Set trim offset to start playback 2 seconds into the source video
engine.block.setTrimOffset(videoFill, 2.0);
// Set trim length to play 5 seconds of the source video
engine.block.setTrimLength(videoFill, 5.0);
const trimOffset = engine.block.getTrimOffset(videoFill);
const trimLength = engine.block.getTrimLength(videoFill);
console.log(
`Trim offset: ${trimOffset} seconds (starts at this point in source)`
);
console.log(
`Trim length: ${trimLength} seconds (plays this much of source)`
);
}
```
## Troubleshooting
### Properties Unavailable Before Resource Load
**Symptom**: Accessing duration, dimensions, or trim values throws an error.
**Cause**: Media resource not yet loaded.
**Solution**: Always `await engine.block.forceLoadAVResource()` before accessing these properties.
### Block Not Playing
**Symptom**: Calling `setPlaying(true)` has no effect.
**Cause**: Block doesn't support playback control or scene not in playback mode.
**Solution**: Check `supportsPlaybackControl()` returns true; ensure scene playback is active.
### Solo Playback Not Working
**Symptom**: Enabling solo doesn't isolate the block.
**Cause**: Solo applied to wrong block type or block not visible.
**Solution**: Apply solo to video fills or audio blocks, ensure block is at current playback time.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Limitations"
description: "Understand video resolution limits, duration constraints, codec support, and browser-specific restrictions in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/create-video/limitations-6a740d/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Limitations](https://img.ly/docs/cesdk/node-native/create-video/limitations-6a740d/)
---
Server-side video processing with CE.SDK native Node.js bindings leverages
native C++ performance and GPU acceleration. This guide covers resolution and
duration limits, codec support, memory management, and hardware considerations
for reliable video workflows.
The native Node.js bindings (`@cesdk/node-native`) support GPU-accelerated video export, unlike the WASM-based `@cesdk/node` package. This enables server-side video rendering with native performance.
## Video Export Support
The native bindings provide video export capabilities directly, without requiring the separate CE.SDK Renderer. Video encoding uses GPU acceleration via:
- **Metal** on macOS (ARM and x64)
- **EGL** on Linux (x64)
```javascript
import CreativeEngine from '@cesdk/node-native';
import fs from 'fs/promises';
const engine = await CreativeEngine.init({ license: 'YOUR_CESDK_LICENSE_KEY' });
try {
// Load a video scene
await engine.scene.loadFromURL('https://example.com/video-scene.scene');
const [page] = engine.block.findByType('page');
// Export as MP4 video — requires an update loop to pump encoding tasks
const exportPromise = engine.block.exportVideo(
page,
'video/mp4',
(rendered, encoded, total) => {
console.log(
`Progress: ${rendered}/${total} rendered, ${encoded}/${total} encoded`,
);
},
{ targetWidth: 1920, targetHeight: 1080, framerate: 30 },
);
// Pump the engine update loop during export
let exportComplete = false;
exportPromise
.then(() => {
exportComplete = true;
})
.catch(() => {
exportComplete = true;
});
while (!exportComplete) {
engine.update();
await new Promise(r => setTimeout(r, 1));
}
// Awaiting the promise rethrows any export error
const videoData = await exportPromise;
await fs.writeFile('./output.mp4', Buffer.from(videoData));
} finally {
engine.dispose();
}
```
## Resolution Limits
Video resolution capabilities depend on hardware resources and GPU capabilities. CE.SDK supports up to 4K UHD for processing and export on capable hardware.
The maximum export size varies by hardware capabilities. Before exporting at high resolutions, verify the target dimensions don't exceed hardware limits.
## Duration Limits
Video duration affects processing time and memory consumption. 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 efficient processing. Videos up to 10 minutes work well on modern hardware. Longer videos are technically possible but may increase memory usage and processing time.
For long-form content, consider these approaches:
- Split longer videos into shorter segments for processing
- Use lower resolution for intermediate processing, then export at full quality
- Monitor memory usage to establish acceptable duration limits for your server configuration
## Frame Rate Support
Frame rate affects processing time and output quality. Server environments can handle high frame rates without the real-time playback concerns of browser implementations.
30 FPS at 1080p is broadly supported and provides efficient processing. 60 FPS and high-resolution combinations require more processing time but are well-supported in server environments.
Variable frame rate sources may have timing precision limitations. For best results with variable frame rate content, consider transcoding to constant frame rate before processing.
## Supported Codecs
### Video Codecs
H.264/AVC in `.mp4` containers has universal support and is the most reliable codec choice for broad compatibility.
H.265/HEVC in `.mp4` containers has platform-dependent support. Availability varies by operating system and installed codec libraries.
### Audio Codecs
MP3 works in `.mp3` files or within `.mp4` containers, with universal support.
AAC in `.m4a`, `.mp4`, or `.mov` containers is widely supported, though some platforms may require additional codec libraries for encoding.
## Hardware Considerations
The native bindings leverage hardware resources directly, providing better performance than the WASM-based package.
### Recommended Server Specifications
| Resource | Minimum | Recommended |
| -------- | --------------------------------------- | ------------------------------------------- |
| Memory | 4 GB | 8+ GB for 4K content |
| CPU | 2 cores | 4+ cores for faster processing |
| GPU | Metal (macOS) or EGL (Linux) compatible | Dedicated GPU for best performance on Linux |
| Storage | SSD recommended | Fast I/O for video file handling |
### GPU Requirements
GPU acceleration is required for video export with the native bindings:
- **macOS**: Metal-compatible GPU (all Apple Silicon Macs, most Intel Macs with discrete GPUs)
- **Linux**: EGL-compatible GPU with appropriate drivers installed
## Memory Constraints
Server-side video processing operates within Node.js memory limits. Monitor consumption using standard Node.js APIs:
```javascript
const memUsage = process.memoryUsage();
console.log(`RSS: ${(memUsage.rss / 1024 / 1024).toFixed(1)} MB`);
console.log(`Heap used: ${(memUsage.heapUsed / 1024 / 1024).toFixed(1)} MB`);
```
## Export Size Limitations
Export dimensions are bounded by hardware texture size limits.
Common limits include:
- **4096 pixels**: Basic configurations
- **8192 pixels**: Most modern server hardware
- **16384 pixels**: High-end GPU configurations
## Troubleshooting
| Issue | Cause | Solution |
| ------------------- | ------------------------------- | ------------------------------------------- |
| Export fails | Memory limits exceeded | Reduce resolution or split into segments |
| Codec not supported | Missing system codecs | Install required codec libraries |
| No GPU detected | Missing GPU drivers | Install Metal/EGL drivers for your platform |
| Slow processing | Insufficient hardware resources | Upgrade server specs or optimize content |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Edit Image"
description: "Use CE.SDK to crop, transform, annotate, or enhance images with editing tools and programmatic APIs."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image-c64912/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/)
---
---
## Related Pages
- [Node.js Image Editor SDK](https://img.ly/docs/cesdk/node-native/edit-image/overview-5249ea/) - The CreativeEditor SDK provides a robust and user-friendly solution for photo and image editing.
- [Replace Colors](https://img.ly/docs/cesdk/node-native/edit-image/replace-colors-6ede17/) - Replace specific colors in images using CE.SDK's Recolor and Green Screen effects with programmatic control.
- [Remove Background](https://img.ly/docs/cesdk/node-native/edit-image/remove-bg-9dfcf7/) - Remove image backgrounds to isolate subjects or prepare assets for compositing and reuse.
- [Transform](https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/) - Crop, resize, rotate, scale, or flip images using CE.SDK's built-in transformation tools.
- [Add Watermark](https://img.ly/docs/cesdk/node-native/edit-image/add-watermark-679de0/) - Add text and image watermarks to protect designs, indicate ownership, or add branding using CE.SDK in Node.js.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add Watermark"
description: "Add text and image watermarks to protect designs, indicate ownership, or add branding using CE.SDK in Node.js."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/add-watermark-679de0/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Add Watermark](https://img.ly/docs/cesdk/node-native/edit-image/add-watermark-679de0/)
---
Add text and image watermarks to designs programmatically using CE.SDK's block API in a headless Node.js environment.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-image-add-watermark-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-image-add-watermark-server-js)
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.
```typescript file=@cesdk_web_examples/guides-edit-image-add-watermark-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Add Watermark
*
* 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
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// 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 });
// Export the watermarked design
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/watermarked-design.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported watermarked design to output/watermarked-design.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
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
We start by initializing CE.SDK in headless mode and creating a scene with a custom page size. The page provides the canvas where we'll add our watermarks.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
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
Text watermarks display copyright notices, URLs, or brand names. We create a text block, set its content, and add it to the page.
```typescript highlight=highlight-create-text-watermark
// 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);
```
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
We configure the font size, color, and opacity to make the watermark visible but unobtrusive.
```typescript highlight=highlight-style-text-watermark
// 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');
```
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
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.
```typescript highlight=highlight-position-text-watermark
// 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);
```
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
Logo watermarks use graphic blocks with image fills to display brand symbols or company logos.
```typescript highlight=highlight-create-logo-watermark
// 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);
```
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
We set fixed dimensions for the logo and apply opacity to match the text watermark.
```typescript highlight=highlight-size-logo-watermark
// 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);
```
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
Drop shadows improve watermark readability against varied backgrounds by adding contrast.
```typescript highlight=highlight-add-drop-shadow
// 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 });
```
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
After adding watermarks, we export the complete page as an image file.
```typescript highlight=highlight-export-watermarked
// Export the watermarked design
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/watermarked-design.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported watermarked design to output/watermarked-design.png');
```
The `engine.block.export()` method returns a blob that can be downloaded or saved to disk. Supported formats include PNG, JPEG, and WebP.
## Cleanup
We must dispose the engine to free system resources when processing is complete.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
Always use a try-finally block to ensure the engine is disposed even if errors occur during processing.
## 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
| 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 |
## Next Steps
- [Text Styling](https://img.ly/docs/cesdk/node-native/text/styling-269c48/) — Style text blocks with fonts, colors, and effects
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) — Export options and formats for watermarked images
- [Crop Images](https://img.ly/docs/cesdk/node-native/edit-image/transform/crop-f67a47/) — Transform images before watermarking
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Node.js Image Editor SDK"
description: "The CreativeEditor SDK provides a robust and user-friendly solution for photo and image editing."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/overview-5249ea/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Overview](https://img.ly/docs/cesdk/node-native/edit-image/overview-5249ea/)
---
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](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/)
## 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.
## Supported Input Formats
The SDK supports a broad range of image input types:
## Output and export options
Export edited images in the following formats:
You can define export resolution, compression level, and file metadata. CE.SDK also supports exporting with transparent backgrounds, underlayers, or color masks.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Remove Background"
description: "Remove image backgrounds to isolate subjects or prepare assets for compositing and reuse."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/remove-bg-9dfcf7/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Remove Background](https://img.ly/docs/cesdk/node-native/edit-image/remove-bg-9dfcf7/) > [Plugins](https://img.ly/docs/cesdk/node-native/plugins-693c48/) > [Background Removal](https://img.ly/docs/cesdk/node-native/edit-image/remove-bg-9dfcf7/)
---
Remove image backgrounds programmatically on the server for automated processing pipelines and batch operations.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-image-remove-bg-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-image-remove-bg-server-js)
The `@imgly/background-removal-node` package provides AI-powered background removal for Node.js applications. Processing runs entirely on your server using native bindings, eliminating external API dependencies and ensuring data privacy.
```typescript file=@cesdk_web_examples/guides-edit-image-remove-bg-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { removeBackground, type Config } from '@imgly/background-removal-node';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Remove Background with @imgly/background-removal-node
*
* Demonstrates configuring and using the background removal library
* for server-side image processing in Node.js.
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } },
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Create a graphic block with an image that has a background
const imageBlock = engine.block.create('graphic');
// Create a rect shape for the image
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
// Create an image fill with a sample portrait image
const imageFill = engine.block.createFill('image');
const imageUri = 'https://img.ly/static/ubq_samples/sample_4.jpg';
engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUri);
// Apply the fill to the graphic block
engine.block.setFill(imageBlock, imageFill);
// Set content fill mode to cover the block
engine.block.setContentFillMode(imageBlock, 'Cover');
// Size and position the image block
const imageWidth = 400;
const imageHeight = 450;
engine.block.setWidth(imageBlock, imageWidth);
engine.block.setHeight(imageBlock, imageHeight);
engine.block.setPositionX(imageBlock, (pageWidth - imageWidth) / 2);
engine.block.setPositionY(imageBlock, (pageHeight - imageHeight) / 2);
// Add to page
engine.block.appendChild(page, imageBlock);
// Create output directory
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Configure background removal options
const removalConfig: Config = {
// Model size: 'small' for speed, 'medium' for balance, 'large' for quality
model: 'medium',
// Output format and quality
output: {
format: 'image/png',
quality: 0.9,
},
// Progress callback for monitoring
progress: (key, current, total) => {
const percentage = Math.round((current / total) * 100);
console.log(` ${key}: ${percentage}%`);
},
};
// Export the image block as PNG blob
console.log('Removing background...');
const imageBlob = await engine.block.export(imageBlock, {
mimeType: 'image/png',
});
// Remove background using the configured options
const processedBlob = await removeBackground(imageBlob, removalConfig);
console.log('✓ Background removed successfully');
// Convert the processed blob to a data URL and apply it back to the scene
const processedBuffer = Buffer.from(await processedBlob.arrayBuffer());
const base64Image = processedBuffer.toString('base64');
const dataUrl = `data:image/png;base64,${base64Image}`;
// Update the image fill with the processed image
engine.block.setString(imageFill, 'fill/image/imageFileURI', dataUrl);
console.log('✓ Applied processed image to scene');
// Export the final result with transparent background
const resultBlob = await engine.block.export(page, { mimeType: 'image/png' });
const resultBuffer = Buffer.from(await resultBlob.arrayBuffer());
writeFileSync(`${outputDir}/remove-bg-result.png`, resultBuffer);
console.log('✓ Exported result to output/remove-bg-result.png');
console.log('\n✓ Processing complete!');
console.log(' Output directory:', outputDir);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers installing the library, configuring processing options, and integrating with CE.SDK for automated image processing pipelines.
## Installing the Library
Install the background removal library alongside the CE.SDK Node engine:
```bash
npm install @cesdk/node @imgly/background-removal-node
```
Import the `removeBackground` function and `Config` type:
```typescript highlight-import
import { removeBackground, type Config } from '@imgly/background-removal-node';
```
## Initializing the Engine
Initialize the CE.SDK engine in headless mode for server-side processing:
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
The engine runs without a UI, making it suitable for automated workflows and background processing tasks.
## Creating an Image Block
Create a scene with a graphic block containing an image:
```typescript highlight-create-image
// Create a graphic block with an image that has a background
const imageBlock = engine.block.create('graphic');
// Create a rect shape for the image
const rectShape = engine.block.createShape('rect');
engine.block.setShape(imageBlock, rectShape);
// Create an image fill with a sample portrait image
const imageFill = engine.block.createFill('image');
const imageUri = 'https://img.ly/static/ubq_samples/sample_4.jpg';
engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUri);
// Apply the fill to the graphic block
engine.block.setFill(imageBlock, imageFill);
// Set content fill mode to cover the block
engine.block.setContentFillMode(imageBlock, 'Cover');
// Size and position the image block
const imageWidth = 400;
const imageHeight = 450;
engine.block.setWidth(imageBlock, imageWidth);
engine.block.setHeight(imageBlock, imageHeight);
engine.block.setPositionX(imageBlock, (pageWidth - imageWidth) / 2);
engine.block.setPositionY(imageBlock, (pageHeight - imageHeight) / 2);
// Add to page
engine.block.appendChild(page, imageBlock);
```
## Configuring Background Removal
The `removeBackground` function accepts a configuration object to customize the processing:
```typescript highlight-configure
// Configure background removal options
const removalConfig: Config = {
// Model size: 'small' for speed, 'medium' for balance, 'large' for quality
model: 'medium',
// Output format and quality
output: {
format: 'image/png',
quality: 0.9,
},
// Progress callback for monitoring
progress: (key, current, total) => {
const percentage = Math.round((current / total) * 100);
console.log(` ${key}: ${percentage}%`);
},
};
```
### Configuration Options
| Option | Type | Description |
| --- | --- | --- |
| `model` | `'small'` | `'medium'` | `'large'` | Model size for quality/speed trade-off |
| `output.format` | string | Output format: `'image/png'`, `'image/webp'`, `'image/jpeg'` |
| `output.quality` | number | Quality for lossy formats (0-1) |
| `progress` | function | Callback receiving `(key, current, total)` for progress updates |
| `debug` | boolean | Enable debug logging |
Model selection guidelines:
- **`'small'`**: Fastest processing, suitable for thumbnails or previews
- **`'medium'`**: Best balance of quality and speed for most use cases
- **`'large'`**: Maximum edge quality, slower processing
## Removing the Background
Export the image block and process it with the configured options:
```typescript highlight-remove-background
// Export the image block as PNG blob
console.log('Removing background...');
const imageBlob = await engine.block.export(imageBlock, {
mimeType: 'image/png',
});
// Remove background using the configured options
const processedBlob = await removeBackground(imageBlob, removalConfig);
console.log('✓ Background removed successfully');
```
The `removeBackground` function accepts various input types:
- **Blob**: Direct blob from CE.SDK export
- **URL string**: Remote image URL
- **File path**: Local file path
- **Buffer**: Node.js Buffer
## Applying the Result
Convert the processed blob to a data URL and apply it back to the scene:
```typescript highlight-apply-result
// Convert the processed blob to a data URL and apply it back to the scene
const processedBuffer = Buffer.from(await processedBlob.arrayBuffer());
const base64Image = processedBuffer.toString('base64');
const dataUrl = `data:image/png;base64,${base64Image}`;
// Update the image fill with the processed image
engine.block.setString(imageFill, 'fill/image/imageFileURI', dataUrl);
console.log('✓ Applied processed image to scene');
```
## Exporting the Final Result
Export the scene with the processed image as a PNG file:
```typescript highlight-export
// Export the final result with transparent background
const resultBlob = await engine.block.export(page, { mimeType: 'image/png' });
const resultBuffer = Buffer.from(await resultBlob.arrayBuffer());
writeFileSync(`${outputDir}/remove-bg-result.png`, resultBuffer);
console.log('✓ Exported result to output/remove-bg-result.png');
```
PNG format preserves the alpha channel, maintaining the transparent background.
## Cleanup
Always dispose the engine when processing completes to free resources:
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
The `finally` block ensures cleanup happens even if an error occurs during processing.
## Additional Functions
The library provides additional functions for specialized use cases:
```typescript
import {
removeBackground,
removeForeground,
segmentForeground,
applySegmentationMask
} from '@imgly/background-removal-node';
// Remove foreground instead of background
const foregroundRemoved = await removeForeground(image, config);
// Get just the segmentation mask
const mask = await segmentForeground(image, config);
// Apply a custom mask to an image
const masked = await applySegmentationMask(image, mask, config);
```
## Performance Considerations
Server-side background removal has different performance characteristics than browser processing:
- **First run**: Downloads AI models (~40MB) which are cached for subsequent runs
- **Memory usage**: Processing large images requires significant memory; monitor server resources
- **CPU utilization**: Background removal is CPU-intensive; consider dedicated workers for high-volume processing
- **Batch optimization**: Process images sequentially or with limited concurrency to avoid memory exhaustion
For production deployments, consider:
- Pre-loading models during server startup
- Implementing job queues for batch processing
- Setting memory limits per processing task
- Using horizontal scaling for high-volume workloads
## Troubleshooting
| Issue | Solution |
| --- | --- |
| Model download slow | First run fetches models; subsequent runs use cache |
| Out of memory | Reduce image size or process fewer images concurrently |
| Poor edge quality | Use higher resolution input or 'medium'/'large' model |
| File not found | Verify file paths are absolute or relative to working directory |
| Permission denied | Check file system permissions for output directory |
## Next Steps
- [Chroma Key](https://img.ly/docs/cesdk/node-native/filters-and-effects/chroma-key-green-screen-1e3e99/) - Alternative background removal using green screen
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Export options for images with transparency
- [Replace Colors](https://img.ly/docs/cesdk/node-native/edit-image/replace-colors-6ede17/) - Replace specific colors in images
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Replace Colors"
description: "Replace specific colors in images using CE.SDK's Recolor and Green Screen effects with programmatic control."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/replace-colors-6ede17/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Replace Colors](https://img.ly/docs/cesdk/node-native/edit-image/replace-colors-6ede17/)
---
Transform images by swapping specific colors using the Recolor effect or remove backgrounds with the Green Screen effect in CE.SDK.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-image-replace-colors-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-image-replace-colors-server-js)
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.
```typescript file=@cesdk_web_examples/guides-edit-image-replace-colors-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { calculateGridLayout } from './utils';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Replace Colors
*
* Demonstrates color replacement using Recolor and Green Screen effects:
* - Creating and applying Recolor effects
* - Creating and applying Green Screen effects
* - Configuring effect properties
* - Managing multiple effects
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } },
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// 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);
// Configure color matching precision for Recolor effect
const block2 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block2);
const recolorEffect2 = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.6,
b: 0.4,
a: 1.0,
}); // Skin tone source
engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', {
r: 0.3,
g: 0.7,
b: 0.3,
a: 1.0,
}); // Green tint
// Adjust color match tolerance (0-1, higher = more inclusive)
engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3);
// Adjust brightness match tolerance
engine.block.setFloat(recolorEffect2, 'effect/recolor/brightnessMatch', 0.2);
// Adjust edge smoothness
engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1);
engine.block.appendEffect(block2, recolorEffect2);
// Create a Green Screen effect to remove green backgrounds
const block3 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block3);
const greenScreenEffect = engine.block.createEffect('green_screen');
// Specify the color to remove (green)
engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
engine.block.appendEffect(block3, greenScreenEffect);
// Fine-tune Green Screen removal parameters
const block4 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block4);
const greenScreenEffect2 = engine.block.createEffect('green_screen');
engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', {
r: 0.2,
g: 0.8,
b: 0.3,
a: 1.0,
}); // Specific green shade
// Adjust color match tolerance
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/colorMatch',
0.4
);
// Adjust edge smoothness for cleaner removal
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/smoothness',
0.2
);
// Reduce color spill from green background
engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5);
engine.block.appendEffect(block4, greenScreenEffect2);
// Demonstrate managing multiple effects on a block
const block5 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block5);
// Add multiple effects to the same block
const recolor1 = engine.block.createEffect('recolor');
engine.block.setColor(recolor1, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
});
engine.block.setColor(recolor1, 'effect/recolor/toColor', {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
});
engine.block.appendEffect(block5, recolor1);
const recolor2 = engine.block.createEffect('recolor');
engine.block.setColor(recolor2, 'effect/recolor/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
engine.block.setColor(recolor2, 'effect/recolor/toColor', {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0,
});
engine.block.appendEffect(block5, recolor2);
// Get all effects on the block
const effects = engine.block.getEffects(block5);
// eslint-disable-next-line no-console
console.log('Number of effects:', effects.length); // 2
// Disable the first effect without removing it
engine.block.setEffectEnabled(effects[0], false);
// Check if effect is enabled
const isEnabled = engine.block.isEffectEnabled(effects[0]);
// eslint-disable-next-line no-console
console.log('First effect enabled:', isEnabled); // false
// Apply consistent color replacement across multiple blocks
const block6 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block6);
// Find all image blocks in the scene
const allBlocks = engine.block.findByType('//ly.img.ubq/graphic');
// Apply a consistent recolor effect to each block
allBlocks.forEach((blockId) => {
// Skip if block already has effects
if (engine.block.getEffects(blockId).length > 0) {
return;
}
const batchRecolor = engine.block.createEffect('recolor');
engine.block.setColor(batchRecolor, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.7,
b: 0.6,
a: 1.0,
});
engine.block.setColor(batchRecolor, 'effect/recolor/toColor', {
r: 0.6,
g: 0.7,
b: 0.9,
a: 1.0,
});
engine.block.setFloat(batchRecolor, 'effect/recolor/colorMatch', 0.25);
engine.block.appendEffect(blockId, batchRecolor);
});
// Position all blocks in a grid layout
const blocks = [block1, block2, block3, block4, block5, block6];
blocks.forEach((block, index) => {
const pos = getPosition(index);
engine.block.setPositionX(block, pos.x);
engine.block.setPositionY(block, pos.y);
});
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/replace-colors-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported result to output/replace-colors-result.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide shows how to apply color replacement effects programmatically using the engine API in a headless Node.js environment.
## Programmatic Color Replacement
### Initialize CE.SDK
To apply color replacement programmatically, we set up CE.SDK with the proper configuration.
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
This initializes the CE.SDK engine in headless mode, ready to process images with color replacement 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.
```typescript highlight-create-recolor-effect
// Create a Recolor effect to swap red colors to blue
const block1 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block1);
const recolorEffect = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
}); // Red source color
engine.block.setColor(recolorEffect, 'effect/recolor/toColor', {
r: 0.0,
g: 0.5,
b: 1.0,
a: 1.0,
}); // Blue target color
engine.block.appendEffect(block1, recolorEffect);
```
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.
> **Tip:** 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
We adjust the matching tolerance and smoothness parameters to control how precisely colors must match before replacement.
```typescript highlight-configure-recolor-matching
// Configure color matching precision for Recolor effect
const block2 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block2);
const recolorEffect2 = engine.block.createEffect('recolor');
engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', {
r: 0.8,
g: 0.6,
b: 0.4,
a: 1.0,
}); // Skin tone source
engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', {
r: 0.3,
g: 0.7,
b: 0.3,
a: 1.0,
}); // Green tint
// Adjust color match tolerance (0-1, higher = more inclusive)
engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3);
// Adjust brightness match tolerance
engine.block.setFloat(recolorEffect2, 'effect/recolor/brightnessMatch', 0.2);
// Adjust edge smoothness
engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1);
engine.block.appendEffect(block2, recolorEffect2);
```
The Recolor effect 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
The Green Screen effect removes backgrounds by making specific colors transparent.
```typescript highlight-create-green-screen-effect
// Create a Green Screen effect to remove green backgrounds
const block3 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block3);
const greenScreenEffect = engine.block.createEffect('green_screen');
// Specify the color to remove (green)
engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
engine.block.appendEffect(block3, greenScreenEffect);
```
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
We adjust the color matching tolerance, edge smoothness, and spill suppression parameters.
```typescript highlight-configure-green-screen
// Fine-tune Green Screen removal parameters
const block4 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block4);
const greenScreenEffect2 = engine.block.createEffect('green_screen');
engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', {
r: 0.2,
g: 0.8,
b: 0.3,
a: 1.0,
}); // Specific green shade
// Adjust color match tolerance
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/colorMatch',
0.4
);
// Adjust edge smoothness for cleaner removal
engine.block.setFloat(
greenScreenEffect2,
'effect/green_screen/smoothness',
0.2
);
// Reduce color spill from green background
engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5);
engine.block.appendEffect(block4, greenScreenEffect2);
```
The Green Screen effect 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
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.
```typescript highlight-manage-effects
// Demonstrate managing multiple effects on a block
const block5 = await engine.block.addImage(imageUri, { size: blockSize });
engine.block.appendChild(page, block5);
// Add multiple effects to the same block
const recolor1 = engine.block.createEffect('recolor');
engine.block.setColor(recolor1, 'effect/recolor/fromColor', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
});
engine.block.setColor(recolor1, 'effect/recolor/toColor', {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
});
engine.block.appendEffect(block5, recolor1);
const recolor2 = engine.block.createEffect('recolor');
engine.block.setColor(recolor2, 'effect/recolor/fromColor', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
engine.block.setColor(recolor2, 'effect/recolor/toColor', {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0,
});
engine.block.appendEffect(block5, recolor2);
// Get all effects on the block
const effects = engine.block.getEffects(block5);
// eslint-disable-next-line no-console
console.log('Number of effects:', effects.length); // 2
// Disable the first effect without removing it
engine.block.setEffectEnabled(effects[0], false);
// Check if effect is enabled
const isEnabled = engine.block.isEffectEnabled(effects[0]);
// eslint-disable-next-line no-console
console.log('First effect enabled:', isEnabled); // false
```
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
We can loop through all image blocks in a scene and apply the same effect configuration to each.
```typescript highlight-batch-processing
// 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);
});
```
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.
## Exporting Results
After applying color replacement effects, we export the final result to the file system.
```typescript highlight-export
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/replace-colors-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported result to output/replace-colors-result.png');
```
The `engine.block.export()` method renders the scene with all effects applied and returns a Blob. We convert this to a Buffer and save it as a PNG file.
## Cleanup
We must dispose the engine to free system resources when processing is complete.
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
Always use a try-finally block to ensure the engine is disposed even if errors occur during processing.
## 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
| 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
- [Export Designs](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) — Save your color-replaced images in various formats
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Transform"
description: "Crop, resize, rotate, scale, or flip images using CE.SDK's built-in transformation tools."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/)
---
---
## Related Pages
- [Move Images](https://img.ly/docs/cesdk/node-native/edit-image/transform/move-818dd9/) - Position images precisely on the canvas using absolute or percentage-based coordinates.
- [Crop](https://img.ly/docs/cesdk/node-native/edit-image/transform/crop-f67a47/) - Cut out specific areas of an image to focus on key content or change aspect ratio.
- [Rotate Images](https://img.ly/docs/cesdk/node-native/edit-image/transform/rotate-5f39c9/) - Rotate images to adjust orientation, correct crooked photos, or create creative effects using CE.SDK.
- [Resize](https://img.ly/docs/cesdk/node-native/edit-image/transform/resize-407242/) - Change image dimensions by setting explicit width and height values using absolute units, percentage-based sizing, or auto-sizing modes.
- [Scale Images](https://img.ly/docs/cesdk/node-native/edit-image/transform/scale-ebe367/) - Scale images programmatically in a Node.js backend using CE.SDK server mode.
- [Flip](https://img.ly/docs/cesdk/node-native/edit-image/transform/flip-035e9f/) - Documentation for Flip
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Crop"
description: "Cut out specific areas of an image to focus on key content or change aspect ratio."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/transform/crop-f67a47/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/) > [Crop](https://img.ly/docs/cesdk/node-native/edit-image/transform/crop-f67a47/)
---
Frame subjects, remove unwanted elements, and prepare images for specific
formats using CE.SDK's comprehensive crop system.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-image-transform-crop-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-image-transform-crop-server-js)
Image cropping selects a region inside an image and discards everything outside that frame. Unlike resizing which changes overall dimensions, cropping lets you focus on specific areas of interest. CE.SDK provides a crop system that works through scale, translation, and rotation to define the visible region of content within a block frame.
```typescript file=@cesdk_web_examples/guides-edit-image-transform-crop-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Crop Images
*
* Demonstrates cropping images programmatically:
* - Checking crop support
* - Cropping to fixed dimensions
* - Content fill modes (Crop, Cover, Contain)
* - Scaling and positioning content
* - Cropping to aspect ratio
* - Rotating and flipping content
* - Locking aspect ratio
* - Exporting cropped images
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific 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);
// Sample image URL for demonstrations
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Add an image block using the convenience addImage API
const imageBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 200 }
});
engine.block.appendChild(page, imageBlock);
engine.block.setPositionX(imageBlock, 50);
engine.block.setPositionY(imageBlock, 50);
// Verify the block supports crop operations before applying them
const supportsCrop = engine.block.supportsCrop(imageBlock);
console.log('Block supports crop:', supportsCrop); // true
// Content fill modes control how images fit within their frame
// Check if the block supports content fill modes
const supportsFillMode = engine.block.supportsContentFillMode(imageBlock);
console.log('Supports content fill mode:', supportsFillMode);
// Get the current content fill mode
const currentMode = engine.block.getContentFillMode(imageBlock);
console.log('Current fill mode:', currentMode);
// Set content fill mode - options are 'Crop', 'Cover', 'Contain'
// 'Cover' automatically scales and positions to fill the entire frame
engine.block.setContentFillMode(imageBlock, 'Cover');
// Create another image block to demonstrate crop scaling
const scaleBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 200 }
});
engine.block.appendChild(page, scaleBlock);
engine.block.setPositionX(scaleBlock, 400);
engine.block.setPositionY(scaleBlock, 50);
// Set content fill mode to 'Crop' for manual control
engine.block.setContentFillMode(scaleBlock, 'Crop');
// Scale the content within the crop frame
// Values > 1 zoom in, values < 1 zoom out
engine.block.setCropScaleX(scaleBlock, 1.5);
engine.block.setCropScaleY(scaleBlock, 1.5);
// Or use uniform scaling from center
engine.block.setCropScaleRatio(scaleBlock, 1.2);
// Get the current scale values
const scaleX = engine.block.getCropScaleX(scaleBlock);
const scaleY = engine.block.getCropScaleY(scaleBlock);
const scaleRatio = engine.block.getCropScaleRatio(scaleBlock);
console.log('Crop scale:', { scaleX, scaleY, scaleRatio });
// Pan the content within the crop frame using translation
// Values are in percentage of the crop frame dimensions
engine.block.setCropTranslationX(scaleBlock, 0.1); // Move 10% right
engine.block.setCropTranslationY(scaleBlock, -0.1); // Move 10% up
// Get the current translation values
const translationX = engine.block.getCropTranslationX(scaleBlock);
const translationY = engine.block.getCropTranslationY(scaleBlock);
console.log('Crop translation:', { translationX, translationY });
// Ensure content covers the entire frame without gaps
// The minScaleRatio parameter sets the minimum scale allowed
const adjustedRatio = engine.block.adjustCropToFillFrame(scaleBlock, 1.0);
console.log('Adjusted scale ratio:', adjustedRatio);
// Create an image block to demonstrate crop rotation
const rotateBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 200 }
});
engine.block.appendChild(page, rotateBlock);
engine.block.setPositionX(rotateBlock, 50);
engine.block.setPositionY(rotateBlock, 300);
engine.block.setContentFillMode(rotateBlock, 'Crop');
// Rotate the content within the crop frame (in radians)
// Math.PI / 4 = 45 degrees
engine.block.setCropRotation(rotateBlock, Math.PI / 4);
// Get the current rotation
const rotation = engine.block.getCropRotation(rotateBlock);
console.log('Crop rotation (radians):', rotation);
// Ensure content still fills the frame after rotation
engine.block.adjustCropToFillFrame(rotateBlock, 1.0);
// Create an image block to demonstrate flipping
const flipBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 200 }
});
engine.block.appendChild(page, flipBlock);
engine.block.setPositionX(flipBlock, 300);
engine.block.setPositionY(flipBlock, 300);
engine.block.setContentFillMode(flipBlock, 'Crop');
// Flip the content horizontally
engine.block.flipCropHorizontal(flipBlock);
// Or flip vertically
// engine.block.flipCropVertical(flipBlock);
// Create an image block to demonstrate aspect ratio locking
const lockBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 200 }
});
engine.block.appendChild(page, lockBlock);
engine.block.setPositionX(lockBlock, 550);
engine.block.setPositionY(lockBlock, 300);
engine.block.setContentFillMode(lockBlock, 'Crop');
// Lock the crop aspect ratio - when locked, crop handles maintain
// the current aspect ratio during resize operations
engine.block.setCropAspectRatioLocked(lockBlock, true);
// Check if aspect ratio is locked
const isLocked = engine.block.isCropAspectRatioLocked(lockBlock);
console.log('Aspect ratio locked:', isLocked);
// Reset crop to default state (sets content fill mode to 'Cover')
engine.block.resetCrop(lockBlock);
// Export the result as PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('crop-images-output.png', buffer);
console.log('Successfully exported crop-images-output.png');
} catch (error) {
console.error('Error:', error);
throw error;
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to crop images programmatically using the block API, including content fill modes, scaling, positioning, rotation, and aspect ratio locking.
## Programmatic Cropping
### Initialize CE.SDK
For applications that need to crop images programmatically—whether for automation, batch processing, or dynamic user experiences—we start by setting up CE.SDK with the proper configuration.
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
This initializes the CE.SDK engine in headless mode, giving you full API access to the crop system without UI dependencies.
### Create a Scene with an Image
Before cropping, we need a scene with an image block. The example creates a page with specific dimensions and adds an image using the convenience `addImage()` API.
```typescript highlight-create-scene
// Create a design scene with specific 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);
// Sample image URL for demonstrations
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Add an image block using the convenience addImage API
const imageBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 200 }
});
engine.block.appendChild(page, imageBlock);
engine.block.setPositionX(imageBlock, 50);
engine.block.setPositionY(imageBlock, 50);
```
### Check Crop Support
Before applying crop operations to a block, verify it supports cropping. Not all block types can be cropped—for example, scene blocks do not support crop operations.
```typescript highlight-supportsCrop
// Verify the block supports crop operations before applying them
const supportsCrop = engine.block.supportsCrop(imageBlock);
console.log('Block supports crop:', supportsCrop); // true
```
Crop support is available for:
- **Graphic blocks** with image fills
- **Graphic blocks** with video fills
- **Shape blocks** with fills
Always verify support before applying crop operations to avoid errors.
### Content Fill Modes
CE.SDK provides three content fill modes that control how images fit within their frame. These modes determine whether you have manual control or automatic behavior.
```typescript highlight-contentFillMode
// Content fill modes control how images fit within their frame
// Check if the block supports content fill modes
const supportsFillMode = engine.block.supportsContentFillMode(imageBlock);
console.log('Supports content fill mode:', supportsFillMode);
// Get the current content fill mode
const currentMode = engine.block.getContentFillMode(imageBlock);
console.log('Current fill mode:', currentMode);
// Set content fill mode - options are 'Crop', 'Cover', 'Contain'
// 'Cover' automatically scales and positions to fill the entire frame
engine.block.setContentFillMode(imageBlock, 'Cover');
```
The available fill modes are:
- **Crop** - Manual control over the exact crop region using scale and translation
- **Cover** - Automatically scales and positions content to cover the entire frame (no gaps)
- **Contain** - Automatically scales and positions content to fit within the frame (may show gaps)
Use `Crop` mode when you need precise control over which part of the image is visible. Use `Cover` for automatic framing that fills the entire space.
### Scale Content
When using `Crop` fill mode, you can scale the content within the crop frame to zoom in or out. Values greater than 1 zoom in, while values less than 1 zoom out.
```typescript highlight-cropScale
// Create another image block to demonstrate crop scaling
const scaleBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 200 }
});
engine.block.appendChild(page, scaleBlock);
engine.block.setPositionX(scaleBlock, 400);
engine.block.setPositionY(scaleBlock, 50);
// Set content fill mode to 'Crop' for manual control
engine.block.setContentFillMode(scaleBlock, 'Crop');
// Scale the content within the crop frame
// Values > 1 zoom in, values < 1 zoom out
engine.block.setCropScaleX(scaleBlock, 1.5);
engine.block.setCropScaleY(scaleBlock, 1.5);
// Or use uniform scaling from center
engine.block.setCropScaleRatio(scaleBlock, 1.2);
// Get the current scale values
const scaleX = engine.block.getCropScaleX(scaleBlock);
const scaleY = engine.block.getCropScaleY(scaleBlock);
const scaleRatio = engine.block.getCropScaleRatio(scaleBlock);
console.log('Crop scale:', { scaleX, scaleY, scaleRatio });
```
CE.SDK provides two scaling approaches:
- **Non-uniform scaling** - `setCropScaleX()` and `setCropScaleY()` for independent horizontal and vertical scaling
- **Uniform scaling** - `setCropScaleRatio()` for proportional scaling from the center of the crop frame
Uniform scaling maintains the image's aspect ratio, while non-uniform scaling can stretch or compress the image.
### Position Content
Pan the content within the crop frame using translation. Translation values represent the offset as a percentage of the crop frame dimensions.
```typescript highlight-cropTranslation
// Pan the content within the crop frame using translation
// Values are in percentage of the crop frame dimensions
engine.block.setCropTranslationX(scaleBlock, 0.1); // Move 10% right
engine.block.setCropTranslationY(scaleBlock, -0.1); // Move 10% up
// Get the current translation values
const translationX = engine.block.getCropTranslationX(scaleBlock);
const translationY = engine.block.getCropTranslationY(scaleBlock);
console.log('Crop translation:', { translationX, translationY });
```
Translation values:
- Positive X values move content to the right
- Negative X values move content to the left
- Positive Y values move content down
- Negative Y values move content up
After adjusting scale and translation, use `adjustCropToFillFrame()` to ensure content covers the entire frame without gaps.
```typescript highlight-adjustCropToFillFrame
// Ensure content covers the entire frame without gaps
// The minScaleRatio parameter sets the minimum scale allowed
const adjustedRatio = engine.block.adjustCropToFillFrame(scaleBlock, 1.0);
console.log('Adjusted scale ratio:', adjustedRatio);
```
The `minScaleRatio` parameter sets the minimum scale allowed—the method returns the actual scale ratio applied.
### Rotate Content
Rotate the content within the crop frame using radians. This rotates the image independently of the block's overall rotation.
```typescript highlight-cropRotation
// Create an image block to demonstrate crop rotation
const rotateBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 200 }
});
engine.block.appendChild(page, rotateBlock);
engine.block.setPositionX(rotateBlock, 50);
engine.block.setPositionY(rotateBlock, 300);
engine.block.setContentFillMode(rotateBlock, 'Crop');
// Rotate the content within the crop frame (in radians)
// Math.PI / 4 = 45 degrees
engine.block.setCropRotation(rotateBlock, Math.PI / 4);
// Get the current rotation
const rotation = engine.block.getCropRotation(rotateBlock);
console.log('Crop rotation (radians):', rotation);
// Ensure content still fills the frame after rotation
engine.block.adjustCropToFillFrame(rotateBlock, 1.0);
```
After rotating content, call `adjustCropToFillFrame()` to ensure the rotated content still covers the entire frame.
The difference between crop rotation and block rotation:
- **Crop rotation** - Rotates the content inside the frame while the frame stays fixed
- **Block rotation** - Rotates the entire block including its frame
### Flip Content
Flip the content horizontally or vertically to create mirror effects.
```typescript highlight-flipCrop
// Create an image block to demonstrate flipping
const flipBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 200 }
});
engine.block.appendChild(page, flipBlock);
engine.block.setPositionX(flipBlock, 300);
engine.block.setPositionY(flipBlock, 300);
engine.block.setContentFillMode(flipBlock, 'Crop');
// Flip the content horizontally
engine.block.flipCropHorizontal(flipBlock);
// Or flip vertically
// engine.block.flipCropVertical(flipBlock);
```
Flipping affects only the content within the frame, not the frame itself.
### Lock Aspect Ratio
When building interactive crop interfaces, you can lock the aspect ratio to maintain proportions during resize operations.
```typescript highlight-lockAspectRatio
// Create an image block to demonstrate aspect ratio locking
const lockBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 200 }
});
engine.block.appendChild(page, lockBlock);
engine.block.setPositionX(lockBlock, 550);
engine.block.setPositionY(lockBlock, 300);
engine.block.setContentFillMode(lockBlock, 'Crop');
// Lock the crop aspect ratio - when locked, crop handles maintain
// the current aspect ratio during resize operations
engine.block.setCropAspectRatioLocked(lockBlock, true);
// Check if aspect ratio is locked
const isLocked = engine.block.isCropAspectRatioLocked(lockBlock);
console.log('Aspect ratio locked:', isLocked);
```
When locked, crop handles maintain the current aspect ratio. This is useful for:
- Preparing images for specific formats (social media, print)
- Maintaining consistent proportions across multiple images
- Preventing accidental distortion during editing
### Reset Crop
To restore default crop settings, use `resetCrop()`. This sets the content fill mode to `Cover` and adjusts crop values so the content covers the block.
```typescript highlight-resetCrop
// Reset crop to default state (sets content fill mode to 'Cover')
engine.block.resetCrop(lockBlock);
```
## Coordinate System
Crop transforms use normalized values:
| Property | Value Type | Description |
|----------|------------|-------------|
| Scale | Float (0.0+) | 1.0 is original size, 2.0 is double, 0.5 is half |
| Translation | Float (-1.0 to 1.0) | Percentage of frame dimensions |
| Rotation | Float (radians) | Math.PI = 180°, Math.PI/2 = 90° |
All crop values are independent of canvas zoom level.
## Combining Crop with Other Transforms
You can combine crop operations with other block transforms like position, rotation, and scale. Crop transforms affect the content within the block, while block transforms affect the block itself on the canvas:
```typescript
// Crop the content (scales/pans the image within its frame)
engine.block.setCropScaleRatio(imageBlock, 1.5);
engine.block.setCropRotation(imageBlock, Math.PI / 12);
// Transform the block itself (moves/rotates the entire block on canvas)
engine.block.setRotation(imageBlock, Math.PI / 6);
engine.block.setWidth(imageBlock, 400);
```
## Export Results
After applying crop transformations, export the processed content to a file.
```typescript highlight-export
// Export the result as PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('crop-images-output.png', buffer);
console.log('Successfully exported crop-images-output.png');
```
Always dispose of the engine instance when processing is complete to free resources.
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Troubleshooting
| Issue | Cause / Fix |
|-------|-------------|
| Crop functions throw error | Verify block supports crop with `supportsCrop()` |
| Black bars after scaling | Call `adjustCropToFillFrame()` or increase scale ratio |
| Content not filling frame | Check content fill mode - use 'Cover' for automatic fill |
| Unexpected crop behavior | Ensure content fill mode is set to 'Crop' for manual control |
## API Reference
| Method | Description |
|--------|-------------|
| `block.supportsCrop(id)` | Check if a block supports cropping |
| `block.setCropScaleX(id, scaleX)` | Set horizontal crop scale |
| `block.setCropScaleY(id, scaleY)` | Set vertical crop scale |
| `block.setCropScaleRatio(id, ratio)` | Set uniform crop scale from center |
| `block.setCropRotation(id, rotation)` | Set crop rotation in radians |
| `block.setCropTranslationX(id, x)` | Set horizontal crop translation |
| `block.setCropTranslationY(id, y)` | Set vertical crop translation |
| `block.adjustCropToFillFrame(id, minRatio)` | Adjust crop to fill frame |
| `block.flipCropHorizontal(id)` | Flip content horizontally |
| `block.flipCropVertical(id)` | Flip content vertically |
| `block.isCropAspectRatioLocked(id)` | Check if aspect ratio is locked |
| `block.setCropAspectRatioLocked(id, locked)` | Lock/unlock aspect ratio |
| `block.resetCrop(id)` | Reset crop to default state |
| `block.supportsContentFillMode(id)` | Check if block supports fill modes |
| `block.setContentFillMode(id, mode)` | Set content fill mode |
| `block.getContentFillMode(id)` | Get current content fill mode |
| `block.getCropScaleX(id)` | Get horizontal crop scale |
| `block.getCropScaleY(id)` | Get vertical crop scale |
| `block.getCropScaleRatio(id)` | Get uniform crop scale ratio |
| `block.getCropRotation(id)` | Get crop rotation in radians |
| `block.getCropTranslationX(id)` | Get horizontal crop translation |
| `block.getCropTranslationY(id)` | Get vertical crop translation |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Flip"
description: "Documentation for Flip"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/transform/flip-035e9f/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/) > [Flip](https://img.ly/docs/cesdk/node-native/edit-image/transform/flip-035e9f/)
---
Flip images horizontally and vertically in CE.SDK for headless server-side processing. Mirror images for orientation correction, reflection effects, and batch operations.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-image-transform-flip-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-image-transform-flip-server-js)
Flipping mirrors content along horizontal or vertical axes. Use flip operations for orientation correction, creating reflection effects, and adapting layouts.
```typescript file=@cesdk_web_examples/guides-edit-image-transform-flip-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
async function flipImagesExample() {
let engine: CreativeEngine | null = null;
try {
// Initialize headless engine for programmatic creation
engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
// Create a new scene programmatically
const scene = engine.scene.create();
// Create and set up the page
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
// Demo 1: Original image (no flip)
const originalImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, originalImage);
engine.block.setPositionX(originalImage, 50);
engine.block.setPositionY(originalImage, 50);
const text1 = engine.block.create('text');
engine.block.setString(text1, 'text/text', 'Original');
engine.block.setFloat(text1, 'text/fontSize', 24);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text1, 150);
engine.block.setPositionX(text1, 50);
engine.block.setPositionY(text1, 210);
engine.block.appendChild(page, text1);
// Demo 2: Horizontal flip
const horizontalFlipImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, horizontalFlipImage);
engine.block.setPositionX(horizontalFlipImage, 225);
engine.block.setPositionY(horizontalFlipImage, 50);
// Flip the block horizontally (mirrors left to right)
engine.block.setFlipHorizontal(horizontalFlipImage, true);
const text2 = engine.block.create('text');
engine.block.setString(text2, 'text/text', 'Horizontal');
engine.block.setFloat(text2, 'text/fontSize', 24);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text2, 150);
engine.block.setPositionX(text2, 225);
engine.block.setPositionY(text2, 210);
engine.block.appendChild(page, text2);
// Demo 3: Vertical flip
const verticalFlipImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, verticalFlipImage);
engine.block.setPositionX(verticalFlipImage, 400);
engine.block.setPositionY(verticalFlipImage, 50);
// Flip the block vertically (mirrors top to bottom)
engine.block.setFlipVertical(verticalFlipImage, true);
const text3 = engine.block.create('text');
engine.block.setString(text3, 'text/text', 'Vertical');
engine.block.setFloat(text3, 'text/fontSize', 24);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text3, 150);
engine.block.setPositionX(text3, 400);
engine.block.setPositionY(text3, 210);
engine.block.appendChild(page, text3);
// Demo 4: Both flips combined
const bothFlipImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, bothFlipImage);
engine.block.setPositionX(bothFlipImage, 575);
engine.block.setPositionY(bothFlipImage, 50);
// Combine horizontal and vertical flips
engine.block.setFlipHorizontal(bothFlipImage, true);
engine.block.setFlipVertical(bothFlipImage, true);
const text4 = engine.block.create('text');
engine.block.setString(text4, 'text/text', 'Both');
engine.block.setFloat(text4, 'text/fontSize', 24);
engine.block.setEnum(text4, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text4, 150);
engine.block.setPositionX(text4, 575);
engine.block.setPositionY(text4, 210);
engine.block.appendChild(page, text4);
// Demo 5: Crop flip (flips content within the crop frame)
const cropFlipImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, cropFlipImage);
engine.block.setPositionX(cropFlipImage, 225);
engine.block.setPositionY(cropFlipImage, 280);
// Flip the content within the crop frame (not the block itself)
engine.block.flipCropHorizontal(cropFlipImage);
const text5 = engine.block.create('text');
engine.block.setString(text5, 'text/text', 'Crop Flip');
engine.block.setFloat(text5, 'text/fontSize', 24);
engine.block.setEnum(text5, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text5, 150);
engine.block.setPositionX(text5, 225);
engine.block.setPositionY(text5, 440);
engine.block.appendChild(page, text5);
// Get current flip state
const isFlippedH = engine.block.getFlipHorizontal(horizontalFlipImage);
const isFlippedV = engine.block.getFlipVertical(verticalFlipImage);
console.log(`Horizontal flip state: ${isFlippedH}`);
console.log(`Vertical flip state: ${isFlippedV}`);
// Toggle flip by reading current state and setting opposite
const currentFlip = engine.block.getFlipHorizontal(originalImage);
engine.block.setFlipHorizontal(originalImage, !currentFlip);
// Toggle back for demo purposes
engine.block.setFlipHorizontal(originalImage, currentFlip);
// Export the result as PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('flip-images-output.png', buffer);
console.log('Successfully exported flip-images-output.png');
} catch (error) {
console.error('Error:', error);
throw error;
} finally {
// Always dispose the engine to free resources
if (engine) {
engine.dispose();
}
}
}
// Run the example
flipImagesExample();
```
This guide covers flipping images horizontally and vertically, understanding the difference between block flip and crop flip, querying flip state, and applying flips to multiple blocks.
## Initialize Headless Engine
Create a headless engine instance for programmatic manipulation:
```typescript highlight=highlight-setup
// Initialize headless engine for programmatic creation
engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
```
## Create Scene
Create a scene and page to hold the images:
```typescript highlight=highlight-create-scene
// Create a new scene programmatically
const scene = engine.scene.create();
// Create and set up the page
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
```
## Flip Types
CE.SDK provides two types of flip operations:
- **Block flip**: Mirrors the entire block including its position context. Use `setFlipHorizontal()` and `setFlipVertical()`.
- **Crop flip**: Mirrors only the image content within its crop frame. Use `flipCropHorizontal()` and `flipCropVertical()`.
## Flip Horizontally
Mirror an image along its vertical axis using `engine.block.setFlipHorizontal()`. This swaps the left and right sides:
```typescript highlight=highlight-flip-horizontal
// Flip the block horizontally (mirrors left to right)
engine.block.setFlipHorizontal(horizontalFlipImage, true);
```
## Flip Vertically
Mirror an image along its horizontal axis using `engine.block.setFlipVertical()`. This swaps the top and bottom:
```typescript highlight=highlight-flip-vertical
// Flip the block vertically (mirrors top to bottom)
engine.block.setFlipVertical(verticalFlipImage, true);
```
## Combine Flips
Apply both horizontal and vertical flips to rotate an image 180 degrees:
```typescript highlight=highlight-flip-both
// Combine horizontal and vertical flips
engine.block.setFlipHorizontal(bothFlipImage, true);
engine.block.setFlipVertical(bothFlipImage, true);
```
## Flip Content Within Crop Frame
Flip image content without affecting the block's position using `engine.block.flipCropHorizontal()` and `engine.block.flipCropVertical()`. These methods toggle the flip state each time they are called:
```typescript highlight=highlight-flip-crop
// Flip the content within the crop frame (not the block itself)
engine.block.flipCropHorizontal(cropFlipImage);
```
## Get Current Flip State
Query the current flip state using `engine.block.getFlipHorizontal()` and `engine.block.getFlipVertical()`:
```typescript highlight=highlight-get-flip-state
// Get current flip state
const isFlippedH = engine.block.getFlipHorizontal(horizontalFlipImage);
const isFlippedV = engine.block.getFlipVertical(verticalFlipImage);
console.log(`Horizontal flip state: ${isFlippedH}`);
console.log(`Vertical flip state: ${isFlippedV}`);
```
## Toggle Flip State
Toggle a flip by reading the current state and setting the opposite value:
```typescript highlight=highlight-toggle-flip
// Toggle flip by reading current state and setting opposite
const currentFlip = engine.block.getFlipHorizontal(originalImage);
engine.block.setFlipHorizontal(originalImage, !currentFlip);
// Toggle back for demo purposes
engine.block.setFlipHorizontal(originalImage, currentFlip);
```
## Export Result
Export the scene as a PNG file:
```typescript highlight=highlight-export
// Export the result as PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('flip-images-output.png', buffer);
console.log('Successfully exported flip-images-output.png');
```
## Cleanup
Dispose the engine to free resources:
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
if (engine) {
engine.dispose();
}
```
## Troubleshooting
### Flip Has No Visual Effect
Ensure the block is appended to a page and the scene is loaded before applying flip. Verify the block exists using `engine.block.isValid()`.
### Content Appears in Wrong Position
Check whether you need block flip or crop flip for your use case. Block flip mirrors the entire element, while crop flip mirrors only the content within the frame.
### Engine Keeps Running
Call `engine.dispose()` in a finally block to ensure resources are freed even if errors occur.
## API Reference
| Method | Description |
| ----------------------------------- | ----------------------------------------------- |
| `CreativeEngine.init()` | Initialize the headless engine |
| `engine.scene.create()` | Create a new scene |
| `engine.block.create()` | Create a block of specified type |
| `engine.block.addImage()` | Add an image block |
| `engine.block.setFlipHorizontal()` | Set horizontal flip state |
| `engine.block.setFlipVertical()` | Set vertical flip state |
| `engine.block.getFlipHorizontal()` | Get horizontal flip state |
| `engine.block.getFlipVertical()` | Get vertical flip state |
| `engine.block.flipCropHorizontal()` | Flip content horizontally within crop frame |
| `engine.block.flipCropVertical()` | Flip content vertically within crop frame |
| `engine.block.export()` | Export block as image |
| `engine.dispose()` | Dispose engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Move Images"
description: "Position images precisely on the canvas using absolute or percentage-based coordinates."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/transform/move-818dd9/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/) > [Move](https://img.ly/docs/cesdk/node-native/edit-image/transform/move-818dd9/)
---
Position images on the canvas using absolute pixel coordinates or percentage-based positioning for responsive layouts.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-image-transform-move-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-image-transform-move-server-js)
Position images on the canvas using coordinates that start at the top-left corner (0, 0). X increases right, Y increases down. Values are relative to the parent block, simplifying nested layouts.
```typescript file=@cesdk_web_examples/guides-edit-image-transform-move-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
async function moveImagesExample() {
let engine: CreativeEngine | null = null;
try {
// Initialize headless engine for programmatic creation
engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
// Create a new scene programmatically
const scene = engine.scene.create();
// Create and set up the page
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
// Demo 1: Movable Image - Can be freely repositioned by user
const movableImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 200, height: 200 }
}
);
engine.block.appendChild(page, movableImage);
engine.block.setPositionX(movableImage, 50);
engine.block.setPositionY(movableImage, 150);
const text1 = engine.block.create('text');
engine.block.setString(text1, 'text/text', 'Movable');
engine.block.setFloat(text1, 'text/fontSize', 32);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text1, 200);
engine.block.setPositionX(text1, 50);
engine.block.setPositionY(text1, 360);
engine.block.appendChild(page, text1);
// Demo 2: Percentage Positioning - Responsive layout
const percentImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_5.jpg',
{
size: { width: 200, height: 200 }
}
);
engine.block.appendChild(page, percentImage);
// Set position mode to percentage (0.0 to 1.0)
engine.block.setPositionXMode(percentImage, 'Percent');
engine.block.setPositionYMode(percentImage, 'Percent');
// Position at 37.5% from left (300px), 30% from top (150px)
engine.block.setPositionX(percentImage, 0.375);
engine.block.setPositionY(percentImage, 0.3);
const text2 = engine.block.create('text');
engine.block.setString(text2, 'text/text', 'Percentage');
engine.block.setFloat(text2, 'text/fontSize', 32);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text2, 200);
engine.block.setPositionX(text2, 300);
engine.block.setPositionY(text2, 360);
engine.block.appendChild(page, text2);
// Demo 3: Locked Image - Cannot be moved, rotated, or scaled
const lockedImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_6.jpg',
{
size: { width: 200, height: 200 }
}
);
engine.block.appendChild(page, lockedImage);
engine.block.setPositionX(lockedImage, 550);
engine.block.setPositionY(lockedImage, 150);
// Lock the transform to prevent user interaction
engine.block.setBool(lockedImage, 'transformLocked', true);
const text3 = engine.block.create('text');
engine.block.setString(text3, 'text/text', 'Locked');
engine.block.setFloat(text3, 'text/fontSize', 32);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text3, 200);
engine.block.setPositionX(text3, 550);
engine.block.setPositionY(text3, 360);
engine.block.appendChild(page, text3);
// Get current position values
const currentX = engine.block.getPositionX(movableImage);
const currentY = engine.block.getPositionY(movableImage);
console.log('Current position:', currentX, currentY);
// Move relative to current position
// const offsetX = engine.block.getPositionX(movableImage);
// const offsetY = engine.block.getPositionY(movableImage);
// engine.block.setPositionX(movableImage, offsetX + 50);
// engine.block.setPositionY(movableImage, offsetY + 50);
// Save the scene for later use or rendering
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
writeFileSync('output/move-images-scene.json', sceneString);
console.log('Saved to output/move-images-scene.json');
} catch (error) {
console.error('Error:', error);
throw error;
} finally {
// Always dispose the engine to free resources
if (engine) {
engine.dispose();
}
}
}
// Run the example
moveImagesExample();
```
This guide covers positioning images with absolute or percentage coordinates, configuring position modes, and locking transforms to prevent repositioning.
## Initialize Headless Engine
Create a headless engine instance for programmatic creation and manipulation of designs:
```typescript highlight-setup
// Initialize headless engine for programmatic creation
engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
```
## Create Scene
Create a new scene and page to hold the positioned images:
```typescript highlight-create-scene
// Create a new scene programmatically
const scene = engine.scene.create();
// Create and set up the page
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
```
## Position Coordinates
Coordinates originate at the top-left (0, 0) of the parent container. Use **absolute** mode for fixed pixel values or **percentage** mode (0.0 to 1.0) for responsive layouts that adapt to parent size changes.
## Positioning Images
Position images using `engine.block.setPositionX()` and `engine.block.setPositionY()` with absolute pixel coordinates:
```typescript highlight-movable-image
engine.block.appendChild(page, movableImage);
engine.block.setPositionX(movableImage, 50);
engine.block.setPositionY(movableImage, 150);
```
## Getting Current Position
Read current position values using `engine.block.getPositionX()` and `engine.block.getPositionY()`. Values are returned in the current position mode (absolute pixels or percentage 0.0-1.0):
```typescript highlight-get-position
// Get current position values
const currentX = engine.block.getPositionX(movableImage);
const currentY = engine.block.getPositionY(movableImage);
```
## Configuring Position Modes
Control how position values are interpreted using `engine.block.setPositionXMode()` and `engine.block.setPositionYMode()`. Set to `'Absolute'` for pixels or `'Percent'` for percentage values (0.0 to 1.0). Check the current mode using `engine.block.getPositionXMode()` and `engine.block.getPositionYMode()`. The Percentage Positioning section below demonstrates setting these modes.
## Percentage Positioning
Position images using percentage values (0.0 to 1.0) for responsive layouts. Set the position mode to `'Percent'`, then use values between 0.0 and 1.0:
```typescript highlight-percentage-positioning
// Set position mode to percentage (0.0 to 1.0)
engine.block.setPositionXMode(percentImage, 'Percent');
engine.block.setPositionYMode(percentImage, 'Percent');
```
Percentage positioning adapts automatically when the parent block dimensions change, maintaining relative positions in responsive designs.
## Relative Positioning
Move images relative to their current position by getting the current coordinates and adding offset values:
```typescript highlight-relative-positioning
// Move relative to current position
// const offsetX = engine.block.getPositionX(movableImage);
// const offsetY = engine.block.getPositionY(movableImage);
// engine.block.setPositionX(movableImage, offsetX + 50);
// engine.block.setPositionY(movableImage, offsetY + 50);
```
## Locking Transforms
Lock transforms to prevent repositioning, rotation, and scaling by setting `transformLocked` to true:
```typescript highlight-locked-image
// Lock the transform to prevent user interaction
engine.block.setBool(lockedImage, 'transformLocked', true);
```
## Save Scene
Save the scene to a file for later use or rendering:
```typescript highlight-export
// Save the scene for later use or rendering
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
writeFileSync('output/move-images-scene.json', sceneString);
console.log('Saved to output/move-images-scene.json');
```
## Cleanup
Always dispose the engine to free resources when done:
```typescript highlight-cleanup
// Always dispose the engine to free resources
if (engine) {
engine.dispose();
}
```
## Troubleshooting
### Image Not Moving
Check if transforms are locked using `engine.block.getBool(block, 'transformLocked')`. Ensure the image block exists and values are within parent bounds.
### Unexpected Position Values
Check position mode using `engine.block.getPositionXMode()` and `engine.block.getPositionYMode()`. Verify if using absolute (pixels) vs percentage (0.0-1.0) values. Review parent block dimensions if using percentage positioning.
### Positioned Outside Visible Area
Verify parent block dimensions and boundaries. Check coordinate system: origin is top-left, not center. Review X/Y values for calculation errors.
### Percentage Positioning Not Responsive
Ensure position mode is set to `'Percent'` using `engine.block.setPositionXMode(block, 'Percent')`. Verify percentage values are between 0.0 and 1.0. Check that parent block dimensions can change.
## API Reference
| Method | Description |
| ------------------------------ | --------------------------------------------------------- |
| `CreativeEngine.init()` | Initializes the headless engine for programmatic creation |
| `engine.scene.create()` | Creates a new scene programmatically |
| `engine.block.create()` | Creates a new block of the specified type |
| `engine.block.setWidth()` | Sets the width of a block |
| `engine.block.setHeight()` | Sets the height of a block |
| `engine.block.appendChild()` | Adds a block as a child of another block |
| `engine.block.addImage()` | Create and position image in one operation |
| `engine.block.setPositionX()` | Set X coordinate value |
| `engine.block.setPositionY()` | Set Y coordinate value |
| `engine.block.getPositionX()` | Get current X coordinate value |
| `engine.block.getPositionY()` | Get current Y coordinate value |
| `engine.block.setPositionXMode()` | Set position mode for X coordinate |
| `engine.block.setPositionYMode()` | Set position mode for Y coordinate |
| `engine.block.getPositionXMode()` | Get position mode for X coordinate |
| `engine.block.getPositionYMode()` | Get position mode for Y coordinate |
| `engine.block.setBool()` | Set transform lock state |
| `engine.block.getBool()` | Get transform lock state |
| `engine.scene.saveToString()` | Save scene to string for later use |
| `engine.dispose()` | Dispose engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Resize"
description: "Change image dimensions by setting explicit width and height values using absolute units, percentage-based sizing, or auto-sizing modes."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/transform/resize-407242/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/) > [Resize](https://img.ly/docs/cesdk/node-native/edit-image/transform/resize-407242/)
---
Change image dimensions using absolute pixel values, percentage-based sizing for responsive layouts, or auto-sizing based on content.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-image-transform-resize-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-image-transform-resize-server-js)
Image resizing changes actual dimensions rather than applying scale multipliers. Use `engine.block.setWidth()` and `engine.block.setHeight()` for individual dimensions, or `engine.block.setSize()` for both at once.
```typescript file=@cesdk_web_examples/guides-edit-image-transform-resize-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
async function resizeImagesExample() {
let engine: CreativeEngine | null = null;
try {
// Initialize headless engine for programmatic creation
engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
// Create a new scene programmatically
const scene = engine.scene.create();
// Create and set up the page
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
// Demo 1: Absolute Sizing - Fixed dimensions
const absoluteImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 180, height: 180 }
}
);
engine.block.appendChild(page, absoluteImage);
engine.block.setPositionX(absoluteImage, 20);
engine.block.setPositionY(absoluteImage, 80);
// Set explicit dimensions using setSize
engine.block.setSize(absoluteImage, 180, 180, {
sizeMode: 'Absolute'
});
const text1 = engine.block.create('text');
engine.block.setString(text1, 'text/text', 'Absolute');
engine.block.setFloat(text1, 'text/fontSize', 28);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text1, 180);
engine.block.setPositionX(text1, 20);
engine.block.setPositionY(text1, 280);
engine.block.appendChild(page, text1);
// Demo 2: Percentage Sizing - Responsive layout
const percentImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_5.jpg',
{
size: { width: 180, height: 180 }
}
);
engine.block.appendChild(page, percentImage);
engine.block.setPositionX(percentImage, 220);
engine.block.setPositionY(percentImage, 80);
// Set size mode to percentage for responsive sizing
engine.block.setWidthMode(percentImage, 'Percent');
engine.block.setHeightMode(percentImage, 'Percent');
// Values 0.0 to 1.0 represent percentage of parent
engine.block.setWidth(percentImage, 0.225);
engine.block.setHeight(percentImage, 0.36);
const text2 = engine.block.create('text');
engine.block.setString(text2, 'text/text', 'Percentage');
engine.block.setFloat(text2, 'text/fontSize', 28);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text2, 180);
engine.block.setPositionX(text2, 220);
engine.block.setPositionY(text2, 280);
engine.block.appendChild(page, text2);
// Demo 3: Resized with maintainCrop
const cropImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_6.jpg',
{
size: { width: 180, height: 180 }
}
);
engine.block.appendChild(page, cropImage);
engine.block.setPositionX(cropImage, 420);
engine.block.setPositionY(cropImage, 80);
// Resize while preserving crop settings
engine.block.setWidth(cropImage, 180, true);
engine.block.setHeight(cropImage, 180, true);
const text3 = engine.block.create('text');
engine.block.setString(text3, 'text/text', 'Maintain Crop');
engine.block.setFloat(text3, 'text/fontSize', 28);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text3, 180);
engine.block.setPositionX(text3, 420);
engine.block.setPositionY(text3, 280);
engine.block.appendChild(page, text3);
// Get current dimensions
const currentWidth = engine.block.getWidth(absoluteImage);
const currentHeight = engine.block.getHeight(absoluteImage);
const widthMode = engine.block.getWidthMode(absoluteImage);
const heightMode = engine.block.getHeightMode(absoluteImage);
console.log('Current dimensions:', currentWidth, 'x', currentHeight);
console.log('Size modes:', widthMode, heightMode);
// Get calculated frame dimensions after layout
const frameWidth = engine.block.getFrameWidth(absoluteImage);
const frameHeight = engine.block.getFrameHeight(absoluteImage);
console.log('Frame dimensions:', frameWidth, 'x', frameHeight);
// Title text at top
const titleText = engine.block.create('text');
engine.block.setString(titleText, 'text/text', 'Image Resize Examples');
engine.block.setFloat(titleText, 'text/fontSize', 36);
engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(titleText, 800);
engine.block.setPositionX(titleText, 0);
engine.block.setPositionY(titleText, 20);
engine.block.appendChild(page, titleText);
// Export the result as PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('resize-images-output.png', buffer);
console.log('✅ Successfully exported resize-images-output.png');
} catch (error) {
console.error('Error:', error);
throw error;
} finally {
// Always dispose the engine to free resources
if (engine) {
engine.dispose();
}
}
}
// Run the example
resizeImagesExample();
```
This guide covers resizing images with absolute or percentage sizing, configuring size modes, and maintaining crop settings during resize.
## Initialize Headless Engine
Create a headless engine instance for programmatic creation and manipulation of designs:
```typescript highlight-setup
// Initialize headless engine for programmatic creation
engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
```
## Create Scene
Create a new scene and page to hold the resized images:
```typescript highlight-create-scene
// Create a new scene programmatically
const scene = engine.scene.create();
// Create and set up the page
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
```
## Understanding Size Modes
Size values are interpreted in three modes. 'Absolute' uses fixed design units, 'Percent' uses parent-relative values (0.0-1.0), and 'Auto' sizes based on content. Use `engine.block.getWidth()` for the configured value and `engine.block.getFrameWidth()` for actual rendered size after layout.
## Setting Absolute Dimensions
Set explicit dimensions using `engine.block.setSize()` with absolute pixel values:
```typescript highlight-set-size
// Set explicit dimensions using setSize
engine.block.setSize(absoluteImage, 180, 180, {
sizeMode: 'Absolute'
});
```
## Percentage Sizing
Use percentage mode for responsive sizing. Values range from 0.0 to 1.0 representing percentage of parent container:
```typescript highlight-percentage-mode
// Set size mode to percentage for responsive sizing
engine.block.setWidthMode(percentImage, 'Percent');
engine.block.setHeightMode(percentImage, 'Percent');
// Values 0.0 to 1.0 represent percentage of parent
engine.block.setWidth(percentImage, 0.225);
engine.block.setHeight(percentImage, 0.36);
```
Percentage sizing adapts automatically when the parent block dimensions change, maintaining relative sizes in responsive designs.
## Maintaining Crop During Resize
Use the `maintainCrop` parameter to preserve existing crop settings when resizing:
```typescript highlight-maintain-crop
// Resize while preserving crop settings
engine.block.setWidth(cropImage, 180, true);
engine.block.setHeight(cropImage, 180, true);
```
Setting `maintainCrop` to `true` automatically adjusts crop values to preserve the visible area.
## Getting Current Dimensions
Read current configured dimensions and size modes:
```typescript highlight-get-dimensions
// Get current dimensions
const currentWidth = engine.block.getWidth(absoluteImage);
const currentHeight = engine.block.getHeight(absoluteImage);
const widthMode = engine.block.getWidthMode(absoluteImage);
const heightMode = engine.block.getHeightMode(absoluteImage);
```
## Getting Frame Dimensions
Get calculated frame dimensions after layout:
```typescript highlight-frame-dimensions
// Get calculated frame dimensions after layout
const frameWidth = engine.block.getFrameWidth(absoluteImage);
const frameHeight = engine.block.getFrameHeight(absoluteImage);
```
The difference between configured values and frame dimensions matters when using percentage or auto sizing modes.
## Export Result
Export the scene as a PNG file to the filesystem:
```typescript highlight-export
// Export the result as PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('resize-images-output.png', buffer);
console.log('✅ Successfully exported resize-images-output.png');
```
## Cleanup
Always dispose the engine to free resources when done:
```typescript highlight-cleanup
// Always dispose the engine to free resources
if (engine) {
engine.dispose();
}
```
## Troubleshooting
### Image Not Resizing
Check if locked using `engine.block.getBool(block, 'constraints/size/locked')`. Verify size constraints aren't limiting changes. Ensure the block exists and confirm correct units for the size mode.
### Unexpected Size Values
Check mode using `engine.block.getWidthMode()` and `engine.block.getHeightMode()`. Verify absolute (design units) vs percentage (0.0-1.0) values. For percentage mode, review parent block dimensions.
### Image Appears Stretched
Calculate and set both dimensions proportionally. Use `maintainCrop: true` when resizing cropped images. Check `scene/aspectRatioLock` for scenes.
## API Reference
| Method | Description |
| ----------------------------------- | --------------------------------------------------------- |
| `CreativeEngine.init()` | Initializes the headless engine for programmatic creation |
| `engine.scene.create()` | Creates a new scene programmatically |
| `engine.block.create()` | Creates a new block of the specified type |
| `engine.block.setWidth()` | Sets the width of a block |
| `engine.block.setHeight()` | Sets the height of a block |
| `engine.block.appendChild()` | Adds a block as a child of another block |
| `engine.block.addImage()` | Create and size image in one operation |
| `engine.block.setSize()` | Set width and height with optional mode |
| `engine.block.getWidth()` | Get current width value |
| `engine.block.getHeight()` | Get current height value |
| `engine.block.setWidthMode()` | Set width interpretation mode |
| `engine.block.setHeightMode()` | Set height interpretation mode |
| `engine.block.getWidthMode()` | Get width interpretation mode |
| `engine.block.getHeightMode()` | Get height interpretation mode |
| `engine.block.getFrameWidth()` | Get calculated frame width |
| `engine.block.getFrameHeight()` | Get calculated frame height |
| `engine.block.export()` | Export block as image |
| `engine.dispose()` | Dispose engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Rotate Images"
description: "Rotate images to adjust orientation, correct crooked photos, or create creative effects using CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/transform/rotate-5f39c9/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/) > [Rotate](https://img.ly/docs/cesdk/node-native/edit-image/transform/rotate-5f39c9/)
---
Rotate images programmatically in headless server-side processing for orientation correction, batch operations, and automated workflows.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-image-transform-rotate-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-image-transform-rotate-server-js)
Rotation uses radians where `Math.PI / 2` equals 90°, `Math.PI` equals 180°, and negative values rotate clockwise. Server-side rotation is useful for normalizing uploads, generating asset variations, and enforcing template rules.
```typescript file=@cesdk_web_examples/guides-edit-image-transform-rotate-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
async function rotateImagesExample() {
let engine: CreativeEngine | null = null;
try {
// Initialize headless engine for programmatic creation
engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
// Create a new scene programmatically
const scene = engine.scene.create();
// Create and set up the page
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
// Demo 1: Original image (no rotation)
const originalImage = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, originalImage);
engine.block.setPositionX(originalImage, 50);
engine.block.setPositionY(originalImage, 50);
const text1 = engine.block.create('text');
engine.block.setString(text1, 'text/text', 'Original');
engine.block.setFloat(text1, 'text/fontSize', 24);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text1, 150);
engine.block.setPositionX(text1, 50);
engine.block.setPositionY(text1, 210);
engine.block.appendChild(page, text1);
// Demo 2: Rotate 45 degrees
const rotated45Image = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, rotated45Image);
engine.block.setPositionX(rotated45Image, 225);
engine.block.setPositionY(rotated45Image, 50);
// Rotate the block by 45 degrees (π/4 radians)
engine.block.setRotation(rotated45Image, Math.PI / 4);
const text2 = engine.block.create('text');
engine.block.setString(text2, 'text/text', '45°');
engine.block.setFloat(text2, 'text/fontSize', 24);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text2, 150);
engine.block.setPositionX(text2, 225);
engine.block.setPositionY(text2, 210);
engine.block.appendChild(page, text2);
// Demo 3: Rotate 90 degrees
const rotated90Image = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, rotated90Image);
engine.block.setPositionX(rotated90Image, 400);
engine.block.setPositionY(rotated90Image, 50);
// Rotate the block by 90 degrees (π/2 radians)
engine.block.setRotation(rotated90Image, Math.PI / 2);
const text3 = engine.block.create('text');
engine.block.setString(text3, 'text/text', '90°');
engine.block.setFloat(text3, 'text/fontSize', 24);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text3, 150);
engine.block.setPositionX(text3, 400);
engine.block.setPositionY(text3, 210);
engine.block.appendChild(page, text3);
// Demo 4: Rotate 180 degrees
const rotated180Image = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 150, height: 150 }
}
);
engine.block.appendChild(page, rotated180Image);
engine.block.setPositionX(rotated180Image, 575);
engine.block.setPositionY(rotated180Image, 50);
// Rotate the block by 180 degrees (π radians)
engine.block.setRotation(rotated180Image, Math.PI);
const text4 = engine.block.create('text');
engine.block.setString(text4, 'text/text', '180°');
engine.block.setFloat(text4, 'text/fontSize', 24);
engine.block.setEnum(text4, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text4, 150);
engine.block.setPositionX(text4, 575);
engine.block.setPositionY(text4, 210);
engine.block.appendChild(page, text4);
// Demo 5: Grouped rotation
const groupedImage1 = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_5.jpg',
{
size: { width: 100, height: 100 }
}
);
engine.block.appendChild(page, groupedImage1);
engine.block.setPositionX(groupedImage1, 150);
engine.block.setPositionY(groupedImage1, 300);
const groupedImage2 = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_6.jpg',
{
size: { width: 100, height: 100 }
}
);
engine.block.appendChild(page, groupedImage2);
engine.block.setPositionX(groupedImage2, 260);
engine.block.setPositionY(groupedImage2, 300);
// Group blocks and rotate them together
const groupId = engine.block.group([groupedImage1, groupedImage2]);
engine.block.setRotation(groupId, Math.PI / 8);
const text5 = engine.block.create('text');
engine.block.setString(text5, 'text/text', 'Grouped');
engine.block.setFloat(text5, 'text/fontSize', 24);
engine.block.setEnum(text5, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text5, 200);
engine.block.setPositionX(text5, 150);
engine.block.setPositionY(text5, 440);
engine.block.appendChild(page, text5);
// Get current rotation value
const currentRotation = engine.block.getRotation(rotated45Image);
console.log('Current rotation (radians):', currentRotation);
console.log(
'Current rotation (degrees):',
(currentRotation * 180) / Math.PI
);
// Reset rotation to original orientation
engine.block.setRotation(originalImage, 0);
// Helpers for degree/radian conversion
const toRadians = (degrees: number) => (degrees * Math.PI) / 180;
const toDegrees = (radians: number) => (radians * 180) / Math.PI;
// Example: rotate by 30 degrees using helper
const targetRadians = toRadians(30);
console.log('30 degrees in radians:', targetRadians);
console.log('Converted back to degrees:', toDegrees(targetRadians));
// Export the result as PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('rotate-images-output.png', buffer);
console.log('Successfully exported rotate-images-output.png');
} catch (error) {
console.error('Error:', error);
throw error;
} finally {
// Always dispose the engine to free resources
if (engine) {
engine.dispose();
}
}
}
// Run the example
rotateImagesExample();
```
This guide covers rotating images by specific angles, reading rotation values, converting between degrees and radians, rotating grouped elements together, and resetting rotation to the original orientation.
## Initialize Headless Engine
Create a headless engine instance for programmatic manipulation:
```typescript highlight=highlight-setup
// Initialize headless engine for programmatic creation
engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
```
## Create Scene
Create a scene and page to hold the images:
```typescript highlight=highlight-create-scene
// Create a new scene programmatically
const scene = engine.scene.create();
// Create and set up the page
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
```
## Rotate an Image
Rotate blocks using `engine.block.setRotation()` with angle values in radians. Use `Math.PI` for 180° or divide for smaller increments:
```typescript highlight=highlight-rotate-45
// Rotate the block by 45 degrees (π/4 radians)
engine.block.setRotation(rotated45Image, Math.PI / 4);
```
## Rotate by 90 Degrees
Rotate a block by 90 degrees using `Math.PI / 2`:
```typescript highlight=highlight-rotate-90
// Rotate the block by 90 degrees (π/2 radians)
engine.block.setRotation(rotated90Image, Math.PI / 2);
```
## Rotate by 180 Degrees
Flip a block upside down by rotating 180 degrees using `Math.PI`:
```typescript highlight=highlight-rotate-180
// Rotate the block by 180 degrees (π radians)
engine.block.setRotation(rotated180Image, Math.PI);
```
## Get Current Rotation
Read the current rotation value using `engine.block.getRotation()`. The returned value is in radians:
```typescript highlight=highlight-get-rotation
// Get current rotation value
const currentRotation = engine.block.getRotation(rotated45Image);
console.log('Current rotation (radians):', currentRotation);
console.log(
'Current rotation (degrees):',
(currentRotation * 180) / Math.PI
);
```
## Reset Rotation
Reset a block to its original orientation by setting rotation to 0:
```typescript highlight=highlight-reset-rotation
// Reset rotation to original orientation
engine.block.setRotation(originalImage, 0);
```
## Convert Between Degrees and Radians
Create helper functions to convert between degrees and radians for more intuitive angle values:
```typescript highlight=highlight-convert-radians
// Helpers for degree/radian conversion
const toRadians = (degrees: number) => (degrees * Math.PI) / 180;
const toDegrees = (radians: number) => (radians * 180) / Math.PI;
// Example: rotate by 30 degrees using helper
const targetRadians = toRadians(30);
console.log('30 degrees in radians:', targetRadians);
console.log('Converted back to degrees:', toDegrees(targetRadians));
```
## Rotate Groups Together
Group multiple blocks and rotate them as a unit to maintain their relative positions:
```typescript highlight=highlight-rotate-group
// Group blocks and rotate them together
const groupId = engine.block.group([groupedImage1, groupedImage2]);
engine.block.setRotation(groupId, Math.PI / 8);
```
## Export Result
Export the scene as a PNG file:
```typescript highlight=highlight-export
// Export the result as PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('rotate-images-output.png', buffer);
console.log('Successfully exported rotate-images-output.png');
```
## Cleanup
Dispose the engine to free resources:
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
if (engine) {
engine.dispose();
}
```
## Troubleshooting
### Rotation Has No Effect
Ensure the block exists and is appended to a page before calling `setRotation()`. Verify the block ID is valid.
### Image Fails to Load
Verify the URI is reachable from your server or switch to a file URI inside the asset bundle.
### Engine Keeps Running
Call `engine.dispose()` in a finally block to ensure resources are freed even if errors occur.
## API Reference
| Method | Description |
| ---------------------------- | ------------------------------------------ |
| `CreativeEngine.init()` | Initialize the headless engine |
| `engine.scene.create()` | Create a new scene |
| `engine.block.create()` | Create a block of specified type |
| `engine.block.addImage()` | Add an image block |
| `engine.block.setRotation()` | Set rotation angle in radians |
| `engine.block.getRotation()` | Get current rotation angle in radians |
| `engine.block.group()` | Group blocks for collective transforms |
| `engine.block.export()` | Export block as image |
| `engine.dispose()` | Dispose engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Scale Images"
description: "Scale images programmatically in a Node.js backend using CE.SDK server mode."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-image/transform/scale-ebe367/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-image/transform-9d189b/) > [Scale](https://img.ly/docs/cesdk/node-native/edit-image/transform/scale-ebe367/)
---
Use the CE.SDK headless Server Mode to resize and scale design blocks without rendering a client editor. This guide shows how to scale image elements with Node.js so you can prepare assets, generate variants, or enforce layout rules in automated jobs.
## Requirements
- **Node.js 18** or newer
- CE.SDK server package: `npm install @cesdk/node`
## What You’ll Learn
- Scale an image block uniformly or along a single axis.
- Scale blocks together while keeping their relative layout.
- Lock scaling and transformations in templates.
## When to Use
Server-side scaling helps when you need to:
- **Emphasize or de-emphasize** elements.
- **Fit** images to available space without cropping.
- Enable **dynamic layouts** or automated resizing.
- Create **zoom** effects programmatically.
## How Scaling Works
Scaling uses the `scale(block, scale:number, anchorX, anchorY)` function, with the following **parameters**:
| Parameter | Description | Values |
| --- | --- | --- |
| `block` | Handle (ID) of the block to scale. | `string`|
| `scale` | Scale factor to apply. | **1.0** keeps the original size.
**>1.0** enlarges the block.**\< 1.0** shrinks it. |
| `anchorX`
`anchorY`| Relative width position
Relative height position | **Top** = 0
**Center** = 0.5 **Bottom** = 1
**Defaults** =`0` |
A value of `2.0`, for example, makes the block twice as large.
Test a sample file
1. Run `npm install @cesdk/node` at the root of your project.
2. Add credentials to `.env`:
```bash
LICENSE_KEY=""
CESDK_BASE_URL="https://cdn.img.ly/packages/imgly/cesdk-node/1.60.0/assets"
```
````
3. Create `scale.js` and paste the following script:
```js title="scale.js"
import 'dotenv/config';
import fs from 'fs/promises';
import path from 'path';
import CreativeEngine from '@cesdk/node';
async function run() {
const engine = await CreativeEngine.init({
license: process.env.LICENSE_KEY,
baseURL: process.env.CESDK_BASE_URL
});
try {
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
const imageUrl = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const graphic = engine.block.create('graphic');
engine.block.setShape(graphic, engine.block.createShape('rect'));
const imageFill = engine.block.createFill('image');
engine.block.setFill(graphic, imageFill);
await engine.block.addImageFileURIToSourceSet(
imageFill,
'fill/image/sourceSet',
imageUrl
);
engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUrl);
engine.block.appendChild(page, graphic);
const [source] = engine.block.getSourceSet(
imageFill,
'fill/image/sourceSet'
);
if (!source) {
throw new Error(`Image metadata not available for ${imageUrl}`);
}
engine.block.setSize(graphic, source.width, source.height, {
sizeMode: 'Absolute'
});
engine.block.resetCrop(graphic);
engine.block.scale(graphic, 2, 0.5, 0.5);
const blob = await engine.block.export(graphic, { mimeType: 'image/png' });
const outputPath = path.resolve('./scaled-image.png');
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, Buffer.from(await blob.arrayBuffer()));
console.log(`Exported scaled image to ${outputPath}`);
} finally {
engine.dispose();
}
}
run().catch((error) => {
console.error('Scaling demo failed:', error);
});
````
4. Run `node scale.js`.
5. Inspect `sample-scaled.png` to confirm that each block doubled its size.
## Scale an Image Uniformly
Scaling uniformly keeps the image’s **aspect ratio intact** while making it larger or smaller. This allows for applying zoom levels without stretching or squashing the artwork. For example:
```js
engine.block.scale(blockId, 1.5);
```
The preceding code:
- Scales the image to 150% of its original size.
- Doesn’t change the origin anchor point.
As a result, the image expands down and to the right.
### Anchor Point
By default, the anchor point for the image when scaling is **the top left**. The scale function has two optional parameters:
- `x` to move the anchor point along the width.
- `y` to move the anchor point along the height.
They can have values between 0.0 and 1.0. For example:
```js
engine.block.scale(blockId, 1.5, 0.5, 0.5)
```
This function:
1. Scales the image to **150%** of its original size.
2. The origin anchor point is 0.5, 0.5, so the image expands from the **center**.
## Scale Non-Uniformly
To stretch or compress only one axis, thus distorting an image, use the **width** or **height** function. For example, to scale an image horizontally, you can use:
```js
engine.block.setWidthMode(graphic, 'Absolute');
const width = engine.block.getWidth(graphic) * 1.5;
engine.block.setWidth(graphic, width, true );
```
The preceding code:
1. Sets the width position as `'Absolute'` so width changes use a fixed pixel value instead of a relative layout mode.
2. Reads the current width and multiplies it by 1.5 to compute a new width that’s 150% of the original.
3. Writes the new width back to the block while keeping the height in proportion when available.
As a result, it stretches the element horizontally by the calculated factor:
Use this to:
- Create panoramic crops.
- Compensate for aspect ratios during automation.
Below are two examples of scaling the original image in the x direction only:
1. Double the width:
```js
const width = engine.block.getWidth(graphic) * 2;
```
2. Scale horizontally by 2.5:
```js
const width = engine.block.getWidth(graphic) * 2.5;
```
## Scale elements together
Group blocks to scale them proportionally:
```js
const groupId = engine.block.group([imageId, textId]);
engine.block.scale(groupId, 1.75, 0.75, 0.5)
```
The preceding code scales the entire group to 75%.
> **Note:** Call `engine.block.findAllProperties(groupId)` if you are unsure whether a block exposes width/height.
## Lock Scaling in Templates
Lock scaling when you must preserve a template’s layout. This is useful for:
- Brand assets
- Campaign creatives
- Collaboration workflows
- Fixed dimensions swapping editors
When working with templates, **prevent users from scaling** blocks by turning off the `layer/resize` scope:
```js
// Prevent scaling/resizing of a block
engine.block.setScopeEnabled(imageId, 'layer/resize', false);
```
### Lock All Transformations
Available block transformations are the following:
- Move
- Resize
- Rotate
To **lock** all transformations, use the following function:
```js
// Lock all transforms
engine.block.setTransformLocked(imageId, true);
```
To check if scaling is currently allowed, log the output if `isScopeEnabled`:
```js
// Check if resize is enabled
const canResize = engine.block.isScopeEnabled(imageId, 'layer/resize');
console.log('layer/resize scope enabled?', canResize);
```
## Troubleshooting
| Issue | Resolution |
| --- | --- |
| `Property not found` when scaling | Read width/height first (`findAllProperties`) and update those values instead of transform keys. |
| Elements distort unexpectedly | Keep the height in sync with the width unless you intentionally stretch. |
| Export shows the original size | Confirm that the code appends the block to a page before scaling, and reuse the updated page when exporting. |
## API references in this guide
| API | Usage |
| --- | --- |
| `block.scale` | Performs uniform or anchored scaling on blocks and groups. |
| `block.setWidthMode` | Enables absolute sizing before changing a single axis. |
| `block.getWidth` | Reads the current width before non-uniform scaling. |
| `block.setWidth` | Writes the adjusted width after single-axis scaling. |
| `block.setCropScaleX` | Pairs directional scaling with crop adjustments. |
| `block.group` | Groups blocks so they scale together. |
| `block.findAllProperties` | Discovers available properties before scaling unfamiliar blocks. |
| `block.getFloat` | Retrieves numeric properties when clamping scale values. |
| `block.setFloat` | Writes constrained width/height values after clamping. |
| `block.setScopeEnabled` | Toggles the `layer/resize` scope to lock scaling in templates. |
| `block.setTransformLocked` | Locks all transform scopes when templates must stay fixed. |
| `block.isScopeEnabled` | Checks whether scaling is currently permitted on a block. |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add Captions"
description: "Documentation for adding captions to videos"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/add-captions-f67565/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Add Captions](https://img.ly/docs/cesdk/node-native/edit-video/add-captions-f67565/)
---
Add synchronized captions to video projects using CE.SDK's caption system in headless mode, with support for importing subtitle files, styling programmatically, and saving scenes with captions.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-add-captions-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-add-captions-server-js)
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.
```typescript file=@cesdk_web_examples/guides-create-video-add-captions-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { mkdir, writeFile } from 'fs/promises';
import { join } from 'path';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Add Captions
*
* Demonstrates adding synchronized captions to video projects:
* - Importing captions from SRT/VTT files
* - Creating captions programmatically
* - Styling captions with typography and background
* - Saving scenes with captions for later rendering
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
// Set page duration to accommodate video content
engine.block.setDuration(page, 10);
// Add a video clip as the base content
const videoUrl =
'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4';
const track = engine.block.create('track');
engine.block.appendChild(page, track);
const videoClip = await engine.block.addVideo(videoUrl, 1920, 1080, {
timeline: { duration: 10, 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`);
// 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`);
// Read caption properties (text, timing)
if (captionBlocks.length > 0) {
const firstCaption = captionBlocks[0];
const text = engine.block.getString(firstCaption, 'caption/text');
const offset = engine.block.getTimeOffset(firstCaption);
const duration = engine.block.getDuration(firstCaption);
console.log(`First caption: "${text}"`);
console.log(` Time: ${offset}s - ${offset + duration}s`);
}
// Style all captions with consistent formatting
for (const captionId of captionBlocks) {
// Set font size
engine.block.setFloat(captionId, 'caption/fontSize', 48);
// Center alignment
engine.block.setEnum(captionId, 'caption/horizontalAlignment', 'Center');
engine.block.setEnum(captionId, 'caption/verticalAlignment', 'Bottom');
// Enable background for readability
engine.block.setBool(captionId, 'backgroundColor/enabled', true);
engine.block.setColor(captionId, 'backgroundColor/color', {
r: 0,
g: 0,
b: 0,
a: 0.7
});
}
console.log('Applied styling to all 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
console.log('Positioned captions at bottom of frame');
}
// Create a caption programmatically (in addition to imported ones)
const manualCaption = engine.block.create('//ly.img.ubq/caption');
engine.block.appendChild(captionTrack, manualCaption);
// Set caption text
engine.block.setString(manualCaption, 'caption/text', 'Manual caption added');
// Set timing - appears at 8 seconds for 2 seconds
engine.block.setTimeOffset(manualCaption, 8);
engine.block.setDuration(manualCaption, 2);
// Apply same styling
engine.block.setFloat(manualCaption, 'caption/fontSize', 48);
engine.block.setEnum(manualCaption, 'caption/horizontalAlignment', 'Center');
engine.block.setEnum(manualCaption, 'caption/verticalAlignment', 'Bottom');
engine.block.setBool(manualCaption, 'backgroundColor/enabled', true);
engine.block.setColor(manualCaption, 'backgroundColor/color', {
r: 0,
g: 0,
b: 0,
a: 0.7
});
console.log('Created manual caption at 8s');
// Add entry animation to captions
for (const captionId of captionBlocks) {
const fadeIn = engine.block.createAnimation('fade');
engine.block.setDuration(fadeIn, 0.2);
engine.block.setInAnimation(captionId, fadeIn);
}
console.log('Added fade-in animations to captions');
// Save scene to file for later rendering
// The scene preserves all caption data, styling, and animations
const sceneString = await engine.scene.saveToString();
// Save to output directory
const outputDir = join(process.cwd(), 'output');
await mkdir(outputDir, { recursive: true });
const outputPath = join(outputDir, 'video-with-captions.scene');
await writeFile(outputPath, sceneString);
console.log(`Scene saved: ${outputPath}`);
console.log('');
console.log('Add Captions guide complete.');
console.log('Scene created with:');
console.log(` - ${captionBlocks.length} imported captions`);
console.log(' - 1 manually created caption');
console.log(' - Consistent styling across all captions');
console.log(' - Fade-in animations');
console.log('');
console.log(
'Use CE.SDK Renderer to export this scene as a video with burned-in captions.'
);
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
```
This guide covers how to import captions from SRT/VTT files, style them using custom properties, create captions programmatically, and save scenes with captions in a headless Node.js environment.
## Understanding Caption Structure
### 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
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 playback. 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.
## Initialize CE.SDK
For headless video processing with captions, we initialize CE.SDK's Node.js engine. This provides full API access without browser dependencies, ideal for server-side automation and batch processing.
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
The headless engine gives you complete control over caption operations, perfect for automated workflows, background processing, and server-side video preparation.
## Creating the Scene
Create a scene with page configuration. The `page` option creates a page with the specified dimensions.
```typescript highlight-create-scene
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
// Set page duration to accommodate video content
engine.block.setDuration(page, 10);
```
Set the page duration to accommodate your video content. The dimensions define the output video resolution.
## Importing Captions from Subtitle Files
### 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.
```typescript highlight-import-captions
// 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`);
```
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
After importing captions, create a caption track to organize them in the composition.
```typescript highlight-create-caption-track
// 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`);
```
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`.
## Reading Caption Properties
Retrieve caption properties for processing, validation, or logging.
```typescript highlight-read-caption-properties
// Read caption properties (text, timing)
if (captionBlocks.length > 0) {
const firstCaption = captionBlocks[0];
const text = engine.block.getString(firstCaption, 'caption/text');
const offset = engine.block.getTimeOffset(firstCaption);
const duration = engine.block.getDuration(firstCaption);
console.log(`First caption: "${text}"`);
console.log(` Time: ${offset}s - ${offset + duration}s`);
}
```
Use `getString` for text, `getTimeOffset` for start time, and `getDuration` for display length. These values are useful for validation or synchronization workflows.
## Creating Captions Programmatically
### Caption Track Setup
For full control over captions, create them programmatically. First, create a caption track and append it to the page.
```typescript
const captionTrack = engine.block.create('//ly.img.ubq/captionTrack');
engine.block.appendChild(page, captionTrack);
```
### Creating Caption Blocks
Create individual captions with text and timing.
```typescript highlight-create-caption-manually
// Create a caption programmatically (in addition to imported ones)
const manualCaption = engine.block.create('//ly.img.ubq/caption');
engine.block.appendChild(captionTrack, manualCaption);
// Set caption text
engine.block.setString(manualCaption, 'caption/text', 'Manual caption added');
// Set timing - appears at 8 seconds for 2 seconds
engine.block.setTimeOffset(manualCaption, 8);
engine.block.setDuration(manualCaption, 2);
// Apply same styling
engine.block.setFloat(manualCaption, 'caption/fontSize', 48);
engine.block.setEnum(manualCaption, 'caption/horizontalAlignment', 'Center');
engine.block.setEnum(manualCaption, 'caption/verticalAlignment', 'Bottom');
engine.block.setBool(manualCaption, 'backgroundColor/enabled', true);
engine.block.setColor(manualCaption, 'backgroundColor/color', {
r: 0,
g: 0,
b: 0,
a: 0.7
});
console.log('Created manual caption at 8s');
```
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
### Typography
Control caption appearance with typography properties.
```typescript highlight-style-captions
// Style all captions with consistent formatting
for (const captionId of captionBlocks) {
// Set font size
engine.block.setFloat(captionId, 'caption/fontSize', 48);
// Center alignment
engine.block.setEnum(captionId, 'caption/horizontalAlignment', 'Center');
engine.block.setEnum(captionId, 'caption/verticalAlignment', 'Bottom');
// Enable background for readability
engine.block.setBool(captionId, 'backgroundColor/enabled', true);
engine.block.setColor(captionId, 'backgroundColor/color', {
r: 0,
g: 0,
b: 0,
a: 0.7
});
}
console.log('Applied styling to all captions');
```
Use `setFloat` for numeric values like `caption/fontSize`, `caption/letterSpacing`, and `caption/lineHeight`. Set alignment with `setEnum` for `caption/horizontalAlignment` (Left, Center, Right) and `caption/verticalAlignment` (Top, Center, Bottom).
### 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
CE.SDK can automatically adjust font size to fit caption text within bounds. Enable automatic sizing and set minimum and maximum size limits.
```typescript
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.
## Caption Animations
### Adding Entry Animations
Make captions more engaging by adding entry animations.
```typescript highlight-add-animation
// Add entry animation to captions
for (const captionId of captionBlocks) {
const fadeIn = engine.block.createAnimation('fade');
engine.block.setDuration(fadeIn, 0.2);
engine.block.setInAnimation(captionId, fadeIn);
}
console.log('Added fade-in animations to captions');
```
Create an animation using `createAnimation` with types like 'fade', 'slide', or 'scale'. Set the animation duration and apply it with `setInAnimation`.
### 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.
## Saving the Scene
After adding captions, save the scene to a `.scene` file for later use or rendering with the CE.SDK Renderer service.
```typescript highlight-save-scene
// Save scene to file for later rendering
// The scene preserves all caption data, styling, and animations
const sceneString = await engine.scene.saveToString();
// Save to output directory
const outputDir = join(process.cwd(), 'output');
await mkdir(outputDir, { recursive: true });
const outputPath = join(outputDir, 'video-with-captions.scene');
await writeFile(outputPath, sceneString);
console.log(`Scene saved: ${outputPath}`);
```
The scene file preserves all caption data including text, timing, styling, and animations. You can load this scene later for further editing or send it to the CE.SDK Renderer for video export.
## Resource Cleanup
Always dispose of the engine when processing is complete to free resources.
```typescript highlight-cleanup
// Always dispose of the engine to free resources
engine.dispose();
```
Use a try/finally pattern to ensure cleanup happens even if an error occurs during processing.
## 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
If captions don't appear in the exported video, 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 caption timing falls within the page duration. Captions outside the page's time range won't appear in the export.
### Import Errors
If `createCaptionsFromURI` fails, verify the URL is accessible from your server and returns valid SRT or VTT content. Common issues include network restrictions and malformed subtitle files. Test the URL with curl to confirm accessibility.
## API Reference
| Method | Purpose |
| --- | --- |
| `engine.scene.create()` | Create a scene |
| `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.scene.saveToString()` | Save scene to string |
| `engine.dispose()` | Clean up engine resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add Watermark"
description: "Add text and image watermarks to videos using CE.SDK for copyright protection, branding, and content attribution with timeline management and visibility controls."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/add-watermark-762ce6/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Add Watermark](https://img.ly/docs/cesdk/node-native/edit-video/add-watermark-762ce6/)
---
Add text and image watermarks to video content for copyright protection, branding, and content attribution using CE.SDK's time-aware block system.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-add-watermark-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-add-watermark-server-js)
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 need their time-based properties configured to remain visible throughout video playback. The key difference from static image watermarking is setting the watermark's `duration` to match the video duration.
```typescript file=@cesdk_web_examples/guides-create-video-add-watermark-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Add Watermark to Video
*
* Demonstrates adding text and image watermarks to videos:
* - Creating text watermarks with styling and drop shadows
* - Creating image watermarks from logos
* - Positioning watermarks on the canvas
* - Setting watermark duration to match video timeline
* - Configuring opacity and blend modes
* - Saving the watermarked scene for rendering
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a video scene and load a sample video
const videoUrl = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
await engine.scene.createFromVideo(videoUrl);
// Get the page and its properties
const page = engine.scene.getCurrentPage()!;
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Get the video duration from the page (set automatically from video)
const videoDuration = engine.block.getDuration(page);
console.log('Video duration:', videoDuration, 'seconds');
console.log('Page dimensions:', pageWidth, 'x', pageHeight);
// ===== TEXT WATERMARK =====
// Create a text block for the watermark
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
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
engine.block.setFloat(textWatermark, 'text/fontSize', 14);
engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // White text
// Set text alignment
engine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left');
// Set 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);
console.log('Text watermark created and added to timeline');
// ===== 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);
console.log('Logo watermark created and added to timeline');
// Save the watermarked scene
// Video export is not supported in Node.js, so we save the scene as JSON
// The saved scene can be rendered using CE.SDK Renderer
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const sceneString = await engine.scene.saveToString();
writeFileSync(`${outputDir}/watermarked-video.scene`, sceneString);
console.log('');
console.log(
'Watermarked video scene saved to output/watermarked-video.scene'
);
console.log('');
console.log('Scene contains:');
console.log(' - Text watermark: "All rights reserved 2025" (bottom-left)');
console.log(' - Logo watermark: IMG.LY logo (top-right)');
console.log(' - Both watermarks span the full video duration');
console.log('');
console.log('To render the video, use CE.SDK Renderer:');
console.log(' https://img.ly/docs/cesdk/renderer/');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
```
This guide covers how to create text and image watermarks programmatically, position them on the canvas, style them for visibility, and configure their duration to span the entire video.
## Initializing the Engine
We initialize the CE.SDK engine in headless mode for server-side processing. The engine runs without a UI, making it suitable for automated watermarking workflows.
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
The engine initializes with default settings. In production, you would add your license key to remove trial watermarks from exports.
## Creating the Scene
We create a scene from a video URL. This automatically sets up the page dimensions and time-based properties based on the video.
```typescript highlight-create-video-scene
// Create a video scene and load a sample video
const videoUrl = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
await 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 in the composition with its duration already set.
## Getting Page and Video Information
Before positioning watermarks, we retrieve the page dimensions and video duration for reference.
```typescript highlight-get-page-info
// Get the page and its properties
const page = engine.scene.getCurrentPage()!;
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Get the video duration from the page (set automatically from video)
const videoDuration = engine.block.getDuration(page);
console.log('Video duration:', videoDuration, 'seconds');
console.log('Page dimensions:', pageWidth, 'x', pageHeight);
```
We use `engine.block.getWidth()` and `engine.block.getHeight()` to get the page dimensions for positioning calculations. `engine.block.getDuration()` returns the video duration in seconds, which we'll use to ensure watermarks span the entire video.
## Creating a Text Watermark
Text watermarks display copyright notices, branding text, or URLs. We create a text block and position it on the canvas.
```typescript highlight-create-text-watermark
// Create a text block for the watermark
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
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);
```
We create a text block with `engine.block.create('text')` and configure it with auto-sizing using `engine.block.setWidthMode()` and `engine.block.setHeightMode()`. This lets the text block grow to fit its content. We set the text content using `engine.block.replaceText()` and position the watermark in the bottom-left corner with padding from the edges.
## Styling Text Watermarks
Style the text for readability across different video backgrounds.
```typescript highlight-style-text-watermark
// Style the text watermark
engine.block.setFloat(textWatermark, 'text/fontSize', 14);
engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // White text
// Set text alignment
engine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left');
// Set opacity for subtle appearance
engine.block.setOpacity(textWatermark, 0.7);
```
We set the font size using `engine.block.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
Drop shadows ensure text remains readable over both light and dark video backgrounds.
```typescript highlight-text-drop-shadow
// 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);
```
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
The watermark must persist throughout video playback. We set its duration to match the video duration.
```typescript highlight-text-timeline
// 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);
```
`engine.block.setDuration()` controls how long the block appears in the composition. `engine.block.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
Image watermarks display logos or graphics. We create a graphic block with an image fill.
```typescript highlight-create-image-watermark
// 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');
```
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
Position the logo in a corner that doesn't obstruct video content.
```typescript highlight-position-image-watermark
// 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);
```
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
Control how the watermark integrates with the video.
```typescript highlight-image-opacity-blend
// 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');
```
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
Like text watermarks, image watermarks need duration configuration.
```typescript highlight-image-timeline
// 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);
```
We set the same duration and time offset as the text watermark so both appear throughout the video. The `engine.block.appendChild()` call adds the logo to the page above existing content.
## Saving the Watermarked Scene
After adding watermarks, we save the scene for later rendering. Since video export is not supported in Node.js, we serialize the scene to a file.
```typescript highlight-save-scene
// Save the watermarked scene
// Video export is not supported in Node.js, so we save the scene as JSON
// The saved scene can be rendered using CE.SDK Renderer
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const sceneString = await engine.scene.saveToString();
writeFileSync(`${outputDir}/watermarked-video.scene`, sceneString);
console.log('');
console.log(
'Watermarked video scene saved to output/watermarked-video.scene'
);
console.log('');
console.log('Scene contains:');
console.log(' - Text watermark: "All rights reserved 2025" (bottom-left)');
console.log(' - Logo watermark: IMG.LY logo (top-right)');
console.log(' - Both watermarks span the full video duration');
console.log('');
console.log('To render the video, use CE.SDK Renderer:');
console.log(' https://img.ly/docs/cesdk/renderer/');
```
The `engine.scene.saveToString()` method serializes the entire scene including the video, watermarks, and all configuration. This scene file can be loaded later in a browser environment for export, or processed using the CE.SDK Renderer for server-side video generation.
## Cleanup
Always dispose of the engine when finished to free system resources.
```typescript highlight-cleanup
// Always dispose of the engine to free resources
engine.dispose();
```
The `engine.dispose()` method releases all resources held by the engine. In server environments, proper cleanup is essential to prevent memory leaks during batch processing.
## 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
### 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
### Time 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
### Batch Processing
- Reuse watermark configuration logic across multiple videos
- Process videos sequentially to manage memory usage
- Always dispose of the engine between sessions or implement proper resource management
## API Reference
| Method | Description |
|--------|-------------|
| `engine.scene.createFromVideo(url)` | Create a video scene from a URL |
| `engine.scene.getCurrentPage()` | Get the current page block |
| `engine.scene.saveToString()` | Serialize the scene to a string |
| `engine.block.create('text')` | Create a text block for text watermarks |
| `engine.block.create('graphic')` | Create a graphic block for image watermarks |
| `engine.block.setWidthMode(id, mode)` | Set width sizing mode ('Auto', 'Absolute', 'Percent') |
| `engine.block.setHeightMode(id, mode)` | Set height sizing mode ('Auto', 'Absolute', 'Percent') |
| `engine.block.replaceText(id, text)` | Set text content for text blocks |
| `engine.block.setFloat(id, property, value)` | Set numeric properties like font size |
| `engine.block.createShape('rect')` | Create a rectangular shape for graphics |
| `engine.block.createFill('image')` | Create an image fill for logo watermarks |
| `engine.block.setString(id, property, value)` | Set string properties like image URI |
| `engine.block.setContentFillMode(id, mode)` | Set content fill mode ('Crop', 'Cover', 'Contain') |
| `engine.block.setDuration(id, duration)` | Set watermark duration |
| `engine.block.setTimeOffset(id, offset)` | Set watermark start time |
| `engine.block.setOpacity(id, opacity)` | Set watermark transparency (0.0-1.0) |
| `engine.block.setDropShadowEnabled(id, enabled)` | Enable/disable drop shadow |
| `engine.block.setDropShadowColor(id, color)` | Set shadow color |
| `engine.block.setDropShadowOffsetX/Y(id, offset)` | Set shadow position |
| `engine.block.setDropShadowBlurRadiusX/Y(id, radius)` | Set shadow blur |
| `engine.block.setBlendMode(id, mode)` | Set blend mode ('Normal', 'Multiply', etc.) |
| `engine.block.appendChild(parent, child)` | Add watermark to page |
| `engine.dispose()` | Release engine resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Join and Arrange Video Clips"
description: "Combine multiple video clips into sequences and organize them on the timeline using tracks and time offsets in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/join-and-arrange-3bbc30/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Join and Arrange](https://img.ly/docs/cesdk/node-native/edit-video/join-and-arrange-3bbc30/)
---
Combine multiple video clips into sequences and organize them in the composition using CE.SDK's track system and programmatic APIs.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-join-and-arrange-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-join-and-arrange-server-js)
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.
```typescript file=@cesdk_web_examples/guides-create-video-join-and-arrange-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Join and Arrange Video Clips
*
* 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
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// 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');
console.log('');
console.log('Join and Arrange guide complete.');
console.log('Video scene created with:');
console.log(' - Main track: 3 clips (C, A, B) playing sequentially');
console.log(' - Overlay track: 1 clip for picture-in-picture effect');
console.log(' - Total duration: 15 seconds');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
```
This guide covers how to programmatically create scenes, add and arrange clips in tracks, control clip timing, and create multi-track compositions.
## Prerequisites and Setup
We start by initializing CE.SDK in headless mode for server-side video composition.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
The engine runs without a UI, making it suitable for automated workflows, batch processing, or backend services.
## Creating the Scene
We create a scene and set up a page for the video composition.
```typescript highlight=highlight-create-scene
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// 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
We create video clips as graphic blocks with video fills. Each clip needs a video fill that references the source media.
```typescript highlight=highlight-create-clips
// 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
Tracks organize clips for sequential playback. We create a track and attach it to the page.
```typescript highlight=highlight-create-track
// 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);
```
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
We add clips to the track using `appendChild`. Clips join the sequence in the order they're added.
```typescript highlight=highlight-add-clips-to-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');
```
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
Each clip needs a duration that determines how long it plays.
```typescript highlight=highlight-set-clip-durations
// Set durations for each clip
engine.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 plays for 5 seconds.
## Arranging Clips
### Time Offsets
Time offsets control when each clip starts playing. We set offsets to position clips at specific points in the composition.
```typescript highlight=highlight-time-offsets
// 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');
```
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
Use `insertChild` to move clips to specific positions within a track. This moves an existing child to a new index.
```typescript highlight=highlight-reorder-clips
// 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');
```
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
Use `getChildren` to inspect the current clip order and verify arrangements.
```typescript highlight=highlight-get-track-children
// 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`);
});
```
This loop outputs each clip's position, time offset, and duration—useful for debugging or building custom timeline logic.
## Multi-Track Compositions
Create layered compositions by adding multiple tracks to a page. Track order determines rendering order—clips in later tracks appear on top.
```typescript highlight=highlight-multi-track
// 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');
```
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.
## Cleanup
Always dispose of the engine when done to free resources.
```typescript highlight=highlight-cleanup
// Always dispose of the engine to free resources
engine.dispose();
```
## Troubleshooting
### Clips Not Appearing
If clips don't appear in the composition, verify they're attached to a track that's attached to the page. Use `getParent` and `getChildren` to inspect the hierarchy:
```typescript
const parent = engine.block.getParent(clipId);
const children = engine.block.getChildren(trackId);
```
### 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
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
| Method | Description | Parameters | Returns |
| --- | --- | --- | --- |
| `scene.create()` | Create a scene | none | `Promise` |
| `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 playing | `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` |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Redact Sensitive Content in Videos"
description: "Redact sensitive video content using blur, pixelization, or solid overlays. Essential for privacy protection when obscuring faces, license plates, or personal information."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/redaction-cf6d03/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Redaction](https://img.ly/docs/cesdk/node-native/edit-video/redaction-cf6d03/)
---
Redact sensitive video content using blur, pixelization, or solid overlays for privacy protection.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-redaction-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-redaction-server-js)
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.
```typescript file=@cesdk_web_examples/guides-create-video-redaction-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { mkdir, writeFile } from 'fs/promises';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Video Redaction
*
* 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
*/
// Video URL for demonstrating redaction scenarios
const VIDEO_URL =
'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4';
// Duration for each video segment (in seconds)
const SEGMENT_DURATION = 5.0;
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with a page
const pageWidth = 1920;
const pageHeight = 1080;
const scene = engine.scene.create('DepthStack');
// Create a page
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// Set page to 16:9 landscape (1920x1080 is standard HD video resolution)
engine.block.setWidth(page, pageWidth);
engine.block.setHeight(page, pageHeight);
// Set page duration to accommodate all videos (5 segments x 5 seconds)
engine.block.setDuration(page, 5 * SEGMENT_DURATION);
// Create a track to hold the video clips
const track = engine.block.create('track');
engine.block.appendChild(page, track);
// Create video blocks for each redaction technique demonstration
console.log('Loading video blocks...');
console.log(' Loading video 1/5 (radial blur)...');
const radialVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: 0 }
});
engine.block.appendChild(track, radialVideo);
console.log(' Loading video 2/5 (full-block blur)...');
const fullBlurVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: SEGMENT_DURATION }
});
engine.block.appendChild(track, fullBlurVideo);
console.log(' Loading video 3/5 (pixelization)...');
const pixelVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: 2 * SEGMENT_DURATION }
});
engine.block.appendChild(track, pixelVideo);
console.log(' Loading video 4/5 (solid overlay)...');
const overlayVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: 3 * SEGMENT_DURATION }
});
engine.block.appendChild(track, overlayVideo);
console.log(' Loading video 5/5 (time-based blur)...');
const timedVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: 4 * SEGMENT_DURATION }
});
engine.block.appendChild(track, timedVideo);
console.log('All videos loaded.');
// Resize all track children to fill the page dimensions
engine.block.fillParent(track);
// Full-Block Blur: Apply blur to entire video
// Use this when the entire video content needs obscuring
console.log('Applying redaction effects...');
// 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.setTimeOffset(overlay, 3 * SEGMENT_DURATION);
engine.block.setDuration(overlay, SEGMENT_DURATION);
engine.block.appendChild(page, overlay);
// 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
// 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);
// Save the scene to a file for later use or export
await mkdir('output', { recursive: true });
const sceneData = await engine.scene.saveToString();
await writeFile('output/redacted-video.scene', sceneData);
console.log('Scene saved to output/redacted-video.scene');
console.log('');
console.log('Video redaction guide complete.');
console.log('Created scene with 5 redaction techniques:');
console.log(' 1. Radial blur (0-5s)');
console.log(' 2. Full-block blur (5-10s)');
console.log(' 3. Pixelization (10-15s)');
console.log(' 4. Solid overlay (15-20s)');
console.log(' 5. Time-based blur (20-25s)');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
```
This guide covers how to apply redaction programmatically using blur, pixelization, solid overlays, and time-based controls.
## Prerequisites and Setup
We start by initializing CE.SDK in headless mode for server-side video processing.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
The engine runs without a UI, making it suitable for automated workflows, batch processing, or backend services.
## Creating the Scene
We create a scene and page for the video composition.
```typescript highlight=highlight-create-scene
// Create a scene with a page
const pageWidth = 1920;
const pageHeight = 1080;
const scene = engine.scene.create('DepthStack');
// Create a page
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
// Set page to 16:9 landscape (1920x1080 is standard HD video resolution)
engine.block.setWidth(page, pageWidth);
engine.block.setHeight(page, pageHeight);
// Set page duration to accommodate all videos (5 segments x 5 seconds)
engine.block.setDuration(page, 5 * SEGMENT_DURATION);
```
We create a scene with `scene.create()`, then explicitly create a page and append it to the scene. The page dimensions set the video resolution, and the page duration determines how long the composition plays.
## Creating Video Blocks
We create a track to hold video clips, then add video blocks for each redaction technique.
```typescript highlight=highlight-create-videos
// Create a track to hold the video clips
const track = engine.block.create('track');
engine.block.appendChild(page, track);
// Create video blocks for each redaction technique demonstration
console.log('Loading video blocks...');
console.log(' Loading video 1/5 (radial blur)...');
const radialVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: 0 }
});
engine.block.appendChild(track, radialVideo);
console.log(' Loading video 2/5 (full-block blur)...');
const fullBlurVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: SEGMENT_DURATION }
});
engine.block.appendChild(track, fullBlurVideo);
console.log(' Loading video 3/5 (pixelization)...');
const pixelVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: 2 * SEGMENT_DURATION }
});
engine.block.appendChild(track, pixelVideo);
console.log(' Loading video 4/5 (solid overlay)...');
const overlayVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: 3 * SEGMENT_DURATION }
});
engine.block.appendChild(track, overlayVideo);
console.log(' Loading video 5/5 (time-based blur)...');
const timedVideo = await engine.block.addVideo(VIDEO_URL, pageWidth, pageHeight, {
timeline: { duration: SEGMENT_DURATION, timeOffset: 4 * SEGMENT_DURATION }
});
engine.block.appendChild(track, timedVideo);
console.log('All videos loaded.');
// Resize all track children to fill the page dimensions
engine.block.fillParent(track);
```
Videos are added to a track which organizes them for sequential playback. Each video is positioned at a different time offset to demonstrate each redaction technique. The `fillParent` call ensures all clips fill the page dimensions.
## Understanding Redaction in CE.SDK
### 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
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
## Programmatic Redaction
### 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.
```typescript highlight-full-block-blur
// 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);
```
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 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.
```typescript highlight-pixelization
// 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);
}
```
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
For complete blocking without any visual hint of the underlying content, create an opaque shape overlay.
```typescript highlight-solid-overlay
// 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.setTimeOffset(overlay, 3 * SEGMENT_DURATION);
engine.block.setDuration(overlay, SEGMENT_DURATION);
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
Redactions can appear only during specific portions of the video. We use `setTimeOffset()` and `setDuration()` to control when the redaction is visible.
```typescript highlight-time-based-redaction
// 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
// 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);
```
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
For face-like regions, radial blur creates a circular blur pattern that works well with rounded subjects.
```typescript highlight-radial-blur
// 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);
```
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`).
## Saving the Scene
After applying redactions, save the scene for later use or export.
```typescript highlight-save-scene
// Save the scene to a file for later use or export
await mkdir('output', { recursive: true });
const sceneData = await engine.scene.saveToString();
await writeFile('output/redacted-video.scene', sceneData);
console.log('Scene saved to output/redacted-video.scene');
```
The scene file contains all blocks, effects, and time-based settings. You can load this scene later to continue editing or export the final video.
## Cleanup
Always dispose of the engine when done to free resources.
```typescript highlight-cleanup
// Always dispose of the engine to free resources
engine.dispose();
```
## 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 processing.
## Troubleshooting
### 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
Reduce blur intensity, use pixelization instead of heavy blur, or switch to solid overlays for some redactions.
## 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
| 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 |
| `block.setDuration(id, duration)` | Set block duration |
| `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 |
| `scene.saveToString()` | Save scene to string for storage |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Split Video and Audio"
description: "Learn how to split video and audio clips at specific time points in CE.SDK, creating two independent segments from a single clip."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/split-464167/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Split](https://img.ly/docs/cesdk/node-native/edit-video/split-464167/)
---
Split video and audio clips at specific time points using CE.SDK's programmatic split API to create independent segments in server-side environments.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-split-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-split-server-js)
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.
```typescript file=@cesdk_web_examples/guides-create-video-split-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
config(); // Load .env file
async function splitVideoExample() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a scene with a page and track for video editing
engine.scene.create('DepthStack', {
page: { size: { width: 1920, height: 1080 } }
});
const page = engine.block.findByType('page')[0]!;
const track = engine.block.create('track');
engine.block.appendChild(page, track);
// Use a sample video URL
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
// Create a video block to demonstrate basic splitting
const videoBlock = engine.block.create('graphic');
const videoFill = engine.block.createFill('video');
engine.block.setString(videoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(videoBlock, videoFill);
engine.block.setWidth(videoBlock, 1920);
engine.block.setHeight(videoBlock, 1080);
engine.block.appendChild(track, videoBlock);
// Load video resource to access duration
await engine.block.forceLoadAVResource(videoFill);
// Set block duration for the timeline
engine.block.setDuration(videoBlock, 10.0);
// Split the video block at 5 seconds
// Returns the ID of the newly created block (second segment)
const newBlock = engine.block.split(videoBlock, 5.0);
console.log(`Basic split - Original: ${videoBlock}, New: ${newBlock}`);
// Create another video block to demonstrate split options
const optionsBlock = engine.block.create('graphic');
const optionsFill = engine.block.createFill('video');
engine.block.setString(optionsFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(optionsBlock, optionsFill);
engine.block.setWidth(optionsBlock, 1920);
engine.block.setHeight(optionsBlock, 1080);
engine.block.appendChild(track, optionsBlock);
await engine.block.forceLoadAVResource(optionsFill);
engine.block.setDuration(optionsBlock, 10.0);
// Split with custom options
const optionsNewBlock = engine.block.split(optionsBlock, 4.0, {
attachToParent: true, // Attach to same parent (default: true)
createParentTrackIfNeeded: false, // Don't create track (default: false)
selectNewBlock: false // Don't select new block (default: true)
});
console.log(`Split with options - New block: ${optionsNewBlock}`);
// Examine trim properties after split
const originalFill = engine.block.getFill(videoBlock);
const newBlockFill = engine.block.getFill(newBlock);
const originalTrimOffset = engine.block.getTrimOffset(originalFill);
const originalTrimLength = engine.block.getTrimLength(originalFill);
const newTrimOffset = engine.block.getTrimOffset(newBlockFill);
const newTrimLength = engine.block.getTrimLength(newBlockFill);
console.log('Split results:');
console.log(
` Original: offset=${originalTrimOffset}s, length=${originalTrimLength}s`
);
console.log(
` New block: offset=${newTrimOffset}s, length=${newTrimLength}s`
);
// Demonstrate split-and-delete workflow for removing middle section
const deleteBlock = engine.block.create('graphic');
const deleteFill = engine.block.createFill('video');
engine.block.setString(deleteFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(deleteBlock, deleteFill);
engine.block.setWidth(deleteBlock, 1920);
engine.block.setHeight(deleteBlock, 1080);
engine.block.appendChild(track, deleteBlock);
await engine.block.forceLoadAVResource(deleteFill);
engine.block.setDuration(deleteBlock, 10.0);
// Split at start of section to remove (2s)
const middleBlock = engine.block.split(deleteBlock, 2.0);
// Split at end of section to remove (3s into middle = 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, kept: ${deleteBlock}, ${endBlock}`
);
// Validate split time before splitting
const validateBlock = engine.block.create('graphic');
const validateFill = engine.block.createFill('video');
engine.block.setString(validateFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(validateBlock, validateFill);
engine.block.setWidth(validateBlock, 1920);
engine.block.setHeight(validateBlock, 1080);
engine.block.appendChild(track, validateBlock);
await engine.block.forceLoadAVResource(validateFill);
engine.block.setDuration(validateBlock, 8.0);
const blockDuration = engine.block.getDuration(validateBlock);
const splitTime = 4.0;
// Validate split time is within bounds (must be > 0 and < duration)
if (splitTime > 0 && splitTime < blockDuration) {
const validatedNewBlock = engine.block.split(validateBlock, splitTime);
console.log(
`Validated split at ${splitTime}s, new block: ${validatedNewBlock}`
);
} else {
console.log('Split time out of range');
}
// Export the scene archive to demonstrate the result
const archiveBlob = await engine.scene.saveToArchive();
const buffer = Buffer.from(await archiveBlob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('./output')) {
mkdirSync('./output', { recursive: true });
}
writeFileSync('./output/split-video.zip', buffer);
console.log('Scene exported to ./output/split-video.zip');
} finally {
engine.dispose();
}
}
splitVideoExample().catch(console.error);
```
This guide covers how to split clips programmatically for server-side automation and batch processing workflows.
## Setting Up the Scene
Before splitting clips, create a scene with the necessary track structure.
```typescript highlight=highlight-setup
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
```
```typescript highlight=highlight-create-video-scene
// Create a scene with a page and track for video editing
engine.scene.create('DepthStack', {
page: { size: { width: 1920, height: 1080 } }
});
const page = engine.block.findByType('page')[0]!;
const track = engine.block.create('track');
engine.block.appendChild(page, track);
```
The scene uses `Video` mode to enable time-based operations. We create a track to contain the video clips that will be split.
## 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 time range.
```typescript highlight=highlight-basic-split
// Create a video block to demonstrate basic splitting
const videoBlock = engine.block.create('graphic');
const videoFill = engine.block.createFill('video');
engine.block.setString(videoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(videoBlock, videoFill);
engine.block.setWidth(videoBlock, 1920);
engine.block.setHeight(videoBlock, 1080);
engine.block.appendChild(track, videoBlock);
// Load video resource to access duration
await engine.block.forceLoadAVResource(videoFill);
// Set block duration for the timeline
engine.block.setDuration(videoBlock, 10.0);
// Split the video block at 5 seconds
// Returns the ID of the newly created block (second segment)
const newBlock = engine.block.split(videoBlock, 5.0);
console.log(`Basic split - Original: ${videoBlock}, New: ${newBlock}`);
```
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
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
```typescript highlight=highlight-split-options
// Create another video block to demonstrate split options
const optionsBlock = engine.block.create('graphic');
const optionsFill = engine.block.createFill('video');
engine.block.setString(optionsFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(optionsBlock, optionsFill);
engine.block.setWidth(optionsBlock, 1920);
engine.block.setHeight(optionsBlock, 1080);
engine.block.appendChild(track, optionsBlock);
await engine.block.forceLoadAVResource(optionsFill);
engine.block.setDuration(optionsBlock, 10.0);
// Split with custom options
const optionsNewBlock = engine.block.split(optionsBlock, 4.0, {
attachToParent: true, // Attach to same parent (default: true)
createParentTrackIfNeeded: false, // Don't create track (default: false)
selectNewBlock: false // Don't select new block (default: true)
});
console.log(`Split with options - New block: ${optionsNewBlock}`);
```
Use `selectNewBlock: false` when splitting multiple clips programmatically to avoid changing selection state between operations.
## Understanding Split Results
After a split operation, both the original and new blocks have updated trim properties.
```typescript highlight=highlight-split-results
// Examine trim properties after split
const originalFill = engine.block.getFill(videoBlock);
const newBlockFill = engine.block.getFill(newBlock);
const originalTrimOffset = engine.block.getTrimOffset(originalFill);
const originalTrimLength = engine.block.getTrimLength(originalFill);
const newTrimOffset = engine.block.getTrimOffset(newBlockFill);
const newTrimLength = engine.block.getTrimLength(newBlockFill);
console.log('Split results:');
console.log(
` Original: offset=${originalTrimOffset}s, length=${originalTrimLength}s`
);
console.log(
` New block: offset=${newTrimOffset}s, length=${newTrimLength}s`
);
```
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.
## Split and Delete Workflow
Remove a middle section from a clip by splitting at both boundaries and deleting the middle segment.
```typescript highlight=highlight-split-and-delete
// Demonstrate split-and-delete workflow for removing middle section
const deleteBlock = engine.block.create('graphic');
const deleteFill = engine.block.createFill('video');
engine.block.setString(deleteFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(deleteBlock, deleteFill);
engine.block.setWidth(deleteBlock, 1920);
engine.block.setHeight(deleteBlock, 1080);
engine.block.appendChild(track, deleteBlock);
await engine.block.forceLoadAVResource(deleteFill);
engine.block.setDuration(deleteBlock, 10.0);
// Split at start of section to remove (2s)
const middleBlock = engine.block.split(deleteBlock, 2.0);
// Split at end of section to remove (3s into middle = 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, kept: ${deleteBlock}, ${endBlock}`
);
```
This workflow is useful for batch processing where you need to remove unwanted sections programmatically.
## 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.
```typescript highlight=highlight-validate-split
// Validate split time before splitting
const validateBlock = engine.block.create('graphic');
const validateFill = engine.block.createFill('video');
engine.block.setString(validateFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(validateBlock, validateFill);
engine.block.setWidth(validateBlock, 1920);
engine.block.setHeight(validateBlock, 1080);
engine.block.appendChild(track, validateBlock);
await engine.block.forceLoadAVResource(validateFill);
engine.block.setDuration(validateBlock, 8.0);
const blockDuration = engine.block.getDuration(validateBlock);
const splitTime = 4.0;
// Validate split time is within bounds (must be > 0 and < duration)
if (splitTime > 0 && splitTime < blockDuration) {
const validatedNewBlock = engine.block.split(validateBlock, splitTime);
console.log(
`Validated split at ${splitTime}s, new block: ${validatedNewBlock}`
);
} else {
console.log('Split time out of range');
}
```
Attempting to split at an invalid time will fail or produce unexpected results.
## Cleanup
Always dispose the engine when finished to release resources.
```typescript highlight=highlight-cleanup
} finally {
engine.dispose();
}
```
## 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` |
| `getTrimOffset(id)` | Get trim offset of media content | `id: DesignBlockId` | `number` |
| `getTrimLength(id)` | Get trim length of media content | `id: DesignBlockId` | `number` |
| `forceLoadAVResource(id)` | Force load media resource metadata | `id: DesignBlockId` | `Promise` |
| `destroy(id)` | Destroy a block | `id: DesignBlockId` | `void` |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Transform"
description: "Documentation for Transform"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/)
---
---
## Related Pages
- [Move Videos](https://img.ly/docs/cesdk/node-native/edit-video/transform/move-aa9d89/) - Position videos on the canvas using absolute or percentage-based coordinates.
- [Crop Videos](https://img.ly/docs/cesdk/node-native/edit-video/transform/crop-8b1741/) - Cut out specific areas of a video to focus on key content or change aspect ratio.
- [Rotate Videos](https://img.ly/docs/cesdk/node-native/edit-video/transform/rotate-eaf662/) - Rotate video elements to adjust orientation and create dynamic compositions.
- [Resize Videos](https://img.ly/docs/cesdk/node-native/edit-video/transform/resize-b1ce14/) - Change the dimensions of video elements to fit specific layout requirements.
- [Scale Videos in Node.js](https://img.ly/docs/cesdk/node-native/edit-video/transform/scale-f75c8a/) - Scale videos programmatically using the CE.SDK headless engine in Node.js.
- [Flip Videos](https://img.ly/docs/cesdk/node-native/edit-video/transform/flip-a603b0/) - Flip videos horizontally or vertically to create mirror effects and symmetrical designs.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Crop Videos"
description: "Cut out specific areas of a video to focus on key content or change aspect ratio."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/transform/crop-8b1741/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/) > [Crop](https://img.ly/docs/cesdk/node-native/edit-video/transform/crop-8b1741/)
---
Video cropping in CreativeEditor SDK (CE.SDK) allows you to trim video content to focus on specific areas, remove unwanted elements, or adjust aspect ratios for different platforms. This transformation is essential for optimizing videos for social media, removing distracting elements, or creating custom framing effects.
You can crop videos both through the built-in user interface and programmatically using the SDK's APIs, providing flexibility for different workflow requirements.
[Launch Web Demo](https://img.ly/showcases/cesdk)
[Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/)
## Cropping Methods
CE.SDK supports several approaches to video cropping:
- **Interactive Cropping**: Visual crop area selection with real-time preview
- **Aspect Ratio Presets**: Quick crop to common social media and platform formats
- **Programmatic Cropping**: Precise crop control through scale, translation, and rotation
- **Group Cropping**: Apply consistent crop settings across multiple video elements
## Applying Crops
### UI-Based Cropping
You can crop videos directly in the CE.SDK user interface using interactive crop controls. Users can drag crop boundaries, select preset aspect ratios, and see real-time previews, making it easy to frame content perfectly for any platform.
### Programmatic Cropping
Developers can apply cropping programmatically, using the SDK's API. This allows for automated cropping based on content analysis, batch processing, and integration with custom workflows or template systems.
## Combining with Other Transforms
Video cropping works seamlessly with other transformation operations like rotation, scaling, and positioning. You can chain multiple transformations to create complex effects while maintaining video quality and performance.
## Guides
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Flip"
description: "Mirror video clips in a Node.js backend using CE.SDK server mode."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/transform/flip-a603b0/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/) > [Flip](https://img.ly/docs/cesdk/node-native/edit-video/transform/flip-a603b0/)
---
Run **CE.SDK** in **Node.js server mode** to mirror video blocks programmatically—no client editor required. Flip clips before handing scenes to a browser runtime, enforcing template rules, or running automation jobs that prepare footage for downstream pipelines.
> **Note:** CE.SDK for Node.js runs **headless**. To preview or export H.264/MP4, send the prepared scene to a browser runtime such as CE.SDK Web or Playwright (WebCodecs support) and encode the video there, or render frames into your encoder of choice.
## Requirements
- CE.SDK server package: `npm install @cesdk/node`
- **Node.js 18** or newer
- CE.SDK license key and `baseURL` to the CE.SDK asset bundle
## What You’ll Learn
- Flip video blocks **horizontally or vertically** server-side.
- Mirror multiple clips together during automation jobs.
- **Reset** or toggle flip states while persisting scene changes.
## When to Use
Server-side flipping helps when you need to:
- Prepare mirrored variants of clips for A/B testing or template variations.
- Align **eyelines** between front and rear camera footage before export.
- Keep branded elements facing inward on split-screen layouts that ship from templates.
## Load and Flip Video Blocks
Every video clip sits inside a **graphic** block with a video fill. After initializing the engine and loading a scene, identify the blocks you want to flip:
```js
import CreativeEngine from '@cesdk/node';
const engine = await CreativeEngine.init({
license: process.env.LICENSE_KEY,
baseURL: process.env.CESDK_BASE_URL
});
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_video_landscape_1.scene'
);
const graphics = engine.block.findByType('graphic');
const videoBlocks = graphics.filter(
(id) => engine.block.getEnum(id, 'fill/type') === 'video'
);
if (videoBlocks.length === 0) {
throw new Error('No video blocks found in the scene.');
}
// Apply flips, then persist the scene before disposing of the engine.
```
Once you have the block IDs, use the flip helpers to mirror each clip as needed. Persist the modified scene with `engine.scene.saveToString()` or `engine.scene.saveToArchive()` before disposing of the engine.
## How Flipping Works
The CreativeEditor represents each video clip as a `graphic` block with:
- Size
- Transforms
- Grouping
- A fill set to a *video* asset
Use the BlockAPI boolean helpers—identical to the image flip workflow—to mirror or restore clips.
### Flip Horizontally or Vertically
Call the flip helper with the block ID and a boolean:
```js
engine.block.setFlipHorizontal(videoBlock, true); // Mirror left/right
engine.block.setFlipVertical(videoBlock, true); // Mirror top/bottom
```
The flip applies immediately in memory. Save or export the scene after you finish updates so the change appears once you open the scene in a browser runtime.
### Query the Flip Status
Check the current flip flags before toggling or resetting:
```js
const flippedH = engine.block.getFlipHorizontal(videoBlock);
const flippedV = engine.block.getFlipVertical(videoBlock);
```
Both helpers return a boolean that reflects the block’s current orientation.
> **Note:** Flipping only affects the **visual track**. Audio (either embedded or separate) isn’t reversed, so **lip-sync and sound design stay intact**.
### Flip Clips Together
Avoid repeating the same flip call for every block by grouping the clips first:
```js
const groupId = await engine.block.group([clipId1, clipId2, clipId3]);
engine.block.setFlipVertical(groupId, true);
```
When you flip the group:
- The flip mirrors every child at once.
- Each clip keeps the same **spacing and order** inside the group hierarchy.
- Future flips on individual clips stack on top of the group-level flip, so track the group transform before applying more flips.
## Restore or Manage Flip States
The CE.SDK provides ways to reset or toggle a flip. You can either:
- **Reset:** when you want to restore a clip to its original orientation.
- **Code a toggle:** when you need a quick way to switch the current flip state on and off.
### Reset a Flip
To reset a clip to its default orientation:
```js
engine.block.setFlipHorizontal(videoBlock, false);
```
### Switch Flip On or Off
Calling the same flip twice:
- **Doesn’t** revert to the original orientation.
- Issues two flips in the same direction.
- Leaves the clip mirrored.
To “toggle” the flip in your code:
```js
const isFlipped = engine.block.getFlipHorizontal(videoBlock);
engine.block.setFlipHorizontal(videoBlock, !isFlipped);
```
In the preceding code, two actions are possible depending on the value stored in `isFlipped`:
- `true`: `!isFlipped` sets the horizontal flip to `false`.
- `false`: `!isFlipped` sets the horizontal flip to `true`.
Use this pattern to:
- Script command-line toggles
- Expose a REST endpoint that flips clips on demand.
## Lock or Constrain Flipping
CE.SDK provides a way to prevent editors from flipping a video. This can be useful to:
- Avoid accidentally mirroring text.
- Protect UI mock-ups or frames that must stay in a fixed position.
- Keep templates brand-locked.
To deactivate flipping, use `setScopeEnabled` with:
- The `"layer/flip"` key
- The boolean set to `false`
```js
engine.block.setScopeEnabled(videoBlock, 'layer/flip', false);
```
## Troubleshooting
| Issue | Solution |
| --- | --- |
| ❌ Flip disappears when opening the scene in the browser editor | ✅ Save the scene (`engine.scene.saveToString()` or archive) after flipping and load that exported scene in the client. |
| ❌ Flip seems ignored | ✅ Check whether the parent group is already flipped; group and block flips stack. |
| ❌ Users can still flip in UI | ✅ Turn off the `"layer/flip"` scope or lock transforms in the template. |
| ❌ Automation fails on certain clips | ✅ Ensure each block’s fill type is `video` before calling the flip helpers. |
## Next Steps
Now that you understand how to flip with the CE.SDK, take time to dive into other related features:
- [Create a template](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) to enforce design rules.
- [Get an overview](https://img.ly/docs/cesdk/node-native/overview-7d12d5/) of the CE.SDK's video editing features.
## API Reference Summary
| BlockAPI `engine.block.<>` | Purpose |
| --- | --- |
| `setFlipHorizontal(blockId, boolean)` | Mirror a block left/right or restore the default orientation. |
| `setFlipVertical(blockId, boolean)` | Mirror a block top/bottom or restore the default orientation. |
| `getFlipHorizontal(blockId)` | Check whether the block is currently flipped horizontally. |
| `getFlipVertical(blockId)` | Check whether the block is currently flipped vertically. |
| `group(blockIds[])` | Group blocks so you can flip them together. |
| `setScopeEnabled(blockId, "layer/flip", boolean)` | Activate or deactivate flip controls to lock orientation. |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Move Videos"
description: "Position videos on the canvas using absolute or percentage-based coordinates."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/transform/move-aa9d89/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/) > [Move](https://img.ly/docs/cesdk/node-native/edit-video/transform/move-aa9d89/)
---
Position videos on the canvas using absolute pixel coordinates or percentage-based positioning for responsive layouts.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-transform-move-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-transform-move-server-js)
Position videos on the canvas using coordinates that start at the top-left corner (0, 0). X increases right, Y increases down. Values are relative to the parent block, simplifying nested layouts.
```typescript file=@cesdk_web_examples/guides-create-video-transform-move-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Move Videos
*
* Demonstrates video positioning in headless mode:
* - Absolute positioning with pixel coordinates
* - Percentage-based positioning for responsive layouts
* - Getting current position values
* - Locking transforms to prevent repositioning
* - Saving scenes for later rendering
*
* Note: Full video export (MP4) requires the CE.SDK Renderer.
* In headless Node.js mode, we save the scene for later use.
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
// Sample video URL for demonstrations
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
// Block size for layout
const blockSize = { width: 200, height: 150 };
// Demo 1: Movable Video - Absolute positioning with pixel coordinates
const movableVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
engine.block.appendChild(page, movableVideo);
engine.block.setPositionX(movableVideo, 50);
engine.block.setPositionY(movableVideo, 100);
// Add label for movable video
const text1 = engine.block.create('text');
engine.block.setString(text1, 'text/text', 'Movable');
engine.block.setFloat(text1, 'text/fontSize', 32);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text1, 200);
engine.block.setPositionX(text1, 50);
engine.block.setPositionY(text1, 260);
engine.block.appendChild(page, text1);
// Demo 2: Percentage Positioning - Responsive layout
const percentVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
engine.block.appendChild(page, percentVideo);
// Set position mode to percentage (0.0 to 1.0)
engine.block.setPositionXMode(percentVideo, 'Percent');
engine.block.setPositionYMode(percentVideo, 'Percent');
// Position at 37.5% from left (300px on 800px width), 20% from top (100px on 500px height)
engine.block.setPositionX(percentVideo, 0.375);
engine.block.setPositionY(percentVideo, 0.2);
// Add label for percentage video
const text2 = engine.block.create('text');
engine.block.setString(text2, 'text/text', 'Percentage');
engine.block.setFloat(text2, 'text/fontSize', 32);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text2, 200);
engine.block.setPositionX(text2, 300);
engine.block.setPositionY(text2, 260);
engine.block.appendChild(page, text2);
// Demo 3: Locked Video - Cannot be moved, rotated, or scaled
const lockedVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
engine.block.appendChild(page, lockedVideo);
engine.block.setPositionX(lockedVideo, 550);
engine.block.setPositionY(lockedVideo, 100);
// Lock the transform to prevent repositioning
engine.block.setBool(lockedVideo, 'transformLocked', true);
// Add label for locked video
const text3 = engine.block.create('text');
engine.block.setString(text3, 'text/text', 'Locked');
engine.block.setFloat(text3, 'text/fontSize', 32);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text3, 200);
engine.block.setPositionX(text3, 550);
engine.block.setPositionY(text3, 260);
engine.block.appendChild(page, text3);
// Get current position values
const currentX = engine.block.getPositionX(movableVideo);
const currentY = engine.block.getPositionY(movableVideo);
console.log('Current position:', currentX, currentY);
// Move relative to current position by adding offset values
const offsetX = engine.block.getPositionX(movableVideo);
const offsetY = engine.block.getPositionY(movableVideo);
engine.block.setPositionX(movableVideo, offsetX + 25);
engine.block.setPositionY(movableVideo, offsetY + 25);
// Save the scene to preserve the positioned videos
// Note: Video export (MP4/PNG with video frames) requires the CE.SDK Renderer.
// In headless Node.js mode, we save the scene file which can be loaded later
// in a browser environment or rendered with the CE.SDK Renderer.
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene to file
writeFileSync('output/move-videos-scene.json', sceneString);
console.log('Saved to output/move-videos-scene.json');
// Log final positions to verify
console.log('Video positions:');
console.log(
` Movable video: (${engine.block.getPositionX(movableVideo)}, ${engine.block.getPositionY(movableVideo)})`
);
console.log(
` Percent video: (${engine.block.getPositionX(percentVideo)}, ${engine.block.getPositionY(percentVideo)}) [Mode: Percent]`
);
console.log(
` Locked video: (${engine.block.getPositionX(lockedVideo)}, ${engine.block.getPositionY(lockedVideo)}) [Transform locked]`
);
console.log('Video positioning guide completed successfully.');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers positioning videos with absolute or percentage coordinates, configuring position modes, and locking transforms to prevent repositioning.
## Initialize Headless Engine
Create a headless engine instance for programmatic video manipulation:
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Create the Scene
Create a scene with specific page dimensions:
```typescript highlight-create-scene
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
```
## Position Coordinates
Coordinates originate at the top-left (0, 0) of the parent container. Use **absolute** mode for fixed pixel values or **percentage** mode (0.0 to 1.0) for responsive layouts that adapt to parent size changes.
## Positioning Videos
Position videos using `engine.block.setPositionX()` and `engine.block.setPositionY()` with absolute pixel coordinates:
```typescript highlight-movable-video
engine.block.appendChild(page, movableVideo);
engine.block.setPositionX(movableVideo, 50);
engine.block.setPositionY(movableVideo, 100);
```
## Getting Current Position
Read current position values using `engine.block.getPositionX()` and `engine.block.getPositionY()`. Values are returned in the current position mode (absolute pixels or percentage 0.0-1.0):
```typescript highlight-get-position
// Get current position values
const currentX = engine.block.getPositionX(movableVideo);
const currentY = engine.block.getPositionY(movableVideo);
```
## Configuring Position Modes
Control how position values are interpreted using `engine.block.setPositionXMode()` and `engine.block.setPositionYMode()`. Set to `'Absolute'` for pixels or `'Percent'` for percentage values (0.0 to 1.0). Check the current mode using `engine.block.getPositionXMode()` and `engine.block.getPositionYMode()`. The Percentage Positioning section below demonstrates setting these modes.
## Percentage Positioning
Position videos using percentage values (0.0 to 1.0) for responsive layouts. Set the position mode to `'Percent'`, then use values between 0.0 and 1.0:
```typescript highlight-percentage-positioning
// Set position mode to percentage (0.0 to 1.0)
engine.block.setPositionXMode(percentVideo, 'Percent');
engine.block.setPositionYMode(percentVideo, 'Percent');
```
Percentage positioning adapts automatically when the parent block dimensions change, maintaining relative positions in responsive designs.
## Relative Positioning
Move videos relative to their current position by getting the current coordinates and adding offset values:
```typescript highlight-relative-positioning
// Move relative to current position by adding offset values
const offsetX = engine.block.getPositionX(movableVideo);
const offsetY = engine.block.getPositionY(movableVideo);
engine.block.setPositionX(movableVideo, offsetX + 25);
engine.block.setPositionY(movableVideo, offsetY + 25);
```
## Locking Transforms
Lock transforms to prevent repositioning, rotation, and scaling by setting `transformLocked` to true:
```typescript highlight-locked-video
// Lock the transform to prevent repositioning
engine.block.setBool(lockedVideo, 'transformLocked', true);
```
## Save Scene
Save the scene to a file. The scene can later be loaded in a browser environment or rendered with the CE.SDK Renderer for full video export:
```typescript highlight-export
// Save the scene to preserve the positioned videos
// Note: Video export (MP4/PNG with video frames) requires the CE.SDK Renderer.
// In headless Node.js mode, we save the scene file which can be loaded later
// in a browser environment or rendered with the CE.SDK Renderer.
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene to file
writeFileSync('output/move-videos-scene.json', sceneString);
console.log('Saved to output/move-videos-scene.json');
// Log final positions to verify
console.log('Video positions:');
console.log(
` Movable video: (${engine.block.getPositionX(movableVideo)}, ${engine.block.getPositionY(movableVideo)})`
);
console.log(
` Percent video: (${engine.block.getPositionX(percentVideo)}, ${engine.block.getPositionY(percentVideo)}) [Mode: Percent]`
);
console.log(
` Locked video: (${engine.block.getPositionX(lockedVideo)}, ${engine.block.getPositionY(lockedVideo)}) [Transform locked]`
);
```
## Cleanup
Always dispose the engine to free resources when done:
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Troubleshooting
### Video Not Moving
Check if transforms are locked using `engine.block.getBool(block, 'transformLocked')`. Ensure the video block exists and values are within parent bounds.
### Unexpected Position Values
Check position mode using `engine.block.getPositionXMode()` and `engine.block.getPositionYMode()`. Verify if using absolute (pixels) vs percentage (0.0-1.0) values. Review parent block dimensions if using percentage positioning.
### Positioned Outside Visible Area
Verify parent block dimensions and boundaries. Check coordinate system: origin is top-left, not center. Review X/Y values for calculation errors.
### Percentage Positioning Not Responsive
Ensure position mode is set to `'Percent'` using `engine.block.setPositionXMode(block, 'Percent')`. Verify percentage values are between 0.0 and 1.0. Check that parent block dimensions can change.
## API Reference
| Method | Description |
| --------------------------------- | --------------------------------------------------------- |
| `CreativeEngine.init()` | Initializes the headless engine for programmatic creation |
| `engine.scene.create()` | Creates a new scene |
| `engine.block.findByType()` | Finds blocks by type |
| `engine.block.addVideo()` | Create and position video in one operation |
| `engine.block.setPositionX()` | Set X coordinate value |
| `engine.block.setPositionY()` | Set Y coordinate value |
| `engine.block.getPositionX()` | Get current X coordinate value |
| `engine.block.getPositionY()` | Get current Y coordinate value |
| `engine.block.setPositionXMode()` | Set position mode for X coordinate |
| `engine.block.setPositionYMode()` | Set position mode for Y coordinate |
| `engine.block.getPositionXMode()` | Get position mode for X coordinate |
| `engine.block.getPositionYMode()` | Get position mode for Y coordinate |
| `engine.block.setBool()` | Set transform lock state |
| `engine.block.getBool()` | Get transform lock state |
| `engine.scene.saveToString()` | Save scene to string for later use |
| `engine.dispose()` | Dispose engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Resize"
description: "Resize videos programmatically in headless mode using the CE.SDK"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/transform/resize-b1ce14/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/) > [Resize](https://img.ly/docs/cesdk/node-native/edit-video/transform/resize-b1ce14/)
---
The **CreativeEditor SDK (CE.SDK)** provides programmatic video resizing for server-side workflows. This guide covers resizing videos in headless mode, from absolute pixel dimensions to percentage-based responsive layouts.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-edit-video-transform-resize-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-edit-video-transform-resize-server-js)
```typescript file=@cesdk_web_examples/guides-edit-video-transform-resize-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Resize Videos
*
* Demonstrates video resizing in headless mode:
* - Absolute sizing with pixel dimensions
* - Percentage-based sizing for responsive layouts
* - Getting current dimensions
* - Locking transforms to prevent resizing
* - Saving scenes for later rendering
*
* Note: Full video export (MP4) requires the CE.SDK Renderer.
* In headless Node.js mode, we save the scene for later use.
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
// Sample video URL for demonstrations
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
// Demo 1: Resizable Video - Absolute sizing with pixel dimensions
console.log('Loading video 1/3 (Resizable)...');
const resizableVideo = await engine.block.addVideo(videoUri, 200, 150);
engine.block.appendChild(page, resizableVideo);
engine.block.setWidth(resizableVideo, 200);
engine.block.setHeight(resizableVideo, 150);
engine.block.setPositionX(resizableVideo, 50);
engine.block.setPositionY(resizableVideo, 100);
console.log('✓ Video 1/3 loaded');
// Add label for resizable video
const text1 = engine.block.create('text');
engine.block.setString(text1, 'text/text', 'Resizable');
engine.block.setFloat(text1, 'text/fontSize', 32);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text1, 200);
engine.block.setPositionX(text1, 50);
engine.block.setPositionY(text1, 260);
engine.block.appendChild(page, text1);
// Demo 2: Percentage Sizing - Responsive layout
console.log('Loading video 2/3 (Percentage)...');
const percentVideo = await engine.block.addVideo(videoUri, 200, 150);
engine.block.appendChild(page, percentVideo);
// Set size mode to percentage (0.0 to 1.0)
engine.block.setWidthMode(percentVideo, 'Percent');
engine.block.setHeightMode(percentVideo, 'Percent');
// Set to 25% width, 30% height of parent
engine.block.setWidth(percentVideo, 0.25);
engine.block.setHeight(percentVideo, 0.3);
engine.block.setPositionX(percentVideo, 300);
engine.block.setPositionY(percentVideo, 100);
console.log('✓ Video 2/3 loaded');
// Add label for percentage video
const text2 = engine.block.create('text');
engine.block.setString(text2, 'text/text', 'Percentage');
engine.block.setFloat(text2, 'text/fontSize', 32);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text2, 200);
engine.block.setPositionX(text2, 300);
engine.block.setPositionY(text2, 260);
engine.block.appendChild(page, text2);
// Demo 3: Locked Video - Cannot be resized
console.log('Loading video 3/3 (Locked)...');
const lockedVideo = await engine.block.addVideo(videoUri, 200, 150);
engine.block.appendChild(page, lockedVideo);
engine.block.setPositionX(lockedVideo, 550);
engine.block.setPositionY(lockedVideo, 100);
// Lock the transform to prevent resizing
engine.block.setTransformLocked(lockedVideo, true);
console.log('✓ Video 3/3 loaded');
// Add label for locked video
const text3 = engine.block.create('text');
engine.block.setString(text3, 'text/text', 'Locked');
engine.block.setFloat(text3, 'text/fontSize', 32);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text3, 200);
engine.block.setPositionX(text3, 550);
engine.block.setPositionY(text3, 260);
engine.block.appendChild(page, text3);
// Get current dimensions
const currentWidth = engine.block.getWidth(resizableVideo);
const currentHeight = engine.block.getHeight(resizableVideo);
console.log('Current dimensions:', currentWidth, 'x', currentHeight);
// Check size mode
const widthMode = engine.block.getWidthMode(percentVideo);
const heightMode = engine.block.getHeightMode(percentVideo);
console.log('Size modes:', widthMode, heightMode);
// Save the scene to preserve the resized videos
// Note: Video export (MP4/PNG with video frames) requires the CE.SDK Renderer.
// In headless Node.js mode, we save the scene file which can be loaded later
// in a browser environment or rendered with the CE.SDK Renderer.
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene to .scene file (standard CE.SDK scene format)
writeFileSync('output/resize-videos-scene.scene', sceneString);
console.log('✓ Saved to output/resize-videos-scene.scene');
// Log final dimensions to verify
console.log('\nVideo dimensions:');
console.log(
` Resizable video: ${engine.block.getWidth(resizableVideo)}x${engine.block.getHeight(resizableVideo)} [Mode: Absolute]`
);
console.log(
` Percent video: ${engine.block.getWidth(percentVideo)}x${engine.block.getHeight(percentVideo)} [Mode: Percent]`
);
console.log(
` Locked video: ${engine.block.getWidth(lockedVideo)}x${engine.block.getHeight(lockedVideo)} [Transform locked]`
);
console.log('\n✓ Video resizing guide completed successfully.');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
## What You'll Learn
- Initialize a **headless engine** for server-side processing.
- Resize clips using **absolute pixel dimensions**.
- Resize using **percentage values** for responsive layouts.
- **Lock transforms** to prevent resizing.
- **Save scenes** for later rendering.
### When to Use
Resize videos programmatically to:
- Fit **template areas** in automated workflows.
- Create videos for multiple **aspect ratios** from a single source.
- Build **batch processing** pipelines.
- Generate videos for different **social media formats**.
- Combine with trimming or cropping in headless workflows.
## Initialize Headless Engine
Create a headless engine instance for programmatic video manipulation:
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Create the Scene
Create a scene with specific page dimensions:
```typescript highlight-create-scene
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
```
## Resize a Video Block with JavaScript
Use the CreativeEngine BlockAPI to edit a video's size. Two methods exist: choose the one that best suits your project.
### Set Absolute Sizing
Set the video's absolute size with `setWidth` and `setHeight`. First, create the video block with `addVideo`, then set explicit pixel dimensions:
```typescript highlight-resizable-video
const resizableVideo = await engine.block.addVideo(videoUri, 200, 150);
engine.block.appendChild(page, resizableVideo);
engine.block.setWidth(resizableVideo, 200);
engine.block.setHeight(resizableVideo, 150);
engine.block.setPositionX(resizableVideo, 50);
engine.block.setPositionY(resizableVideo, 100);
```
### Set Relative Sizing
Set the video's size relative to its parent (for example, to the page):
1. Set the size mode with `setWidthMode` and `setHeightMode`.
2. Define the mode as `'Percent'`.
3. Set the size in normalized values, with `1` being full-width.
```typescript highlight-percentage-sizing
// Set size mode to percentage (0.0 to 1.0)
engine.block.setWidthMode(percentVideo, 'Percent');
engine.block.setHeightMode(percentVideo, 'Percent');
// Set to 25% width, 30% height of parent
engine.block.setWidth(percentVideo, 0.25);
engine.block.setHeight(percentVideo, 0.3);
```
The preceding code:
- Sets the clip to 25% width of its parent.
- Makes the clip 30% as tall.
- Stays responsive to the parent's size changes.
This method allows for the clip to follow the parent's size changes while maintaining proportional dimensions.
### Get Current Dimensions
Read current size values using `getWidth` and `getHeight`. Values are returned in the current size mode (absolute pixels or percentage 0.0-1.0):
```typescript highlight-get-dimensions
// Get current dimensions
const currentWidth = engine.block.getWidth(resizableVideo);
const currentHeight = engine.block.getHeight(resizableVideo);
```
### Check Size Mode
Query the current size mode using `getWidthMode` and `getHeightMode`:
```typescript highlight-get-size-mode
// Check size mode
const widthMode = engine.block.getWidthMode(percentVideo);
const heightMode = engine.block.getHeightMode(percentVideo);
```
## Lock or Constrain Resizing
Lock all transforms entirely to prevent resizing, repositioning, and rotation:
```typescript highlight-locked-video
// Lock the transform to prevent resizing
engine.block.setTransformLocked(lockedVideo, true);
```
This deactivates all transform actions, resize included.
## Save Scene
Save the scene to a file. The scene can later be loaded in a browser environment or rendered with the CE.SDK Renderer for full video export:
```typescript highlight-export
// Save the scene to preserve the resized videos
// Note: Video export (MP4/PNG with video frames) requires the CE.SDK Renderer.
// In headless Node.js mode, we save the scene file which can be loaded later
// in a browser environment or rendered with the CE.SDK Renderer.
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene to .scene file (standard CE.SDK scene format)
writeFileSync('output/resize-videos-scene.scene', sceneString);
console.log('✓ Saved to output/resize-videos-scene.scene');
// Log final dimensions to verify
console.log('\nVideo dimensions:');
console.log(
` Resizable video: ${engine.block.getWidth(resizableVideo)}x${engine.block.getHeight(resizableVideo)} [Mode: Absolute]`
);
console.log(
` Percent video: ${engine.block.getWidth(percentVideo)}x${engine.block.getHeight(percentVideo)} [Mode: Percent]`
);
console.log(
` Locked video: ${engine.block.getWidth(lockedVideo)}x${engine.block.getHeight(lockedVideo)} [Transform locked]`
);
```
## Cleanup
Always dispose the engine to free resources when done:
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Notes on Resizing with CE.SDK
| Topic | What you want to do | What happens |
| --- | --- | --- |
| **Block duration** | Resize the block's on-canvas frame. | No need to retime; duration and trims stay untouched. |
| **Content fill** | Switch the block to `.contain` or `.cover`. | Update it with `setContentFillMode`. |
| **Batch processing** | Resize multiple videos programmatically. | Use loops and save each scene to separate files. |
## API Reference
| Method | Description |
| --- | --- |
| `CreativeEngine.init()` | Initializes the headless engine for programmatic creation |
| `engine.scene.create()` | Creates a new scene |
| `engine.block.findByType()` | Finds blocks by type |
| `engine.block.addVideo()` | Create and size video in one operation |
| `engine.block.setWidth()` | Set block width value |
| `engine.block.setHeight()` | Set block height value |
| `engine.block.getWidth()` | Get current width value |
| `engine.block.getHeight()` | Get current height value |
| `engine.block.setWidthMode()` | Set width mode (Absolute or Percent) |
| `engine.block.setHeightMode()` | Set height mode (Absolute or Percent) |
| `engine.block.getWidthMode()` | Get current width mode |
| `engine.block.getHeightMode()` | Get current height mode |
| `engine.block.setTransformLocked()` | Lock all transforms including resize |
| `engine.scene.saveToString()` | Save scene to string for later use |
| `engine.dispose()` | Dispose engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Rotate Videos"
description: "Rotate video elements to adjust orientation and create dynamic compositions."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/transform/rotate-eaf662/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/) > [Rotate](https://img.ly/docs/cesdk/node-native/edit-video/transform/rotate-eaf662/)
---
Rotate video elements to any angle using radians or degrees, with precise programmatic control in headless Node.js environments.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-transform-rotate-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-transform-rotate-server-js)
Rotation in CE.SDK occurs around the block's center point. All rotation values use radians, where `Math.PI` equals 180 degrees. Positive values rotate counterclockwise, negative values rotate clockwise.
```typescript file=@cesdk_web_examples/guides-create-video-transform-rotate-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Rotate Videos
*
* Demonstrates video rotation in headless mode:
* - Setting rotation angles in radians
* - Converting between degrees and radians
* - Getting current rotation values
* - Locking rotation on specific blocks
* - Saving scenes for later rendering
*
* Note: Full video export (MP4) requires the CE.SDK Renderer.
* In headless Node.js mode, we save the scene for later use.
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
// Sample video URL for demonstrations
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
// Block size for layout
const blockSize = { width: 200, height: 150 };
// Helper functions for angle conversion
const toRadians = (degrees: number) => (degrees * Math.PI) / 180;
const toDegrees = (radians: number) => (radians * 180) / Math.PI;
// Demo 1: Rotated Video - 45 degree rotation
console.log('Loading video 1 of 3...');
const rotatedVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
engine.block.appendChild(page, rotatedVideo);
engine.block.setPositionX(rotatedVideo, 50);
engine.block.setPositionY(rotatedVideo, 100);
console.log('✓ Video 1 loaded');
// Rotate the video 45 degrees (convert to radians)
engine.block.setRotation(rotatedVideo, toRadians(45));
// Add label for rotated video
const text1 = engine.block.create('text');
engine.block.setString(text1, 'text/text', '45° Rotation');
engine.block.setFloat(text1, 'text/fontSize', 28);
engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text1, 200);
engine.block.setPositionX(text1, 50);
engine.block.setPositionY(text1, 280);
engine.block.appendChild(page, text1);
// Get current rotation value
const currentRotation = engine.block.getRotation(rotatedVideo);
console.log('Current rotation:', toDegrees(currentRotation), 'degrees');
// Demo 2: 90 Degree Rotation
console.log('Loading video 2 of 3...');
const rotatedVideo90 = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
engine.block.appendChild(page, rotatedVideo90);
engine.block.setPositionX(rotatedVideo90, 300);
engine.block.setPositionY(rotatedVideo90, 100);
console.log('✓ Video 2 loaded');
// Rotate 90 degrees using Math.PI / 2
engine.block.setRotation(rotatedVideo90, Math.PI / 2);
// Add label for 90 degree rotation
const text2 = engine.block.create('text');
engine.block.setString(text2, 'text/text', '90° Rotation');
engine.block.setFloat(text2, 'text/fontSize', 28);
engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text2, 200);
engine.block.setPositionX(text2, 300);
engine.block.setPositionY(text2, 280);
engine.block.appendChild(page, text2);
// Demo 3: Locked Rotation - Rotation is disabled for this block
console.log('Loading video 3 of 3...');
const lockedVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
engine.block.appendChild(page, lockedVideo);
engine.block.setPositionX(lockedVideo, 550);
engine.block.setPositionY(lockedVideo, 150);
console.log('✓ Video 3 loaded');
// Disable rotation for this specific block
engine.block.setScopeEnabled(lockedVideo, 'layer/rotate', false);
// Add label for locked video
const text3 = engine.block.create('text');
engine.block.setString(text3, 'text/text', 'Rotation Locked');
engine.block.setFloat(text3, 'text/fontSize', 28);
engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center');
engine.block.setWidth(text3, 200);
engine.block.setPositionX(text3, 550);
engine.block.setPositionY(text3, 320);
engine.block.appendChild(page, text3);
// Check if rotation is enabled for a block
const canRotate = engine.block.isScopeEnabled(lockedVideo, 'layer/rotate');
console.log('Rotation enabled:', canRotate);
// Save the scene to preserve the rotated videos
// Note: Video export (MP4/PNG with video frames) requires the CE.SDK Renderer.
// In headless Node.js mode, we save the scene file which can be loaded later
// in a browser environment or rendered with the CE.SDK Renderer.
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene to file
writeFileSync('output/rotate-videos.scene', sceneString);
console.log('Saved to output/rotate-videos.scene');
// Log final rotation values to verify
console.log('Video rotations:');
console.log(
` Rotated video (45°): ${toDegrees(engine.block.getRotation(rotatedVideo)).toFixed(1)}°`
);
console.log(
` Rotated video (90°): ${toDegrees(engine.block.getRotation(rotatedVideo90)).toFixed(1)}°`
);
console.log(
` Locked video: ${toDegrees(engine.block.getRotation(lockedVideo)).toFixed(1)}° [Rotation locked]`
);
console.log('Video rotation guide completed successfully.');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers rotating videos programmatically, converting between degrees and radians, grouping blocks for collective rotation, and locking rotation permissions.
## Initialize Headless Engine
Create a headless engine instance for programmatic video manipulation:
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Create the Scene
Create a scene with specific page dimensions:
```typescript highlight-create-scene
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
```
## Converting Degrees and Radians
CE.SDK uses radians for rotation values. Create helper functions to convert between degrees and radians:
```typescript highlight-convert-angles
const toRadians = (degrees: number) => (degrees * Math.PI) / 180;
const toDegrees = (radians: number) => (radians * 180) / Math.PI;
```
## Rotating Videos
Rotate videos using `engine.block.setRotation()` with the angle in radians. Convert from degrees using the formula `radians = degrees * Math.PI / 180`:
```typescript highlight-rotate-video
// Rotate the video 45 degrees (convert to radians)
engine.block.setRotation(rotatedVideo, toRadians(45));
```
## Getting Current Rotation
Read the current rotation value using `engine.block.getRotation()`. The value is returned in radians. Convert to degrees with `degrees = radians * 180 / Math.PI`:
```typescript highlight-get-rotation
// Get current rotation value
const currentRotation = engine.block.getRotation(rotatedVideo);
console.log('Current rotation:', toDegrees(currentRotation), 'degrees');
```
## Common Rotation Angles
For 90-degree rotations, use `Math.PI / 2`. For 180 degrees, use `Math.PI`. For 270 degrees, use `3 * Math.PI / 2`:
```typescript highlight-rotate-90
// Rotate 90 degrees using Math.PI / 2
engine.block.setRotation(rotatedVideo90, Math.PI / 2);
```
## Locking Rotation
Disable rotation for specific blocks using `engine.block.setScopeEnabled()` with the `'layer/rotate'` scope set to false:
```typescript highlight-lock-rotation
// Disable rotation for this specific block
engine.block.setScopeEnabled(lockedVideo, 'layer/rotate', false);
```
## Checking Rotation Permissions
Check if rotation is enabled for a block using `engine.block.isScopeEnabled()`:
```typescript highlight-check-scope
// Check if rotation is enabled for a block
const canRotate = engine.block.isScopeEnabled(lockedVideo, 'layer/rotate');
console.log('Rotation enabled:', canRotate);
```
## Save Scene
Save the scene to a file. The scene can later be loaded in a browser environment or rendered with the CE.SDK Renderer for full video export:
```typescript highlight-export
// Save the scene to preserve the rotated videos
// Note: Video export (MP4/PNG with video frames) requires the CE.SDK Renderer.
// In headless Node.js mode, we save the scene file which can be loaded later
// in a browser environment or rendered with the CE.SDK Renderer.
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene to file
writeFileSync('output/rotate-videos.scene', sceneString);
console.log('Saved to output/rotate-videos.scene');
// Log final rotation values to verify
console.log('Video rotations:');
console.log(
` Rotated video (45°): ${toDegrees(engine.block.getRotation(rotatedVideo)).toFixed(1)}°`
);
console.log(
` Rotated video (90°): ${toDegrees(engine.block.getRotation(rotatedVideo90)).toFixed(1)}°`
);
console.log(
` Locked video: ${toDegrees(engine.block.getRotation(lockedVideo)).toFixed(1)}° [Rotation locked]`
);
```
## Cleanup
Always dispose the engine to free resources when done:
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Troubleshooting
### Rotation Has No Effect
Verify the block exists in the scene and is not a page block. Check if rotation is locked using `engine.block.isScopeEnabled(block, 'layer/rotate')`.
### Unexpected Rotation Direction
Remember that positive values rotate counterclockwise in CE.SDK. To rotate clockwise, use negative radian values.
## API Reference
| Method | Description |
| ---------------------------------- | --------------------------------------------------------- |
| `CreativeEngine.init()` | Initializes the headless engine for programmatic creation |
| `engine.scene.create()` | Creates a new scene |
| `engine.block.findByType()` | Finds blocks by type |
| `engine.block.setRotation()` | Set block rotation in radians |
| `engine.block.getRotation()` | Get current rotation in radians |
| `engine.block.setScopeEnabled()` | Enable/disable `'layer/rotate'` scope |
| `engine.block.isScopeEnabled()` | Check if rotation is allowed |
| `engine.block.setTransformLocked()`| Lock all transforms including rotation |
| `engine.scene.saveToString()` | Save scene to string for later use |
| `engine.dispose()` | Dispose engine and free resources |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Scale Videos in Node.js"
description: "Scale videos programmatically using the CE.SDK headless engine in Node.js."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/transform/scale-f75c8a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/node-native/edit-video/transform-369f28/) > [Scale](https://img.ly/docs/cesdk/node-native/edit-video/transform/scale-f75c8a/)
---
Scale videos proportionally from different anchor points, stretch along a single axis, or group elements to scale together using the headless CE.SDK engine.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-transform-scale-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-transform-scale-server-js)
## What You'll Learn
- Scale videos through **JavaScript**.
- Scale **proportionally** or non-uniformly.
- **Group** elements to scale them together.
- Apply or lock scaling **constraints** in templates.
## When to Use
Use scaling to:
- **Emphasize** or de-emphasize a clip in a composition.
- **Fit** footage to a free-form layout without cropping.
- Drive zoom gestures or **responsive** designs.
## How Scaling Works
Scaling uses the `scale(block, scale, anchorX, anchorY)` function, with the following **parameters**:
| Parameter | Description | Values |
| ------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| `block` | Handle (ID) of the block to scale. | `number` |
| `scale` | Scale factor to apply. | **1.0** keeps the original size. **>1.0** enlarges the block. **\< 1.0** shrinks it. |
| `anchorX` `anchorY` | Origin point of scale along the width and height | **Top/Left** = 0, **Center** = 0.5, **Bottom/Right** = 1. **Defaults** = `0` |
For example:
- A value of `1.0` sets the original block's size.
- A value of `2.0` makes the block twice as large.
```typescript file=@cesdk_web_examples/guides-create-video-transform-scale-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Scale Videos
*
* Demonstrates video scaling in headless mode:
* - Uniform scaling with different anchor points
* - Non-uniform scaling (width/height independently)
* - Locking transforms to prevent scaling
* - Saving scenes for later rendering
*
* Note: Full video export (MP4) requires the CE.SDK Renderer.
* In headless Node.js mode, we save the scene for later use.
*/
console.log('🚀 Starting CE.SDK Scale Videos Guide...');
console.log('⏳ Initializing engine...');
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
console.log('✓ Engine initialized');
try {
// Create a scene with a page
console.log('⏳ Creating scene...');
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
console.log('✓ Scene created');
// Sample video URL for demonstrations
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
// Centered 2x2 grid layout for 800x500 canvas
// Videos: 120x90, scaled to 180x135
const leftColumnX = 200;
const rightColumnX = 420;
const topRowY = 50;
const bottomRowY = 265;
// For center-scaled video, compensate for position shift
// Center scaling shifts top-left by (-30, -22.5) for 1.5x scale on 120x90
const centerScaleOffsetX = 30;
const centerScaleOffsetY = 22.5;
// Demo 1: Uniform scaling from default top-left anchor
console.log('⏳ Adding uniform scaled video...');
const uniformVideo = await engine.block.addVideo(videoUri, 120, 90);
engine.block.appendChild(page, uniformVideo);
engine.block.setPositionX(uniformVideo, leftColumnX);
engine.block.setPositionY(uniformVideo, topRowY);
// Scale the video to 150% from the default top-left anchor
engine.block.scale(uniformVideo, 1.5);
console.log('✓ Uniform scaled video added (150% from top-left)');
// Add label for uniform scaled video
const text1 = engine.block.create('text');
engine.block.setString(text1, 'text/text', 'Uniform Scale (150%)');
engine.block.setFloat(text1, 'text/fontSize', 16);
engine.block.setWidth(text1, 200);
engine.block.setPositionX(text1, leftColumnX);
engine.block.setPositionY(text1, topRowY + 145);
engine.block.appendChild(page, text1);
// Demo 2: Scaling from center anchor
console.log('⏳ Adding center-scaled video...');
const centerVideo = await engine.block.addVideo(videoUri, 120, 90);
engine.block.appendChild(page, centerVideo);
// Position compensates for center scaling shift so final position aligns with grid
engine.block.setPositionX(centerVideo, rightColumnX + centerScaleOffsetX);
engine.block.setPositionY(centerVideo, topRowY + centerScaleOffsetY);
// Scale from center anchor (0.5, 0.5)
engine.block.scale(centerVideo, 1.5, 0.5, 0.5);
console.log('✓ Center-scaled video added (150% from center)');
// Add label for center scaled video
const text2 = engine.block.create('text');
engine.block.setString(text2, 'text/text', 'Center Scale (150%)');
engine.block.setFloat(text2, 'text/fontSize', 16);
engine.block.setWidth(text2, 200);
engine.block.setPositionX(text2, rightColumnX);
engine.block.setPositionY(text2, topRowY + 145);
engine.block.appendChild(page, text2);
// Demo 3: Non-uniform scaling (width only)
console.log('⏳ Adding width-stretched video...');
const stretchVideo = await engine.block.addVideo(videoUri, 120, 90);
engine.block.appendChild(page, stretchVideo);
engine.block.setPositionX(stretchVideo, leftColumnX);
engine.block.setPositionY(stretchVideo, bottomRowY);
// Stretch only the width by 1.5x
engine.block.setWidthMode(stretchVideo, 'Absolute');
const currentWidth = engine.block.getWidth(stretchVideo);
engine.block.setWidth(stretchVideo, currentWidth * 1.5, true);
console.log('✓ Width-stretched video added (150% width only)');
// Add label for stretched video
const text3 = engine.block.create('text');
engine.block.setString(text3, 'text/text', 'Width Stretch (150%)');
engine.block.setFloat(text3, 'text/fontSize', 16);
engine.block.setWidth(text3, 200);
engine.block.setPositionX(text3, leftColumnX);
engine.block.setPositionY(text3, bottomRowY + 145);
engine.block.appendChild(page, text3);
// Demo 4: Locked scaling
console.log('⏳ Adding transform-locked video...');
const lockedVideo = await engine.block.addVideo(videoUri, 120, 90);
engine.block.appendChild(page, lockedVideo);
engine.block.setPositionX(lockedVideo, rightColumnX);
engine.block.setPositionY(lockedVideo, bottomRowY);
// Lock all transforms to prevent scaling
engine.block.setTransformLocked(lockedVideo, true);
console.log('✓ Transform-locked video added');
// Add label for locked video
const text4 = engine.block.create('text');
engine.block.setString(text4, 'text/text', 'Transform Locked');
engine.block.setFloat(text4, 'text/fontSize', 16);
engine.block.setWidth(text4, 200);
engine.block.setPositionX(text4, rightColumnX);
engine.block.setPositionY(text4, bottomRowY + 145);
engine.block.appendChild(page, text4);
// Save the scene to preserve the scaled videos
// Note: Video export (MP4/PNG with video frames) requires the CE.SDK Renderer.
// In headless Node.js mode, we save the scene file which can be loaded later
// in a browser environment or rendered with the CE.SDK Renderer.
console.log('⏳ Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene to .scene file
writeFileSync('output/scale-videos.scene', sceneString);
console.log('✓ Scene saved to output/scale-videos.scene');
// Log scale information to verify
console.log('\n📊 Video scaling results:');
console.log(
` Uniform video: ${engine.block.getWidth(uniformVideo).toFixed(1)}x${engine.block.getHeight(uniformVideo).toFixed(1)}`
);
console.log(
` Center video: ${engine.block.getWidth(centerVideo).toFixed(1)}x${engine.block.getHeight(centerVideo).toFixed(1)}`
);
console.log(
` Stretched video: ${engine.block.getWidth(stretchVideo).toFixed(1)}x${engine.block.getHeight(stretchVideo).toFixed(1)}`
);
console.log(` Locked video: Transform locked = true`);
console.log('\n✅ Scale videos guide completed successfully!');
} finally {
// Always dispose the engine to free resources
console.log('⏳ Disposing engine...');
engine.dispose();
console.log('✓ Engine disposed');
}
```
## Initialize Headless Engine
Create a headless engine instance for programmatic video manipulation:
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Create the Scene
Create a scene with specific page dimensions:
```typescript highlight-create-scene
// Create a scene with a page
console.log('⏳ Creating scene...');
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 500);
engine.block.appendChild(scene, page);
```
## Scale a Video Uniformly
To change the clip size without distorting its proportions, use uniform scaling. Uniform scaling multiplies both width and height by the **same factor** to keep the frame's aspect ratio intact.
Scaling a video uniformly allows you to:
- Enlarge or shrink footage without altering the content.
- Maintain per-pixel sharpness.
- Align with layout constraints.
CE.SDK lets you use the same high-level API on all graphic blocks, videos included. To scale any block, use `engine.block.scale()`:
```typescript highlight-uniform-scale
const uniformVideo = await engine.block.addVideo(videoUri, 120, 90);
engine.block.appendChild(page, uniformVideo);
engine.block.setPositionX(uniformVideo, leftColumnX);
engine.block.setPositionY(uniformVideo, topRowY);
// Scale the video to 150% from the default top-left anchor
engine.block.scale(uniformVideo, 1.5);
```
The preceding code:
- Scales the video to 150% of its original size.
- Doesn't change the origin anchor point.
As a result, the video expands down and to the right.
### Anchor Point
The anchor point is the point around which a layer scales. All changes happen around the anchor's point position. By default, any block's anchor point is **the top left**.
To **change the anchor point**, the scale function has two optional parameters:
- `anchorX` to move the anchor point along the width.
- `anchorY` to move the anchor point along the height.
Both can have values between 0.0 and 1.0. For example, to scale from the center:
```typescript highlight-center-scale
const centerVideo = await engine.block.addVideo(videoUri, 120, 90);
engine.block.appendChild(page, centerVideo);
// Position compensates for center scaling shift so final position aligns with grid
engine.block.setPositionX(centerVideo, rightColumnX + centerScaleOffsetX);
engine.block.setPositionY(centerVideo, topRowY + centerScaleOffsetY);
// Scale from center anchor (0.5, 0.5)
engine.block.scale(centerVideo, 1.5, 0.5, 0.5);
```
This function:
1. Scales the video to **150%** of its original size.
2. Sets the origin anchor point at the center with `0.5, 0.5`.
This way, the video expands from the center equally in all directions.
## Scale Videos Non-Uniformly
You might need to stretch a video only horizontally or vertically. To stretch or compress only one axis, thus distorting a video, use the **width** or **height** functions.
```typescript highlight-nonuniform-scale
const stretchVideo = await engine.block.addVideo(videoUri, 120, 90);
engine.block.appendChild(page, stretchVideo);
engine.block.setPositionX(stretchVideo, leftColumnX);
engine.block.setPositionY(stretchVideo, bottomRowY);
// Stretch only the width by 1.5x
engine.block.setWidthMode(stretchVideo, 'Absolute');
const currentWidth = engine.block.getWidth(stretchVideo);
engine.block.setWidth(stretchVideo, currentWidth * 1.5, true);
```
The preceding code:
1. Sets the width mode to `'Absolute'` to edit the video using a fixed pixel value instead of a relative layout mode.
2. Reads the current width.
3. Multiplies it by 1.5 to compute a new width that's 150% of the original.
4. Writes the new width back to the block with `maintainCrop` set to `true`.
Use this to:
- Create panoramic crops.
- Compensate for aspect ratios during automation.
### Respect the Existing Crop
The crop defines which part of the clip stays visible. Stretching the block without preserving its crop might:
- Reveal unwanted areas.
- Cut off the focal point.
The `maintainCrop` parameter (third argument to `setWidth`) keeps the visible region intact and avoids distortion. Consider using `maintainCrop` if a **template** already uses cropping to frame a subject or hide a watermark.
## Scale Clips Together
Grouping blocks is a useful way of scaling them proportionally. Use `engine.block.group()` to combine blocks into a group, then scale the group as a single unit:
```typescript
const groupId = engine.block.group([videoBlockId, textBlockId]);
engine.block.scale(groupId, 1.5, 0.5, 0.5);
```
The preceding code scales the entire group to 150% from the center anchor.
> **Warning:** You can't group `page` with other blocks. Group elements on the **top** of the page, **not** with the page itself.
## Lock Scaling in Templates
To preserve a template's layout, consider locking the scaling option. This is useful for:
- Brand assets
- Campaign creatives
- Collaboration workflows
- Fixed dimensions swapping editors
```typescript highlight-locked-scale
const lockedVideo = await engine.block.addVideo(videoUri, 120, 90);
engine.block.appendChild(page, lockedVideo);
engine.block.setPositionX(lockedVideo, rightColumnX);
engine.block.setPositionY(lockedVideo, bottomRowY);
// Lock all transforms to prevent scaling
engine.block.setTransformLocked(lockedVideo, true);
```
### Disable Resize Scope
Disable the `layer/resize` scope when working with templates to **prevent users from scaling** blocks:
```typescript
engine.block.setScopeEnabled(blockId, 'layer/resize', false);
```
### Lock All Transformations
To **lock** all transformations (move, resize, rotate), use `setTransformLocked`:
```typescript
engine.block.setTransformLocked(blockId, true);
```
To check if scaling is currently allowed:
```typescript
const canResize = engine.block.isScopeEnabled(blockId, 'layer/resize');
console.log('layer/resize scope enabled?', canResize);
```
## Save Scene
Save the scene to a file. The scene can later be loaded in a browser environment or rendered with the CE.SDK Renderer for full video export:
```typescript highlight-export
// Save the scene to preserve the scaled videos
// Note: Video export (MP4/PNG with video frames) requires the CE.SDK Renderer.
// In headless Node.js mode, we save the scene file which can be loaded later
// in a browser environment or rendered with the CE.SDK Renderer.
console.log('⏳ Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save scene to .scene file
writeFileSync('output/scale-videos.scene', sceneString);
console.log('✓ Scene saved to output/scale-videos.scene');
// Log scale information to verify
console.log('\n📊 Video scaling results:');
console.log(
` Uniform video: ${engine.block.getWidth(uniformVideo).toFixed(1)}x${engine.block.getHeight(uniformVideo).toFixed(1)}`
);
console.log(
` Center video: ${engine.block.getWidth(centerVideo).toFixed(1)}x${engine.block.getHeight(centerVideo).toFixed(1)}`
);
console.log(
` Stretched video: ${engine.block.getWidth(stretchVideo).toFixed(1)}x${engine.block.getHeight(stretchVideo).toFixed(1)}`
);
console.log(` Locked video: Transform locked = true`);
```
## Cleanup
Always dispose the engine to free resources when done:
```typescript highlight-cleanup
// Always dispose the engine to free resources
console.log('⏳ Disposing engine...');
engine.dispose();
console.log('✓ Engine disposed');
```
## Troubleshooting
### Video Not Scaling
Check if transforms are locked using `engine.block.isTransformLocked(block)`. Ensure the block exists and is a valid design block.
### Unexpected Position After Scale
Verify the anchor point coordinates. Default anchor (0, 0) causes expansion to the right and down. Use (0.5, 0.5) for center-based scaling.
### Crop Region Shifting
When using `setWidth` or `setHeight`, pass `true` as the third parameter to maintain the crop region.
## Recap
| Usage | How To |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| Uniform scaling | `engine.block.scale(blockId, scaleFactor)` + optional anchor |
| Stretching an axis | Set width mode to `'Absolute'`, then use `setWidth()` or `setHeight()` |
| Group scaling | 1. Group with `engine.block.group([blockId_1, blockId_2])` 2. Scale the group |
| Constraints | Adjust scopes or lock transforms to protect templates |
## API Reference
| API | Usage |
| ---------------------------- | ------------------------------------------------------------------ |
| `CreativeEngine.init` | Initializes the headless engine for programmatic creation. |
| `scene.create` | Creates a new scene. |
| `block.scale` | Performs uniform or anchored scaling on blocks and groups. |
| `block.setWidthMode` | Enables absolute sizing before changing a single axis. |
| `block.getWidth` | Reads the current width before non-uniform scaling. |
| `block.setWidth` | Writes the adjusted width after single-axis scaling. |
| `block.setHeightMode` | Enables absolute sizing for height changes. |
| `block.getHeight` | Reads the current height before non-uniform scaling. |
| `block.setHeight` | Writes the adjusted height after single-axis scaling. |
| `block.group` | Group blocks so they scale together. |
| `block.setScopeEnabled` | Toggles the `layer/resize` scope to lock scaling in templates. |
| `block.setTransformLocked` | Locks all transform scopes when templates must stay fixed. |
| `block.isScopeEnabled` | Checks whether scaling is currently permitted on a block. |
| `block.isTransformLocked` | Checks whether all transforms are locked on a block. |
| `scene.saveToString` | Saves the scene for later use or rendering. |
| `engine.dispose` | Disposes the engine and frees resources. |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Trim Video Clips"
description: "Learn how to trim video clips programmatically using CE.SDK's Engine API in headless mode."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/edit-video/trim-4f688b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) > [Trim](https://img.ly/docs/cesdk/node-native/edit-video/trim-4f688b/)
---
Control video playback timing by trimming clips to specific start points and
durations using CE.SDK's programmatic trim API in headless mode.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-create-video-trim-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-create-video-trim-server-js)
Understanding the difference between **fill-level trimming** and **block-level timing** is key. 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 the composition. These two systems work together to give you complete control over video playback.
```typescript file=@cesdk_web_examples/guides-create-video-trim-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Trim Video and Audio
*
* Demonstrates trimming video clips in headless mode:
* - Loading video resources with forceLoadAVResource
* - Basic video trimming with setTrimOffset/setTrimLength
* - Getting current trim values
* - Getting source media duration
* - Coordinating trim with block duration
* - Trimming with looping enabled
* - Checking trim support
* - Frame-accurate trimming
* - Batch trimming multiple videos
* - Saving the scene for later use
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1280);
engine.block.setHeight(page, 720);
engine.block.appendChild(scene, page);
// Set page duration to accommodate all demonstrations
engine.block.setDuration(page, 30);
// Sample video URL for demonstrations
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
// Block size for layout
const blockSize = { width: 320, height: 180 };
// Create a video block to demonstrate trim support checking
const sampleVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
// 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
// Load the video resource before accessing trim properties
// This ensures metadata (duration, frame rate, etc.) is available
await engine.block.forceLoadAVResource(videoFill);
// Get the total duration of the source video
const totalDuration = engine.block.getAVResourceTotalDuration(videoFill);
console.log('Total video duration:', totalDuration, 'seconds');
// Create a video block for basic trim demonstration
const basicTrimVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
// 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);
console.log('Basic trim applied: offset 2s, length 5s');
// Get current trim values to verify or modify
const currentOffset = engine.block.getTrimOffset(basicTrimFill);
const currentLength = engine.block.getTrimLength(basicTrimFill);
console.log(
`Current trim values - Offset: ${currentOffset}s, Length: ${currentLength}s`
);
// Get the total duration of source media to validate trim values
const sourceDuration = engine.block.getAVResourceTotalDuration(basicTrimFill);
console.log('Source media duration:', sourceDuration, 'seconds');
// Validate trim doesn't exceed source length
const maxTrimLength = sourceDuration - currentOffset;
console.log('Maximum trim length from current offset:', maxTrimLength, 's');
// Create a video block demonstrating trim + duration coordination
const durationTrimVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
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');
// Create a video block with trim + looping
const loopingTrimVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
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');
// Create a video block for frame-accurate trimming
const frameAccurateTrimVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
const frameFill = engine.block.getFill(frameAccurateTrimVideo);
await engine.block.forceLoadAVResource(frameFill);
// For frame-accurate trimming, assume a common frame rate (e.g., 30fps)
// In production, you may know the frame rate from your video metadata
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, Start: ${startFrame}, Duration: ${trimFrames} frames`
);
// Batch process multiple video clips with consistent 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 (const uri of batchVideoUris) {
const batchVideo = await engine.block.addVideo(
uri,
blockSize.width,
blockSize.height
);
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 ${batchVideos.length} videos`
);
// ===== Position all blocks in grid layout =====
const spacing = 20;
const margin = 40;
const cols = 3;
const getPosition = (index: number) => ({
x: margin + (index % cols) * (blockSize.width + spacing),
y: margin + Math.floor(index / cols) * (blockSize.height + spacing)
});
const blocks = [
sampleVideo,
basicTrimVideo,
durationTrimVideo,
loopingTrimVideo,
frameAccurateTrimVideo,
...batchVideos
];
blocks.forEach((block, index) => {
const pos = getPosition(index);
engine.block.setPositionX(block, pos.x);
engine.block.setPositionY(block, pos.y);
});
// Save the scene as a .scene file for later use or rendering
// This preserves all trim settings and can be loaded in any CE.SDK environment
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/trimmed-video.scene', sceneString);
console.log('Exported to output/trimmed-video.scene');
console.log('');
console.log('Video trim guide completed successfully.');
console.log('Scene saved with:');
console.log(' - 8 video blocks with various trim configurations');
console.log(' - Basic trim, duration coordination, looping, and batch trim');
console.log(' - Ready for rendering with CE.SDK Renderer');
} finally {
engine.dispose();
}
```
This guide covers how to trim videos programmatically using the Engine API in a headless Node.js environment.
## Understanding Trim Concepts
### 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.
> **Note:** 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 is separate from trimming and controls when and how long a block exists in the composition. `setTimeOffset` determines when the block becomes active in the composition (useful for track-based layouts). `setDuration` controls how long the block appears in the composition.
The *trim* controls what plays from the source media, while the *duration* controls how long that playback appears in the composition. 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
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
## Programmatic Video Trimming
### Initialize CE.SDK
For headless video processing, we initialize CE.SDK's Node.js engine. This provides full API access to the trimming system without browser dependencies, making it ideal for server-side automation and batch processing.
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
The headless engine gives you complete control over video trimming operations, perfect for automated workflows, background processing, and server-side video preparation.
### Loading Video Resources
Before accessing trim properties or setting trim values, we must load the video resource metadata using `forceLoadAVResource`. This step ensures CE.SDK has information about the video's duration and other properties needed for accurate trimming.
```typescript highlight-load-video-resource
// Load the video resource before accessing trim properties
// This ensures metadata (duration, frame rate, etc.) is available
await engine.block.forceLoadAVResource(videoFill);
// Get the total duration of the source video
const totalDuration = engine.block.getAVResourceTotalDuration(videoFill);
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 or produce unexpected results. Always await `forceLoadAVResource` before calling any trim methods.
Once loaded, we can access metadata like `totalDuration` 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
Before applying trim operations, we verify that a block supports trimming. While video fills typically support trimming, other block types like pages and scenes do not.
```typescript highlight-check-trim-support
// Create a video block to demonstrate trim support checking
const sampleVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
// 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
```
Checking support prevents runtime errors and allows you to build robust applications that only apply trim operations to compatible blocks. Graphic blocks with video fills also support trimming, not just top-level video blocks.
### 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.
```typescript highlight-basic-video-trim
// Create a video block for basic trim demonstration
const basicTrimVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
// 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);
console.log('Basic trim applied: offset 2s, length 5s');
```
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
We can retrieve the current trim settings to verify values, make relative adjustments, or log processing information.
```typescript highlight-get-trim-values
// Get current trim values to verify or modify
const currentOffset = engine.block.getTrimOffset(basicTrimFill);
const currentLength = engine.block.getTrimLength(basicTrimFill);
console.log(
`Current trim values - Offset: ${currentOffset}s, Length: ${currentLength}s`
);
```
These getter methods return the current trim offset and length in seconds. Use them to validate settings before export or to calculate remaining media duration.
### Getting Source Media Duration
Use `getAVResourceTotalDuration()` to determine the total duration of the source media file. This helps validate that trim values don't exceed available media.
```typescript highlight-get-source-duration
// Get the total duration of source media to validate trim values
const sourceDuration = engine.block.getAVResourceTotalDuration(basicTrimFill);
console.log('Source media duration:', sourceDuration, 'seconds');
// Validate trim doesn't exceed source length
const maxTrimLength = sourceDuration - currentOffset;
console.log('Maximum trim length from current offset:', maxTrimLength, 's');
```
Always check the source duration when processing videos of unknown length. This prevents setting trim offsets beyond the video's end and ensures your trim length doesn't exceed available content.
## Additional Trimming Techniques
### Trimming with Block Duration
Trim length and block duration interact to determine playback behavior. When block duration equals trim length, the entire trimmed portion plays exactly once.
```typescript highlight-trim-with-duration
// Create a video block demonstrating trim + duration coordination
const durationTrimVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
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');
```
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
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.
```typescript highlight-trim-with-looping
// Create a video block with trim + looping
const loopingTrimVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
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');
```
The 3-second trimmed segment loops 3 times to fill the 9-second duration. This technique is useful 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. Use `isLooping()` to check the current looping state.
### Frame-Accurate Trimming
For precise editing, we can trim to specific frame boundaries rather than arbitrary time values. Using the video's frame rate, we calculate exact frame-based trim points.
```typescript highlight-frame-accurate-trim
// Create a video block for frame-accurate trimming
const frameAccurateTrimVideo = await engine.block.addVideo(
videoUri,
blockSize.width,
blockSize.height
);
const frameFill = engine.block.getFill(frameAccurateTrimVideo);
await engine.block.forceLoadAVResource(frameFill);
// For frame-accurate trimming, assume a common frame rate (e.g., 30fps)
// In production, you may know the frame rate from your video metadata
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, Start: ${startFrame}, Duration: ${trimFrames} frames`
);
```
Convert frame numbers to time offsets by dividing by the frame rate. Starting at frame 60 with a 30fps video gives exactly 2.0 seconds. Trimming for 150 frames provides exactly 5.0 seconds of playback.
This technique ensures frame-accurate edits for professional video processing workflows. Keep in mind that codec compression may affect true frame accuracy—test with your target codecs to verify precision.
### Batch Processing Multiple Videos
When working with multiple video clips that need consistent trimming, iterate through collections and apply the same trim settings programmatically.
```typescript highlight-batch-trim-videos
// Batch process multiple video clips with consistent 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 (const uri of batchVideoUris) {
const batchVideo = await engine.block.addVideo(
uri,
blockSize.width,
blockSize.height
);
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 ${batchVideos.length} videos`
);
```
We create multiple video blocks and apply identical trim settings to each one. This ensures consistency across clips—useful 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. Check total duration to ensure your trim values don't exceed available media.
## Exporting Results
After applying trim settings, export the processed content to a file. In headless mode, we export a PNG snapshot to verify the trim configuration.
```typescript highlight-export
// Save the scene as a .scene file for later use or rendering
// This preserves all trim settings and can be loaded in any CE.SDK environment
console.log('Saving scene...');
const sceneString = await engine.scene.saveToString();
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/trimmed-video.scene', sceneString);
console.log('Exported to output/trimmed-video.scene');
```
The export operation renders all video blocks at their current trim positions and saves the result to the file system. For full video encoding and export, use the browser SDK with video export capabilities. Always dispose of the engine instance when processing is complete to free resources.
## Trim vs Duration Interaction
### 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
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 playback 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 automated pipelines, always coordinate trim and duration values. 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
CE.SDK's headless engine is optimized for server-side processing, but keep these factors in mind:
- **Resource loading**: `forceLoadAVResource` has network and processing overhead. Load resources once, then apply trim operations as needed.
- **Trim adjustments**: Changing trim values is lightweight—CE.SDK updates the playback range without reprocessing the video.
- **Memory usage**: Each loaded video consumes memory. For batch processing many videos, consider processing in smaller batches.
- **Long videos**: Very long source videos (30+ minutes) may have slower seeking to trim offsets.
Test your trim operations with representative workloads to ensure acceptable performance for your use case.
## Troubleshooting
### 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 composition, while `setTrimOffset` controls where in the source media playback starts.
### 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 `getAVResourceTotalDuration` to understand the available media length.
Also check that trim offset plus trim length doesn't exceed total duration. CE.SDK may clamp values automatically, but validating before setting prevents unexpected behavior.
### 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
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
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.
### Export Issues
If export fails or produces unexpected output:
- Verify all video resources loaded successfully before export
- Check that trim values are within valid ranges
- Ensure sufficient disk space for output files
- For large videos, monitor memory usage during export
## Best Practices
### 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. Validate trim values don't exceed total media duration
6. Dispose of engine instances when processing is complete
### 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
- Avoid redundant `forceLoadAVResource` calls—load once, trim multiple times
- Process videos in batches to manage memory usage
- Consider parallel processing for independent trim operations
- Test on target environments early to identify performance bottlenecks
## 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` |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Engine Interface"
description: "Understand CE.SDK's architecture and learn when to use direct Engine access for automation workflows"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/engine-interface-6fb7cf/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Engine](https://img.ly/docs/cesdk/node-native/engine-interface-6fb7cf/)
---
Access CE.SDK's cross-platform C++ engine programmatically for server-side automation, batch processing, and high-resolution exports in Node.js.
The `@cesdk/node` package provides the same Engine API compiled for Node.js. The API is identical to `@cesdk/engine` in the browser, so you can share code between client and server.
## Engine API Namespaces
The Engine organizes its functionality into six namespaces:
- **engine.block**: Create, modify, and export design elements (shapes, text, images, videos)
- **engine.scene**: Load, save, and manage scenes and pages
- **engine.asset**: Register and query asset sources (images, templates, fonts)
- **engine.editor**: Configure editor settings, manage edit modes, handle undo/redo
- **engine.variable**: Define and update template variables for data merge
- **engine.event**: Subscribe to engine events (selection changes, state updates)
## Basic Usage
```javascript
const { CreativeEngine } = require('@cesdk/node');
const engine = await CreativeEngine.init({
license: process.env.CESDK_LICENSE,
});
try {
await engine.scene.loadFromURL('https://example.com/template.scene');
const page = engine.scene.getPages()[0];
const blob = await engine.block.export(page, 'image/png', {
targetWidth: 3840,
targetHeight: 2160,
dpi: 300,
});
// Save blob to file or storage
} finally {
engine.dispose();
}
```
## Server-Side Benefits
**Resources**: Access more CPU, memory, and storage than client devices for print-quality exports.
**Secure Assets**: Process private templates and assets without exposing them to clients.
**Background Operations**: Handle long-running tasks without blocking users.
## Hybrid Workflows
Users design on the client with instant feedback. Send the scene to your server for high-resolution export:
```javascript
// Client: Save scene and send to server
const sceneData = await cesdk.engine.scene.saveToString();
// Server: Load and export at high resolution
const engine = await CreativeEngine.init({ license: process.env.CESDK_LICENSE });
try {
await engine.scene.loadFromString(sceneData);
const blob = await engine.block.export(engine.scene.getPages()[0], 'image/png', {
targetWidth: 3840,
targetHeight: 2160,
dpi: 300,
});
} finally {
engine.dispose();
}
```
## Troubleshooting
**Engine not initialized**: Ensure `CreativeEngine.init()` completes before calling other methods.
**Memory issues**: Dispose Engine instances after each use with `engine.dispose()`.
**License errors**: Verify `CESDK_LICENSE` environment variable is set correctly.
## Next Steps
- [Automation Overview](https://img.ly/docs/cesdk/node-native/automation/overview-34d971/) for workflow examples
- [Data Merge](https://img.ly/docs/cesdk/node-native/automation/data-merge-ae087c/) for template personalization
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Thumbnail"
description: "Generate thumbnail preview images from CE.SDK scenes by exporting with target dimensions for galleries, file browsers, and design management interfaces."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/create-thumbnail-749be1/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [Create Thumbnail](https://img.ly/docs/cesdk/node-native/export-save-publish/create-thumbnail-749be1/)
---
Generate thumbnail preview images from CE.SDK scenes by exporting with target dimensions for galleries and design management.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-create-thumbnail-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-create-thumbnail-server-js)
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.
```typescript file=@cesdk_web_examples/guides-export-save-publish-create-thumbnail-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { createInterface } from 'readline';
config();
// Helper function to prompt for user input
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
// Display thumbnail options menu
console.log('=== Thumbnail Generation Options ===\n');
console.log('1. Small thumbnail (150×150 JPEG)');
console.log('2. Medium thumbnail (400×300 JPEG)');
console.log('3. PNG thumbnail (400×300)');
console.log('4. WebP thumbnail (400×300)');
console.log('5. All formats\n');
const choice = (await prompt('Select thumbnail option (1-5): ')) || '5';
console.log('\n⏳ Initializing engine...');
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.block.findByType('page')[0];
if (!page) throw new Error('No page found');
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
console.log('⏳ Generating thumbnails...\n');
if (choice === '1' || choice === '5') {
const blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
targetWidth: 150,
targetHeight: 150,
jpegQuality: 0.8
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/thumbnail-small.jpg`, buffer);
console.log(
`✓ Small thumbnail: ${outputDir}/thumbnail-small.jpg (${(blob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '2' || choice === '5') {
const blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
targetWidth: 400,
targetHeight: 300,
jpegQuality: 0.85
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/thumbnail-medium.jpg`, buffer);
console.log(
`✓ Medium thumbnail: ${outputDir}/thumbnail-medium.jpg (${(blob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '3' || choice === '5') {
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 400,
targetHeight: 300,
pngCompressionLevel: 6
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/thumbnail.png`, buffer);
console.log(
`✓ PNG thumbnail: ${outputDir}/thumbnail.png (${(blob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '4' || choice === '5') {
const blob = await engine.block.export(page, {
mimeType: 'image/webp',
targetWidth: 400,
targetHeight: 300,
webpQuality: 0.8
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/thumbnail.webp`, buffer);
console.log(
`✓ WebP thumbnail: ${outputDir}/thumbnail.webp (${(blob.size / 1024).toFixed(1)} KB)`
);
}
console.log('\n✓ Thumbnail generation completed');
} finally {
engine.dispose();
}
```
This guide covers exporting thumbnails at specific dimensions, choosing formats, optimizing quality and file size, and saving thumbnails to the file system.
## 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.
```typescript highlight=highlight-thumbnail-small
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
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 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.
```typescript highlight=highlight-thumbnail-medium
const blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
targetWidth: 400,
targetHeight: 300,
jpegQuality: 0.85
});
```
### PNG Thumbnails
PNG provides lossless quality with transparency support. Control encoding speed vs. file size with `pngCompressionLevel` (0-9, default 5).
```typescript highlight=highlight-thumbnail-png
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 400,
targetHeight: 300,
pngCompressionLevel: 6
});
```
### WebP Thumbnails
WebP offers the best compression for modern browsers. Control quality with `webpQuality` (0-1, default 1.0 for lossless).
```typescript highlight=highlight-thumbnail-webp
const blob = await engine.block.export(page, {
mimeType: 'image/webp',
targetWidth: 400,
targetHeight: 300,
webpQuality: 0.8
});
```
## 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 |
## Save to File System
After exporting, convert the Blob to a Buffer and write to the file system. Create the output directory if it doesn't exist.
```typescript
const blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
targetWidth: 400,
targetHeight: 300,
jpegQuality: 0.8
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output/thumbnail.jpg', buffer);
```
## 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
| Method | Description |
| ------ | ----------- |
| `engine.block.export(blockId, options)` | Export a block as image with format and dimension options |
| `engine.block.findByType('page')` | Find all pages in the current scene |
| `engine.dispose()` | Clean up engine resources when done |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Export"
description: "Explore export options, supported formats, and configuration features for sharing or rendering output."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/)
---
---
## Related Pages
- [Options](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Explore export options, supported formats, and configuration features for sharing or rendering output.
- [Export for Social Media](https://img.ly/docs/cesdk/node-native/export-save-publish/for-social-media-0e8a92/) - Export images with Instagram portrait dimensions and quality settings using CE.SDK in Node.js.
- [For Audio Processing](https://img.ly/docs/cesdk/node-native/guides/export-save-publish/export/audio-68de25/) - Learn how to export audio in WAV or MP4 format from any block type in CE.SDK for Node.js server environments.
- [To PDF](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-pdf-95e04b/) - Export your designs as PDF documents with options for print compatibility, underlayer generation, and output control.
- [To JPEG](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-jpeg-6f88e9/) - Export CE.SDK designs to JPEG format with configurable quality settings for photographs, web images, and social media content.
- [To PNG](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-png-f87eaf/) - Export your designs as PNG images with transparency support and configurable compression for web graphics, UI elements, and content requiring crisp edges.
- [To WebP](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-webp-aef6f4/) - Export your CE.SDK designs to WebP format for optimized web delivery with lossy and lossless compression options.
- [Export to Raw Data](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-raw-data-abd7da/) - Export designs to uncompressed RGBA pixel data for custom image processing, server-side rendering pipelines, and integration with machine learning workflows in Node.js.
- [Compress](https://img.ly/docs/cesdk/node-native/export-save-publish/export/compress-29105e/) - Reduce file sizes when exporting images by configuring compression and quality settings for PNG, JPEG, and WebP formats.
- [Export with Color Mask](https://img.ly/docs/cesdk/node-native/export-save-publish/export/with-color-mask-4f868f/) - Export design blocks with color masking in CE.SDK for Node.js, removing specific colors and generating alpha masks for print workflows and automation.
- [Pre-Export Validation](https://img.ly/docs/cesdk/node-native/export-save-publish/pre-export-validation-3a2cba/) - Documentation for Pre-Export Validation
- [Partial Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export/partial-export-89aaf6/) - Documentation for Partial Export
- [Size Limits](https://img.ly/docs/cesdk/node-native/export-save-publish/export/size-limits-6f0695/) - Configure and understand CE.SDK's image and video size limits in Node.js server environments to optimize performance and memory usage in headless workflows.
- [Create Thumbnail](https://img.ly/docs/cesdk/node-native/export-save-publish/create-thumbnail-749be1/) - Generate thumbnail preview images from CE.SDK scenes by exporting with target dimensions for galleries, file browsers, and design management interfaces.
- [Export for Printing](https://img.ly/docs/cesdk/node-native/export-save-publish/for-printing-bca896/) - Export designs from CE.SDK as print-ready PDFs with professional output options including high compatibility mode, underlayers for special media, and scene DPI configuration.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Compress"
description: "Reduce file sizes when exporting images by configuring compression and quality settings for PNG, JPEG, and WebP formats."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/compress-29105e/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [Compress](https://img.ly/docs/cesdk/node-native/export-save-publish/export/compress-29105e/)
---
Reduce file sizes when exporting images by configuring compression and quality settings for PNG, JPEG, and WebP formats.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-compress-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-compress-server-js)
Image compression reduces file sizes while maintaining acceptable visual quality. CE.SDK supports format-specific compression controls: lossless compression for PNG, lossy quality settings for JPEG, and both modes for WebP.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-compress-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { createInterface } from 'readline';
config();
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
// Display export options menu
console.log('=== Compression Export Options ===\n');
console.log('1. PNG with compression levels');
console.log('2. JPEG with quality settings');
console.log('3. WebP with quality settings');
console.log('4. All formats\n');
const choice = (await prompt('Select export option (1-4): ')) || '4';
console.log('\n⏳ Initializing engine...');
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.block.findByType('page')[0];
if (page == null) throw new Error('No page found');
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
console.log('⏳ Exporting...\n');
if (choice === '1' || choice === '4') {
const pngBlob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9
});
const buffer = Buffer.from(await pngBlob.arrayBuffer());
writeFileSync(`${outputDir}/compressed.png`, buffer);
console.log(
`✓ PNG (level 9): ${outputDir}/compressed.png (${(pngBlob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '2' || choice === '4') {
const jpegBlob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 0.8
});
const buffer = Buffer.from(await jpegBlob.arrayBuffer());
writeFileSync(`${outputDir}/compressed.jpg`, buffer);
console.log(
`✓ JPEG (quality 0.8): ${outputDir}/compressed.jpg (${(jpegBlob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '3' || choice === '4') {
const webpBlob = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: 0.8
});
const buffer = Buffer.from(await webpBlob.arrayBuffer());
writeFileSync(`${outputDir}/compressed.webp`, buffer);
console.log(
`✓ WebP (quality 0.8): ${outputDir}/compressed.webp (${(webpBlob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '4') {
const scaledBlob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1200,
targetHeight: 630
});
const buffer = Buffer.from(await scaledBlob.arrayBuffer());
writeFileSync(`${outputDir}/scaled.png`, buffer);
console.log(
`✓ Scaled (1200x630): ${outputDir}/scaled.png (${(scaledBlob.size / 1024).toFixed(1)} KB)`
);
}
console.log('\n✓ Export completed');
} finally {
engine.dispose();
}
```
This guide covers exporting with compression settings, configuring quality levels, and controlling output dimensions.
## Export with Compression
Call `engine.block.export()` with format-specific compression options. Each format uses different parameters to control file size and quality.
### PNG Compression
PNG uses lossless compression controlled by `pngCompressionLevel` (0-9). Higher values produce smaller files but take longer to encode. Quality remains identical at all levels.
```typescript highlight=highlight-png-compression
const pngBlob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9
});
```
Use level 5-6 for balanced results, or level 9 when file size is critical and encoding time is acceptable.
### JPEG Quality
JPEG uses lossy compression controlled by `jpegQuality` (0-1). Lower values produce smaller files with more visible artifacts.
```typescript highlight=highlight-jpeg-quality
const jpegBlob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 0.8
});
```
Quality 0.8 provides a good balance for web delivery. Use 0.9+ for archival or print workflows.
### WebP Quality
WebP supports both lossless and lossy modes via `webpQuality` (0-1). At 1.0, WebP uses lossless encoding. Values below 1.0 enable lossy compression.
```typescript highlight=highlight-webp-quality
const webpBlob = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: 0.8
});
```
WebP typically produces 20-30% smaller files than JPEG at equivalent quality, with optional transparency support.
## Export Options
All image exports support these compression-related options:
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `pngCompressionLevel` | `number` | `5` | PNG compression level 0-9. Higher values produce smaller files but take longer. Quality is unaffected. |
| `jpegQuality` | `number` | `0.9` | JPEG quality from >0 to 1. Higher values preserve more detail. |
| `webpQuality` | `number` | `1.0` | WebP quality from >0 to 1. Value of 1.0 enables lossless mode. |
| `targetWidth` | `number` | — | Target output width in pixels. Must be used with `targetHeight`. |
| `targetHeight` | `number` | — | Target output height in pixels. Must be used with `targetWidth`. |
| `abortSignal` | `AbortSignal` | — | Signal to cancel the export operation. |
### Target Dimensions
Use `targetWidth` and `targetHeight` together to export at specific dimensions. The block renders large enough to fill the target size while maintaining aspect ratio.
```typescript highlight=highlight-target-size
const scaledBlob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1200,
targetHeight: 630
});
```
Combining dimension scaling with compression produces smaller files suitable for specific platforms like social media thumbnails.
## Save to File System
After exporting, convert the Blob to a Buffer and write to the file system.
```typescript
const blob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output/compressed.png', buffer);
```
Create the output directory if it doesn't exist before writing files.
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.block.export(blockId, options)` | Export a block with compression and format options |
| `engine.block.findByType('page')` | Find all pages in the current scene |
| `engine.scene.loadFromURL(url)` | Load a scene from a remote URL |
| `engine.dispose()` | Clean up engine resources when done |
## Next Steps
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Compare all supported export formats
- [Export to PNG](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-png-f87eaf/) - Full PNG export options and transparency handling
- [Export to JPEG](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-jpeg-6f88e9/) - JPEG-specific options for photographs
- [Export to WebP](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-webp-aef6f4/) - WebP format with lossless and lossy modes
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Options"
description: "Explore export options, supported formats, and configuration features for sharing or rendering output."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/)
---
Export your designs to multiple formats including PNG, JPEG, WebP, SVG, and PDF.
CE.SDK handles all export processing entirely on the server side, giving you
fine-grained control over format-specific options like compression, quality,
and target dimensions.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples)
Whether you're building a content automation workflow, batch processing system, or server-side rendering pipeline, understanding export options helps you deliver the right output for each use case. This guide covers supported formats, their options, and how to export programmatically.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-overview-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import * as readline from 'readline';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Export Overview
*
* Demonstrates export options and formats:
* - Exporting to different image formats (PNG, JPEG, WebP)
* - Configuring format-specific options
* - Exporting to PDF
* - Exporting with color masks for print workflows
* - Checking device export limits
* - Target size control
*/
// Prompt user to select export format
async function selectFormat(): Promise {
const formats = [
{ key: '1', label: 'PNG', value: 'png' },
{ key: '2', label: 'JPEG', value: 'jpeg' },
{ key: '3', label: 'WebP', value: 'webp' },
{ key: '4', label: 'PDF', value: 'pdf' },
{ key: '5', label: 'HD PNG (1920x1080)', value: 'hd' },
{ key: '6', label: 'Color Mask (PNG + alpha)', value: 'mask' },
{ key: '7', label: 'All formats', value: 'all' }
];
console.log('\nSelect export format:');
formats.forEach((f) => console.log(` ${f.key}) ${f.label}`));
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question('\nEnter choice (1-7): ', (answer) => {
rl.close();
const selected = formats.find((f) => f.key === answer.trim());
if (!selected) {
console.error(
`\n✗ Invalid choice: "${answer.trim()}". Please enter 1-7.`
);
process.exit(1);
}
resolve(selected.value);
});
});
}
// Initialize CE.SDK engine with baseURL for asset loading
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
// Load a template scene from a remote URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.block.findByType('page')[0];
if (!page) {
throw new Error('No page found');
}
// Create output directory
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Get user's format selection
const format = await selectFormat();
console.log('');
if (format === 'png' || format === 'all') {
// Export to PNG with compression
const pngBlob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 5 // 0-9, higher = smaller file, slower
});
const pngBuffer = Buffer.from(await pngBlob.arrayBuffer());
writeFileSync(`${outputDir}/design.png`, pngBuffer);
console.log(`✓ PNG exported (${(pngBlob.size / 1024).toFixed(1)} KB)`);
}
if (format === 'jpeg' || format === 'all') {
// Export to JPEG with quality setting
const jpegBlob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 0.9 // 0-1, higher = better quality, larger file
});
const jpegBuffer = Buffer.from(await jpegBlob.arrayBuffer());
writeFileSync(`${outputDir}/design.jpg`, jpegBuffer);
console.log(`✓ JPEG exported (${(jpegBlob.size / 1024).toFixed(1)} KB)`);
}
if (format === 'webp' || format === 'all') {
// Export to WebP with lossless quality
const webpBlob = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: 1.0 // 1.0 = lossless, smaller files than PNG
});
const webpBuffer = Buffer.from(await webpBlob.arrayBuffer());
writeFileSync(`${outputDir}/design.webp`, webpBuffer);
console.log(`✓ WebP exported (${(webpBlob.size / 1024).toFixed(1)} KB)`);
}
if (format === 'pdf' || format === 'all') {
// Export to PDF
const pdfBlob = await engine.block.export(page, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true // Rasterize for broader viewer support
});
const pdfBuffer = Buffer.from(await pdfBlob.arrayBuffer());
writeFileSync(`${outputDir}/design.pdf`, pdfBuffer);
console.log(`✓ PDF exported (${(pdfBlob.size / 1024).toFixed(1)} KB)`);
}
if (format === 'hd' || format === 'all') {
// Export with target size
const hdBlob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1920,
targetHeight: 1080
});
const hdBuffer = Buffer.from(await hdBlob.arrayBuffer());
writeFileSync(`${outputDir}/design-hd.png`, hdBuffer);
console.log(`✓ HD export complete (${(hdBlob.size / 1024).toFixed(1)} KB)`);
}
if (format === 'mask' || format === 'all') {
// Export with color mask - RGB values are in 0.0-1.0 range
// Pure magenta (1.0, 0.0, 1.0) is commonly used for registration marks
const [maskedImage, alphaMask] = await engine.block.exportWithColorMask(
page,
1.0, // maskColorR - red component
0.0, // maskColorG - green component
1.0, // maskColorB - blue component (RGB: pure magenta)
{ mimeType: 'image/png' }
);
const maskedBuffer = Buffer.from(await maskedImage.arrayBuffer());
const maskBuffer = Buffer.from(await alphaMask.arrayBuffer());
writeFileSync(`${outputDir}/design-masked.png`, maskedBuffer);
writeFileSync(`${outputDir}/design-alpha-mask.png`, maskBuffer);
console.log(
`✓ Color mask export: image (${(maskedImage.size / 1024).toFixed(1)} KB) + mask (${(alphaMask.size / 1024).toFixed(1)} KB)`
);
}
// Check device export limits
const maxExportSize = engine.editor.getMaxExportSize();
const availableMemory = engine.editor.getAvailableMemory();
console.log(`\nDevice limits:`);
console.log(` Max export size: ${maxExportSize}px`);
console.log(
` Available memory: ${(Number(availableMemory) / 1024 / 1024).toFixed(0)} MB`
);
console.log('\n✓ Export completed successfully');
console.log(` Output files saved to: ${outputDir}/`);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to export designs in different formats, configure format-specific options, check device limits, and save exports to the file system.
## Supported Export Formats
CE.SDK supports exporting scenes, pages, groups, or individual blocks in these formats:
| Format | MIME Type | Transparency | Best For |
| ------ | -------------------------- | -------------- | ------------------------------------------------- |
| PNG | `image/png` | Yes | Web graphics, UI elements, logos |
| JPEG | `image/jpeg` | No | Photographs, web images |
| WebP | `image/webp` | Yes (lossless) | Web delivery, smaller files |
| SVG | `image/svg+xml` | Yes | Scalable graphics, web embedding, post-processing |
| PDF | `application/pdf` | Partial | Print, documents |
| Binary | `application/octet-stream` | Yes | Raw data processing |
Each format serves different purposes. PNG preserves transparency and works well for graphics with sharp edges or text. JPEG compresses photographs efficiently but drops transparency. WebP provides excellent compression with optional lossless mode. SVG produces scalable vector output ideal for web embedding and post-processing with standard SVG tooling. PDF preserves vector information for print workflows.
## Export Images
### Export to PNG
PNG export uses lossless compression with a configurable compression level. Higher compression produces smaller files but takes longer to encode. Quality is not affected.
```typescript highlight-export-png
// Export to PNG with compression
const pngBlob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 5 // 0-9, higher = smaller file, slower
});
```
The `pngCompressionLevel` ranges from 0 (no compression, fastest) to 9 (maximum compression, slowest). The default is 5, which balances file size and encoding speed.
### Export to JPEG
JPEG export uses lossy compression controlled by the quality setting. Lower quality produces smaller files but introduces visible artifacts.
```typescript highlight-export-jpeg
// Export to JPEG with quality setting
const jpegBlob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 0.9 // 0-1, higher = better quality, larger file
});
```
The `jpegQuality` ranges from 0 to 1. Values above 0.9 provide excellent quality for most use cases. The default is 0.9.
> **Caution:** JPEG drops transparency from exports. Transparent areas render with a solid
> background, which may produce unexpected results for designs relying on alpha
> channels.
### Export to WebP
WebP provides better compression than PNG or JPEG for web delivery. A quality of 1.0 enables lossless mode.
```typescript highlight-export-webp
// Export to WebP with lossless quality
const webpBlob = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: 1.0 // 1.0 = lossless, smaller files than PNG
});
```
The `webpQuality` ranges from 0 to 1. At 1.0, WebP uses lossless compression that typically produces smaller files than equivalent PNG exports.
### Export to SVG
SVG export produces scalable vector graphics that can be post-processed with standard SVG tooling or scaled to any resolution without quality loss.
```typescript
const blob = await engine.block.export(page, {
mimeType: 'image/svg+xml'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output/design.svg', buffer);
```
Text is exported as vector paths to ensure consistent rendering across environments without requiring the original fonts. Shapes, strokes, and gradients are exported as native SVG elements.
> **Note:** Drop shadows, blur, effects (filters, adjustments), and raster images cannot be represented as native SVG vector elements. These features are rasterized and embedded as PNG images within the SVG. This preserves visual fidelity but increases file size and means those parts of the output are not scalable.
> **Note:** SVG export renders a single page. To export a multi-page scene, export each page individually.
### Image Export Options
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `mimeType` | `string` | - | Output format: `'image/png'`, `'image/jpeg'`, `'image/webp'`, or `'image/svg+xml'` |
| `pngCompressionLevel` | `number` | `5` | PNG compression level (0-9). Higher = smaller file, slower encoding |
| `jpegQuality` | `number` | `0.9` | JPEG quality (0-1). Higher = better quality, larger file |
| `webpQuality` | `number` | `0.8` | WebP quality (0-1). Set to 1.0 for lossless compression |
| `targetWidth` | `number` | - | Target output width in pixels |
| `targetHeight` | `number` | - | Target output height in pixels |
## Export PDF
PDF export preserves vector information and supports print workflows. The high compatibility option rasterizes content for broader viewer support.
```typescript highlight-export-pdf
// Export to PDF
const pdfBlob = await engine.block.export(page, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true // Rasterize for broader viewer support
});
```
When `exportPdfWithHighCompatibility` is `true` (the default), images and effects are rasterized according to the scene's DPI setting. Set it to `false` for faster exports, though gradients with transparency may not render correctly in some PDF viewers.
The underlayer options are useful for print workflows where you need a solid base layer (often white ink) beneath the design elements. The `underlayerSpotColorName` should match a spot color defined in your print workflow.
### PDF Export Options
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `mimeType` | `string` | - | Must be `'application/pdf'` |
| `exportPdfWithHighCompatibility` | `boolean` | `true` | Rasterize images and effects (like gradients) according to the scene's DPI setting for broader viewer support |
| `exportPdfWithUnderlayer` | `boolean` | `false` | Add an underlayer behind existing elements matching the shape of page content |
| `underlayerSpotColorName` | `string` | `''` | Spot color name for the underlayer fill (used with print workflows) |
| `underlayerOffset` | `number` | `0` | Size adjustment for the underlayer shape in design units |
| `targetWidth` | `number` | - | Target output width in pixels |
| `targetHeight` | `number` | - | Target output height in pixels |
## Export with Color Mask
Color mask export removes pixels matching a specific RGB color and generates two output files: the masked image with transparency applied, and an alpha mask showing which pixels were removed.
```typescript highlight-export-color-mask
// Export with color mask - RGB values are in 0.0-1.0 range
// Pure magenta (1.0, 0.0, 1.0) is commonly used for registration marks
const [maskedImage, alphaMask] = await engine.block.exportWithColorMask(
page,
1.0, // maskColorR - red component
0.0, // maskColorG - green component
1.0, // maskColorB - blue component (RGB: pure magenta)
{ mimeType: 'image/png' }
);
```
The `exportWithColorMask()` method accepts the block to export, three RGB color components (0.0-1.0 range), and optional export options. RGB values use floating-point notation where 1.0 equals 255 in standard color notation.
Common mask colors for print workflows:
- Pure red: `(1.0, 0.0, 0.0)` — Registration marks
- Pure magenta: `(1.0, 0.0, 1.0)` — Distinctive marker color
- Pure cyan: `(0.0, 1.0, 1.0)` — Alternative marker color
The method returns a Promise resolving to an array of two Blobs: the masked image (with matched pixels made transparent) and the alpha mask (black pixels for removed areas, white for retained areas).
> **Note:** Color matching is exact. Only pixels with RGB values precisely matching the
> specified color are removed. Anti-aliased edges or color variations will not
> be affected.
### Color Mask Export Options
The `exportWithColorMask()` method accepts the same options as image export:
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `mimeType` | `string` | `'image/png'` | Output format: `'image/png'`, `'image/jpeg'`, or `'image/webp'` |
| `pngCompressionLevel` | `number` | `5` | PNG compression level (0-9) |
| `jpegQuality` | `number` | `0.9` | JPEG quality (0-1) |
| `webpQuality` | `number` | `0.8` | WebP quality (0-1) |
| `targetWidth` | `number` | - | Target output width in pixels |
| `targetHeight` | `number` | - | Target output height in pixels |
## Target Size Control
You can export at specific dimensions regardless of the block's actual size. The `targetWidth` and `targetHeight` options render the block large enough to fill the target size while maintaining aspect ratio.
```typescript highlight-export-target-size
// Export with target size
const hdBlob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1920,
targetHeight: 1080
});
```
If the target aspect ratio differs from the block's aspect ratio, the output fills the target dimensions completely. The output may extend beyond the target size on one axis to preserve correct proportions.
## Device Export Limits
Before exporting large designs, check the device's export capabilities. Memory constraints may prevent exports that exceed certain dimensions.
```typescript highlight-check-limits
// Check device export limits
const maxExportSize = engine.editor.getMaxExportSize();
const availableMemory = engine.editor.getAvailableMemory();
```
`getMaxExportSize()` returns the maximum width or height in pixels. Both dimensions must stay below this limit. `getAvailableMemory()` returns available memory in bytes, helping you assess whether large exports are feasible.
> **Note:** The max export size is an upper bound. Exports may still fail due to memory
> constraints even when within size limits. For high-resolution exports,
> consider checking available memory first.
## Save to File System
After exporting, convert the Blob to a Buffer and write to the file system.
```typescript
const blob = await engine.block.export(page, {
mimeType: 'image/png',
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output/design.png', buffer);
```
This approach works for all export formats. Adjust the filename extension to match the exported format.
## API Reference
| Method | Description |
| ------------------------------------ | ------------------------------------------------------------------------------------- |
| `engine.block.export()` | Export a block with format and quality options |
| `engine.block.exportWithColorMask()` | Export a block with specific RGB color removed, returning masked image and alpha mask |
| `engine.editor.getMaxExportSize()` | Get maximum export dimension in pixels |
| `engine.editor.getAvailableMemory()` | Get available memory in bytes |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Partial Export"
description: "Documentation for Partial Export"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/partial-export-89aaf6/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [Partial Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export/partial-export-89aaf6/)
---
Export individual design elements, grouped blocks, or specific pages from your
scene server-side using CE.SDK's headless Node.js engine for automated workflows,
batch processing, and backend integrations.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-partial-export-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-partial-export-server-js)
Server-side partial export enables fine-grained control over output generation in headless environments. Export individual images, text blocks, shapes, grouped elements, or specific pages programmatically for asset generation pipelines, automated report creation, or serverless functions. This guide demonstrates how to implement partial exports using CE.SDK's Node.js engine (`@cesdk/node`).
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-partial-export-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { calculateGridLayout } from './utils';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Partial Export
*
* Demonstrates exporting specific blocks, groups, and pages programmatically:
* - Exporting individual design blocks
* - Exporting grouped elements
* - Exporting specific pages
* - Export format selection and options
* - Understanding block hierarchy in exports
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 1200, height: 900 } },
});
const page = engine.block.findByType('page')[0];
if (!page) {
throw new Error('No page found');
}
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Calculate responsive grid layout for 6 examples
const layout = calculateGridLayout(pageWidth, pageHeight, 6);
const { blockWidth, blockHeight, getPosition } = layout;
// Sample image URI for demonstrations
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const blockSize = { width: blockWidth, height: blockHeight };
// Create first image block
const imageBlock1 = await engine.block.addImage(imageUri, {
size: blockSize,
});
engine.block.appendChild(page, imageBlock1);
// Export the block as PNG
const individualBlob = await engine.block.export(imageBlock1, {
mimeType: 'image/png',
pngCompressionLevel: 5,
});
// Save individual block export
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const individualBuffer = Buffer.from(await individualBlob.arrayBuffer());
writeFileSync(`${outputDir}/individual-block.png`, individualBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported individual block to output/individual-block.png');
// Create second image block with different styling
const imageBlock2 = await engine.block.addImage(imageUri, {
size: blockSize,
cornerRadius: 20,
});
engine.block.appendChild(page, imageBlock2);
// Export as PNG with high compression
const pngBlob = await engine.block.export(imageBlock2, {
mimeType: 'image/png',
pngCompressionLevel: 9, // Maximum compression
});
const pngBuffer = Buffer.from(await pngBlob.arrayBuffer());
writeFileSync(`${outputDir}/export-png.png`, pngBuffer);
// Export as JPEG with quality setting
const jpegBlob = await engine.block.export(imageBlock2, {
mimeType: 'image/jpeg',
jpegQuality: 0.95, // High quality
});
const jpegBuffer = Buffer.from(await jpegBlob.arrayBuffer());
writeFileSync(`${outputDir}/export-jpeg.jpg`, jpegBuffer);
// Export as WEBP
const webpBlob = await engine.block.export(imageBlock2, {
mimeType: 'image/webp',
webpQuality: 0.9,
});
const webpBuffer = Buffer.from(await webpBlob.arrayBuffer());
writeFileSync(`${outputDir}/export-webp.webp`, webpBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported different formats (PNG, JPEG, WEBP)');
// Create two shapes for grouping demonstration
const groupShape1 = engine.block.create('//ly.img.ubq/graphic');
const rect = engine.block.createShape('rect');
engine.block.setShape(groupShape1, rect);
engine.block.setWidth(groupShape1, blockWidth * 0.4);
engine.block.setHeight(groupShape1, blockHeight * 0.4);
const groupFill1 = engine.block.createFill('color');
engine.block.setFill(groupShape1, groupFill1);
engine.block.setColor(groupFill1, 'fill/color/value', {
r: 0.3,
g: 0.6,
b: 0.9,
a: 1.0,
});
engine.block.appendChild(page, groupShape1);
const groupShape2 = engine.block.create('//ly.img.ubq/graphic');
const ellipse = engine.block.createShape('ellipse');
engine.block.setShape(groupShape2, ellipse);
engine.block.setWidth(groupShape2, blockWidth * 0.4);
engine.block.setHeight(groupShape2, blockHeight * 0.4);
const groupFill2 = engine.block.createFill('color');
engine.block.setFill(groupShape2, groupFill2);
engine.block.setColor(groupFill2, 'fill/color/value', {
r: 0.9,
g: 0.3,
b: 0.5,
a: 1.0,
});
engine.block.appendChild(page, groupShape2);
// Group the blocks together
const exportGroup = engine.block.group([groupShape1, groupShape2]);
// Export the group (includes all children)
const groupBlob = await engine.block.export(exportGroup, {
mimeType: 'image/png',
});
const groupBuffer = Buffer.from(await groupBlob.arrayBuffer());
writeFileSync(`${outputDir}/group-export.png`, groupBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported grouped elements to output/group-export.png');
// Create shape block for resizing demonstration
const shapeBlock = engine.block.create('//ly.img.ubq/graphic');
const shape = engine.block.createShape('star');
engine.block.setShape(shapeBlock, shape);
engine.block.setWidth(shapeBlock, blockWidth);
engine.block.setHeight(shapeBlock, blockHeight);
// Add a color fill to the shape
const shapeFill = engine.block.createFill('color');
engine.block.setFill(shapeBlock, shapeFill);
engine.block.setColor(shapeFill, 'fill/color/value', {
r: 1.0,
g: 0.7,
b: 0.0,
a: 1.0,
});
engine.block.appendChild(page, shapeBlock);
// Export with specific target dimensions
const resizedBlob = await engine.block.export(shapeBlock, {
mimeType: 'image/png',
targetWidth: 400,
targetHeight: 400, // Aspect ratio is preserved
});
const resizedBuffer = Buffer.from(await resizedBlob.arrayBuffer());
writeFileSync(`${outputDir}/resized-export.png`, resizedBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported resized block to output/resized-export.png');
// Export the entire page
const pageBlob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 5,
});
const pageBuffer = Buffer.from(await pageBlob.arrayBuffer());
writeFileSync(`${outputDir}/page-export.png`, pageBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported current page to output/page-export.png');
// Export all pages individually (for multi-page documents)
const pages = engine.scene.getPages();
for (let i = 0; i < pages.length; i++) {
const pageBlob = await engine.block.export(pages[i], {
mimeType: 'image/png',
});
const pageBuffer = Buffer.from(await pageBlob.arrayBuffer());
writeFileSync(`${outputDir}/page-${i + 1}.png`, pageBuffer);
}
// eslint-disable-next-line no-console
console.log(`✓ Exported ${pages.length} page(s) individually`);
// Export as PDF to preserve vector information
const pdfBlob = await engine.block.export(page, {
mimeType: 'application/pdf',
});
const pdfBuffer = Buffer.from(await pdfBlob.arrayBuffer());
writeFileSync(`${outputDir}/export.pdf`, pdfBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported page as PDF to output/export.pdf');
// Get maximum export size
const maxExportSize = engine.editor.getMaxExportSize();
// eslint-disable-next-line no-console
console.log('Maximum export size:', maxExportSize, 'pixels');
// Get available memory
const availableMemory = engine.editor.getAvailableMemory();
// eslint-disable-next-line no-console
console.log('Available memory:', availableMemory, 'bytes');
// Position all blocks in grid layout for the first page
const blocks = [
imageBlock1,
imageBlock2,
exportGroup,
shapeBlock,
groupShape1,
groupShape2,
];
blocks.forEach((block, index) => {
if (index < 4) {
// Position first 4 blocks (group contains 2)
const pos = getPosition(index);
engine.block.setPositionX(block, pos.x);
engine.block.setPositionY(block, pos.y);
}
});
// Position grouped shapes relative to group
const groupPos = getPosition(2);
engine.block.setPositionX(exportGroup, groupPos.x);
engine.block.setPositionY(exportGroup, groupPos.y);
engine.block.setPositionX(groupShape1, 10);
engine.block.setPositionY(groupShape1, 10);
engine.block.setPositionX(groupShape2, 60);
engine.block.setPositionY(groupShape2, 60);
// Export the complete scene for reference
const completeBlob = await engine.block.export(page, {
mimeType: 'image/png',
});
const completeBuffer = Buffer.from(await completeBlob.arrayBuffer());
writeFileSync(`${outputDir}/partial-export-result.png`, completeBuffer);
// eslint-disable-next-line no-console
console.log(
'\n✓ Exported complete result to output/partial-export-result.png'
);
// eslint-disable-next-line no-console
console.log('✓ Partial export examples completed successfully');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers exporting individual blocks, grouped elements, and pages in Node.js environments, saving outputs to the file system or cloud storage.
## Server-side vs Browser Differences
### Engine Initialization
Server-side uses the `@cesdk/node` package and `CreativeEngine.init()` instead of browser's `@cesdk/cesdk-js` and `CreativeEditorSDK.create()`. The Node.js engine runs headlessly without UI components.
```typescript highlight-initialize-engine
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({});
```
## Understanding Block Hierarchy and Export
### How Block Hierarchy Affects Exports
CE.SDK organizes content in a tree structure: Scene → Pages → Groups → Individual Blocks. When you export a block, the export automatically includes all child elements in the hierarchy.
Exporting a page exports every element on that page. Exporting a group exports all blocks within that group. Exporting an individual block (like an image or text) exports only that specific element.
This hierarchical behavior lets you control export scope by choosing which level of the hierarchy to target. Export an image block for a single asset. Export a parent group for a complete layout section.
> **Note:** Only blocks that belong to the scene hierarchy can be exported. Orphaned blocks
> (created but not added to the page) cannot be exported until they're attached
> to the scene tree.
### Export Behavior
The export API applies several transformations to ensure consistent output. If the exported block itself is rotated, it will be exported without that rotation—the content appears upright in the output file. Any margin set on the block is included in the export bounds. Outside strokes are included for most block types.
## Exporting Individual Blocks
### Basic Block Export
Once we have a scene, we can export individual blocks. Find blocks using `findByType()`, then export them with specific format options.
```typescript highlight-export-individual-block
// Create first image block
const imageBlock1 = await engine.block.addImage(imageUri, {
size: blockSize,
});
engine.block.appendChild(page, imageBlock1);
// Export the block as PNG
const individualBlob = await engine.block.export(imageBlock1, {
mimeType: 'image/png',
pngCompressionLevel: 5,
});
// Save individual block export
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const individualBuffer = Buffer.from(await individualBlob.arrayBuffer());
writeFileSync(`${outputDir}/individual-block.png`, individualBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported individual block to output/individual-block.png');
```
The `mimeType` determines the output format. CE.SDK supports PNG, JPEG, WEBP, and PDF. Each format has specific options—PNG uses `pngCompressionLevel` (0-9), JPEG uses `jpegQuality` (0-1), and WEBP uses `webpQuality` (0-1).
PNG supports transparency for UI elements and logos. JPEG suits photographs without transparency needs (replaces transparent areas with solid background). WEBP offers better compression for web delivery. PDF preserves vector data for print workflows. PNG compression (0-9) balances size and encoding speed. JPEG quality (0-1) controls artifacts and file size. WEBP quality (0-1) achieves smallest files with good visual fidelity.
## Exporting with Different Options
### Resizing Exports
Control output dimensions using `targetWidth` and `targetHeight`. The engine maintains the block's aspect ratio while fitting within your specified dimensions. Specify one dimension to auto-calculate the other while preserving aspect ratio. Specify both to fit content within bounds (one dimension may be smaller to maintain proportions). Useful for thumbnails, responsive sizes, or platform constraints.
### Checking Export Constraints
Server environments may have memory limits or maximum texture size constraints. Query these limits before attempting large exports. `getMaxExportSize()` returns maximum dimension in pixels (exceeding causes errors). `getAvailableMemory()` returns available memory in bytes for estimating large export viability. Critical for serverless functions (AWS Lambda: 128 MB to 10 GB limits) to prevent termination.
## Exporting Pages
### Single Page Export
Exporting a page captures all content on that page as a single output. The page dimensions determine the export size.
```typescript highlight-export-current-page
// Export the entire page
const pageBlob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 5,
});
const pageBuffer = Buffer.from(await pageBlob.arrayBuffer());
writeFileSync(`${outputDir}/page-export.png`, pageBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported current page to output/page-export.png');
```
Page exports include all child elements—graphics, text, shapes, and groups. The page background fill is also included. This is useful for generating preview images of multi-page documents or exporting individual pages from a template.
### Multi-page Document Export
For documents with multiple pages, export each page individually to create separate output files.
```typescript highlight-export-multiple-pages
// Export all pages individually (for multi-page documents)
const pages = engine.scene.getPages();
for (let i = 0; i < pages.length; i++) {
const pageBlob = await engine.block.export(pages[i], {
mimeType: 'image/png',
});
const pageBuffer = Buffer.from(await pageBlob.arrayBuffer());
writeFileSync(`${outputDir}/page-${i + 1}.png`, pageBuffer);
}
// eslint-disable-next-line no-console
console.log(`✓ Exported ${pages.length} page(s) individually`);
```
The `getPages()` method returns all pages in scene order. Export each page with sequential numbering for easy identification. This pattern works for generating page previews, creating print-ready files for each page, or splitting multi-page documents into individual images.
For print workflows, consider exporting pages as PDF to preserve vector information and text quality. For web previews, PNG or WEBP provides good quality at manageable file sizes.
## PDF Exports
### Vector Format Output
PDF exports preserve vector information, making them ideal for print workflows, scalable graphics, or archival purposes.
```typescript highlight-export-as-pdf
// Export as PDF to preserve vector information
const pdfBlob = await engine.block.export(page, {
mimeType: 'application/pdf',
});
const pdfBuffer = Buffer.from(await pdfBlob.arrayBuffer());
writeFileSync(`${outputDir}/export.pdf`, pdfBuffer);
// eslint-disable-next-line no-console
console.log('✓ Exported page as PDF to output/export.pdf');
```
PDF preserves vector paths, text selectability, and transparency. Produces smaller files than high-resolution PNG for vector-heavy content. Preferred for professional printing (scalability, text editability). Use PNG or WEBP for fixed-dimension web delivery.
## Practical Use Cases
### Asset Generation Pipeline
Generate social media assets from templates by loading a template, modifying text/images programmatically, and exporting different sizes.
```typescript
const engine = await CreativeEngine.init(config);
await engine.scene.loadFromURL('https://example.com/template.scene');
// Customize content
const textBlocks = engine.block.findByType('//ly.img.ubq/text');
engine.block.setString(textBlocks[0], 'text/text', 'Custom Headline');
// Export multiple sizes
const page = engine.block.findByType('page')[0];
const instagram = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1080,
targetHeight: 1080
});
const facebook = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1200,
targetHeight: 630
});
const twitter = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1200,
targetHeight: 675
});
```
This workflow is ideal for automated social media content generation, email campaign graphics, or personalized marketing materials.
### Thumbnail Generation
Generate thumbnails for preview grids by exporting pages or blocks with small target dimensions.
```typescript
const pages = engine.scene.getPages();
for (const page of pages) {
const thumbnail = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: 0.8,
targetWidth: 300,
targetHeight: 300
});
// Save or upload thumbnail
}
```
Small dimensions and lower quality settings (0.7-0.8) produce small file sizes suitable for fast loading in preview galleries.
## API Reference
| Method | Description |
| --- | --- |
| `engine.block.export()` | Export a block with specified format and quality options |
| `engine.block.findByType()` | Find blocks by type identifier |
| `engine.block.group()` | Group multiple blocks into a single logical unit |
| `engine.scene.getPages()` | Get all pages in the current scene |
| `engine.editor.getMaxExportSize()` | Get maximum export dimension in pixels |
| `engine.editor.getAvailableMemory()` | Get available engine memory in bytes |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Size Limits"
description: "Configure and understand CE.SDK's image and video size limits in Node.js server environments to optimize performance and memory usage in headless workflows."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/size-limits-6f0695/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [Size Limits](https://img.ly/docs/cesdk/node-native/export-save-publish/export/size-limits-6f0695/)
---
Configure size limits to balance quality and performance in headless Node.js workflows.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples)
CE.SDK processes images and videos in server environments using CPU and GPU resources, which means size limits depend on your server's hardware capabilities and available memory. Understanding and configuring these limits helps you build automation workflows that deliver high-quality results while maintaining efficient resource usage in batch processing, serverless functions, and containerized deployments.
```typescript file=@cesdk_web_examples/guides-export-save-publish-size-limits-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Size Limits
*
* Demonstrates how to configure and work with image size limits:
* - Reading current maxImageSize setting
* - Configuring maxImageSize for different scenarios
* - Observing settings changes
* - Understanding export size constraints
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Read the current maxImageSize setting
const currentMaxImageSize = engine.editor.getSetting('maxImageSize');
// eslint-disable-next-line no-console
console.log(`Current maxImageSize: ${currentMaxImageSize}px`);
// Default is 4096 pixels - safe baseline for universal compatibility
// Subscribe to settings changes to react to configuration updates
const unsubscribe = engine.editor.onSettingsChanged(() => {
const newMaxImageSize = engine.editor.getSetting('maxImageSize');
// eslint-disable-next-line no-console
console.log(`maxImageSize changed to: ${newMaxImageSize}px`);
});
// Configure maxImageSize for low memory environments
// This must be set BEFORE loading images to ensure they're downscaled
engine.editor.setSetting('maxImageSize', 2048);
// eslint-disable-next-line no-console
console.log(
`Updated maxImageSize: ${engine.editor.getSetting('maxImageSize')}px`
);
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } },
});
const page = engine.block.findByType('page')[0];
// Use sample image for demonstration
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Add an image to the page for demonstration
// Note: In Node.js, size parameter is required (no DOM Image API)
// The size parameter controls display dimensions, while maxImageSize controls texture loading
const imageBlock = await engine.block.addImage(imageUri, {
size: { width: 800, height: 600 },
});
engine.block.appendChild(page, imageBlock);
// Position image to fill the page
engine.block.setPositionX(imageBlock, 0);
engine.block.setPositionY(imageBlock, 0);
// Validate export dimensions before processing
// Check if dimensions are safe for the server's memory and timeout constraints
const exportWidth = engine.block.getWidth(page);
const exportHeight = engine.block.getHeight(page);
const maxSafeSize = 8192; // Configure based on server capabilities
// eslint-disable-next-line no-console
console.log(`Export dimensions: ${exportWidth}×${exportHeight}`);
if (exportWidth > maxSafeSize || exportHeight > maxSafeSize) {
// eslint-disable-next-line no-console
console.warn(
`⚠ Export dimensions (${exportWidth}×${exportHeight}) exceed safe limits (${maxSafeSize}×${maxSafeSize})`
);
// eslint-disable-next-line no-console
console.warn(
'Consider reducing dimensions or using a higher-memory server'
);
} else {
// eslint-disable-next-line no-console
console.log('✓ Export dimensions are within safe limits');
}
// Export the result to PNG
// Note: Export size is not limited by maxImageSize setting
// Export may fail if output exceeds server memory or rendering limits
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
try {
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/size-limits-result.png`, buffer);
// eslint-disable-next-line no-console
console.log('✓ Exported result to output/size-limits-result.png');
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
// eslint-disable-next-line no-console
console.error('Export failed:', errorMessage);
// Check if error is size-related
if (
errorMessage.includes('memory') ||
errorMessage.includes('size') ||
errorMessage.includes('texture')
) {
// eslint-disable-next-line no-console
console.error('Size-related export failure detected');
// eslint-disable-next-line no-console
console.error(
'Remediation: Reduce maxImageSize, decrease export dimensions, or use compression'
);
// Implement automatic retry with reduced settings
engine.editor.setSetting('maxImageSize', 2048);
// eslint-disable-next-line no-console
console.log('Retrying export with reduced maxImageSize: 2048px');
// Retry export with lower quality
const retryBlob = await engine.block.export(page, {
mimeType: 'image/png',
});
const retryBuffer = Buffer.from(await retryBlob.arrayBuffer());
writeFileSync(`${outputDir}/size-limits-result-reduced.png`, retryBuffer);
// eslint-disable-next-line no-console
console.log(
'✓ Exported reduced quality result to output/size-limits-result-reduced.png'
);
} else {
throw error; // Re-throw if not size-related
}
}
// Display final configuration summary
// eslint-disable-next-line no-console
console.log('\n=== Size Limits Configuration Summary ===');
// eslint-disable-next-line no-console
console.log(
`Current maxImageSize: ${engine.editor.getSetting('maxImageSize')}px`
);
// eslint-disable-next-line no-console
console.log(
`Page dimensions: ${engine.block.getWidth(page)}×${engine.block.getHeight(page)}`
);
// eslint-disable-next-line no-console
console.log(
`Image dimensions: ${engine.block.getWidth(imageBlock)}×${engine.block.getHeight(imageBlock)}`
);
// Unsubscribe from settings changes
unsubscribe();
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to configure the `maxImageSize` setting for headless workflows, understand server-side constraints, and implement robust error handling for size-related scenarios in production deployments.
## Understanding Size Limits
CE.SDK manages size limits at two stages: **input** (when loading images) and **output** (when exporting). The `maxImageSize` setting controls input resolution, automatically downscaling images that exceed the configured limit (default: 4096×4096px). This prevents memory issues in serverless functions and containerized environments. Export resolution has no artificial limits—the theoretical maximum is 16,384×16,384 pixels, constrained by server GPU/CPU capabilities, available RAM, and deployment environment (serverless memory limits, container quotas, VM allocations). Headless environments use software rendering with conservative limits for universal compatibility.
## Resolution & Duration Limits
## Configuring maxImageSize
You can read and modify the `maxImageSize` setting using the Settings API to match your server infrastructure and automation requirements.
### Reading the Current Setting
To check what `maxImageSize` value is currently configured:
```typescript highlight-get-max-image-size
// Read the current maxImageSize setting
const currentMaxImageSize = engine.editor.getSetting('maxImageSize');
// eslint-disable-next-line no-console
console.log(`Current maxImageSize: ${currentMaxImageSize}px`);
// Default is 4096 pixels - safe baseline for universal compatibility
```
This returns the maximum size in pixels as an integer value (e.g., `4096` for the default 4096×4096 limit). You might log this value during server startup to verify configuration, use it to make runtime decisions about asset loading strategies, or include it in diagnostics when troubleshooting export failures.
### Setting a New Value
Configure `maxImageSize` to minimize memory usage in serverless and containerized environments:
```typescript highlight-set-max-image-size
// Configure maxImageSize for low memory environments
// This must be set BEFORE loading images to ensure they're downscaled
engine.editor.setSetting('maxImageSize', 2048);
// eslint-disable-next-line no-console
console.log(
`Updated maxImageSize: ${engine.editor.getSetting('maxImageSize')}px`
);
```
The setting takes effect immediately for newly loaded images. Images already loaded in the scene retain their current resolution until reloaded.
### Observing Settings Changes
Subscribe to setting changes to log configuration updates or trigger automation workflows:
```typescript highlight-observe-settings-changes
// Subscribe to settings changes to react to configuration updates
const unsubscribe = engine.editor.onSettingsChanged(() => {
const newMaxImageSize = engine.editor.getSetting('maxImageSize');
// eslint-disable-next-line no-console
console.log(`maxImageSize changed to: ${newMaxImageSize}px`);
});
```
This callback fires whenever any setting changes through the Settings API. You can use it to log configuration changes for auditing, update internal metrics tracking, or synchronize settings across distributed processing nodes.
## GPU Capability Detection
In server environments, rendering capabilities depend on the available hardware rather than browser-based WebGL. CE.SDK automatically detects and uses the best available rendering backend for your server configuration.
When CE.SDK runs in headless Node.js environments, it uses software rendering with conservative limits that provide universal compatibility. On servers with GPU access (such as cloud instances with GPU support), CE.SDK can leverage hardware acceleration for faster processing and larger export dimensions.
You can use this information to:
- Set conservative `maxImageSize` defaults based on your server's hardware specifications
- Configure different limits for serverless functions vs. dedicated GPU servers
- Implement automatic fallback to lower resolutions when memory is constrained
- Calculate safe export dimensions based on available RAM and processing time limits
For production deployments, test your specific server configuration to determine optimal size limits that balance quality with resource constraints.
## Troubleshooting
Common issues and solutions when working with size limits in server environments:
| Issue | Cause | Solution |
| ------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
| Images appear blurry in exports | `maxImageSize` too low | Increase `maxImageSize` if server memory supports it (test with monitoring) |
| Out of memory errors during processing | `maxImageSize` too high | Decrease `maxImageSize` to reduce memory footprint, or increase server memory allocation |
| Export fails silently | Output exceeds rendering backend limits | Reduce export dimensions or test server's actual rendering capabilities |
| Serverless function timeout | Export takes too long for large images | Lower `maxImageSize` to 2048, reduce export dimensions, or use dedicated server |
| Inconsistent results across nodes | Different server configurations | Set conservative `maxImageSize` (4096) or normalize infrastructure |
| Batch processing runs out of memory | Concurrent exports exceed available RAM | Process sequentially, reduce `maxImageSize`, or increase server memory |
| Container OOM killed | Export exceeds container memory limit | Increase container memory quota or reduce `maxImageSize` |
## API Reference
Core methods for managing size limits and export operations:
| Method | Description |
| ------------------------------- | --------------------------------------- |
| `engine.editor.getSetting()` | Retrieves the current value of a setting |
| `engine.editor.setSetting()` | Updates a setting value |
| `engine.editor.onSettingsChanged()` | Subscribes to setting change events |
| `engine.block.export()` | Exports a block as an image or video |
| `engine.block.getWidth()` | Gets the width of a block in pixels |
| `engine.block.getHeight()` | Gets the height of a block in pixels |
## Next Steps
Explore related guides to build complete server automation workflows:
- [Settings Guide](https://img.ly/docs/cesdk/node-native/settings-970c98/) - Complete Settings API reference and configuration options
- [File Format Support](https://img.ly/docs/cesdk/node-native/file-format-support-3c4b2a/) - Supported image and video formats with capabilities
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Fundamentals of exporting images and videos from CE.SDK
- [Export to PDF](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-pdf-95e04b/) - PDF export guide with multi-page support and print optimization
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To JPEG"
description: "Export CE.SDK designs to JPEG format with configurable quality settings for photographs, web images, and social media content."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-jpeg-6f88e9/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [To JPEG](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-jpeg-6f88e9/)
---
Export CE.SDK designs to JPEG format—ideal for photographs, social media, and web content where file size matters more than transparency.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-to-jpeg-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-to-jpeg-server-js)
JPEG uses lossy compression optimized for photographs and smooth color gradients. Unlike PNG, JPEG does not support transparency—transparent areas render with a solid background.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-jpeg-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import * as readline from 'readline';
config();
const OUTPUT_DIR = './output';
async function promptChoice(): Promise {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log('\n┌───────────────────────────────────┐');
console.log('│ JPEG Export Options │');
console.log('├───────────────────────────────────┤');
console.log('│ 1. Standard (quality: 0.9) │');
console.log('│ 2. High Quality (quality: 1.0) │');
console.log('│ 3. HD (1920×1080) │');
console.log('└───────────────────────────────────┘\n');
return new Promise((resolve) => {
rl.question('Select option (1-3): ', (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
function showProgress(msg: string): () => void {
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let i = 0;
process.stdout.write(`${frames[0]} ${msg}`);
const id = setInterval(() => {
i = (i + 1) % frames.length;
process.stdout.write(`\r${frames[i]} ${msg}`);
}, 80);
return () => {
clearInterval(id);
process.stdout.write(`\r✓ ${msg}\n`);
};
}
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
const choice = await promptChoice();
let done = showProgress('Loading scene...');
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
done();
const page = engine.block.findByType('page')[0];
if (!page) throw new Error('No page found');
if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true });
done = showProgress('Exporting JPEG...');
let blob: Blob;
let filename: string;
switch (choice) {
case '2':
blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 1.0
});
filename = 'high-quality.jpg';
break;
case '3':
blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
targetWidth: 1920,
targetHeight: 1080
});
filename = 'hd-1920x1080.jpg';
break;
default:
blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 0.9
});
filename = 'standard.jpg';
}
done();
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${OUTPUT_DIR}/${filename}`, buffer);
console.log(`\n✓ Saved: ${OUTPUT_DIR}/${filename}`);
console.log(` Size: ${(blob.size / 1024).toFixed(1)} KB\n`);
} finally {
engine.dispose();
}
```
This guide covers exporting to JPEG, configuring quality and dimensions, and saving exports to the file system.
## Export to JPEG
Export a design block to JPEG by calling `engine.block.export()` with `mimeType: 'image/jpeg'`. Convert the blob to a buffer and write to disk.
```typescript highlight=highlight-export-jpeg
blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 0.9
});
```
The `jpegQuality` parameter accepts values from greater than 0 to 1. Higher values produce better quality at larger file sizes. The default is 0.9.
## Export Options
JPEG export supports these configuration options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `mimeType` | `string` | `'image/png'` | Set to `'image/jpeg'` for JPEG |
| `jpegQuality` | `number` | `0.9` | Quality from >0 to 1 |
| `targetWidth` | `number` | — | Output width in pixels |
| `targetHeight` | `number` | — | Output height in pixels |
### Quality Control
Set `jpegQuality` to 1.0 for maximum quality with minimal compression artifacts. This is useful for archival or print preparation.
```typescript highlight=highlight-export-quality
blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 1.0
});
```
For web delivery, values around 0.8 balance quality and file size effectively.
### Target Dimensions
Specify `targetWidth` and `targetHeight` to export at exact dimensions. The output fills the target size while maintaining aspect ratio.
```typescript highlight=highlight-export-size
blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
targetWidth: 1920,
targetHeight: 1080
});
```
## Save to File System
Convert the exported blob to a buffer and write it to disk using Node.js file system APIs.
```typescript highlight=highlight-save-file
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${OUTPUT_DIR}/${filename}`, buffer);
```
## When to Use JPEG
JPEG works well for:
- Photographs and images with gradual color transitions
- Social media posts and web content
- Scenarios where file size matters more than perfect quality
> **Note:** For graphics with sharp edges, text, or transparency, use PNG instead. For modern web delivery with better compression, consider WebP.
## Troubleshooting
**Output looks blurry** — Increase `jpegQuality` toward 1.0, or use PNG for graphics with hard edges.
**File size too large** — Decrease `jpegQuality` to 0.7–0.8, or reduce dimensions with `targetWidth` and `targetHeight`.
**Unexpected background** — JPEG does not support transparency. Use PNG or WebP for transparent content.
## API Reference
| Method | Description |
|--------|-------------|
| `engine.block.export(block, options)` | Export a block to the specified format |
| `engine.scene.loadFromURL(url)` | Load a scene from a remote URL |
| `engine.block.findByType(type)` | Find all blocks of a specific type |
| `writeFileSync(path, buffer)` | Write buffer to file system (Node.js) |
## Next Steps
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) — Compare all available export formats
- [Export to PDF](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-pdf-95e04b/) — Export for print and document workflows
- [Partial Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export/partial-export-89aaf6/) — Export specific regions or elements
- [Size Limits](https://img.ly/docs/cesdk/node-native/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To PDF"
description: "Export your designs as PDF documents with options for print compatibility, underlayer generation, and output control."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-pdf-95e04b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [To PDF](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-pdf-95e04b/)
---
Export your designs as PDF documents with high compatibility mode and underlayer support for special media printing.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-to-pdf-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-to-pdf-server-js)
PDF provides a universal document format for sharing and printing designs. CE.SDK exports PDF files that preserve vector graphics, support multi-page documents, and include options for print compatibility. You can configure high compatibility mode to ensure consistent rendering across different PDF viewers, and generate underlayers for special media printing like fabric, glass, or DTF transfers.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-pdf-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { createInterface } from 'readline';
config();
// Helper function to prompt for user input
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
// Display export options menu
console.log('=== PDF Export Options ===\n');
console.log('1. Default PDF');
console.log('2. High Compatibility PDF');
console.log('3. PDF with Underlayer');
console.log('4. A4 @ 300 DPI PDF');
console.log('5. All formats\n');
const choice = (await prompt('Select export option (1-5): ')) || '5';
console.log('\n⏳ Initializing engine...');
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
// Get the scene block for PDF export (includes all pages)
const scene = engine.scene.get();
if (!scene) throw new Error('No scene found');
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
console.log('⏳ Exporting...\n');
if (choice === '1' || choice === '5') {
// Export scene as PDF (includes all pages)
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/design.pdf`, buffer);
console.log(
`✓ Default PDF: ${outputDir}/design.pdf (${(blob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '2' || choice === '5') {
// Enable high compatibility mode for consistent rendering across PDF viewers
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/design-high-compatibility.pdf`, buffer);
console.log(
`✓ High Compatibility PDF: ${outputDir}/design-high-compatibility.pdf (${(blob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '3' || choice === '5') {
engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8);
// Export with underlayer for special media printing
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true,
exportPdfWithUnderlayer: true,
underlayerSpotColorName: 'RDG_WHITE',
underlayerOffset: -2.0
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/design-with-underlayer.pdf`, buffer);
console.log(
`✓ PDF with Underlayer: ${outputDir}/design-with-underlayer.pdf (${(blob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '4' || choice === '5') {
// Export with specific dimensions for print output
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
targetWidth: 2480,
targetHeight: 3508
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/design-a4.pdf`, buffer);
console.log(
`✓ A4 PDF: ${outputDir}/design-a4.pdf (${(blob.size / 1024).toFixed(1)} KB)`
);
}
console.log('\n✓ Export completed');
} finally {
engine.dispose();
}
```
This guide covers exporting designs to PDF format, configuring high compatibility mode, generating underlayers with spot colors, and controlling output dimensions.
## Export to PDF
Call `engine.block.export()` with `mimeType: 'application/pdf'` to export any block as a PDF document. The method returns a Blob containing the PDF data.
```typescript highlight=highlight-export-pdf
// Export scene as PDF (includes all pages)
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf'
});
```
Pass the scene ID from `engine.scene.get()` to export all pages as a multi-page PDF. You can also pass a single page ID from `engine.scene.getCurrentPage()` if you only need to export one page.
## Configure High Compatibility Mode
Enable `exportPdfWithHighCompatibility` to rasterize complex elements like gradients with transparency at the scene's DPI. This ensures consistent rendering across all PDF viewers.
```typescript highlight=highlight-high-compatibility
// Enable high compatibility mode for consistent rendering across PDF viewers
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true
});
```
Use high compatibility mode when:
- Designs contain gradients with transparency
- Effects or blend modes render inconsistently across viewers
- Maximum compatibility matters more than vector precision
High compatibility mode increases file size because complex elements are converted to raster images rather than remaining as vectors.
## Generate Underlayers for Special Media
Underlayers provide a base ink layer (typically white) for printing on transparent or non-white substrates like fabric, glass, or acrylic. The underlayer sits behind your design elements and provides opacity on transparent materials.
### Define the Underlayer Spot Color
Before exporting, define a spot color that represents the underlayer ink. The RGB values provide a preview representation in PDF viewers.
```typescript highlight=highlight-spot-color
engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8);
```
The spot color name (e.g., `'RDG_WHITE'`) must match your print provider's requirements. Common names include `RDG_WHITE` for Roland DG printers and `White` for other systems.
### Export with Underlayer Options
Configure the underlayer spot color name and optional offset. The `underlayerOffset` adjusts the underlayer size in design units—negative values shrink it inward to prevent visible edges from print misalignment (trapping).
```typescript highlight=highlight-underlayer
// Export with underlayer for special media printing
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true,
exportPdfWithUnderlayer: true,
underlayerSpotColorName: 'RDG_WHITE',
underlayerOffset: -2.0
});
```
The underlayer is generated automatically from the contours of all design elements on the page. Elements with transparency will have proportionally reduced underlayer opacity.
## Export at Target Dimensions
Use `targetWidth` and `targetHeight` to control the exported PDF dimensions in pixels. The block renders large enough to fill the target size while maintaining aspect ratio.
```typescript highlight=highlight-target-size
// Export with specific dimensions for print output
const blob = await engine.block.export(scene, {
mimeType: 'application/pdf',
targetWidth: 2480,
targetHeight: 3508
});
```
For print output, calculate the target dimensions based on your desired DPI:
- A4 at 300 DPI: 2480 × 3508 pixels
- Letter at 300 DPI: 2550 × 3300 pixels
## PDF Export Options
| Option | Description |
| ------ | ----------- |
| `mimeType` | Output format. Must be `'application/pdf'`. |
| `exportPdfWithHighCompatibility` | Rasterize complex elements at scene DPI for consistent rendering. Defaults to `true`. |
| `exportPdfWithUnderlayer` | Generate an underlayer from design contours. Defaults to `false`. |
| `underlayerSpotColorName` | Spot color name for the underlayer ink. Required when `exportPdfWithUnderlayer` is true. |
| `underlayerOffset` | Size adjustment in design units. Negative values shrink the underlayer inward. |
| `targetWidth` | Target output width in pixels. Must be used with `targetHeight`. |
| `targetHeight` | Target output height in pixels. Must be used with `targetWidth`. |
| `abortSignal` | Signal to cancel the export operation. |
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.block.export(blockId, options)` | Export a block as PDF with format and compatibility options |
| `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color for underlayer ink |
| `engine.scene.get()` | Get the scene for multi-page PDF export |
| `engine.scene.getCurrentPage()` | Get the current page for single-page export |
## Next Steps
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Compare all supported export formats
- [Export for Printing](https://img.ly/docs/cesdk/node-native/export-save-publish/for-printing-bca896/) - Print workflows with DPI and color management
- [Spot Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/spot-c3a150/) - Define and use spot colors in designs
- [Export Size Limits](https://img.ly/docs/cesdk/node-native/export-save-publish/export/size-limits-6f0695/) - Check device limits before exporting large designs
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To PNG"
description: "Export your designs as PNG images with transparency support and configurable compression for web graphics, UI elements, and content requiring crisp edges."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-png-f87eaf/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [To PNG](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-png-f87eaf/)
---
Export your designs as PNG images with full transparency support and configurable compression.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-to-png-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-to-png-server-js)
PNG (Portable Network Graphics) provides lossless compression with full alpha channel support. It's ideal for web graphics, UI elements, and content requiring crisp edges or transparency.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-png-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { createInterface } from 'readline';
config();
// Helper function to prompt for user input
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
// Display export options menu
console.log('=== PNG Export Options ===\n');
console.log('1. Default PNG (balanced compression)');
console.log('2. Maximum compression (smaller file)');
console.log('3. HD export (1920x1080)');
console.log('4. All formats\n');
const choice = (await prompt('Select export option (1-4): ')) || '4';
console.log('\n⏳ Initializing engine...');
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.block.findByType('page')[0];
if (!page) throw new Error('No page found');
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
console.log('⏳ Exporting...\n');
if (choice === '1' || choice === '4') {
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/design.png`, buffer);
console.log(
`✓ Default PNG: ${outputDir}/design.png (${(blob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '2' || choice === '4') {
const blob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/design-compressed.png`, buffer);
console.log(
`✓ Compressed PNG: ${outputDir}/design-compressed.png (${(blob.size / 1024).toFixed(1)} KB)`
);
}
if (choice === '3' || choice === '4') {
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1920,
targetHeight: 1080
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/design-hd.png`, buffer);
console.log(
`✓ HD PNG: ${outputDir}/design-hd.png (${(blob.size / 1024).toFixed(1)} KB)`
);
}
console.log('\n✓ Export completed');
} finally {
engine.dispose();
}
```
This guide covers exporting designs to PNG, configuring compression, controlling output dimensions, and saving exports to the file system.
## Export to PNG
Call `engine.block.export()` with `mimeType: 'image/png'` to export any block as a PNG image. The method returns a Blob containing the image data.
```typescript highlight=highlight-export-png
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
```
Pass the page ID from `engine.block.findByType('page')[0]` or any block ID to export specific elements.
## Export Options
PNG export supports several configuration options for compression, dimensions, and text rendering.
### Compression Level
The `pngCompressionLevel` option (0-9) controls file size vs. encoding speed. Higher values produce smaller files but take longer to encode. PNG compression is lossless, so quality remains unchanged.
```typescript highlight=highlight-compression
const blob = await engine.block.export(page, {
mimeType: 'image/png',
pngCompressionLevel: 9
});
```
- **0**: No compression, fastest encoding
- **5**: Balanced (default)
- **9**: Maximum compression, slowest encoding
### Target Dimensions
Use `targetWidth` and `targetHeight` together to export at specific dimensions. The block renders large enough to fill the target size while maintaining aspect ratio.
```typescript highlight=highlight-target-size
const blob = await engine.block.export(page, {
mimeType: 'image/png',
targetWidth: 1920,
targetHeight: 1080
});
```
If the target aspect ratio differs from the block's aspect ratio, the output extends beyond the target on one axis to preserve proportions.
### All PNG Export Options
- **`mimeType`** (`'image/png'`): Output format. Defaults to `'image/png'`.
- **`pngCompressionLevel`** (`number`): Compression level from 0-9. Higher values produce smaller files but take longer. Quality is unaffected. Defaults to `5`.
- **`targetWidth`** (`number`): Target output width in pixels. Must be used with `targetHeight`.
- **`targetHeight`** (`number`): Target output height in pixels. Must be used with `targetWidth`.
- **`allowTextOverhang`** (`boolean`): When `true`, text blocks with glyphs that extend beyond their frame (e.g., decorative fonts with swashes) export with full glyph bounds visible. Defaults to `false`.
- **`abortSignal`** (`AbortSignal`): Signal to cancel the export operation.
## Save to File System
After exporting, convert the Blob to a Buffer and write to the file system. Create the output directory if it doesn't exist.
```typescript
const blob = await engine.block.export(page, {
mimeType: 'image/png'
});
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync('output/design.png', buffer);
```
This pattern works for all export formats. Adjust the filename extension to match the exported format.
## API Reference
| Method | Description |
| ------ | ----------- |
| `engine.block.export(blockId, options)` | Export a block as PNG with format and quality options |
| `engine.block.findByType('page')` | Find all pages in the current scene |
| `engine.dispose()` | Clean up engine resources when done |
## Next Steps
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Compare all supported export formats
- [Export Size Limits](https://img.ly/docs/cesdk/node-native/export-save-publish/export/size-limits-6f0695/) - Check device limits before exporting large designs
- [Export with Color Mask](https://img.ly/docs/cesdk/node-native/export-save-publish/export/with-color-mask-4f868f/) - Remove specific colors and generate alpha masks
- [Partial Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export/partial-export-89aaf6/) - Export specific blocks or regions
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Export to Raw Data"
description: "Export designs to uncompressed RGBA pixel data for custom image processing, server-side rendering pipelines, and integration with machine learning workflows in Node.js."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-raw-data-abd7da/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [To Raw Data](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-raw-data-abd7da/)
---
Exporting designs to raw pixel data in Node.js gives you direct access to uncompressed
RGBA bytes from CE.SDK, enabling custom server-side image processing, automated content pipelines, and
integration with headless rendering workflows.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-to-raw-data-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-to-raw-data-server-js)
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-raw-data-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Export to Raw Data
*
* Demonstrates exporting designs to uncompressed RGBA pixel data for custom
* image processing in Node.js:
* - Exporting blocks to raw pixel data
* - Processing pixels with custom algorithms
* - Integrating with image processing libraries (Sharp)
* - Saving processed data to files
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({});
try {
// Create a design scene with a single image
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } },
});
const page = engine.block.findByType('page')[0];
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const imageBlock = await engine.block.addImage(imageUri, {
size: { width: 800, height: 600 },
});
engine.block.appendChild(page, imageBlock);
engine.block.setPositionX(imageBlock, 0);
engine.block.setPositionY(imageBlock, 0);
// Export to raw pixel data
const width = Math.floor(engine.block.getWidth(imageBlock));
const height = Math.floor(engine.block.getHeight(imageBlock));
const blob = await engine.block.export(imageBlock, {
mimeType: 'application/octet-stream',
targetWidth: width,
targetHeight: height,
});
// Convert blob to Buffer for Node.js processing
const arrayBuffer = await blob.arrayBuffer();
const pixelData = Buffer.from(arrayBuffer);
// eslint-disable-next-line no-console
console.log(`✓ Exported ${pixelData.length} bytes (${width}x${height} RGBA)`);
// Apply grayscale processing to demonstrate pixel manipulation
const processedData = toGrayscale(pixelData);
// eslint-disable-next-line no-console
console.log('✓ Applied grayscale effect');
// Save processed raw data to file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
writeFileSync(`${outputDir}/raw-data.bin`, processedData);
// eslint-disable-next-line no-console
console.log(`✓ Saved raw data to ${outputDir}/raw-data.bin`);
// Integration with Sharp for converting raw data to PNG
// Note: This requires 'sharp' to be installed: npm install sharp
//
// import sharp from 'sharp';
//
// await sharp(processedData, {
// raw: {
// width,
// height,
// channels: 4 // RGBA
// }
// })
// .png({ compressionLevel: 9 })
// .toFile(`${outputDir}/processed.png`);
//
// console.log('✓ Saved PNG to output/processed.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
/**
* Convert image to grayscale by averaging RGB channels
*/
function toGrayscale(pixelData: Buffer): Buffer {
const result = Buffer.from(pixelData);
for (let i = 0; i < result.length; i += 4) {
const avg = Math.round((result[i] + result[i + 1] + result[i + 2]) / 3);
result[i] = avg; // R
result[i + 1] = avg; // G
result[i + 2] = avg; // B
// Keep alpha unchanged: result[i + 3]
}
return result;
}
```
Unlike compressed formats like PNG or JPEG, raw data export from CE.SDK provides the pixel buffer without encoding overhead, making it ideal for headless server applications, batch processing, and scenarios where you need to manipulate individual pixels programmatically.
This guide covers exporting CE.SDK designs to raw data, processing pixels with custom algorithms, and integrating with image processing libraries like Sharp.
## When to Use Raw Data Export
Raw pixel data export provides direct access to uncompressed RGBA bytes from CE.SDK in Node.js, giving you complete control over individual pixels for server-side processing workflows.
Use raw data export when you need pixel-level access to exported designs for custom algorithms, integrations, or batch processing. For standard image storage or transfer, use PNG or JPEG exports instead, as they provide compression and are universally supported.
## Understanding Raw Data Format
When you export with `mimeType: 'application/octet-stream'`, CE.SDK returns a Blob containing uncompressed RGBA pixel data. The format is straightforward:
- **4 bytes per pixel** representing Red, Green, Blue, and Alpha channels
- **Values from 0-255** for each channel (8-bit unsigned integers)
- **Row-major order** with pixels arranged left-to-right, top-to-bottom
- **Total size** equals width × height × 4 bytes
## How to Export Raw Data
To export a block as raw pixel data in Node.js, use the `engine.block.export()` method with `mimeType: 'application/octet-stream'`:
```typescript highlight-export-to-raw-data
// Export to raw pixel data
const width = Math.floor(engine.block.getWidth(imageBlock));
const height = Math.floor(engine.block.getHeight(imageBlock));
const blob = await engine.block.export(imageBlock, {
mimeType: 'application/octet-stream',
targetWidth: width,
targetHeight: height,
});
// Convert blob to Buffer for Node.js processing
const arrayBuffer = await blob.arrayBuffer();
const pixelData = Buffer.from(arrayBuffer);
// eslint-disable-next-line no-console
console.log(`✓ Exported ${pixelData.length} bytes (${width}x${height} RGBA)`);
```
This returns a Blob containing uncompressed RGBA pixel data that you can process with custom algorithms.
## Download Exported Data
After exporting to raw data and processing the pixels, you can save the result to the file system using Node.js file operations:
```typescript highlight-save-raw-data
// Save processed raw data to file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
writeFileSync(`${outputDir}/raw-data.bin`, processedData);
// eslint-disable-next-line no-console
console.log(`✓ Saved raw data to ${outputDir}/raw-data.bin`);
```
For production use, you'll typically convert the raw pixel data to a standard image format using libraries like Sharp for efficient storage and delivery.
## Performance Considerations
Raw data export from CE.SDK involves trade-offs between flexibility and efficiency in server environments:
### Memory Usage
Raw RGBA data requires 4 bytes per pixel. A 1920×1080 CE.SDK export uses approximately 8.3 MB uncompressed, compared to 1-3 MB for PNG. In server environments with limited memory, reduce export resolution using `targetWidth` and `targetHeight` export options:
```typescript
const blob = await engine.block.export(blockId, {
mimeType: 'application/octet-stream',
targetWidth: 960,
targetHeight: 540
});
```
Check the maximum export size before exporting large CE.SDK designs:
```typescript
const maxSize = engine.editor.getMaxExportSize();
console.log(`Maximum export size: ${maxSize} pixels`);
```
### When to Use Raw vs. Compressed for CE.SDK Exports
- **Use raw data** if you need custom post-processing on CE.SDK exports before delivery
- **Use PNG or JPEG** if you're just saving CE.SDK designs to disk
- **Use raw data** for intermediate processing steps in automated pipelines
- **Use compressed formats** for final output or network transfer
- **Consider Sharp** for high-performance transformations instead of manual pixel manipulation
## Cleanup
Always dispose of the engine to free resources when you're done processing.
```typescript highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
This ensures all resources are properly cleaned up, preventing memory leaks in long-running server applications.
## API Reference
| Method | Description |
| -------------------------- | --------------------------------------------------------- |
| `CreativeEngine.init()` | Initializes the headless CE.SDK engine for server-side rendering |
| `engine.block.export()` | Exports a CE.SDK block with `mimeType: 'application/octet-stream'` for raw RGBA data |
| `engine.block.getWidth()` | Returns the width of a CE.SDK block in pixels |
| `engine.block.getHeight()` | Returns the height of a CE.SDK block in pixels |
| `Blob.arrayBuffer()` | Converts the blob to an ArrayBuffer for raw data access |
| `Buffer.from()` | Creates a Node.js Buffer from an ArrayBuffer |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To WebP"
description: "Export your CE.SDK designs to WebP format for optimized web delivery with lossy and lossless compression options."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-webp-aef6f4/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [To WebP](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-webp-aef6f4/)
---
Export designs to WebP format for optimized web delivery with smaller file sizes than PNG or JPEG.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-to-webp-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-to-webp-server-js)
WebP delivers smaller file sizes than PNG and JPEG while preserving image quality and transparency support. This makes it ideal for server-side processing where bandwidth and storage costs matter.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-webp-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import * as readline from 'readline';
config();
/**
* CE.SDK Server Guide: Export to WebP
*
* Demonstrates:
* - Interactive format selection
* - Export with quality options
* - Loading indicators
* - Saving to output directory
*/
// Prompt user for input
async function prompt(question: string): Promise {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
// Loading spinner
function showLoading(message: string): () => void {
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let i = 0;
const interval = setInterval(() => {
process.stdout.write(`\r${frames[i++ % frames.length]} ${message}`);
}, 80);
return () => {
clearInterval(interval);
process.stdout.write('\r');
};
}
// Display format options
console.log('\n🖼️ CE.SDK WebP Export\n');
console.log('Select export quality:');
console.log(' 1. Lossy (0.8) - Smaller file, good quality');
console.log(' 2. High (0.95) - Balanced quality and size');
console.log(' 3. Lossless (1.0) - Perfect quality, larger file\n');
const choice = await prompt('Enter choice (1-3): ');
const qualityMap: Record = {
'1': { quality: 0.8, name: 'lossy' },
'2': { quality: 0.95, name: 'high' },
'3': { quality: 1.0, name: 'lossless' }
};
const selected = qualityMap[choice] || qualityMap['1'];
console.log(`\nSelected: ${selected.name} (quality: ${selected.quality})\n`);
// Initialize engine with loading indicator
const stopLoading = showLoading('Initializing CE.SDK engine...');
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
stopLoading();
console.log('✓ Engine initialized\n');
try {
const stopSceneLoading = showLoading('Loading template...');
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
stopSceneLoading();
console.log('✓ Template loaded\n');
const page = engine.block.findByType('page')[0];
if (!page) {
throw new Error('No page found');
}
// Create output directory
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Export to WebP with selected quality
const stopExport = showLoading('Exporting to WebP...');
const webpBlob = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: selected.quality
});
stopExport();
// Save to output directory
const buffer = Buffer.from(await webpBlob.arrayBuffer());
const filename = `design-${selected.name}.webp`;
writeFileSync(`${outputDir}/${filename}`, buffer);
console.log(`✓ Exported: ${outputDir}/${filename}`);
console.log(` Size: ${(webpBlob.size / 1024).toFixed(1)} KB\n`);
// Export with target dimensions
const resizedBlob = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: selected.quality,
targetWidth: 1200,
targetHeight: 630
});
const resizedBuffer = Buffer.from(await resizedBlob.arrayBuffer());
writeFileSync(`${outputDir}/design-resized.webp`, resizedBuffer);
console.log(`✓ Exported: ${outputDir}/design-resized.webp (1200x630)`);
console.log(` Size: ${(resizedBlob.size / 1024).toFixed(1)} KB\n`);
} finally {
engine.dispose();
}
```
This guide covers exporting to WebP, configuring quality settings, and saving files to disk.
## Export to WebP
Call `engine.block.export()` with `mimeType: 'image/webp'` and a `webpQuality` value between 0 and 1.
```typescript highlight=highlight-export-webp
// Export to WebP with selected quality
const stopExport = showLoading('Exporting to WebP...');
const webpBlob = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: selected.quality
});
stopExport();
```
The `webpQuality` parameter controls compression. A value of 0.8 provides a good balance between file size and visual quality for most use cases.
## Export Options
WebP export supports these options:
| Option | Type | Description |
|--------|------|-------------|
| `mimeType` | `'image/webp'` | Required. Specifies WebP format |
| `webpQuality` | `number` | Quality from 0 to 1. Default 1.0 (lossless) |
| `targetWidth` | `number` | Optional resize width |
| `targetHeight` | `number` | Optional resize height |
Combine `targetWidth` and `targetHeight` to resize the output, useful for generating thumbnails or optimized versions.
```typescript highlight=highlight-export-options
// Export with target dimensions
const resizedBlob = await engine.block.export(page, {
mimeType: 'image/webp',
webpQuality: selected.quality,
targetWidth: 1200,
targetHeight: 630
});
```
Set `webpQuality` to 1.0 for lossless compression when pixel-perfect output is required.
## Save to File
Convert the exported blob to a buffer and write it to disk.
```typescript highlight=highlight-save-file
// Save to output directory
const buffer = Buffer.from(await webpBlob.arrayBuffer());
const filename = `design-${selected.name}.webp`;
writeFileSync(`${outputDir}/${filename}`, buffer);
```
The export returns a `Blob`. Convert it to an `ArrayBuffer`, then wrap in a `Buffer` for Node.js file system operations.
> **Note:** WebP is widely supported across platforms. For systems without WebP support, consider PNG or JPEG as fallback formats.
## API Reference
| API | Description |
|-----|-------------|
| `engine.block.export()` | Exports a block to an image blob with format and quality options |
| `writeFileSync()` | Node.js API to write buffer data to a file |
| `Buffer.from()` | Converts ArrayBuffer to Node.js Buffer for file operations |
## Next Steps
[Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Learn about all supported export formats and their options.
[Export to PDF](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-pdf-95e04b/) - Generate print-ready PDF documents from your designs.
[Size Limits](https://img.ly/docs/cesdk/node-native/export-save-publish/export/size-limits-6f0695/) - Understand export size constraints and optimization strategies.
[Partial Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export/partial-export-89aaf6/) - Export specific blocks or regions instead of the full design.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Export with Color Mask"
description: "Export design blocks with color masking in CE.SDK for Node.js, removing specific colors and generating alpha masks for print workflows and automation."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/export/with-color-mask-4f868f/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [With a Color Mask](https://img.ly/docs/cesdk/node-native/export-save-publish/export/with-color-mask-4f868f/)
---
Export design blocks with specific colors masked to transparency, while generating separate alpha mask files for print workflows and automated processing.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-with-color-mask-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-with-color-mask-server-js)
When exporting, CE.SDK can remove specific RGB colors by replacing matching pixels with transparency. The export generates two files: the masked image with transparent areas and an alpha mask showing removed pixels.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-with-color-mask-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Export with Color Mask
*
* Demonstrates color mask exports for removing specific colors:
* - Basic color mask export with RGB values
* - Specifying and converting mask colors
* - Export format options (PNG, compression)
* - Saving masked image and alpha mask files
* - Different block types (pages, groups, individual blocks)
* - Use cases (print workflows, transparency creation)
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// Output directory for exported files
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Create an image with registration marks that we'll mask out
const imageBlock = await engine.block.addImage(imageUri, {
size: { width: pageWidth * 0.8, height: pageHeight * 0.8 }
});
engine.block.appendChild(page, imageBlock);
// Center the image on the page
const imageWidth = engine.block.getWidth(imageBlock);
const imageHeight = engine.block.getHeight(imageBlock);
engine.block.setPositionX(imageBlock, (pageWidth - imageWidth) / 2);
engine.block.setPositionY(imageBlock, (pageHeight - imageHeight) / 2);
// Add registration marks at the corners (pure red)
const markSize = 30;
const imageX = engine.block.getPositionX(imageBlock);
const imageY = engine.block.getPositionY(imageBlock);
const markPositions = [
{ x: imageX - markSize - 10, y: imageY - markSize - 10 }, // Top-left
{ x: imageX + imageWidth + 10, y: imageY - markSize - 10 }, // Top-right
{ x: imageX - markSize - 10, y: imageY + imageHeight + 10 }, // Bottom-left
{ x: imageX + imageWidth + 10, y: imageY + imageHeight + 10 } // Bottom-right
];
markPositions.forEach((pos) => {
const mark = engine.block.create('graphic');
engine.block.setShape(mark, engine.block.createShape('rect'));
const redFill = engine.block.createFill('color');
engine.block.setColor(redFill, 'fill/color/value', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0
});
engine.block.setFill(mark, redFill);
engine.block.setWidth(mark, markSize);
engine.block.setHeight(mark, markSize);
engine.block.setPositionX(mark, pos.x);
engine.block.setPositionY(mark, pos.y);
engine.block.appendChild(page, mark);
});
// Export with red color masked - removes registration marks
const [maskedImage1, alphaMask1] = await engine.block.exportWithColorMask(
page,
1.0, // Red component (0.0-1.0)
0.0, // Green component
0.0, // Blue component (RGB: pure red)
{ mimeType: 'image/png' }
);
// Save both files to the file system
const buffer1 = Buffer.from(await maskedImage1.arrayBuffer());
writeFileSync(`${outputDir}/example1-masked.png`, buffer1);
const maskBuffer1 = Buffer.from(await alphaMask1.arrayBuffer());
writeFileSync(`${outputDir}/example1-mask.png`, maskBuffer1);
// Convert RGB values from 0-255 to 0.0-1.0 range
// Clear previous marks for new example
const allBlocks = engine.block.findByType('graphic');
allBlocks.forEach((block) => {
if (block !== imageBlock) {
engine.block.destroy(block);
}
});
// RGB values in 0-255 range
const rgb255 = { r: 255, g: 128, b: 64 };
// Convert to 0.0-1.0 range
const rgb01 = {
r: rgb255.r / 255, // 1.0
g: rgb255.g / 255, // 0.502
b: rgb255.b / 255 // 0.251
};
// Add custom color marks
markPositions.forEach((pos) => {
const mark = engine.block.create('graphic');
engine.block.setShape(mark, engine.block.createShape('rect'));
const customFill = engine.block.createFill('color');
engine.block.setColor(customFill, 'fill/color/value', {
r: rgb01.r,
g: rgb01.g,
b: rgb01.b,
a: 1.0
});
engine.block.setFill(mark, customFill);
engine.block.setWidth(mark, markSize);
engine.block.setHeight(mark, markSize);
engine.block.setPositionX(mark, pos.x);
engine.block.setPositionY(mark, pos.y);
engine.block.appendChild(page, mark);
});
// Export with custom color masked
const [maskedImage2, alphaMask2] = await engine.block.exportWithColorMask(
page,
rgb01.r,
rgb01.g,
rgb01.b,
{ mimeType: 'image/png' }
);
const buffer2 = Buffer.from(await maskedImage2.arrayBuffer());
writeFileSync(`${outputDir}/example2-masked.png`, buffer2);
const maskBuffer2 = Buffer.from(await alphaMask2.arrayBuffer());
writeFileSync(`${outputDir}/example2-mask.png`, maskBuffer2);
// Export with specific format options (compression, size)
// Clear previous marks
const blocks2 = engine.block.findByType('graphic');
blocks2.forEach((block) => {
if (block !== imageBlock) {
engine.block.destroy(block);
}
});
// Add magenta marks
markPositions.forEach((pos) => {
const mark = engine.block.create('graphic');
engine.block.setShape(mark, engine.block.createShape('rect'));
const magentaFill = engine.block.createFill('color');
engine.block.setColor(magentaFill, 'fill/color/value', {
r: 1.0,
g: 0.0,
b: 1.0,
a: 1.0
});
engine.block.setFill(mark, magentaFill);
engine.block.setWidth(mark, markSize);
engine.block.setHeight(mark, markSize);
engine.block.setPositionX(mark, pos.x);
engine.block.setPositionY(mark, pos.y);
engine.block.appendChild(page, mark);
});
// Export with format options
const [maskedImage3, alphaMask3] = await engine.block.exportWithColorMask(
page,
1.0,
0.0,
1.0, // Magenta
{
mimeType: 'image/png',
pngCompressionLevel: 9, // Maximum compression
targetWidth: 400,
targetHeight: 300
}
);
const buffer3 = Buffer.from(await maskedImage3.arrayBuffer());
writeFileSync(`${outputDir}/example3-masked.png`, buffer3);
const maskBuffer3 = Buffer.from(await alphaMask3.arrayBuffer());
writeFileSync(`${outputDir}/example3-mask.png`, maskBuffer3);
// Demonstrate saving both masked image and alpha mask
const blocks3 = engine.block.findByType('graphic');
blocks3.forEach((block) => {
if (block !== imageBlock) {
engine.block.destroy(block);
}
});
// Add cyan marks
markPositions.forEach((pos) => {
const mark = engine.block.create('graphic');
engine.block.setShape(mark, engine.block.createShape('rect'));
const cyanFill = engine.block.createFill('color');
engine.block.setColor(cyanFill, 'fill/color/value', {
r: 0.0,
g: 1.0,
b: 1.0,
a: 1.0
});
engine.block.setFill(mark, cyanFill);
engine.block.setWidth(mark, markSize);
engine.block.setHeight(mark, markSize);
engine.block.setPositionX(mark, pos.x);
engine.block.setPositionY(mark, pos.y);
engine.block.appendChild(page, mark);
});
// Export and save both files
const [maskedImage, alphaMask] = await engine.block.exportWithColorMask(
page,
0.0,
1.0,
1.0, // Cyan
{ mimeType: 'image/png' }
);
// Save masked image
const maskedBuffer = Buffer.from(await maskedImage.arrayBuffer());
writeFileSync(`${outputDir}/saved-masked.png`, maskedBuffer);
// Save alpha mask
const maskBuffer = Buffer.from(await alphaMask.arrayBuffer());
writeFileSync(`${outputDir}/saved-mask.png`, maskBuffer);
// eslint-disable-next-line no-console
console.log('✓ Saved masked image and alpha mask to output/');
// Export different block types with color masking
const blocks4 = engine.block.findByType('graphic');
blocks4.forEach((block) => {
if (block !== imageBlock) {
engine.block.destroy(block);
}
});
// Create a group of blocks with green element
const greenRect = engine.block.create('graphic');
engine.block.setShape(greenRect, engine.block.createShape('rect'));
const greenFill = engine.block.createFill('color');
engine.block.setColor(greenFill, 'fill/color/value', {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0
});
engine.block.setFill(greenRect, greenFill);
engine.block.setWidth(greenRect, 200);
engine.block.setHeight(greenRect, 200);
engine.block.setPositionX(greenRect, (pageWidth - 200) / 2);
engine.block.setPositionY(greenRect, (pageHeight - 200) / 2);
engine.block.appendChild(page, greenRect);
// Group the image and rectangle
const group = engine.block.group([imageBlock, greenRect]);
// Export the entire group with green masked
const [groupMasked, groupMask] = await engine.block.exportWithColorMask(
group,
0.0,
1.0,
0.0, // Green
{ mimeType: 'image/png' }
);
const groupBuffer = Buffer.from(await groupMasked.arrayBuffer());
writeFileSync(`${outputDir}/group-masked.png`, groupBuffer);
const groupMaskBuffer = Buffer.from(await groupMask.arrayBuffer());
writeFileSync(`${outputDir}/group-mask.png`, groupMaskBuffer);
// Use case: Remove registration marks for print workflows
engine.block.ungroup(group);
engine.block.destroy(greenRect);
// Add red registration marks for print workflow example
markPositions.forEach((pos) => {
const mark = engine.block.create('graphic');
engine.block.setShape(mark, engine.block.createShape('rect'));
const markFill = engine.block.createFill('color');
engine.block.setColor(markFill, 'fill/color/value', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0
});
engine.block.setFill(mark, markFill);
engine.block.setWidth(mark, markSize);
engine.block.setHeight(mark, markSize);
engine.block.setPositionX(mark, pos.x);
engine.block.setPositionY(mark, pos.y);
engine.block.appendChild(page, mark);
});
// Export with registration marks removed
const [printReady, registrationMask] = await engine.block.exportWithColorMask(
page,
1.0,
0.0,
0.0, // Remove pure red
{
mimeType: 'image/png',
pngCompressionLevel: 0 // Fast export for print
}
);
const printBuffer = Buffer.from(await printReady.arrayBuffer());
writeFileSync(`${outputDir}/print-ready.png`, printBuffer);
const regMaskBuffer = Buffer.from(await registrationMask.arrayBuffer());
writeFileSync(`${outputDir}/registration-mask.png`, regMaskBuffer);
// eslint-disable-next-line no-console
console.log('✓ Print-ready export with registration marks removed');
// eslint-disable-next-line no-console
console.log('✓ Exported all color mask examples to output/');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
Color mask exports work through exact RGB color matching—pixels that precisely match your specified color values (0.0-1.0 range) are removed. This is useful for print workflows (removing registration marks), batch processing (converting background colors to transparency), or generating alpha masks for compositing pipelines.
## Exporting with Color Masks
We export blocks with color masking using the `exportWithColorMask` method. This method removes specific RGB colors from the rendered output and generates both a masked image and an alpha mask.
```typescript highlight-export-with-color-mask
// Export with red color masked - removes registration marks
const [maskedImage1, alphaMask1] = await engine.block.exportWithColorMask(
page,
1.0, // Red component (0.0-1.0)
0.0, // Green component
0.0, // Blue component (RGB: pure red)
{ mimeType: 'image/png' }
);
```
The method accepts the block to export, three RGB color components (0.0-1.0 range), and optional export options like MIME type. This example uses pure red `(1.0, 0.0, 0.0)` to identify and remove registration marks from the design.
The export operation returns a Promise that resolves to an array containing two Blobs. The first Blob is the masked image with transparency applied where the specified color was found. The second Blob is the alpha mask—a black and white image showing which pixels were removed (black) and which remained (white).
We then convert both Blobs to buffers and save them to the file system using Node.js `writeFileSync()`.
### Specifying RGB Color Values
RGB color components in CE.SDK use floating-point values from 0.0 to 1.0, not the 0-255 integer values common in design tools:
- Pure red: `(1.0, 0.0, 0.0)` - Common for registration marks
- Pure magenta: `(1.0, 0.0, 1.0)` - Distinctive marker color
- Pure cyan: `(0.0, 1.0, 1.0)` - Alternative marker color
- Pure yellow: `(1.0, 1.0, 0.0)` - Useful for exclusion zones
When converting from standard 0-255 RGB values, divide each component by 255. For example, RGB(255, 128, 0) becomes `(1.0, 0.502, 0.0)`.
## How to Export with Color Masks
We convert the exported Blobs to buffers and save both the masked image and alpha mask files to the file system. This allows you to use the files in automated workflows or send them to print services.
```typescript highlight-save-export-files
// Export with red color masked - removes registration marks
const [maskedImage1, alphaMask1] = await engine.block.exportWithColorMask(
page,
1.0, // Red component (0.0-1.0)
0.0, // Green component
0.0, // Blue component (RGB: pure red)
{ mimeType: 'image/png' }
);
// Save both files to the file system
const buffer1 = Buffer.from(await maskedImage1.arrayBuffer());
writeFileSync(`${outputDir}/example1-masked.png`, buffer1);
const maskBuffer1 = Buffer.from(await alphaMask1.arrayBuffer());
writeFileSync(`${outputDir}/example1-mask.png`, maskBuffer1);
```
The masked image is print-ready with the specified color removed. The alpha mask shows exactly where pixels were removed, useful for verification or compositing in external applications.
## API Reference
| Method | Description |
| ------------------------------------ | --------------------------------------------------------- |
| `engine.block.exportWithColorMask()` | Exports a block with specific RGB color removed, generating masked image and alpha mask |
| `engine.block.export()` | Exports a block without color masking |
| `engine.scene.getCurrentPage()` | Gets the currently active page in the scene |
| `engine.block.create()` | Creates a new block of the specified type |
| `engine.block.createFill()` | Creates a fill definition for blocks |
| `engine.block.setColor()` | Sets the color value for a fill |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Export for Printing"
description: "Export designs from CE.SDK as print-ready PDFs with professional output options including high compatibility mode, underlayers for special media, and scene DPI configuration."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/for-printing-bca896/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [For Printing](https://img.ly/docs/cesdk/node-native/export-save-publish/for-printing-bca896/)
---
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.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-for-printing-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-for-printing-server-js)
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.
```typescript file=@cesdk_web_examples/guides-export-save-publish-for-printing-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync } from 'fs';
/**
* CE.SDK Server Example: Export for Printing
*
* 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
*/
async function main(): Promise {
// Initialize the Creative Engine
const engine = await CreativeEngine.init({
license: process.env.CESDK_LICENSE ?? ''
});
try {
// Load a template scene - this will be our print design
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/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);
// Enable high compatibility mode for consistent rendering across PDF viewers
// This rasterizes complex elements like gradients with transparency at the scene's DPI
const highCompatPdf = await engine.block.export(page, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true
});
// Convert blob to buffer and write to file system
const highCompatBuffer = Buffer.from(await highCompatPdf.arrayBuffer());
writeFileSync('print-high-compatibility.pdf', highCompatBuffer);
console.log(
`High compatibility PDF exported (${(highCompatBuffer.length / 1024).toFixed(1)} KB)`
);
// Disable high compatibility for faster exports when targeting modern PDF viewers
// Complex elements remain as vectors but may render differently across viewers
const standardPdf = await engine.block.export(page, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: false
});
const standardBuffer = Buffer.from(await standardPdf.arrayBuffer());
writeFileSync('print-standard.pdf', standardBuffer);
console.log(
`Standard PDF exported (${(standardBuffer.length / 1024).toFixed(1)} KB)`
);
// 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 underlayerPdf = 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
});
const underlayerBuffer = Buffer.from(await underlayerPdf.arrayBuffer());
writeFileSync('print-with-underlayer.pdf', underlayerBuffer);
console.log(
`PDF with underlayer exported (${(underlayerBuffer.length / 1024).toFixed(1)} KB)`
);
// Export with specific dimensions for print output
// targetWidth and targetHeight control the exported PDF dimensions in pixels
const targetSizePdf = await engine.block.export(page, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true,
targetWidth: 2480, // A4 at 300 DPI (210mm)
targetHeight: 3508 // A4 at 300 DPI (297mm)
});
const targetSizeBuffer = Buffer.from(await targetSizePdf.arrayBuffer());
writeFileSync('print-a4-300dpi.pdf', targetSizeBuffer);
console.log(
`A4 PDF exported (${(targetSizeBuffer.length / 1024).toFixed(1)} KB)`
);
console.log('\nAll PDFs exported successfully!');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
## 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
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.
```typescript highlight=highlight-setup
// Load a template scene - this will be our print design
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/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);
```
## PDF Export Options for Print
Export a page as PDF using `engine.block.export()` with `mimeType: 'application/pdf'`.
### 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
```typescript highlight=highlight-export-high-compatibility
// Enable high compatibility mode for consistent rendering across PDF viewers
// This rasterizes complex elements like gradients with transparency at the scene's DPI
const highCompatPdf = 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
When targeting modern PDF viewers where file size and export speed matter more than universal compatibility:
```typescript highlight=highlight-export-standard-pdf
// Disable high compatibility for faster exports when targeting modern PDF viewers
// Complex elements remain as vectors but may render differently across viewers
const standardPdf = await engine.block.export(page, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: false
});
```
## 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
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.
```typescript highlight=highlight-define-spot-color
// 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
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.
```typescript highlight=highlight-export-with-underlayer
// Export with underlayer enabled for DTF or special media printing
// The underlayer generates a shape behind design elements filled with the spot color
const underlayerPdf = 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
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
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.
```typescript highlight=highlight-export-target-size
// Export with specific dimensions for print output
// targetWidth and targetHeight control the exported PDF dimensions in pixels
const targetSizePdf = await engine.block.export(page, {
mimeType: 'application/pdf',
exportPdfWithHighCompatibility: true,
targetWidth: 2480, // A4 at 300 DPI (210mm)
targetHeight: 3508 // A4 at 300 DPI (297mm)
});
```
## Save to File System
Convert the exported blob to a buffer and write it to disk using Node.js file system APIs.
```typescript highlight=highlight-save-file
// Convert blob to buffer and write to file system
const highCompatBuffer = Buffer.from(await highCompatPdf.arrayBuffer());
writeFileSync('print-high-compatibility.pdf', highCompatBuffer);
console.log(
`High compatibility PDF exported (${(highCompatBuffer.length / 1024).toFixed(1)} KB)`
);
```
## 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](https://img.ly/docs/cesdk/node-native/plugins/print-ready-pdf-iroalu/) for setup and usage.
## Troubleshooting
### 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
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
Standard export uses RGB. Use the Print Ready PDF plugin with appropriate ICC profiles for accurate CMYK reproduction.
### 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
| 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 |
| `writeFileSync(path, buffer)` | Write buffer to file system (Node.js) |
## Next Steps
- [Print Ready PDF Plugin](https://img.ly/docs/cesdk/node-native/plugins/print-ready-pdf-iroalu/) - CMYK PDFs with ICC profiles
- [CMYK Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/cmyk-8a1334/) - Configure CMYK colors
- [Spot Colors](https://img.ly/docs/cesdk/node-native/colors/for-print/spot-c3a150/) - Define and use spot colors
- [Export to PDF](https://img.ly/docs/cesdk/node-native/export-save-publish/export/to-pdf-95e04b/) - General PDF export options
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Export for Social Media"
description: "Export images with Instagram portrait dimensions and quality settings using CE.SDK in Node.js."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/for-social-media-0e8a92/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [For Social Media](https://img.ly/docs/cesdk/node-native/export-save-publish/for-social-media-0e8a92/)
---
Export designs for social media with the correct dimensions and quality settings.
Configure image exports with exact pixel dimensions optimized for Instagram portrait posts.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-for-social-media-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-for-social-media-server-js)
Instagram portrait posts use a 4:5 aspect ratio at 1080×1350 pixels, providing more vertical screen real estate than square posts. This guide demonstrates how to export images with these dimensions using CE.SDK in Node.js.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-for-social-media-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Export for Social Media
*
* Demonstrates exporting an image with Instagram portrait dimensions (1080x1350).
*
* Note: Video export is not supported in @cesdk/node.
* For video exports, use the CE.SDK Renderer on Linux.
*/
// Initialize CE.SDK engine with baseURL for asset loading
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
// Load a template scene from a remote URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.block.findByType('page')[0];
if (!page) {
throw new Error('No page found in scene');
}
// Export with Instagram portrait dimensions (4:5 aspect ratio)
const blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 0.9,
targetWidth: 1080,
targetHeight: 1350
});
// Create output directory if it doesn't exist
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Convert Blob to Buffer and save to file system
const buffer = Buffer.from(await blob.arrayBuffer());
const filename = `${outputDir}/instagram-portrait.jpg`;
writeFileSync(filename, buffer);
console.log(
`Exported: instagram-portrait.jpg (1080x1350, ${(blob.size / 1024).toFixed(1)} KB)`
);
console.log(`File saved to: ${filename}`);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers loading a template scene, exporting with specific dimensions and quality settings, and saving the result to the file system.
> **Video Export:** The `@cesdk/node` package supports image exports only. For video exports on the server, use [CE.SDK Renderer](#broken-link-7f3e9a)—a native Linux binary with hardware-accelerated video encoding for MP4 output.
## Loading a Scene
Before exporting, load a template scene with visual content.
```typescript highlight-setup
// Initialize CE.SDK engine with baseURL for asset loading
const engine = await CreativeEngine.init({
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`
});
try {
// Load a template scene from a remote URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.block.findByType('page')[0];
if (!page) {
throw new Error('No page found in scene');
}
```
We initialize the engine, load a template from a remote URL, and locate the page for export.
## Exporting the Image
Export the page using `engine.block.export()`. The `targetWidth` and `targetHeight` options scale the design to exact pixel dimensions for Instagram portrait format.
```typescript highlight-export
// Export with Instagram portrait dimensions (4:5 aspect ratio)
const blob = await engine.block.export(page, {
mimeType: 'image/jpeg',
jpegQuality: 0.9,
targetWidth: 1080,
targetHeight: 1350
});
```
The export options control the output:
- **mimeType**: `image/jpeg` for social media (smaller file sizes than PNG)
- **jpegQuality**: 0.9 provides high quality with reasonable file size
- **targetWidth/targetHeight**: 1080×1350 pixels for Instagram portrait (4:5 aspect ratio)
## Saving to File System
After export, convert the Blob to a Buffer and write to the file system.
```typescript highlight-save
// Create output directory if it doesn't exist
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Convert Blob to Buffer and save to file system
const buffer = Buffer.from(await blob.arrayBuffer());
const filename = `${outputDir}/instagram-portrait.jpg`;
writeFileSync(filename, buffer);
```
The output directory is created if it doesn't exist. The console output confirms the export with file size, verifying the processing completed successfully.
## API Reference
| Method | Purpose |
|--------|---------|
| `engine.block.export()` | Export block as image (PNG, JPEG, WebP, PDF) |
| `engine.block.findByType()` | Find blocks by type (page, text, image, etc.) |
| `engine.scene.loadFromURL()` | Load a scene from a remote URL |
### Export Options (Images)
| Option | Type | Description |
|--------|------|-------------|
| `mimeType` | `string` | Output format: `image/jpeg`, `image/png`, `image/webp` |
| `jpegQuality` | `number` | JPEG compression (0.0-1.0), default 0.9 |
| `targetWidth` | `number` | Output width in pixels |
| `targetHeight` | `number` | Output height in pixels |
## Next Steps
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Complete export options including PNG, WebP, and PDF
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Pre-Export Validation"
description: "Documentation for Pre-Export Validation"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/pre-export-validation-3a2cba/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [Pre-Export Validation](https://img.ly/docs/cesdk/node-native/export-save-publish/pre-export-validation-3a2cba/)
---
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 in headless environments.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-export-pre-export-validation-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-export-pre-export-validation-server-js)
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.
```typescript file=@cesdk_web_examples/guides-export-save-publish-export-pre-export-validation-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
config();
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[];
}
const engine = await CreativeEngine.init({
license: process.env.CESDK_LICENSE
});
try {
// Create a scene with test elements that demonstrate validation issues
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
const centerY = pageHeight / 2;
// Row layout: Main validation examples (horizontally aligned)
const row1Y = centerY - 50;
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);
// 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 past right
engine.block.setPositionY(protrudingImage, row1Y);
engine.block.appendChild(page, protrudingImage);
// Validate design before export
const result = validateDesign(engine);
console.log('=== Pre-Export Validation ===');
// Log all issues for debugging
if (result.errors.length > 0) {
console.error(`Found ${result.errors.length} error(s):`);
result.errors.forEach((err) =>
console.error(` - ${err.blockName}: ${err.message}`)
);
}
if (result.warnings.length > 0) {
console.warn(`Found ${result.warnings.length} warning(s):`);
result.warnings.forEach((warn) =>
console.warn(` - ${warn.blockName}: ${warn.message}`)
);
}
// Block export for errors
if (result.errors.length > 0) {
console.error('\nExport blocked: Fix errors before exporting');
process.exit(1);
}
// Allow export with warnings
if (result.warnings.length > 0) {
console.log('\nProceeding with export despite warnings...');
} else {
console.log('\nValidation passed - no issues found');
}
// Export the design
const outputDir = './output';
if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/validated-design.png`, buffer);
console.log('Export successful: output/validated-design.png');
} finally {
engine.dispose();
}
function getBoundingBox(
engine: InstanceType,
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];
}
function 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;
}
function getBlockName(
engine: InstanceType,
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);
}
function getRelevantBlocks(
engine: InstanceType
): number[] {
return [
...engine.block.findByType('text'),
...engine.block.findByType('graphic')
];
}
function findOutsideBlocks(
engine: InstanceType,
page: number
): ValidationIssue[] {
const issues: ValidationIssue[] = [];
const pageBounds = getBoundingBox(engine, page);
for (const blockId of getRelevantBlocks(engine)) {
if (!engine.block.isValid(blockId)) continue;
const blockBounds = getBoundingBox(engine, blockId);
const overlap = calculateOverlap(blockBounds, pageBounds);
if (overlap === 0) {
// Element is completely outside the page
issues.push({
type: 'outside_page',
severity: 'error',
blockId,
blockName: getBlockName(engine, blockId),
message: 'Element is completely outside the visible page area'
});
}
}
return issues;
}
function findProtrudingBlocks(
engine: InstanceType,
page: number
): ValidationIssue[] {
const issues: ValidationIssue[] = [];
const pageBounds = getBoundingBox(engine, page);
for (const blockId of getRelevantBlocks(engine)) {
if (!engine.block.isValid(blockId)) continue;
// Compare element bounds against page bounds
const blockBounds = getBoundingBox(engine, blockId);
const overlap = 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: getBlockName(engine, blockId),
message: 'Element extends beyond page boundaries'
});
}
}
return issues;
}
function findObscuredText(
engine: InstanceType,
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 = calculateOverlap(
getBoundingBox(engine, textId),
getBoundingBox(engine, aboveId)
);
if (overlap > 0) {
// Text is obscured by element above it
issues.push({
type: 'text_obscured',
severity: 'warning',
blockId: textId,
blockName: getBlockName(engine, textId),
message: 'Text may be partially hidden by overlapping elements'
});
break;
}
}
}
return issues;
}
function findUnfilledPlaceholders(
engine: InstanceType
): ValidationIssue[] {
const issues: ValidationIssue[] = [];
const placeholders = engine.block.findAllPlaceholders();
for (const blockId of placeholders) {
if (!engine.block.isValid(blockId)) continue;
if (!isPlaceholderFilled(engine, blockId)) {
issues.push({
type: 'unfilled_placeholder',
severity: 'error',
blockId,
blockName: getBlockName(engine, blockId),
message: 'Placeholder has not been filled with content'
});
}
}
return issues;
}
function isPlaceholderFilled(
engine: InstanceType,
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;
}
function validateDesign(
engine: InstanceType
): ValidationResult {
const page = engine.block.findByType('page')[0];
const outsideIssues = findOutsideBlocks(engine, page);
const protrudingIssues = findProtrudingBlocks(engine, page);
const obscuredIssues = findObscuredText(engine, page);
const placeholderIssues = findUnfilledPlaceholders(engine);
const allIssues = [
...outsideIssues,
...protrudingIssues,
...obscuredIssues,
...placeholderIssues
];
return {
errors: allIssues.filter((i) => i.severity === 'error'),
warnings: allIssues.filter((i) => i.severity === 'warning')
};
}
```
This guide demonstrates how to detect elements outside the page, find protruding content, identify obscured text, and integrate validation into the export workflow in headless Node.js environments.
## 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:
```typescript highlight-get-bounding-box
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
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:
```typescript highlight-find-outside-blocks
const blockBounds = getBoundingBox(engine, blockId);
const overlap = 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
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:
```typescript highlight-find-protruding-blocks
// Compare element bounds against page bounds
const blockBounds = getBoundingBox(engine, blockId);
const overlap = 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
Text hidden behind other elements may be unreadable in the final export. First, get the stacking order and all text blocks:
```typescript highlight-find-obscured-text
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:
```typescript highlight-check-text-obscured
// 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 = calculateOverlap(
getBoundingBox(engine, textId),
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
Placeholders mark areas where users must add content before export. First, find all placeholder blocks in the design:
```typescript highlight-find-placeholders
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:
```typescript highlight-check-placeholder-filled
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;
}
```
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.
## Integrating with Export
In headless environments, run validation before export and handle results programmatically. Errors block the export entirely, while warnings can be logged but allow the export to proceed:
```typescript highlight-export-with-validation
// Validate design before export
const result = validateDesign(engine);
console.log('=== Pre-Export Validation ===');
// Log all issues for debugging
if (result.errors.length > 0) {
console.error(`Found ${result.errors.length} error(s):`);
result.errors.forEach((err) =>
console.error(` - ${err.blockName}: ${err.message}`)
);
}
if (result.warnings.length > 0) {
console.warn(`Found ${result.warnings.length} warning(s):`);
result.warnings.forEach((warn) =>
console.warn(` - ${warn.blockName}: ${warn.message}`)
);
}
// Block export for errors
if (result.errors.length > 0) {
console.error('\nExport blocked: Fix errors before exporting');
process.exit(1);
}
// Allow export with warnings
if (result.warnings.length > 0) {
console.log('\nProceeding with export despite warnings...');
} else {
console.log('\nValidation passed - no issues found');
}
// Export the design
const outputDir = './output';
if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/validated-design.png`, buffer);
console.log('Export successful: output/validated-design.png');
```
When validation fails, log the issues and exit with an error code so calling systems know the export was blocked.
## 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.findAllPlaceholders()` | Find all placeholder blocks |
| `engine.block.getFill(id)` | Get the fill block |
| `engine.block.getString(id, property)` | Get a string property value |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Save"
description: "Save design progress locally or to a backend service to allow for later editing or publishing."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/save-c8b124/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Save](https://img.ly/docs/cesdk/node-native/export-save-publish/save-c8b124/)
---
Save and serialize designs in CE.SDK for later retrieval, sharing, or storage using string or archive formats.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples)
CE.SDK provides two formats for persisting designs. Choose the format based on your storage and portability requirements.
```typescript file=@cesdk_web_examples/guides-export-save-publish-save-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { CompressionFormat, CompressionLevel } from '@cesdk/node';
import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'fs';
import { createInterface } from 'readline';
import { config } from 'dotenv';
import path from 'path';
config();
/**
* CE.SDK Server Guide: Save Designs
*
* Demonstrates how to save and serialize designs:
* - Saving scenes to string format for database storage
* - Saving scenes to archive format with embedded assets
* - Loading saved content back into the engine
*/
function prompt(question: string): Promise {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
console.log('\n=== CE.SDK Save Designs ===\n');
console.log('Select save format:');
console.log(' 1. String (for database storage)');
console.log(' 2. Archive (self-contained ZIP)');
console.log(' 3. Both formats\n');
const choice = await prompt('Enter choice (1/2/3): ');
const saveString =
choice === '1' || choice === '3' || !['1', '2', '3'].includes(choice);
const saveArchive =
choice === '2' || choice === '3' || !['1', '2', '3'].includes(choice);
if (!['1', '2', '3'].includes(choice)) {
console.log('Invalid choice. Defaulting to both formats.\n');
}
console.log('⏳ Initializing Creative Engine...');
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
console.log('⏳ Loading template scene...');
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'
);
const page = engine.scene.getCurrentPage();
if (page == null) {
throw new Error('No page found in scene');
}
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
console.log('✅ Scene loaded\n');
if (saveString) {
console.log('⏳ Saving to string...');
const sceneString = await engine.scene.saveToString();
writeFileSync(`${outputDir}/scene.scene`, sceneString);
console.log(
`✅ Scene saved: output/scene.scene (${(sceneString.length / 1024).toFixed(1)} KB)`
);
// Example: Save with compression (requires local build)
// To run with compression: npm run dev:local
const compressed = await engine.scene.saveToString(
undefined, // allowedResourceSchemes
undefined, // onDisallowedResourceScheme
CompressionFormat.Zstd, // compression format
CompressionLevel.Default // compression level
);
writeFileSync(`${outputDir}/scene-compressed.scene`, compressed);
console.log(
`✅ Compressed scene saved: output/scene-compressed.scene (${(compressed.length / 1024).toFixed(1)} KB, ${((1 - compressed.length / sceneString.length) * 100).toFixed(1)}% smaller)`
);
}
if (saveArchive) {
console.log('⏳ Saving to archive...');
const archiveBlob = await engine.scene.saveToArchive();
const archiveBuffer = Buffer.from(await archiveBlob.arrayBuffer());
writeFileSync(`${outputDir}/scene.zip`, archiveBuffer);
console.log(
`✅ Archive saved: output/scene.zip (${(archiveBuffer.length / 1024).toFixed(1)} KB)`
);
}
if (saveString) {
console.log('\n⏳ Loading from saved scene file...');
const sceneString = readFileSync(`${outputDir}/scene.scene`, 'utf-8');
await engine.scene.loadFromString(sceneString);
console.log('✅ Scene loaded from file');
}
if (saveArchive) {
console.log('⏳ Loading from saved archive...');
const archivePath = path.resolve(`${outputDir}/scene.zip`);
const archiveFileUrl = `file://${archivePath}`;
await engine.scene.loadFromArchiveURL(archiveFileUrl);
console.log('✅ Scene loaded from archive');
}
console.log('\n🎉 Complete! Files saved to:', outputDir);
} finally {
engine.dispose();
}
```
## 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
Serialize the current scene to a Base64-encoded string suitable for database storage.
```typescript highlight=highlight-save-to-string
const sceneString = await engine.scene.saveToString();
```
The string contains the complete scene structure but references assets by their original URLs.
## Save to Archive
Create a self-contained ZIP file with the scene and all embedded assets.
```typescript highlight=highlight-save-to-archive
const archiveBlob = await engine.scene.saveToArchive();
```
The archive includes all pages, elements, and asset data in a single portable file.
## Compression Options (Preview)
CE.SDK supports optional compression for saved scenes to reduce file size. Compression is particularly useful for large scenes or when storage space is limited.
```typescript
import { CompressionFormat, CompressionLevel } from '@cesdk/node';
// Save with Zstd compression (recommended)
const compressed = await engine.scene.saveToString(
undefined, // allowedResourceSchemes
undefined, // onDisallowedResourceScheme
CompressionFormat.Zstd, // compression format
CompressionLevel.Default // compression level
);
```
**Compression Formats:**
- `CompressionFormat.None` - No compression (default)
- `CompressionFormat.Zstd` - Zstandard compression (recommended for best performance)
**Compression Levels:**
- `CompressionLevel.Fastest` - Fastest compression, larger output
- `CompressionLevel.Default` - Balanced speed and size (recommended)
- `CompressionLevel.Best` - Best compression, slower
**Performance:** Compression adds minimal overhead (\<50ms) while reducing scene size by approximately 64%. The Default level provides the best balance of speed and compression ratio.
**Availability:**
- **Browser (Web)**: Available in current release via `@cesdk/cesdk-js`
- **Node.js**: Available in development builds. Use `npm run dev:local` in examples to test with local build, or wait for the next package release
- **iOS/Android**: Planned for future releases
## Write to File System
Use Node.js `writeFileSync` to persist saved designs to the file system.
Scene strings can be written directly as text:
```typescript highlight=highlight-write-scene
writeFileSync(`${outputDir}/scene.scene`, sceneString);
```
For archives, convert the Blob to a Buffer before writing:
```typescript highlight=highlight-write-archive
const archiveBuffer = Buffer.from(await archiveBlob.arrayBuffer());
writeFileSync(`${outputDir}/scene.zip`, archiveBuffer);
```
## Load Scene from File
Read a previously saved `.scene` file from disk and restore it to the engine.
```typescript highlight=highlight-load-scene
const sceneString = readFileSync(`${outputDir}/scene.scene`, 'utf-8');
await engine.scene.loadFromString(sceneString);
```
Scene files are lightweight but require the original asset URLs to remain accessible.
## Load Archive from File
Read a self-contained `.zip` archive from disk with all embedded assets.
```typescript highlight=highlight-load-archive
const archivePath = path.resolve(`${outputDir}/scene.zip`);
const archiveFileUrl = `file://${archivePath}`;
await engine.scene.loadFromArchiveURL(archiveFileUrl);
```
Archives are portable and work offline since all assets are bundled within the file.
## 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.loadFromURL()` | Load scene from remote URL |
| `engine.scene.loadFromArchiveURL()` | Load scene from URL (file://, http://, https://, or object URL) |
## Next Steps
- [Export Overview](https://img.ly/docs/cesdk/node-native/export-save-publish/export/overview-9ed3a8/) - Export designs to image, PDF, and video formats
- [Load Scene](https://img.ly/docs/cesdk/node-native/open-the-editor/load-scene-478833/) - Load scenes from remote URLs and archives
- [Store Custom Metadata](https://img.ly/docs/cesdk/node-native/export-save-publish/store-custom-metadata-337248/) - Attach metadata like tags or version info to designs
- [Partial Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export/partial-export-89aaf6/) - Export individual blocks or selections
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Store Custom Metadata"
description: "Attach, retrieve, and manage custom key-value metadata on design blocks in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/export-save-publish/store-custom-metadata-337248/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Store Custom Metadata](https://img.ly/docs/cesdk/node-native/export-save-publish/store-custom-metadata-337248/)
---
Attach custom key-value metadata to design blocks in CE.SDK for tracking asset
origins, storing application state, or linking to external systems.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-export-save-publish-store-custom-metadata-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-export-save-publish-store-custom-metadata-server-js)
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.
```typescript file=@cesdk_web_examples/guides-export-save-publish-store-custom-metadata-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Store Custom Metadata
*
* 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
*/
async function main() {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// 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);
console.log('Metadata operations completed successfully!');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
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
We start by initializing the CE.SDK engine with a basic configuration. The metadata APIs are available on the `engine.block` namespace.
```typescript highlight-setup
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// 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
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.
```typescript highlight-set-metadata
// 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');
```
You can attach multiple metadata entries to a single block. Each entry is independent and can be accessed, modified, or removed separately.
## 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.
```typescript highlight-get-metadata
// Retrieve a metadata value by key
if (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
Use `engine.block.findAllMetadata()` to get an array of all metadata keys stored on a block.
```typescript highlight-find-all-metadata
// 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}`);
}
```
This is useful for iterating through all metadata on a block or debugging what metadata is attached.
## 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.
```typescript highlight-store-structured-data
// 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);
```
This pattern lets you store complex objects like configuration settings, generation parameters, or any structured information that can be serialized to JSON.
## 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.
```typescript highlight-remove-metadata
// Remove a metadata key
engine.block.removeMetadata(imageBlock, 'uploadedBy');
console.log('Removed metadata key: uploadedBy');
```
After removal, you can verify the key was deleted by checking with `hasMetadata()`.
```typescript highlight-verify-removal
// 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);
```
## 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.
> **Note:** 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
### getMetadata Throws Error
If `getMetadata()` throws an error, the key doesn't exist on the block. Always use `hasMetadata()` to check before retrieving:
```typescript
if (engine.block.hasMetadata(block, 'myKey')) {
const value = engine.block.getMetadata(block, 'myKey');
}
```
### 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
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
| 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 |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "File Format Support"
description: "See which image, video, audio, font, and template formats CE.SDK supports for import and export."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/file-format-support-3c4b2a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/node-native/compatibility-fef719/) > [File Format Support](https://img.ly/docs/cesdk/node-native/file-format-support-3c4b2a/)
---
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, documents, or fonts, CE.SDK provides an editing environment with excellent media compatibility and performance—optimized for modern hardware.
This guide outlines supported formats and known limitations across media types.
## Importing Media
### SVG Limitations
## Exporting Media
## Importing Templates
## Font Formats
## Size Limits
### Image Resolution Limits
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Fills"
description: "Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/fills-402ddc/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/node-native/fills-402ddc/)
---
---
## Related Pages
- [Fills](https://img.ly/docs/cesdk/node-native/fills/overview-3895ee/) - Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements.
- [Color Fills](https://img.ly/docs/cesdk/node-native/fills/color-7129cd/) - Get to know all available fill types and their properties
- [Gradient](https://img.ly/docs/cesdk/node-native/filters-and-effects/gradients-0ff079/) - Documentation for Gradients
- [Image Fills](https://img.ly/docs/cesdk/node-native/fills/image-e9cb5c/) - Apply photos, textures, and patterns to design elements using image fills in CE.SDK for server-side Node.js applications.
- [Video Fills](https://img.ly/docs/cesdk/node-native/fills/video-ec7f9f/) - Fill shapes and graphics with video content in headless server environments using CE.SDK's video fill system. Create video-filled graphics, control content positioning, access video metadata, and optimize resources.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Color Fills"
description: "Get to know all available fill types and their properties"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/fills/color-7129cd/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/node-native/fills-402ddc/) > [Solid Color](https://img.ly/docs/cesdk/node-native/fills/color-7129cd/)
---
Apply uniform solid colors to shapes, text, and design blocks using CE.SDK's
comprehensive color fill system with support for multiple color spaces.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-fills-color-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-fills-color-server-js)
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.
```typescript file=@cesdk_web_examples/guides-fills-color-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
config();
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Check if block supports fills
const canHaveFill = engine.block.supportsFill(page);
if (!canHaveFill) {
throw new Error('Block does not support fills');
}
// Create a color fill
const colorFill = engine.block.createFill('color');
// Create a graphic block with a shape
const block = engine.block.create('graphic');
engine.block.setShape(block, engine.block.createShape('rect'));
engine.block.setWidth(block, 200);
engine.block.setHeight(block, 150);
engine.block.setPositionX(block, 50);
engine.block.setPositionY(block, 50);
engine.block.appendChild(page, block);
// Apply the fill to the block
engine.block.setFill(block, colorFill);
// Set the fill color using RGB values
engine.block.setColor(colorFill, '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)
});
// Get the current fill from a block
const currentFill = engine.block.getFill(block);
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(colorFill, 'fill/color/value');
console.log('Current color:', currentColor);
// Create a block with CMYK color for print workflows
const cmykBlock = engine.block.create('graphic');
engine.block.setShape(cmykBlock, engine.block.createShape('ellipse'));
engine.block.setWidth(cmykBlock, 150);
engine.block.setHeight(cmykBlock, 150);
engine.block.setPositionX(cmykBlock, 300);
engine.block.setPositionY(cmykBlock, 50);
engine.block.appendChild(page, cmykBlock);
const cmykFill = engine.block.createFill('color');
engine.block.setFill(cmykBlock, cmykFill);
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)
});
// First define the spot color globally
engine.editor.setSpotColorRGB('BrandRed', 0.9, 0.1, 0.1);
// Then apply to fill
const spotBlock = engine.block.create('graphic');
engine.block.setShape(spotBlock, engine.block.createShape('ellipse'));
engine.block.setWidth(spotBlock, 150);
engine.block.setHeight(spotBlock, 150);
engine.block.setPositionX(spotBlock, 500);
engine.block.setPositionY(spotBlock, 50);
engine.block.appendChild(page, spotBlock);
const spotFill = engine.block.createFill('color');
engine.block.setFill(spotBlock, spotFill);
engine.block.setColor(spotFill, 'fill/color/value', {
name: 'BrandRed',
tint: 1.0,
externalReference: '' // Optional reference system
});
// Toggle fill visibility
const toggleBlock = engine.block.create('graphic');
engine.block.setShape(toggleBlock, engine.block.createShape('rect'));
engine.block.setWidth(toggleBlock, 150);
engine.block.setHeight(toggleBlock, 100);
engine.block.setPositionX(toggleBlock, 50);
engine.block.setPositionY(toggleBlock, 250);
engine.block.appendChild(page, toggleBlock);
const toggleFill = engine.block.createFill('color');
engine.block.setFill(toggleBlock, toggleFill);
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
// Disable the fill (block becomes transparent)
engine.block.setFillEnabled(toggleBlock, false);
// Re-enable
engine.block.setFillEnabled(toggleBlock, true);
// Share a single fill instance between multiple blocks
const block1 = engine.block.create('graphic');
engine.block.setShape(block1, engine.block.createShape('rect'));
engine.block.setWidth(block1, 100);
engine.block.setHeight(block1, 100);
engine.block.setPositionX(block1, 250);
engine.block.setPositionY(block1, 250);
engine.block.appendChild(page, block1);
const block2 = engine.block.create('graphic');
engine.block.setShape(block2, engine.block.createShape('rect'));
engine.block.setWidth(block2, 100);
engine.block.setHeight(block2, 100);
engine.block.setPositionX(block2, 370);
engine.block.setPositionY(block2, 250);
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);
// Now changing the fill affects both blocks
engine.block.setColor(sharedFill, 'fill/color/value', {
r: 0.0,
g: 0.5,
b: 0.5,
a: 1.0
});
// Convert colors between different color spaces
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);
// Define and apply brand colors as spot colors
engine.editor.setSpotColorRGB('PrimaryBrand', 0.2, 0.4, 0.8);
engine.editor.setSpotColorRGB('SecondaryBrand', 0.9, 0.5, 0.1);
const brandBlock = engine.block.create('graphic');
engine.block.setShape(brandBlock, engine.block.createShape('rect'));
engine.block.setWidth(brandBlock, 150);
engine.block.setHeight(brandBlock, 100);
engine.block.setPositionX(brandBlock, 500);
engine.block.setPositionY(brandBlock, 250);
engine.block.appendChild(page, brandBlock);
const brandFill = engine.block.createFill('color');
engine.block.setFill(brandBlock, brandFill);
// Apply brand color
const brandColor = {
name: 'PrimaryBrand',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(brandFill, 'fill/color/value', brandColor);
// Create semi-transparent overlay
const transparentBlock = engine.block.create('graphic');
engine.block.setShape(transparentBlock, engine.block.createShape('rect'));
engine.block.setWidth(transparentBlock, 150);
engine.block.setHeight(transparentBlock, 100);
engine.block.setPositionX(transparentBlock, 50);
engine.block.setPositionY(transparentBlock, 400);
engine.block.appendChild(page, transparentBlock);
const transparentFill = engine.block.createFill('color');
engine.block.setFill(transparentBlock, transparentFill);
engine.block.setColor(transparentFill, 'fill/color/value', {
r: 0.0,
g: 0.8,
b: 0.2,
a: 0.5 // 50% opacity
});
// Use CMYK color space for print production
const printBlock = engine.block.create('graphic');
engine.block.setShape(printBlock, engine.block.createShape('rect'));
engine.block.setWidth(printBlock, 150);
engine.block.setHeight(printBlock, 100);
engine.block.setPositionX(printBlock, 250);
engine.block.setPositionY(printBlock, 400);
engine.block.appendChild(page, printBlock);
const printFill = engine.block.createFill('color');
engine.block.setFill(printBlock, printFill);
const printColor = {
c: 0.0,
m: 0.85,
y: 1.0,
k: 0.0,
tint: 1.0
};
engine.block.setColor(printFill, 'fill/color/value', printColor);
// Export the result
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('./output')) {
mkdirSync('./output');
}
writeFileSync('./output/solid-color-fills.png', buffer);
console.log('Exported to ./output/solid-color-fills.png');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
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
### 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
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
### 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 blocks typically don't support fills.
```typescript highlight=highlight-check-fill-support
// Check if block supports fills
const canHaveFill = engine.block.supportsFill(page);
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 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'`.
```typescript highlight=highlight-create-fill
// Create a color fill
const 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.
## Applying Color Fills
### Setting a Fill on a Block
Once you've created a color fill, attach it to a block using `setFill()`:
```typescript highlight=highlight-apply-fill
// Create a graphic block with a shape
const block = engine.block.create('graphic');
engine.block.setShape(block, engine.block.createShape('rect'));
engine.block.setWidth(block, 200);
engine.block.setHeight(block, 150);
engine.block.setPositionX(block, 50);
engine.block.setPositionY(block, 50);
engine.block.appendChild(page, block);
// Apply the fill to the block
engine.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
Retrieve the current fill attached to a block using `getFill()` and inspect its type:
```typescript highlight=highlight-get-fill
// Get the current fill from a block
const currentFill = engine.block.getFill(block);
const fillType = engine.block.getType(currentFill);
console.log('Fill type:', fillType); // '//ly.img.ubq/fill/color'
```
## Modifying Color Fill Properties
### 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.
```typescript highlight=highlight-set-rgb
// Set the fill color using RGB values
engine.block.setColor(colorFill, '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
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.
```typescript highlight=highlight-set-cmyk
// Create a block with CMYK color for print workflows
const cmykBlock = engine.block.create('graphic');
engine.block.setShape(cmykBlock, engine.block.createShape('ellipse'));
engine.block.setWidth(cmykBlock, 150);
engine.block.setHeight(cmykBlock, 150);
engine.block.setPositionX(cmykBlock, 300);
engine.block.setPositionY(cmykBlock, 50);
engine.block.appendChild(page, cmykBlock);
const cmykFill = engine.block.createFill('color');
engine.block.setFill(cmykBlock, cmykFill);
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
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.
```typescript highlight=highlight-set-spot
// First define the spot color globally
engine.editor.setSpotColorRGB('BrandRed', 0.9, 0.1, 0.1);
// Then apply to fill
const spotBlock = engine.block.create('graphic');
engine.block.setShape(spotBlock, engine.block.createShape('ellipse'));
engine.block.setWidth(spotBlock, 150);
engine.block.setHeight(spotBlock, 150);
engine.block.setPositionX(spotBlock, 500);
engine.block.setPositionY(spotBlock, 50);
engine.block.appendChild(page, spotBlock);
const spotFill = engine.block.createFill('color');
engine.block.setFill(spotBlock, spotFill);
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
Retrieve the current color value from a fill using `getColor()`:
```typescript highlight=highlight-get-color
// Get the current color value
const currentColor = engine.block.getColor(colorFill, 'fill/color/value');
console.log('Current color:', currentColor);
```
## Enabling and Disabling Color Fills
### 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:
```typescript highlight=highlight-toggle-fill
// Toggle fill visibility
const toggleBlock = engine.block.create('graphic');
engine.block.setShape(toggleBlock, engine.block.createShape('rect'));
engine.block.setWidth(toggleBlock, 150);
engine.block.setHeight(toggleBlock, 100);
engine.block.setPositionX(toggleBlock, 50);
engine.block.setPositionY(toggleBlock, 250);
engine.block.appendChild(page, toggleBlock);
const toggleFill = engine.block.createFill('color');
engine.block.setFill(toggleBlock, toggleFill);
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
// Disable the fill (block becomes transparent)
engine.block.setFillEnabled(toggleBlock, false);
// Re-enable
engine.block.setFillEnabled(toggleBlock, 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
### Sharing Color Fills
You can share a single fill instance between multiple blocks. Changes to the shared fill affect all blocks using it:
```typescript highlight=highlight-share-fill
// Share a single fill instance between multiple blocks
const block1 = engine.block.create('graphic');
engine.block.setShape(block1, engine.block.createShape('rect'));
engine.block.setWidth(block1, 100);
engine.block.setHeight(block1, 100);
engine.block.setPositionX(block1, 250);
engine.block.setPositionY(block1, 250);
engine.block.appendChild(page, block1);
const block2 = engine.block.create('graphic');
engine.block.setShape(block2, engine.block.createShape('rect'));
engine.block.setWidth(block2, 100);
engine.block.setHeight(block2, 100);
engine.block.setPositionX(block2, 370);
engine.block.setPositionY(block2, 250);
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);
// Now changing the fill affects both blocks
engine.block.setColor(sharedFill, 'fill/color/value', {
r: 0.0,
g: 0.5,
b: 0.5,
a: 1.0
});
```
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
Convert colors between different color spaces using `convertColorToColorSpace()`:
```typescript highlight=highlight-convert-color
// Convert colors between different color spaces
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);
```
This is useful when you need to ensure color consistency across different output mediums (screen vs. print).
## Common Use Cases
### Brand Color Application
Define and apply brand colors as spot colors to maintain consistency across all design elements:
```typescript highlight=highlight-brand-colors
// Define and apply brand colors as spot colors
engine.editor.setSpotColorRGB('PrimaryBrand', 0.2, 0.4, 0.8);
engine.editor.setSpotColorRGB('SecondaryBrand', 0.9, 0.5, 0.1);
const brandBlock = engine.block.create('graphic');
engine.block.setShape(brandBlock, engine.block.createShape('rect'));
engine.block.setWidth(brandBlock, 150);
engine.block.setHeight(brandBlock, 100);
engine.block.setPositionX(brandBlock, 500);
engine.block.setPositionY(brandBlock, 250);
engine.block.appendChild(page, brandBlock);
const brandFill = engine.block.createFill('color');
engine.block.setFill(brandBlock, brandFill);
// Apply brand color
const brandColor = {
name: 'PrimaryBrand',
tint: 1.0,
externalReference: ''
};
engine.block.setColor(brandFill, '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
Create semi-transparent overlays and visual effects by adjusting the alpha channel:
```typescript highlight=highlight-transparency
// Create semi-transparent overlay
const transparentBlock = engine.block.create('graphic');
engine.block.setShape(transparentBlock, engine.block.createShape('rect'));
engine.block.setWidth(transparentBlock, 150);
engine.block.setHeight(transparentBlock, 100);
engine.block.setPositionX(transparentBlock, 50);
engine.block.setPositionY(transparentBlock, 400);
engine.block.appendChild(page, transparentBlock);
const transparentFill = engine.block.createFill('color');
engine.block.setFill(transparentBlock, transparentFill);
engine.block.setColor(transparentFill, 'fill/color/value', {
r: 0.0,
g: 0.8,
b: 0.2,
a: 0.5 // 50% opacity
});
```
### Print-Ready Colors
Use CMYK color space for designs destined for print production:
```typescript highlight=highlight-print-colors
// Use CMYK color space for print production
const printBlock = engine.block.create('graphic');
engine.block.setShape(printBlock, engine.block.createShape('rect'));
engine.block.setWidth(printBlock, 150);
engine.block.setHeight(printBlock, 100);
engine.block.setPositionX(printBlock, 250);
engine.block.setPositionY(printBlock, 400);
engine.block.appendChild(page, printBlock);
const printFill = engine.block.createFill('color');
engine.block.setFill(printBlock, printFill);
const printColor = {
c: 0.0,
m: 0.85,
y: 1.0,
k: 0.0,
tint: 1.0
};
engine.block.setColor(printFill, 'fill/color/value', printColor);
```
## Troubleshooting
### 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
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
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
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
| 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
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
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Image Fills"
description: "Apply photos, textures, and patterns to design elements using image fills in CE.SDK for server-side Node.js applications."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/fills/image-e9cb5c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/node-native/fills-402ddc/) > [Image](https://img.ly/docs/cesdk/node-native/fills/image-e9cb5c/)
---
Fill shapes, text, and design blocks with photos and images from URLs or data
URIs using CE.SDK's versatile image fill system.
> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-fills-image-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-fills-image-server-js)
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, data URIs, or file paths, with built-in support for responsive images through source sets and multiple content fill modes for flexible positioning.
```typescript file=@cesdk_web_examples/guides-fills-image-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
config();
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Check if 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);
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// ===== Section 1: Create Image Fill with Convenience API =====
// Create a new image fill using the convenience API
const coverImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
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, 200);
engine.block.setHeight(manualBlock, 150);
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 2: 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: { width: 200, height: 150 }
}
);
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: { width: 200, height: 150 }
}
);
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 3: 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, 200);
engine.block.setHeight(responsiveBlock, 150);
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 4: Data URI / Base64 Images =====
// Use data URI for embedded images
// Fetch an image and convert to base64 data URI
const imageResponse = await fetch(
'https://img.ly/static/ubq_samples/thumbnails/sample_1.jpg'
);
const imageBuffer = Buffer.from(await imageResponse.arrayBuffer());
const base64Image = imageBuffer.toString('base64');
const imageDataUri = `data:image/jpeg;base64,${base64Image}`;
const dataUriBlock = engine.block.create('graphic');
engine.block.setShape(dataUriBlock, engine.block.createShape('rect'));
engine.block.setWidth(dataUriBlock, 200);
engine.block.setHeight(dataUriBlock, 150);
const dataUriFill = engine.block.createFill('image');
engine.block.setString(dataUriFill, 'fill/image/imageFileURI', imageDataUri);
engine.block.setFill(dataUriBlock, dataUriFill);
engine.block.appendChild(page, dataUriBlock);
// ===== Section 5: Opacity =====
// Control opacity for transparency effects
const opacityBlock = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_6.jpg',
{
size: { width: 200, height: 150 }
}
);
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
];
// Position blocks in a grid layout
const cols = 3;
const spacing = 10;
const margin = 50;
const blockWidth = 200;
const blockHeight = 150;
blocks.forEach((block, index) => {
const col = index % cols;
const row = Math.floor(index / cols);
engine.block.setPositionX(block, margin + col * (blockWidth + spacing));
engine.block.setPositionY(block, margin + row * (blockHeight + spacing));
});
// Export the result
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('./output')) {
mkdirSync('./output');
}
writeFileSync('./output/image-fills.png', buffer);
console.log('Exported to ./output/image-fills.png');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
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
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
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.
```typescript highlight=highlight-check-fill-support
// Check if 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);
```
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
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
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:
```typescript highlight=highlight-create-image-fill
// Create a new image fill using the convenience API
const coverImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
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
For more control over the fill configuration or to apply fills to existing blocks, you can create fills manually:
```typescript highlight=highlight-manual-fill-creation
// Or create manually for more control
const manualBlock = engine.block.create('graphic');
engine.block.setShape(manualBlock, engine.block.createShape('rect'));
engine.block.setWidth(manualBlock, 200);
engine.block.setHeight(manualBlock, 150);
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
You can retrieve the fill from any block and inspect its type to verify it's an image fill:
```typescript highlight=highlight-get-current-fill
// 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'
```
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
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 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:
```typescript highlight=highlight-fill-mode-cover
// Cover mode: Fill entire block, may crop image
const coverBlock = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_3.jpg',
{
size: { width: 200, height: 150 }
}
);
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 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:
```typescript highlight=highlight-fill-mode-contain
// 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: { width: 200, height: 150 }
}
);
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
You can query the current fill mode to understand how the image is being displayed:
```typescript highlight=highlight-get-fill-mode
// Get current fill mode
const 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
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
A source set is an array of image sources, each with a URI and dimensions:
```typescript highlight=highlight-source-set
// Use source sets for responsive images
const responsiveBlock = engine.block.create('graphic');
engine.block.setShape(responsiveBlock, engine.block.createShape('rect'));
engine.block.setWidth(responsiveBlock, 200);
engine.block.setHeight(responsiveBlock, 150);
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.
> **Note:** 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
You can get the current source set from a fill to inspect or modify it:
```typescript highlight=highlight-get-source-set
// Get current source set
const sourceSet = engine.block.getSourceSet(
responsiveFill,
'fill/image/sourceSet'
);
console.log('Source set entries:', sourceSet.length);
```
## 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
You can embed image data directly using data URIs, which is particularly useful for small images, icons, or dynamically generated graphics:
```typescript highlight=highlight-data-uri
// Use data URI for embedded images
// Fetch an image and convert to base64 data URI
const imageResponse = await fetch(
'https://img.ly/static/ubq_samples/thumbnails/sample_1.jpg'
);
const imageBuffer = Buffer.from(await imageResponse.arrayBuffer());
const base64Image = imageBuffer.toString('base64');
const imageDataUri = `data:image/jpeg;base64,${base64Image}`;
const dataUriBlock = engine.block.create('graphic');
engine.block.setShape(dataUriBlock, engine.block.createShape('rect'));
engine.block.setWidth(dataUriBlock, 200);
engine.block.setHeight(dataUriBlock, 150);
const dataUriFill = engine.block.createFill('image');
engine.block.setString(dataUriFill, 'fill/image/imageFileURI', imageDataUri);
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
### Controlling Opacity
You can control the overall opacity of blocks with image fills, affecting the entire block including its fill:
```typescript highlight=highlight-opacity
// Control opacity for transparency effects
const opacityBlock = await engine.block.addImage(
'https://img.ly/static/ubq_samples/sample_6.jpg',
{
size: { width: 200, height: 150 }
}
);
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.
> **Note:** 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
### 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
| 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
| Property | Type | Values | Description |
| ------------------ | ---- | ------------------ | ------------------------------------- |
| `contentFill/mode` | Enum | 'Cover', 'Contain' | How the image scales within its block |
### SourceSet Interface
```typescript
interface SourceSetEntry {
uri: string; // Image URI
width: number; // Image width in pixels
height: number; // Image height in pixels
}
```
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Fills"
description: "Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/fills/overview-3895ee/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/node-native/fills-402ddc/) > [Overview](https://img.ly/docs/cesdk/node-native/fills/overview-3895ee/)
---
Some [design blocks](https://img.ly/docs/cesdk/node-native/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](https://img.ly/docs/cesdk/node-native/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
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.
```javascript
engine.block.supportsFill(scene); // Returns false
engine.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.
```javascript
const colorFill = engine.block.getFill(block);
const defaultRectFillType = engine.block.getType(colorFill);
```
## 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](https://img.ly/docs/cesdk/node-native/concepts/blocks-90241e/) for a complete list of all available properties for each type of fill.
```javascript
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.
```javascript
engine.block.setColor(colorFill, 'fill/color/value', {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0
});
```
## 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.
```javascript
engine.block.setFillEnabled(block, false);
engine.block.setFillEnabled(block, !engine.block.isFillEnabled(block));
```
## 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`.
```javascript
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.
```javascript
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
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.
```javascript
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
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.
```javascript
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));
```
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Video Fills"
description: "Fill shapes and graphics with video content in headless server environments using CE.SDK's video fill system. Create video-filled graphics, control content positioning, access video metadata, and optimize resources."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/fills/video-ec7f9f/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/node-native/fills-402ddc/) > [Video](https://img.ly/docs/cesdk/node-native/fills/video-ec7f9f/)
---
Video fills let you fill shapes and graphics with video content. Unlike video blocks which represent standalone video elements, video fills apply video as a visual property to existing blocks — similar to how you might fill a shape with an image or color.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-fills-video-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-fills-video-server-js)
## Context: Video Fills vs Video Blocks
**Video fills** apply video as a fill property to graphic blocks. This is useful when you want to use video as a texture within shapes, mask video to specific areas, or create video-filled design elements. Video fills attach to graphic blocks via the fill API.
**Video blocks** are standalone elements that represent video content directly on the canvas. They're created with `engine.block.create('video')` and represent complete video clips.
## Reference Example
The following code demonstrates the complete video fills API. Below, each section is explained in detail.
```typescript file=@cesdk_web_examples/guides-fills-video-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
config();
async function main() {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 600);
engine.block.appendChild(scene, page);
// Check if 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);
const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4';
// ===== Section 1: Create Video Fill =====
// Create a graphic block with a video fill
const basicBlock = engine.block.create('graphic');
engine.block.setShape(basicBlock, engine.block.createShape('rect'));
engine.block.setWidth(basicBlock, 200);
engine.block.setHeight(basicBlock, 150);
// Create a video fill
const videoFill = 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(videoFill, 'fill/video/fileURI', videoUri);
// Apply the fill to the block
engine.block.setFill(basicBlock, videoFill);
engine.block.appendChild(page, basicBlock);
// 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'
// ===== Section 2: Content Fill Modes =====
// Cover mode: Fill entire block, may crop video
const coverBlock = engine.block.create('graphic');
engine.block.setShape(coverBlock, engine.block.createShape('rect'));
engine.block.setWidth(coverBlock, 200);
engine.block.setHeight(coverBlock, 150);
const coverVideoFill = engine.block.createFill('video');
engine.block.setString(coverVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(coverBlock, coverVideoFill);
engine.block.appendChild(page, coverBlock);
// 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'
// Contain mode: Fit entire video, may leave empty space
const containBlock = engine.block.create('graphic');
engine.block.setShape(containBlock, engine.block.createShape('rect'));
engine.block.setWidth(containBlock, 200);
engine.block.setHeight(containBlock, 150);
const containVideoFill = engine.block.createFill('video');
engine.block.setString(containVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(containBlock, containVideoFill);
engine.block.appendChild(page, containBlock);
// Set content fill mode to Contain
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 3: Force Load Video Resource =====
// Force load video resource to access metadata
const metadataBlock = engine.block.create('graphic');
engine.block.setShape(metadataBlock, engine.block.createShape('rect'));
engine.block.setWidth(metadataBlock, 200);
engine.block.setHeight(metadataBlock, 150);
const metadataVideoFill = engine.block.createFill('video');
engine.block.setString(metadataVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(metadataBlock, metadataVideoFill);
engine.block.appendChild(page, metadataBlock);
// Force load the video resource before accessing metadata
await engine.block.forceLoadAVResource(metadataVideoFill);
// Now we can access video metadata
const totalDuration = engine.block.getDouble(
metadataVideoFill,
'fill/video/totalDuration'
);
console.log('Video total duration:', totalDuration, 'seconds');
// ===== Section 4: Video as Shape Fill =====
// Fill a shape with video content
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, 200);
engine.block.setHeight(ellipseBlock, 150);
const ellipseVideoFill = engine.block.createFill('video');
engine.block.setString(ellipseVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(ellipseBlock, ellipseVideoFill);
engine.block.appendChild(page, ellipseBlock);
// ===== Section 5: Opacity =====
// Control opacity for transparency effects
const opacityBlock = engine.block.create('graphic');
engine.block.setShape(opacityBlock, engine.block.createShape('rect'));
engine.block.setWidth(opacityBlock, 200);
engine.block.setHeight(opacityBlock, 150);
const opacityVideoFill = engine.block.createFill('video');
engine.block.setString(opacityVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(opacityBlock, opacityVideoFill);
engine.block.appendChild(page, opacityBlock);
// Set block opacity to 70%
engine.block.setFloat(opacityBlock, 'opacity', 0.7);
// ===== Section 6: Shared Video Fill =====
// Share one video fill between multiple blocks for memory efficiency
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, 200);
engine.block.setHeight(sharedBlock1, 150);
engine.block.setFill(sharedBlock1, sharedFill);
engine.block.appendChild(page, sharedBlock1);
// 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, 160);
engine.block.setHeight(sharedBlock2, 120);
engine.block.setFill(sharedBlock2, sharedFill);
engine.block.appendChild(page, sharedBlock2);
console.log('Shared fill - Two blocks using the same video fill instance');
// ===== Position all blocks in grid layout =====
const blocks = [
basicBlock, // Position 0
coverBlock, // Position 1
containBlock, // Position 2
metadataBlock, // Position 3
ellipseBlock, // Position 4
opacityBlock, // Position 5
sharedBlock1, // Position 6
sharedBlock2 // Position 7
];
// Position blocks in a grid layout
const cols = 4;
const spacing = 10;
const margin = 50;
const blockWidth = 200;
const blockHeight = 150;
blocks.forEach((block, index) => {
const col = index % cols;
const row = Math.floor(index / cols);
engine.block.setPositionX(block, margin + col * (blockWidth + spacing));
engine.block.setPositionY(block, margin + row * (blockHeight + spacing));
});
console.log('Video fills demonstration complete.');
console.log('Created', blocks.length, 'blocks with video fills.');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
## Understanding Video Fills
Video fills work in any scene. Create a scene and add a page to get started.
```typescript highlight=highlight-create-scene
// Create a scene with a page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 600);
engine.block.appendChild(scene, page);
```
Before working with fills, verify that the block supports fills.
```typescript highlight=highlight-check-fill-support
// Check if 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);
```
## Creating a Video Fill
Create a video fill and apply it to a graphic block:
```typescript highlight=highlight-create-video-fill
// Create a graphic block with a video fill
const basicBlock = engine.block.create('graphic');
engine.block.setShape(basicBlock, engine.block.createShape('rect'));
engine.block.setWidth(basicBlock, 200);
engine.block.setHeight(basicBlock, 150);
// Create a video fill
const videoFill = 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(videoFill, 'fill/video/fileURI', videoUri);
// Apply the fill to the block
engine.block.setFill(basicBlock, videoFill);
engine.block.appendChild(page, basicBlock);
```
Retrieve the fill from a block and verify its type:
```typescript highlight=highlight-get-current-fill
// 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'
```
## Content Fill Modes
Content fill modes control how video content is positioned within the block bounds.
### Cover Mode
Cover mode scales the video to completely fill the block area. Parts of the video may be cropped if the aspect ratios differ.
```typescript highlight=highlight-fill-mode-cover
// Cover mode: Fill entire block, may crop video
const coverBlock = engine.block.create('graphic');
engine.block.setShape(coverBlock, engine.block.createShape('rect'));
engine.block.setWidth(coverBlock, 200);
engine.block.setHeight(coverBlock, 150);
const coverVideoFill = engine.block.createFill('video');
engine.block.setString(coverVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(coverBlock, coverVideoFill);
engine.block.appendChild(page, coverBlock);
// 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'
```
### Contain Mode
Contain mode scales the video to fit entirely within the block bounds. The complete video is visible, but empty space may appear if aspect ratios differ.
```typescript highlight=highlight-fill-mode-contain
// Contain mode: Fit entire video, may leave empty space
const containBlock = engine.block.create('graphic');
engine.block.setShape(containBlock, engine.block.createShape('rect'));
engine.block.setWidth(containBlock, 200);
engine.block.setHeight(containBlock, 150);
const containVideoFill = engine.block.createFill('video');
engine.block.setString(containVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(containBlock, containVideoFill);
engine.block.appendChild(page, containBlock);
// Set content fill mode to Contain
engine.block.setEnum(containBlock, 'contentFill/mode', 'Contain');
```
### Getting the Current Mode
```typescript highlight=highlight-get-fill-mode
// Get current fill mode
const currentMode = engine.block.getEnum(containBlock, 'contentFill/mode');
console.log('Current fill mode:', currentMode);
```
The available modes are:
- `'Crop'` — Default mode, uses crop scale to position content
- `'Cover'` — Fill entire area, may crop content
- `'Contain'` — Show all content, may leave empty space
## Loading Video Resources
Video metadata like duration is only available after the resource loads. Use `forceLoadAVResource()` to ensure the video is loaded before accessing metadata.
```typescript highlight=highlight-force-load-resource
// Force load video resource to access metadata
const metadataBlock = engine.block.create('graphic');
engine.block.setShape(metadataBlock, engine.block.createShape('rect'));
engine.block.setWidth(metadataBlock, 200);
engine.block.setHeight(metadataBlock, 150);
const metadataVideoFill = engine.block.createFill('video');
engine.block.setString(metadataVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(metadataBlock, metadataVideoFill);
engine.block.appendChild(page, metadataBlock);
// Force load the video resource before accessing metadata
await engine.block.forceLoadAVResource(metadataVideoFill);
// Now we can access video metadata
const totalDuration = engine.block.getDouble(
metadataVideoFill,
'fill/video/totalDuration'
);
console.log('Video total duration:', totalDuration, 'seconds');
```
## Common Use Cases
### Video in Shapes
Fill any shape with video content:
```typescript highlight=highlight-video-shape-fill
// Fill a shape with video content
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, 200);
engine.block.setHeight(ellipseBlock, 150);
const ellipseVideoFill = engine.block.createFill('video');
engine.block.setString(ellipseVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(ellipseBlock, ellipseVideoFill);
engine.block.appendChild(page, ellipseBlock);
```
### Opacity Control
Adjust block opacity for transparency effects:
```typescript highlight=highlight-opacity
// Control opacity for transparency effects
const opacityBlock = engine.block.create('graphic');
engine.block.setShape(opacityBlock, engine.block.createShape('rect'));
engine.block.setWidth(opacityBlock, 200);
engine.block.setHeight(opacityBlock, 150);
const opacityVideoFill = engine.block.createFill('video');
engine.block.setString(opacityVideoFill, 'fill/video/fileURI', videoUri);
engine.block.setFill(opacityBlock, opacityVideoFill);
engine.block.appendChild(page, opacityBlock);
// Set block opacity to 70%
engine.block.setFloat(opacityBlock, 'opacity', 0.7);
```
## Additional Techniques
### Shared Video Fills
Multiple blocks can share the same fill instance, which improves memory efficiency when displaying the same video in multiple locations.
```typescript highlight=highlight-shared-fill
// Share one video fill between multiple blocks for memory efficiency
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, 200);
engine.block.setHeight(sharedBlock1, 150);
engine.block.setFill(sharedBlock1, sharedFill);
engine.block.appendChild(page, sharedBlock1);
// 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, 160);
engine.block.setHeight(sharedBlock2, 120);
engine.block.setFill(sharedBlock2, sharedFill);
engine.block.appendChild(page, sharedBlock2);
console.log('Shared fill - Two blocks using the same video fill instance');
```
## Cleanup
Always dispose of the engine when finished to release resources.
```typescript highlight=highlight-cleanup
} finally {
engine.dispose();
}
```
## Troubleshooting
### Video Fill Not Displaying
If video fills don't appear:
1. Ensure the video URI is accessible and valid
2. Check that the block has a shape assigned before setting the fill
3. Confirm the block has been appended to the page
### Metadata Not Available
If `fill/video/totalDuration` returns unexpected values:
1. Call `engine.block.forceLoadAVResource()` before accessing metadata
2. Await the promise to ensure loading completes
3. Verify the video file exists and is accessible
## API Reference
| Property | Type | Description |
| -------------------------- | -------- | -------------------------------------- |
| `fill/video/fileURI` | `string` | URI of the video file |
| `fill/video/totalDuration` | `double` | Total duration of the video in seconds |
| `contentFill/mode` | `enum` | Fill mode: 'Crop', 'Cover', 'Contain' |
| `opacity` | `float` | Block opacity from 0.0 to 1.0 |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Filters and Effects"
description: "Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/)
---
---
## Related Pages
- [Node.js Filters & Effects Library](https://img.ly/docs/cesdk/node-native/filters-and-effects/overview-299b15/) - Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying.
- [Supported Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/support-a666dd/) - View the full list of visual effects and filters available in CE.SDK, including both built-in and custom options.
- [Apply a Filter or Effect](https://img.ly/docs/cesdk/node-native/filters-and-effects/apply-2764e4/) - Documentation for Apply a Filter or Effect
- [Create Custom Filters](https://img.ly/docs/cesdk/node-native/filters-and-effects/create-custom-filters-c796ba/) - Extend CE.SDK with custom LUT filter asset sources for brand-specific color grading and filter collections.
- [Chroma Key (Green Screen)](https://img.ly/docs/cesdk/node-native/filters-and-effects/chroma-key-green-screen-1e3e99/) - Apply the green screen effect to images and videos, replacing specific colors with transparency for compositing workflows.
- [Blur Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/blur-71d642/) - Apply blur effects to design elements to create depth, focus attention, or soften backgrounds.
- [Create a Custom LUT Filter](https://img.ly/docs/cesdk/node-native/filters-and-effects/create-custom-lut-filter-6e3f49/) - Create and apply custom LUT filters to achieve consistent, brand-aligned visual styles.
- [Distortion Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/distortion-5b5a66/) - Apply distortion effects to warp, shift, and transform design elements for dynamic artistic visuals in CE.SDK.
- [Duotone](https://img.ly/docs/cesdk/node-native/filters-and-effects/duotone-831fc5/) - Apply duotone effects to images, mapping tones to two colors for stylized visuals, vintage aesthetics, or brand-specific treatments.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Apply a Filter or Effect"
description: "Documentation for Apply a Filter or Effect"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/apply-2764e4/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Apply Filter or Effect](https://img.ly/docs/cesdk/node-native/filters-and-effects/apply-2764e4/)
---
Apply professional color grading, blur effects, and artistic treatments to
design elements using CE.SDK's visual effects system.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-filters-and-effects-apply-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-filters-and-effects-apply-server-js)
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.
```typescript file=@cesdk_web_examples/guides-filters-and-effects-apply-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Apply Filters and Effects
*
* Demonstrates applying various filters and effects to image blocks:
* - Checking effect support
* - Applying basic effects (blur)
* - Configuring effect parameters (adjustments)
* - Applying LUT filters
* - Applying duotone filters
* - Combining multiple effects
* - Managing effect stacks
* - Batch processing
* - Exporting results
*/
/**
* Convert hex color string to RGBA values (0-1 range)
*/
function hexToRgba(hex: string): {
r: number;
g: number;
b: number;
a: number;
} {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16) / 255,
g: parseInt(result[2], 16) / 255,
b: parseInt(result[3], 16) / 255,
a: 1.0
}
: { r: 0, g: 0, b: 0, a: 1.0 };
}
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Sample image URL for demonstrations
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Block size for grid layout (will be positioned at the end)
const blockSize = { width: 200, height: 150 };
// Create an image block to check effect support
const sampleBlock = await engine.block.addImage(imageUri, {
size: blockSize
});
engine.block.appendChild(page, sampleBlock);
// Check if a block supports effects - graphic blocks with image fills support effects
const supportsEffects = engine.block.supportsEffects(sampleBlock);
console.log('Block supports effects:', supportsEffects); // true
// Page blocks support effects when they have fills applied
const pageSupportsEffects = engine.block.supportsEffects(page);
console.log('Page supports effects:', pageSupportsEffects);
// 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 (0.0 to 1.0)
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);
// 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
);
// Find all available properties for this effect
const adjustmentProperties =
engine.block.findAllProperties(adjustmentsEffect);
console.log('Available adjustment properties:', adjustmentProperties);
// Demonstrate LUT filters by applying 2 different presets (Grid positions 3-4)
// LUT configurations with different color grading styles
const lutConfigs = [
{
uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png',
horizontalTileCount: 5,
verticalTileCount: 5
},
{
uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_bw_5_5_128.png',
horizontalTileCount: 5,
verticalTileCount: 5
}
];
const lutImageBlocks = [];
for (const lutConfig of lutConfigs) {
const lutImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
engine.block.appendChild(page, lutImageBlock);
lutImageBlocks.push(lutImageBlock);
// Create LUT filter effect
const lutEffect = engine.block.createEffect(
'//ly.img.ubq/effect/lut_filter'
);
// Configure LUT with preset filter settings
engine.block.setString(
lutEffect,
'effect/lut_filter/lutFileURI',
lutConfig.uri
);
engine.block.setInt(
lutEffect,
'effect/lut_filter/horizontalTileCount',
lutConfig.horizontalTileCount
);
engine.block.setInt(
lutEffect,
'effect/lut_filter/verticalTileCount',
lutConfig.verticalTileCount
);
engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.85);
engine.block.appendEffect(lutImageBlock, lutEffect);
}
// Demonstrate Duotone filters by applying 2 different color combinations (Grid positions 5-6)
// Duotone configurations with different color schemes
const duotoneConfigs = [
{ darkColor: '#0b3d5b', lightColor: '#f8bc60' }, // Blue/Orange
{ darkColor: '#2d1e3e', lightColor: '#e8d5b7' } // Purple/Cream
];
const duotoneImageBlocks = [];
for (const duotoneConfig of duotoneConfigs) {
const duotoneImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
engine.block.appendChild(page, duotoneImageBlock);
duotoneImageBlocks.push(duotoneImageBlock);
// Create duotone filter effect
const duotoneEffect = engine.block.createEffect(
'//ly.img.ubq/effect/duotone_filter'
);
// Configure duotone colors using hex to RGBA conversion
const darkColor = hexToRgba(duotoneConfig.darkColor);
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/darkColor',
darkColor
);
const lightColor = hexToRgba(duotoneConfig.lightColor);
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/lightColor',
lightColor
);
engine.block.setFloat(
duotoneEffect,
'effect/duotone_filter/intensity',
0.8
);
engine.block.appendEffect(duotoneImageBlock, duotoneEffect);
}
// Create an image block to demonstrate combining multiple effects (Grid position 7)
const combinedImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
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 and toggle
const isBlurEnabled = engine.block.isEffectEnabled(combinedBlur);
console.log('Blur effect is enabled:', isBlurEnabled);
// Temporarily disable the blur effect
engine.block.setEffectEnabled(combinedBlur, false);
console.log(
'Blur effect disabled:',
!engine.block.isEffectEnabled(combinedBlur)
);
// Re-enable for final export
engine.block.setEffectEnabled(combinedBlur, true);
// Create a temporary block to demonstrate effect removal (Grid position 8)
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 from the block
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);
console.log('Effect removed and destroyed');
// ===== Position all blocks in grid layout =====
// Calculate grid positions for 9 blocks (3x3 grid)
const spacing = 20;
const margin = 40;
const cols = 3;
const totalGridWidth = cols * blockSize.width + (cols - 1) * spacing;
const startX = (800 - totalGridWidth) / 2;
const startY = margin;
const getPosition = (index: number) => ({
x: startX + (index % cols) * (blockSize.width + spacing),
y: startY + Math.floor(index / cols) * (blockSize.height + spacing)
});
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 (batch processing)
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('Batch processing complete');
// Export the scene to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/filters-and-effects.png', buffer);
console.log('Exported to output/filters-and-effects.png');
} finally {
engine.dispose();
}
```
This guide covers how to apply and manage effects programmatically using the block API.
## Programmatic Effect Application
### Initialize CE.SDK
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.
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
This initializes the CE.SDK engine in headless mode, giving you full API access to the effects system without UI dependencies.
### 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, scene blocks do not support effects.
```typescript highlight-supportsEffects
// Create an image block to check effect support
const sampleBlock = await engine.block.addImage(imageUri, {
size: blockSize
});
engine.block.appendChild(page, sampleBlock);
// Check if a block supports effects - graphic blocks with image fills support effects
const supportsEffects = engine.block.supportsEffects(sampleBlock);
console.log('Block supports effects:', supportsEffects); // true
// Page blocks support effects when they have fills applied
const pageSupportsEffects = engine.block.supportsEffects(page);
console.log('Page supports effects:', pageSupportsEffects);
```
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.
### 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.
> **Tip:** 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
> time-based properties.
```typescript highlight-createEffect
// 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 (0.0 to 1.0)
engine.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
> **Note:** `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
After creating an effect, we can customize its appearance by setting properties. Each effect exposes different parameters depending on its type and capabilities.
```typescript highlight-modifyProperties
// 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);
// 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
);
```
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
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.
```typescript highlight-apply-lut-filter
// Demonstrate LUT filters by applying 2 different presets (Grid positions 3-4)
// LUT configurations with different color grading styles
const lutConfigs = [
{
uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png',
horizontalTileCount: 5,
verticalTileCount: 5
},
{
uri: 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_bw_5_5_128.png',
horizontalTileCount: 5,
verticalTileCount: 5
}
];
const lutImageBlocks = [];
for (const lutConfig of lutConfigs) {
const lutImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
engine.block.appendChild(page, lutImageBlock);
lutImageBlocks.push(lutImageBlock);
// Create LUT filter effect
const lutEffect = engine.block.createEffect(
'//ly.img.ubq/effect/lut_filter'
);
// Configure LUT with preset filter settings
engine.block.setString(
lutEffect,
'effect/lut_filter/lutFileURI',
lutConfig.uri
);
engine.block.setInt(
lutEffect,
'effect/lut_filter/horizontalTileCount',
lutConfig.horizontalTileCount
);
engine.block.setInt(
lutEffect,
'effect/lut_filter/verticalTileCount',
lutConfig.verticalTileCount
);
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
**LUT configuration**: Each LUT file requires specifying the `lutFileURI` pointing to the LUT image, along with `horizontalTileCount` and `verticalTileCount` describing the grid layout of color transformation cubes.
### 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 applies duotone filters using direct color configuration. The `hexToRgba` utility converts hex color values to RGBA format required by the `setColor` API.
```typescript highlight-apply-duotone-filter
// Demonstrate Duotone filters by applying 2 different color combinations (Grid positions 5-6)
// Duotone configurations with different color schemes
const duotoneConfigs = [
{ darkColor: '#0b3d5b', lightColor: '#f8bc60' }, // Blue/Orange
{ darkColor: '#2d1e3e', lightColor: '#e8d5b7' } // Purple/Cream
];
const duotoneImageBlocks = [];
for (const duotoneConfig of duotoneConfigs) {
const duotoneImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
engine.block.appendChild(page, duotoneImageBlock);
duotoneImageBlocks.push(duotoneImageBlock);
// Create duotone filter effect
const duotoneEffect = engine.block.createEffect(
'//ly.img.ubq/effect/duotone_filter'
);
// Configure duotone colors using hex to RGBA conversion
const darkColor = hexToRgba(duotoneConfig.darkColor);
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/darkColor',
darkColor
);
const lightColor = hexToRgba(duotoneConfig.lightColor);
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)
**Color format**: Duotone colors use RGBA values in the 0.0 to 1.0 range. Convert hex colors using a helper function as shown in the example.
### 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.
> **Note:** 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.
```typescript highlight-addEffect
// Create an image block to demonstrate combining multiple effects (Grid position 7)
const combinedImageBlock = await engine.block.addImage(imageUri, {
size: { width: 200, height: 150 }
});
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 duotone
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
### List and Access Effects
We can retrieve all effects applied to a block and inspect their properties. This is useful for building effect management logic or debugging effect configurations.
```typescript highlight-getEffects
// 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}`);
});
```
This allows you to iterate through all applied effects, read their properties, and make modifications as needed.
### Enable/Disable 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 conditional rendering in processing pipelines.
```typescript highlight-disableEffect
// Check if effect is enabled and toggle
const isBlurEnabled = engine.block.isEffectEnabled(combinedBlur);
console.log('Blur effect is enabled:', isBlurEnabled);
// Temporarily disable the blur effect
engine.block.setEffectEnabled(combinedBlur, false);
console.log(
'Blur effect disabled:',
!engine.block.isEffectEnabled(combinedBlur)
);
// Re-enable for final export
engine.block.setEffectEnabled(combinedBlur, true);
```
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 comparison outputs, implement conditional processing, or optimize rendering based on output requirements.
### 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.
```typescript highlight-destroyEffect
// Create a temporary block to demonstrate effect removal (Grid position 8)
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 from the block
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);
console.log('Effect removed and destroyed');
```
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
### 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.
```typescript highlight-batch-processing
// Apply same effects to multiple blocks (batch processing)
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('Batch processing complete');
```
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.
### 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:
```typescript
// Create a reusable preset function
async function applyVintagePreset(engine: CreativeEngine, imageBlock: number) {
// Apply LUT filter
const lutEffect = engine.block.createEffect('lut_filter');
engine.block.setString(
lutEffect,
'lut_filter/lutFileURI',
'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.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 preset
const 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
- **Processing pipelines** - Apply standardized effects across batch operations
### Export Results
After applying effects, export the processed content to a file. CE.SDK supports various export formats including PNG, JPEG, and PDF.
```typescript highlight-export
// Export the scene to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/filters-and-effects.png', buffer);
console.log('Exported to output/filters-and-effects.png');
```
The export operation renders all effects and saves the result to the file system. Always dispose of the engine instance when processing is complete to free resources.
## Performance Considerations
CE.SDK's effect system is optimized for performance, but understanding these considerations helps you build efficient processing pipelines:
- **GPU acceleration**: Effects leverage GPU rendering for smooth performance on modern systems
- **Effect complexity**: Blur and LUT filters are computationally expensive compared to simple adjustments
- **Video effects**: Apply effects sparingly to video blocks to maintain smooth processing
- **Batch optimization**: When processing many images, consider reusing engine instances rather than reinitializing
Test your effect combinations with representative workloads early in development to ensure acceptable performance.
## Troubleshooting
### 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
If you experience slow processing:
- Reduce the number of effects per element
- Lower blur radius values or use smaller LUT files
- Temporarily disable effects during intermediate operations with `setEffectEnabled()`
- Profile your processing pipeline to identify bottlenecks
### 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
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 support effects when they have fills applied (such as background fills)
- Scene blocks cannot have effects applied
- Check the block type with `block.getType()` and use `block.supportsEffects()` before attempting to apply effects
## 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
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 see the results of each effect in the exported output
- **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.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Blur Effects"
description: "Apply blur effects to design elements to create depth, focus attention, or soften backgrounds."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/blur-71d642/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Apply Blur](https://img.ly/docs/cesdk/node-native/filters-and-effects/blur-71d642/)
---
Apply blur effects to design elements using CE.SDK's dedicated blur system for
creating depth, focus, and atmospheric effects.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-filters-and-effects-blur-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-filters-and-effects-blur-server-js)
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.
```typescript file=@cesdk_web_examples/guides-filters-and-effects-blur-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Blur Effects
*
* Demonstrates applying blur effects to design elements:
* - Checking blur support
* - Creating and applying blur effects
* - Configuring blur parameters for different types
* - Enabling/disabling blur
* - Sharing blur across blocks
* - Exporting results
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Sample image URL for demonstrations
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
// Create an image block to check blur support
const sampleBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, sampleBlock);
// Check if the block supports blur - graphic blocks with shapes support blur
const supportsBlur = engine.block.supportsBlur(sampleBlock);
console.log('Block supports blur:', supportsBlur);
// Create a radial blur effect
const blur = engine.block.createBlur('//ly.img.ubq/blur/radial');
// Configure radial blur parameters
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);
// Apply the blur to the image block
engine.block.setBlur(sampleBlock, blur);
engine.block.setBlurEnabled(sampleBlock, true);
// Access the applied blur and its properties
const appliedBlur = engine.block.getBlur(sampleBlock);
const isEnabled = engine.block.isBlurEnabled(sampleBlock);
const blurType = engine.block.getType(appliedBlur);
console.log('Blur type:', blurType, 'Enabled:', isEnabled);
// Disable blur temporarily
engine.block.setBlurEnabled(sampleBlock, false);
console.log('Blur disabled:', !engine.block.isBlurEnabled(sampleBlock));
// Re-enable for final export
engine.block.setBlurEnabled(sampleBlock, true);
// Create additional blocks to demonstrate different blur types
// Create a uniform blur for even softening
const uniformBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, uniformBlock);
engine.block.setPositionX(uniformBlock, 350);
const uniformBlur = engine.block.createBlur('//ly.img.ubq/blur/uniform');
engine.block.setFloat(uniformBlur, 'blur/uniform/intensity', 0.6);
engine.block.setBlur(uniformBlock, uniformBlur);
engine.block.setBlurEnabled(uniformBlock, true);
// Create a linear blur for gradient effect
const linearBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, linearBlock);
engine.block.setPositionY(linearBlock, 275);
const linearBlur = engine.block.createBlur('//ly.img.ubq/blur/linear');
engine.block.setFloat(linearBlur, 'blur/linear/blurRadius', 35);
engine.block.setFloat(linearBlur, 'blur/linear/x1', 0);
engine.block.setFloat(linearBlur, 'blur/linear/y1', 0);
engine.block.setFloat(linearBlur, 'blur/linear/x2', 1);
engine.block.setFloat(linearBlur, 'blur/linear/y2', 1);
engine.block.setBlur(linearBlock, linearBlur);
engine.block.setBlurEnabled(linearBlock, true);
// Create a mirrored blur for tilt-shift effect
const mirroredBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, mirroredBlock);
engine.block.setPositionX(mirroredBlock, 350);
engine.block.setPositionY(mirroredBlock, 275);
const mirroredBlur = engine.block.createBlur('//ly.img.ubq/blur/mirrored');
engine.block.setFloat(mirroredBlur, 'blur/mirrored/blurRadius', 30);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/gradientSize', 50);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/size', 75);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/x1', 0);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/y1', 0.3);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/x2', 1);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/y2', 0.7);
engine.block.setBlur(mirroredBlock, mirroredBlur);
engine.block.setBlurEnabled(mirroredBlock, true);
// Export the scene to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/blur-effects.png', buffer);
console.log('Exported to output/blur-effects.png');
} finally {
engine.dispose();
}
```
This guide covers how to apply blur effects programmatically using the block API.
## Programmatic Blur Application
### Initialize CE.SDK
For applications that need to apply blur programmatically—whether for automation, batch processing, or dynamic content generation—we start by setting up CE.SDK with the proper configuration.
```typescript highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
This initializes the CE.SDK engine in headless mode, giving you full API access to the blur system without UI dependencies.
### Check Blur Support
Before applying blur to a block, verify it supports blur effects. Graphic blocks with shapes and pages support blur.
```typescript highlight-check-blur-support
// Create an image block to check blur support
const sampleBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, sampleBlock);
// Check if the block supports blur - graphic blocks with shapes support blur
const supportsBlur = engine.block.supportsBlur(sampleBlock);
console.log('Block supports blur:', supportsBlur);
```
Always check support before creating and applying blur to avoid errors.
### 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()`.
```typescript highlight-create-blur
// Create a radial blur effect
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
> **Note:** Omitting the prefix is also accepted, e.g., `'radial'` instead of
> `'//ly.img.ubq/blur/radial'`.
### Configure Blur Parameters
Each blur type has specific parameters to control its appearance. Configure them using `setFloat()`.
```typescript highlight-configure-blur
// Configure radial blur parameters
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
After configuring the blur, apply it to the target block and enable it.
```typescript highlight-apply-blur
// Apply the blur to the image block
engine.block.setBlur(sampleBlock, blur);
engine.block.setBlurEnabled(sampleBlock, true);
```
The blur takes effect immediately once enabled. You can modify parameters at any time.
## Managing Blur
### Access Existing Blur
Retrieve the blur applied to a block using `getBlur()`. You can then read or modify its properties.
```typescript highlight-read-blur
// Access the applied blur and its properties
const appliedBlur = engine.block.getBlur(sampleBlock);
const isEnabled = engine.block.isBlurEnabled(sampleBlock);
const blurType = engine.block.getType(appliedBlur);
console.log('Blur type:', blurType, 'Enabled:', isEnabled);
```
### Enable/Disable Blur
Toggle blur on and off without removing it using `setBlurEnabled()`. This preserves all blur parameters for conditional processing.
```typescript highlight-toggle-blur
// Disable blur temporarily
engine.block.setBlurEnabled(sampleBlock, false);
console.log('Blur disabled:', !engine.block.isBlurEnabled(sampleBlock));
// Re-enable for final export
engine.block.setBlurEnabled(sampleBlock, true);
```
When disabled, the blur remains attached to the block but doesn't render until re-enabled.
## Blur Types
### Uniform Blur
Applies consistent softening across the entire element. Best for simple background blur or overall softening.
```typescript highlight-uniform-blur
// Create a uniform blur for even softening
const uniformBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, uniformBlock);
engine.block.setPositionX(uniformBlock, 350);
const uniformBlur = engine.block.createBlur('//ly.img.ubq/blur/uniform');
engine.block.setFloat(uniformBlur, 'blur/uniform/intensity', 0.6);
engine.block.setBlur(uniformBlock, uniformBlur);
engine.block.setBlurEnabled(uniformBlock, true);
```
### Linear Blur
Creates a gradient blur effect along a line defined by two control points. Useful for directional focus effects.
```typescript highlight-linear-blur
// Create a linear blur for gradient effect
const linearBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, linearBlock);
engine.block.setPositionY(linearBlock, 275);
const linearBlur = engine.block.createBlur('//ly.img.ubq/blur/linear');
engine.block.setFloat(linearBlur, 'blur/linear/blurRadius', 35);
engine.block.setFloat(linearBlur, 'blur/linear/x1', 0);
engine.block.setFloat(linearBlur, 'blur/linear/y1', 0);
engine.block.setFloat(linearBlur, 'blur/linear/x2', 1);
engine.block.setFloat(linearBlur, 'blur/linear/y2', 1);
engine.block.setBlur(linearBlock, linearBlur);
engine.block.setBlurEnabled(linearBlock, true);
```
### Mirrored Blur
Creates a band of focus with blur on both sides, commonly known as a tilt-shift effect. The control points define the focus band orientation.
```typescript highlight-mirrored-blur
// Create a mirrored blur for tilt-shift effect
const mirroredBlock = await engine.block.addImage(imageUri, {
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, mirroredBlock);
engine.block.setPositionX(mirroredBlock, 350);
engine.block.setPositionY(mirroredBlock, 275);
const mirroredBlur = engine.block.createBlur('//ly.img.ubq/blur/mirrored');
engine.block.setFloat(mirroredBlur, 'blur/mirrored/blurRadius', 30);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/gradientSize', 50);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/size', 75);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/x1', 0);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/y1', 0.3);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/x2', 1);
engine.block.setFloat(mirroredBlur, 'blur/mirrored/y2', 0.7);
engine.block.setBlur(mirroredBlock, mirroredBlur);
engine.block.setBlurEnabled(mirroredBlock, true);
```
## Export Results
After applying blur effects, export the processed content to a file. CE.SDK supports various export formats including PNG, JPEG, and PDF.
```typescript highlight-export
// Export the scene to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/blur-effects.png', buffer);
console.log('Exported to output/blur-effects.png');
```
The export operation renders all blur effects and saves the result to the file system. Always dispose of the engine instance when processing is complete to free resources.
## Troubleshooting
### 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
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
- Increase or decrease `blurRadius` or `intensity` values
- For radial blur, adjust `gradientRadius` to control the transition softness
## 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 |
## Linear Type
A blur effect applied along a linear gradient.
This section describes the properties available for the **Linear Type** (`//ly.img.ubq/blur/linear`) block type.
| Property | Type | Default | Description |
| ------------------------ | ------- | ------- | ------------------------ |
| `blur/linear/blurRadius` | `Float` | `30` | Blur intensity. |
| `blur/linear/x1` | `Float` | `0` | Control point 1 x-value. |
| `blur/linear/x2` | `Float` | `1` | Control point 2 x-value. |
| `blur/linear/y1` | `Float` | `0.5` | Control point 1 y-value. |
| `blur/linear/y2` | `Float` | `0.5` | Control point 2 y-value. |
## Mirrored Type
A blur effect applied in a mirrored linear fashion.
This section describes the properties available for the **Mirrored Type** (`//ly.img.ubq/blur/mirrored`) block type.
| Property | Type | Default | Description |
| ---------------------------- | ------- | ------- | ------------------------ |
| `blur/mirrored/blurRadius` | `Float` | `30` | Blur intensity. |
| `blur/mirrored/gradientSize` | `Float` | `50` | Hardness of gradients. |
| `blur/mirrored/size` | `Float` | `75` | Size of blurred area. |
| `blur/mirrored/x1` | `Float` | `0` | Control point 1 x-value. |
| `blur/mirrored/x2` | `Float` | `1` | Control point 2 x-value. |
| `blur/mirrored/y1` | `Float` | `0.5` | Control point 1 y-value. |
| `blur/mirrored/y2` | `Float` | `0.5` | Control point 2 y-value. |
## Radial Type
A blur effect applied radially from a center point.
This section describes the properties available for the **Radial Type** (`//ly.img.ubq/blur/radial`) block type.
| Property | Type | Default | Description |
| ---------------------------- | ------- | ------- | ------------------------- |
| `blur/radial/blurRadius` | `Float` | `30` | Blur intensity. |
| `blur/radial/gradientRadius` | `Float` | `50` | Size of blurred area. |
| `blur/radial/radius` | `Float` | `75` | Size of non-blurred area. |
| `blur/radial/x` | `Float` | `0.5` | Center point x-value. |
| `blur/radial/y` | `Float` | `0.5` | Center point y-value. |
## Uniform Type
A blur effect with uniform intensity.
This section describes the properties available for the **Uniform Type** (`//ly.img.ubq/blur/uniform`) block type.
| Property | Type | Default | Description |
| ------------------------ | ------- | ------- | ------------------- |
| `blur/uniform/intensity` | `Float` | `0.5` | The blur intensity. |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Chroma Key (Green Screen)"
description: "Apply the green screen effect to images and videos, replacing specific colors with transparency for compositing workflows."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/chroma-key-green-screen-1e3e99/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Apply Chroma Key (Green Screen)](https://img.ly/docs/cesdk/node-native/filters-and-effects/chroma-key-green-screen-1e3e99/)
---
Replace specific colors with transparency using CE.SDK's green screen effect
for video compositing and virtual background applications.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-filters-and-effects-chroma-key-green-screen-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-filters-and-effects-chroma-key-green-screen-server-js)
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.
```typescript file=@cesdk_web_examples/guides-filters-and-effects-chroma-key-green-screen-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
// Load environment variables from .env file
config();
/**
* CE.SDK Server Example: Chroma Key (Green Screen)
*
* Demonstrates the green screen effect for color keying:
* - Applying the green screen effect to an image
* - Configuring color, colorMatch, smoothness, and spill parameters
* - Toggling the effect programmatically
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// 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 (off-white to remove bright sky)
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 key out more color variations (useful for uneven lighting)
engine.block.setFloat(
greenScreenEffect,
'effect/green_screen/colorMatch',
0.26
);
// Control edge smoothness
// Higher values create softer edges that blend naturally 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);
// 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 the effect for export
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 export demonstration
const newGreenScreenEffect = engine.block.createEffect('green_screen');
engine.block.appendEffect(imageBlock, newGreenScreenEffect);
engine.block.setColor(newGreenScreenEffect, 'effect/green_screen/fromColor', {
r: 0.98,
g: 0.98,
b: 0.98,
a: 1.0
});
engine.block.setFloat(
newGreenScreenEffect,
'effect/green_screen/colorMatch',
0.26
);
engine.block.setFloat(
newGreenScreenEffect,
'effect/green_screen/smoothness',
1.0
);
engine.block.setFloat(newGreenScreenEffect, 'effect/green_screen/spill', 1.0);
// Export the page to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
const outputPath = `${outputDir}/chroma-key-result.png`;
writeFileSync(outputPath, buffer);
console.log(`Exported chroma key result to: ${outputPath}`);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers how to apply the green screen effect programmatically, configure its parameters for optimal keying, toggle the effect, and manage effects on blocks.
## 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.
```typescript highlight-create-effect
// 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);
```
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
The green screen effect targets a specific color to key out. We set this color using `setColor()` with the `effect/green_screen/fromColor` property.
```typescript highlight-configure-color
// Set the target color to key out (off-white to remove bright sky)
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. You can specify any color—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.
## 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()`.
```typescript highlight-adjust-color-match
// Adjust color matching tolerance
// Higher values key out more color variations (useful for uneven lighting)
engine.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
The `smoothness` parameter controls the transition between opaque and transparent areas. This affects how sharp or soft the edges appear around keyed subjects.
```typescript highlight-adjust-smoothness
// Control edge smoothness
// Higher values create softer edges that blend naturally with backgrounds
engine.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
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.
```typescript highlight-adjust-spill
// 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);
```
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.
## Toggle the Effect
We can check whether an effect is enabled using `isEffectEnabled()`.
```typescript highlight-check-enabled
// Check if the effect is currently enabled
const 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.
```typescript highlight-set-enabled
// Toggle the effect on or off
engine.block.setEffectEnabled(greenScreenEffect, !isEnabled);
```
Toggling effects is useful for before/after comparisons or conditional processing without removing and recreating 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.
```typescript highlight-manage-effects
// 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');
```
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
### 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
- 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
- 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
| 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
- [Apply Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/apply-2764e4/) - Learn the fundamentals of the effect system
- [Blur](https://img.ly/docs/cesdk/node-native/filters-and-effects/blur-71d642/) - Add blur effects for depth of field
- [Duotone](https://img.ly/docs/cesdk/node-native/filters-and-effects/duotone-831fc5/) - Create two-color artistic treatments
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Custom Filters"
description: "Extend CE.SDK with custom LUT filter asset sources for brand-specific color grading and filter collections."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/create-custom-filters-c796ba/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Create Custom Filters](https://img.ly/docs/cesdk/node-native/filters-and-effects/create-custom-filters-c796ba/)
---
Extend CE.SDK with your own LUT filters by creating and registering custom filter asset sources for server-side image processing pipelines.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-filters-and-effects-add-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-filters-and-effects-add-server-js)
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 for automated batch processing and server-side image workflows. Once registered, custom filters can be queried and applied programmatically.
```typescript file=@cesdk_web_examples/guides-filters-and-effects-add-server-js/server-js.ts reference-only
import CreativeEngine, {
AssetSource,
AssetQueryData,
AssetsQueryResult,
AssetResult
} from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Create Custom Filters
*
* 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
* - Exporting the result
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Define custom LUT filter assets with metadata
const customFilters: AssetResult[] = [
{
id: 'vintage-warm',
label: 'Vintage Warm',
tags: ['vintage', 'warm', 'retro'],
meta: {
uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.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/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_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/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_5_5_128.png',
horizontalTileCount: '5',
verticalTileCount: '5',
blockType: '//ly.img.ubq/effect/lut_filter'
}
}
];
// Create a custom filter asset source
const customFilterSource: AssetSource = {
id: 'my-custom-filters',
async findAssets(
queryData: AssetQueryData
): Promise {
// Filter by query if provided
let filteredAssets = customFilters;
if (queryData.query) {
const searchTerm = queryData.query.toLowerCase();
filteredAssets = customFilters.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 currentPage = queryData.page ?? 0;
const perPage = queryData.perPage ?? 10;
const startIndex = currentPage * perPage;
const paginatedAssets = filteredAssets.slice(
startIndex,
startIndex + perPage
);
return {
assets: paginatedAssets,
total: filteredAssets.length,
currentPage,
nextPage:
startIndex + perPage < filteredAssets.length
? currentPage + 1
: undefined
};
},
// Return available filter categories
async getGroups(): Promise {
return ['vintage', 'cinema', 'black and white'];
}
};
// Register the custom filter source
engine.asset.addSource(customFilterSource);
console.log('Registered custom filter source: my-custom-filters');
// 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/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.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/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_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 the custom source
const customFilterResults = await engine.asset.findAssets(
'my-custom-filters',
{
page: 0,
perPage: 10
}
);
console.log('Found', customFilterResults.total, 'filters in custom source');
// Query filters from the JSON source
const jsonFilterResults = await engine.asset.findAssets('my-json-filters', {
page: 0,
perPage: 10
});
console.log('Found', jsonFilterResults.total, 'filters in JSON source');
// List all registered asset sources
const allSources = engine.asset.findAllSources();
console.log('Registered sources:', allSources);
// Create an image block
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const imageBlock = await engine.block.addImage(imageUri, {
x: 50,
y: 50,
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, imageBlock);
// Get the first 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);
console.log('Applied filter:', filterAsset.label);
}
// Create a second image block with a filter from JSON source
const imageBlock2 = await engine.block.addImage(imageUri, {
x: 450,
y: 50,
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, imageBlock2);
// Get a filter from the JSON source
const jsonFilterAsset = jsonFilterResults.assets[0];
if (jsonFilterAsset && jsonFilterAsset.meta) {
const lutEffect2 = engine.block.createEffect(
'//ly.img.ubq/effect/lut_filter'
);
engine.block.setString(
lutEffect2,
'effect/lut_filter/lutFileURI',
jsonFilterAsset.meta.uri as string
);
engine.block.setInt(
lutEffect2,
'effect/lut_filter/horizontalTileCount',
parseInt(jsonFilterAsset.meta.horizontalTileCount as string, 10)
);
engine.block.setInt(
lutEffect2,
'effect/lut_filter/verticalTileCount',
parseInt(jsonFilterAsset.meta.verticalTileCount as string, 10)
);
engine.block.setFloat(lutEffect2, 'effect/lut_filter/intensity', 0.85);
engine.block.appendEffect(imageBlock2, lutEffect2);
console.log('Applied JSON filter:', jsonFilterAsset.label);
}
// Export the scene to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/custom-filters.png', buffer);
console.log('Exported to output/custom-filters.png');
} finally {
engine.dispose();
}
```
This guide covers how to create filter asset sources, define filter metadata, load filters from JSON configuration, query custom sources, and apply filters to images.
## 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
We register a custom filter source using `engine.asset.addSource()` with a `findAssets` callback. This callback returns filter assets matching the query parameters including pagination, search terms, and category filters.
```typescript highlight-create-custom-source
// Define custom LUT filter assets with metadata
const customFilters: AssetResult[] = [
{
id: 'vintage-warm',
label: 'Vintage Warm',
tags: ['vintage', 'warm', 'retro'],
meta: {
uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.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/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_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/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_5_5_128.png',
horizontalTileCount: '5',
verticalTileCount: '5',
blockType: '//ly.img.ubq/effect/lut_filter'
}
}
];
// Create a custom filter asset source
const customFilterSource: AssetSource = {
id: 'my-custom-filters',
async findAssets(
queryData: AssetQueryData
): Promise {
// Filter by query if provided
let filteredAssets = customFilters;
if (queryData.query) {
const searchTerm = queryData.query.toLowerCase();
filteredAssets = customFilters.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 currentPage = queryData.page ?? 0;
const perPage = queryData.perPage ?? 10;
const startIndex = currentPage * perPage;
const paginatedAssets = filteredAssets.slice(
startIndex,
startIndex + perPage
);
return {
assets: paginatedAssets,
total: filteredAssets.length,
currentPage,
nextPage:
startIndex + perPage < filteredAssets.length
? currentPage + 1
: undefined
};
},
// Return available filter categories
async getGroups(): Promise {
return ['vintage', 'cinema', 'black and white'];
}
};
// Register the custom filter source
engine.asset.addSource(customFilterSource);
console.log('Registered custom filter source: my-custom-filters');
```
The `findAssets` callback receives query parameters including pagination (`page`, `perPage`), search terms (`query`), and category filters (`groups`). We filter and paginate the results accordingly.
### Filter Asset Structure
Each filter asset returned by `findAssets` needs:
- **`id`** - Unique identifier for the filter
- **`label`** - Display name for the filter
- **`tags`** - Keywords for search filtering
- **`meta`** - Object containing LUT configuration (uri, thumbUri, tile counts, blockType)
The optional `getGroups()` method returns available filter categories for organizing filters.
## 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.
```typescript highlight-load-from-json-string
// 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/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.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/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_5_5_128.png',
thumbUri:
'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_bw_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);
```
### 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 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.
## Querying and Applying Filters
We query filters from registered sources using `engine.asset.findAssets()`, then create and configure LUT filter effects from the returned metadata.
```typescript highlight-query-custom-filters
// Query filters from the custom source
const customFilterResults = await engine.asset.findAssets(
'my-custom-filters',
{
page: 0,
perPage: 10
}
);
console.log('Found', customFilterResults.total, 'filters in custom source');
// Query filters from the JSON source
const jsonFilterResults = await engine.asset.findAssets('my-json-filters', {
page: 0,
perPage: 10
});
console.log('Found', jsonFilterResults.total, 'filters in JSON source');
// List all registered asset sources
const allSources = engine.asset.findAllSources();
console.log('Registered sources:', allSources);
```
We create a `lut_filter` effect using `engine.block.createEffect()` and configure it with metadata from the queried filter asset. The effect is then attached to an image block.
```typescript highlight-apply-custom-filter
// Create an image block
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const imageBlock = await engine.block.addImage(imageUri, {
x: 50,
y: 50,
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, imageBlock);
// Get the first 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);
console.log('Applied filter:', filterAsset.label);
}
```
We can apply filters from any registered source, including JSON-based sources.
```typescript highlight-apply-json-filter
// Create a second image block with a filter from JSON source
const imageBlock2 = await engine.block.addImage(imageUri, {
x: 450,
y: 50,
size: { width: 300, height: 225 }
});
engine.block.appendChild(page, imageBlock2);
// Get a filter from the JSON source
const jsonFilterAsset = jsonFilterResults.assets[0];
if (jsonFilterAsset && jsonFilterAsset.meta) {
const lutEffect2 = engine.block.createEffect(
'//ly.img.ubq/effect/lut_filter'
);
engine.block.setString(
lutEffect2,
'effect/lut_filter/lutFileURI',
jsonFilterAsset.meta.uri as string
);
engine.block.setInt(
lutEffect2,
'effect/lut_filter/horizontalTileCount',
parseInt(jsonFilterAsset.meta.horizontalTileCount as string, 10)
);
engine.block.setInt(
lutEffect2,
'effect/lut_filter/verticalTileCount',
parseInt(jsonFilterAsset.meta.verticalTileCount as string, 10)
);
engine.block.setFloat(lutEffect2, 'effect/lut_filter/intensity', 0.85);
engine.block.appendEffect(imageBlock2, lutEffect2);
console.log('Applied JSON filter:', jsonFilterAsset.label);
}
```
After applying filters, we export the scene to PNG.
```typescript highlight-export
// Export the scene to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/custom-filters.png', buffer);
console.log('Exported to output/custom-filters.png');
```
## Troubleshooting
### Filters Not Found in Query
- Verify the asset source is registered with `engine.asset.addSource()` or `addLocalAssetSourceFromJSONString()`
- 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 the source ID matches when calling `findAssets()`
### LUT Not Rendering Correctly
- Verify tile count values match the actual LUT image grid dimensions
- Check that the LUT image URL is accessible from your server
- Confirm the LUT image uses PNG format
### JSON Source Not Loading
- Verify JSON structure matches the required format with `version`, `id`, and `assets`
- Ensure asset metadata includes required fields
- Check for JSON syntax errors
## 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 |
| `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
Now that you understand how to create and register custom filter sources, explore related topics:
- [Apply Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/apply-2764e4/) - Learn to apply filters to design elements and manage effect stacks
- [Create a Custom LUT Filter](https://img.ly/docs/cesdk/node-native/filters-and-effects/create-custom-lut-filter-6e3f49/) - Understand LUT image format and create your own color grading filters
- [Blur Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/blur-71d642/) - Add blur effects to images and videos
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create a Custom LUT Filter"
description: "Create and apply custom LUT filters to achieve consistent, brand-aligned visual styles."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/create-custom-lut-filter-6e3f49/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Apply Custom LUT Filter](https://img.ly/docs/cesdk/node-native/filters-and-effects/create-custom-lut-filter-6e3f49/)
---
Apply custom LUT (Look-Up Table) filters to achieve brand-consistent color grading in server-side applications using CE.SDK's headless Creative Engine.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-filters-and-effects-create-custom-lut-filter-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-filters-and-effects-create-custom-lut-filter-server-js)
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 in server-side Node.js applications.
```typescript file=@cesdk_web_examples/guides-filters-and-effects-create-custom-lut-filter-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Create Custom LUT Filter
*
* 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
* - Exporting the result
*/
async function main() {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// Add an image block to apply the LUT filter
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const imageBlock = await engine.block.addImage(imageUri, {
x: 150,
y: 100,
size: { width: 500, height: 400 }
});
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/assets/v4/ly.img.filter.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);
// Toggle the effect off and back on
engine.block.setEffectEnabled(lutEffect, false);
const isEnabled = engine.block.isEffectEnabled(lutEffect);
console.log('Effect enabled:', isEnabled); // false
engine.block.setEffectEnabled(lutEffect, true);
// 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
// Export the result to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/custom-lut-filter.png`, buffer);
console.log('Exported result to output/custom-lut-filter.png');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
## 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
### 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
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
You can write a Python script using PIL/Pillow and NumPy to convert `.cube` files:
```python
# 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
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.
## Initialize the Engine
We start by initializing the headless Creative Engine with a scene and page for our image processing workflow.
```typescript highlight=highlight-setup
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
```
## Add an Image Block
Add an image block to apply the LUT filter. The `addImage()` convenience API simplifies image block creation.
```typescript highlight=highlight-add-image
// Add an image block to apply the LUT filter
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
const imageBlock = await engine.block.addImage(imageUri, {
x: 150,
y: 100,
size: { width: 500, height: 400 }
});
engine.block.appendChild(page, imageBlock);
```
## Create the LUT Effect
Create a `lut_filter` effect instance using the effect API.
```typescript highlight=highlight-create-effect
// Create a LUT filter effect
const lutEffect = engine.block.createEffect('//ly.img.ubq/effect/lut_filter');
```
This creates an effect that can be configured and applied to image blocks.
## Configure LUT Properties
Set the LUT file URL and tile dimensions to match your LUT image.
```typescript highlight=highlight-configure-lut
// Configure the LUT file URI - this is a tiled PNG containing the color lookup table
const lutUrl =
'https://cdn.img.ly/assets/v4/ly.img.filter.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);
```
The tile counts must match the actual LUT image grid structure. Using incorrect values produces distorted colors.
## Set Filter Intensity
Control the strength of the color transformation with intensity.
```typescript highlight=highlight-set-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.
## Apply the Effect
Attach the configured effect to an image block.
```typescript highlight=highlight-apply-effect
// Apply the effect to the image block
engine.block.appendEffect(imageBlock, lutEffect);
```
The effect renders immediately after being applied.
## Toggle the Effect
Enable or disable the effect without removing it.
```typescript highlight=highlight-toggle-effect
// Toggle the effect off and back on
engine.block.setEffectEnabled(lutEffect, false);
const isEnabled = engine.block.isEffectEnabled(lutEffect);
console.log('Effect enabled:', isEnabled); // false
engine.block.setEffectEnabled(lutEffect, true);
```
Disabling preserves all effect settings while temporarily removing the visual transformation.
## Manage Effects
Retrieve and inspect effects applied to a block.
```typescript highlight=highlight-manage-effects
// 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
```
Use `getEffects()` to access all effects on a block and `supportsEffects()` to verify compatibility before applying.
## Export and Cleanup
After applying the LUT filter, we export the result to a file and dispose of the engine to free resources.
```typescript highlight=highlight-export
// Export the result to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/custom-lut-filter.png`, buffer);
console.log('Exported result to output/custom-lut-filter.png');
```
Always dispose of the engine in a finally block to ensure resources are freed even if an error occurs.
```typescript highlight=highlight-cleanup
// Always dispose of the engine to free resources
engine.dispose();
```
## Troubleshooting
### LUT Not Rendering
- Verify the LUT image URL is accessible from your server
- Confirm the image uses PNG format
- Check that tile count values match the actual image grid
### Colors Look Wrong
- Verify tile counts match the LUT image structure
- Ensure the LUT was generated with sRGB color space
### Effect Not Visible in Export
- Verify the effect is enabled with `isEffectEnabled()`
- Ensure the effect was appended to the block, not just created
- Check the block supports effects using `supportsEffects()`
## 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 |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Distortion Effects"
description: "Apply distortion effects to warp, shift, and transform design elements for dynamic artistic visuals in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/distortion-5b5a66/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Distortion](https://img.ly/docs/cesdk/node-native/filters-and-effects/distortion-5b5a66/)
---
Apply distortion effects to warp, shift, and transform images for dynamic artistic visuals in server-side workflows.
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.
CE.SDK's distortion effects work identically in server environments, making them ideal for batch processing, automated image enhancement, and headless rendering pipelines.
```javascript file=@cesdk_web_examples/guides-filters-and-effects-distortion-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { calculateGridLayout } from './utils';
/**
* CE.SDK Server Guide: Distortion Effects
*
* 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
* - Managing effects (enable/disable/remove)
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
const pageWidth = engine.block.getWidth(page);
const pageHeight = engine.block.getHeight(page);
// 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);
});
// Export the page to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Ensure output directory exists
if (!existsSync('output')) {
mkdirSync('output');
}
// Save to file
writeFileSync('output/distortion-effects.png', buffer);
console.log('✅ Exported distortion effects to output/distortion-effects.png');
} finally {
engine.dispose();
}
```
This guide covers how to apply and configure distortion effects programmatically using the block API in server-side Node.js environments.
## Check Effect Support
Before applying distortion effects, verify the block supports them. Graphic blocks with image fills support effects, while scene blocks do not.
```javascript highlight-check-support
// 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);
```
## 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.
```javascript highlight-liquid-effect
// 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);
```
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
The mirror effect reflects the image along a configurable side, creating symmetrical compositions.
```javascript highlight-mirror-effect
// 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);
```
The `side` parameter uses integer values: `0` (Left), `1` (Right), `2` (Top), or `3` (Bottom) to specify the reflection axis.
## Apply Shifter Effect
The shifter effect displaces color channels at an angle, creating chromatic aberration commonly seen in glitch art and retro visuals.
```javascript highlight-shifter-effect
// 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);
```
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
The radial pixel effect pixelates the image in a circular pattern emanating from the center, useful for focus effects or stylized treatments.
```javascript highlight-radial-pixel-effect
// 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);
```
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
The TV glitch effect simulates analog television interference with horizontal distortion and rolling effects, popular for retro and digital aesthetics.
```javascript highlight-tv-glitch-effect
// 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);
```
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
Retrieve all effects applied to a block to inspect or iterate over them.
```javascript highlight-get-effects
// 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}`);
});
```
This returns an array of effect IDs in the order they were applied.
## Enable and Disable Effects
Toggle effects on and off without removing them from the block. This preserves all effect parameters while controlling visibility.
```javascript highlight-toggle-effect
// 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);
```
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 from a block when they're no longer needed. Always destroy removed effects to free memory.
```javascript highlight-remove-effect
// 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);
```
## Discover Effect Properties
Use `findAllProperties()` to discover all available properties for any effect type.
```javascript highlight-effect-properties
// Find all available properties for an effect
const 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
| 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
| 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
- [Apply Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/apply-2764e4/) - Learn the foundational effect APIs
- [Blur Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/blur-71d642/) - Apply blur techniques for depth and focus effects
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Duotone"
description: "Apply duotone effects to images, mapping tones to two colors for stylized visuals, vintage aesthetics, or brand-specific treatments."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/duotone-831fc5/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Duotone](https://img.ly/docs/cesdk/node-native/filters-and-effects/duotone-831fc5/)
---
Apply duotone effects to images in server-side workflows for batch processing and automated image enhancement.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-filters-and-effects-duotone-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-filters-and-effects-duotone-server-js)
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. CE.SDK's duotone system works identically in server environments, making it ideal for batch processing, automated pipelines, and headless image generation.
```typescript file=@cesdk_web_examples/guides-filters-and-effects-duotone-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { hexToRgba } from './utils';
// Load environment variables from .env file
config();
/**
* CE.SDK Server Example: Duotone
*
* Demonstrates applying duotone effects to images in server-side workflows:
* - Querying duotone presets from the asset library
* - Applying duotone with preset colors
* - Creating custom duotone color combinations
* - Managing and removing effects
*/
async function main(): Promise {
// Initialize CE.SDK engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE,
});
try {
// Load the filter asset source to access duotone presets
const defaultAssetsUrl = `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets/v4/`;
let hasAssetSources = false;
try {
await engine.asset.addLocalAssetSourceFromJSONURI(`${defaultAssetsUrl}ly.img.filter/content.json`);
hasAssetSources = true;
} catch {
console.warn('Filter asset source not available, skipping presets');
}
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// 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 = hasAssetSources
? await engine.asset.findAssets('ly.img.filter', {
page: 0,
perPage: 10
})
: { assets: [] };
const duotonePresets = duotoneResults.assets;
// Apply a preset to the first image (or use fallback colors)
// Create a new duotone effect block
const duotoneEffect = engine.block.createEffect('duotone_filter');
// Configure effect with preset colors (convert hex to RGBA)
if (duotonePresets.length > 0) {
const preset = duotonePresets[0];
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)
);
}
} else {
// Fallback: Desert preset colors (#5c3a15, #f0d8b8)
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/darkColor',
hexToRgba('#5c3a15')
);
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/lightColor',
hexToRgba('#f0d8b8')
);
}
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);
// Export the page to a PNG file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
const outputPath = `${outputDir}/duotone-result.png`;
writeFileSync(outputPath, buffer);
console.log(`Exported duotone result to: ${outputPath}`);
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
## 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.
## Check Effect Support
Before applying effects programmatically, verify the block supports them. Only graphic blocks with image or video fills can have effects applied:
```typescript highlight=highlight-check-support
// Verify a block supports effects before applying them
const 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
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
Use the Asset API to retrieve available duotone presets from the asset library:
```typescript highlight=highlight-query-presets
// Query duotone presets from the asset library
const duotoneResults = hasAssetSources
? await engine.asset.findAssets('ly.img.filter', {
page: 0,
perPage: 10
})
: { assets: [] };
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 a new duotone effect block that can be configured and attached to an image:
```typescript highlight=highlight-create-effect
// Create a new duotone effect block
const duotoneEffect = engine.block.createEffect('duotone_filter');
```
### Configure Preset Colors
Apply the preset's color values to the effect using `setColor()` for colors and `setFloat()` for intensity:
```typescript highlight=highlight-apply-preset
// Configure effect with preset colors (convert hex to RGBA)
if (duotonePresets.length > 0) {
const preset = duotonePresets[0];
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)
);
}
} else {
// Fallback: Desert preset colors (#5c3a15, #f0d8b8)
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/darkColor',
hexToRgba('#5c3a15')
);
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/lightColor',
hexToRgba('#f0d8b8')
);
}
engine.block.setFloat(
duotoneEffect,
'effect/duotone_filter/intensity',
0.9
);
```
### Append Effect to Block
Attach the fully configured effect to an image block:
```typescript highlight=highlight-append-effect
// Attach the configured effect to the image block
engine.block.appendEffect(presetImageBlock, duotoneEffect);
```
## Creating Custom Colors
For brand-specific treatments or unique creative effects, define your own color combinations using `engine.block.setColor()`:
```typescript highlight=highlight-custom-colors
// 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);
```
### 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`)
> **Tip:** 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
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:
```typescript highlight=highlight-combine-effects
// 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);
```
**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
Once effects are applied to a block, you can list, toggle, and remove them programmatically.
### List Applied Effects
Retrieve all effect block IDs currently attached to a block:
```typescript highlight=highlight-list-effects
// Get all effects currently applied to a block
const appliedEffects = engine.block.getEffects(presetImageBlock);
```
### Toggle Effect Visibility
Disable an effect temporarily without removing it from the block:
```typescript highlight=highlight-toggle-effects
// 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 Effects
Detach an effect from a block by specifying its index in the effect stack:
```typescript highlight=highlight-remove-effect
// 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);
}
```
### Clean Up Resources
After removing an effect, destroy it to free memory:
```typescript highlight=highlight-cleanup-effect
// Destroy removed effect blocks to free memory
if (effectsOnCustom.length > 0) {
engine.block.destroy(effectsOnCustom[0]);
}
```
> **Caution:** When removing effects, always destroy the effect block afterward to prevent memory leaks. Effects are independent blocks that persist until explicitly destroyed.
### 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
### 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 memory
- **Dispose the engine**: In server environments, always call `engine.dispose()` when done to release all resources
- **Process in batches**: For large image sets, process images in batches rather than loading all at once to manage memory usage
### 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
### 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 |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Gradient"
description: "Documentation for Gradients"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/gradients-0ff079/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/node-native/fills-402ddc/) > [Gradient](https://img.ly/docs/cesdk/node-native/filters-and-effects/gradients-0ff079/)
---
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.
> **Reading time:** 20 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-fills-gradient-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-fills-gradient-server-js)
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.
```typescript file=@cesdk_web_examples/guides-fills-gradient-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Gradient Fills
*
* Demonstrates programmatic gradient fill creation and configuration:
* - Creating linear, radial, and conical gradient fills
* - Configuring gradient color stops
* - Positioning gradients
* - Using different color spaces in gradients
* - Exporting scenes with gradient fills
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene with specific page dimensions
engine.scene.create('VerticalStack', {
page: { size: { width: 1200, height: 900 } }
});
const page = engine.block.findByType('page')[0];
// 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
});
// Helper function to create a shape with a gradient 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, 200);
engine.block.setHeight(block, 200);
// 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, 200);
engine.block.setHeight(block1, 90);
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, 200);
engine.block.setHeight(block2, 90);
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);
// =============================================================================
// 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
];
// Calculate grid layout
const cols = 5;
const spacing = 20;
const margin = 40;
blocks.forEach((block, index) => {
const x = margin + (index % cols) * (200 + spacing);
const y = margin + Math.floor(index / cols) * (200 + spacing);
engine.block.setPositionX(block, x);
engine.block.setPositionY(block, y);
});
// Position block2 below block1 in the same grid cell
const block1X = margin + (13 % cols) * (200 + spacing);
const block1Y = margin + Math.floor(13 / cols) * (200 + spacing);
engine.block.setPositionX(block2, block1X);
engine.block.setPositionY(block2, block1Y + 100);
// Export the scene to PNG
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
// Save to output directory
mkdirSync('output', { recursive: true });
const outputPath = 'output/gradient-fills.png';
writeFileSync(outputPath, buffer);
console.log(`Created gradient fills and exported to ${outputPath}`);
console.log('Example complete');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
```
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
### 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
#### 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.
```typescript highlight-linear-gradient
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 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.
```typescript highlight-radial-gradient
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 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.
```typescript highlight-conical-gradient
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
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 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.
```typescript highlight-color-stops
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 }
]);
```
## Checking Gradient Fill Support
### 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.
```typescript highlight-check-fill-support
// Check if block supports fills
const 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 a New Linear Gradient
Create a new linear gradient fill using the `createFill()` method with the type `'gradient/linear'`:
```typescript highlight-create-linear
const { block: linearVerticalBlock, fill: linearVertical } =
createShapeWithFill('gradient/linear');
```
### Creating a Radial Gradient
Create a radial gradient using the type `'gradient/radial'`:
```typescript highlight-create-radial
const { block: radialCenteredBlock, fill: radialCentered } =
createShapeWithFill('gradient/radial');
```
### Creating a Conical Gradient
Create a conical gradient using the type `'gradient/conical'`:
```typescript highlight-create-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
### Setting a Gradient Fill on a Block
Once you've created a gradient fill, attach it to a block using `setFill()`:
```typescript highlight-apply-gradient
// Create gradient fill
const gradientFill = engine.block.createFill(fillType);
// Apply the fill to the block
engine.block.setFill(block, gradientFill);
```
### Getting the Current Fill
Retrieve the current fill attached to a block and inspect its type:
```typescript highlight-get-fill
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'
```
## Configuring Gradient Color Stops
### Setting Color Stops
Set color stops using the `setGradientColorStops()` method with an array of color and position pairs:
```typescript highlight-set-color-stops
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
Retrieve the current color stops from a gradient fill:
```typescript highlight-get-color-stops
// Get gradient color stops
const colorStops = engine.block.getGradientColorStops(
inspectGradient,
'fill/gradient/colors'
);
console.log('Color stops:', colorStops);
```
### Using Different Color Spaces
Gradient color stops support multiple color spaces:
```typescript highlight-color-spaces
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);
```
## Positioning Linear Gradients
### 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:
```typescript highlight-linear-position
// 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
**Horizontal (Left to Right):**
```typescript
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):**
```typescript
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
Retrieve the current position values:
```typescript highlight-get-linear-position
// 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 });
```
## Positioning Radial Gradients
### Setting Center Point and Radius
Radial gradients are positioned using a center point and radius:
```typescript highlight-radial-position
// 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
**Centered Circle:**
```typescript
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:**
```typescript
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:**
```typescript
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
### Setting Center Point
Conical gradients are positioned using a center point. The rotation starts at the top (12 o'clock) and proceeds clockwise:
```typescript highlight-conical-position
// 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
### Sharing Gradient Fills
You can share a single gradient fill between multiple blocks. Changes to the shared gradient affect all blocks using it:
```typescript highlight-share-gradient
const block1 = engine.block.create('graphic');
const shape1 = engine.block.createShape('rect');
engine.block.setShape(block1, shape1);
engine.block.setWidth(block1, 200);
engine.block.setHeight(block1, 90);
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, 200);
engine.block.setHeight(block2, 90);
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);
```
### 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
### Modern Hero Background (Aurora Effect)
Create dreamy multi-color gradient backgrounds for hero sections:
```typescript highlight-aurora-gradient
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
Use radial gradients to add depth and highlight effects to buttons:
```typescript highlight-button-gradient
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);
```
### Loading Spinner (Conical)
Create circular progress indicators and loading animations with conical gradients:
```typescript highlight-spinner-gradient
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
Create smooth transparency effects with alpha channel transitions:
```typescript highlight-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);
```
### Duotone Effect
Create modern two-color gradient overlays:
```typescript highlight-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);
```
## Troubleshooting
### 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
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
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
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
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
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
### 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
| 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
| 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
| 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
```typescript
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
```
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Node.js Filters & Effects Library"
description: "Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/overview-299b15/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Overview](https://img.ly/docs/cesdk/node-native/filters-and-effects/overview-299b15/)
---
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.
[Launch Web Demo](https://img.ly/showcases/cesdk)
[Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/)
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Supported Filters and Effects"
description: "View the full list of visual effects and filters available in CE.SDK, including both built-in and custom options."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/filters-and-effects/support-a666dd/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) > [Supported Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/support-a666dd/)
---
Discover all available filters and effects in CE.SDK and learn how to check
if a block supports them.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-filters-and-effects-support-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-filters-and-effects-support-server-js)
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.
```typescript file=@cesdk_web_examples/guides-filters-and-effects-support-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
/**
* CE.SDK Server Guide: 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
*/
async function main() {
// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a page
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// 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 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);
// 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, 11);
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, 8);
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}`);
});
// Export the result to a file
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/effect-support-demo.png`, buffer);
console.log('Exported result to output/effect-support-demo.png');
} finally {
// Always dispose of the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
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](https://img.ly/docs/cesdk/node-native/filters-and-effects/apply-2764e4/) guide.
## Check Effect Support
Before applying effects to a block, verify whether it supports them using `supportsEffects()`. Not all block types can have effects applied.
```typescript highlight-check-effect-support
// 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
```
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
Create an effect using `createEffect()` with the effect type identifier, then attach it to a block with `appendEffect()`.
```typescript highlight-add-effect
// 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
Configure effect parameters using setter methods. Property paths follow the format `effect/{effect-type}/{property-name}`.
```typescript highlight-configure-effect
// 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);
```
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
Use `getEffects()` to retrieve all effects applied to a block.
```typescript highlight-get-effects
// 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}`);
});
```
## Effects
The following tables document all available effect types and their configurable properties.
## 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
- [Apply Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects/apply-2764e4/) - Learn how to configure, combine, and manage multiple effects
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Agent Skills"
description: "Install CE.SDK documentation and code generation skills for AI coding assistants."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/agent-skills-f7g8h9/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Build with AI](https://img.ly/docs/cesdk/node-native/get-started/build-with-ai-k7m9p2/) > [Agent Skills](https://img.ly/docs/cesdk/node-native/get-started/agent-skills-f7g8h9/)
---
The CE.SDK Agent Skills plugin gives AI coding assistants bundled documentation, guided code generation, and autonomous project scaffolding for building applications with CreativeEditor SDK across 10 Web frameworks.
## What Are Agent Skills?
[Agent Skills](https://agentskills.io) are portable knowledge packs that plug into AI coding assistants. By installing the CE.SDK skills, you get:
- **Offline documentation**: All guides, API references, and best practices bundled locally — no external API calls
- **Guided code generation**: Build and explain skills that walk through CE.SDK implementation step by step
- **Autonomous scaffolding**: A builder agent that creates complete CE.SDK projects from scratch
## Available Skills
| Skill | Description |
|-------|-------------|
| `docs-react` | Look up CE.SDK React reference guides and documentation |
| `docs-vue` | Look up CE.SDK Vue.js reference guides and documentation |
| `docs-svelte` | Look up CE.SDK Svelte reference guides and documentation |
| `docs-sveltekit` | Look up CE.SDK SvelteKit reference guides and documentation |
| `docs-angular` | Look up CE.SDK Angular reference guides and documentation |
| `docs-nextjs` | Look up CE.SDK Next.js reference guides and documentation |
| `docs-nuxtjs` | Look up CE.SDK Nuxt.js reference guides and documentation |
| `docs-electron` | Look up CE.SDK Electron reference guides and documentation |
| `docs-js` | Look up CE.SDK Vanilla JavaScript reference guides and documentation |
| `docs-node` | Look up CE.SDK Node.js reference guides and documentation |
| `build` | Implement features, write code, and set up CE.SDK Web projects |
| `explain` | Explain how CE.SDK Web features work — concepts, architecture, workflows |
The plugin also includes a **builder** agent that autonomously scaffolds complete CE.SDK web applications — detecting your framework, applying starter kit templates, and implementing features end-to-end.
## Setup Instructions
### Claude Code Plugin
Add the marketplace and install the plugin:
```bash
# Add the marketplace (one-time setup)
claude plugin marketplace add imgly/agent-skills
# Install the plugin
claude plugin install cesdk@imgly
```
### Vercel Skills CLI
Install using the [Vercel Skills CLI](https://github.com/vercel-labs/skills):
```bash
# Install all skills for Claude Code
npx skills add imgly/agent-skills -a claude-code
# Install a specific skill only
npx skills add imgly/agent-skills --skill docs-react -a claude-code
# List available skills first
npx skills add imgly/agent-skills --list
```
### Manual Copy
For any skills-compatible agent, copy skill folders directly from the [GitHub repository](https://github.com/imgly/agent-skills):
```bash
# Clone the repo
git clone https://github.com/imgly/agent-skills.git
# Copy a specific skill into your Claude Code project
cp -r agent-skills/plugins/cesdk/skills/docs-react .claude/skills/cesdk-docs-react
# Or copy the builder agent
cp agent-skills/plugins/cesdk/agents/builder.md .claude/agents/cesdk-builder.md
```
## Usage
Once installed, invoke skills with slash commands in your AI coding assistant:
### Look up documentation
```
/cesdk:docs-react configuration
/cesdk:docs-vue getting started
/cesdk:docs-nextjs server-side rendering
```
### Build a feature
```
/cesdk:build add text overlays to images
/cesdk:build create a photo editor with filters
```
### Explain a concept
```
/cesdk:explain how the block hierarchy works
/cesdk:explain export pipeline and output formats
```
## How It Works
Each documentation skill bundles the complete CE.SDK guides and API references for its framework in a compressed index. Skills read directly from these local files — no external services or MCP servers are required.
The build skill includes starter kit templates for common use cases like design editors, video editors, and photo editors. It detects your project's framework and generates code accordingly.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Build with AI"
description: "Give your AI coding assistant context about CE.SDK to generate accurate code and get instant answers."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/build-with-ai-k7m9p2/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Build with AI](https://img.ly/docs/cesdk/node-native/get-started/build-with-ai-k7m9p2/)
---
Give your AI coding assistant full context about CE.SDK to generate accurate
code and get instant answers. Choose the integration that fits your workflow.
## Choose Your Approach
### Using a Coding Agent?
If you're working with an AI coding assistant like Claude Code or OpenAI Codex, install our **Agent Skills** for the best experience. Skills bundle offline documentation, guided code generation and autonomous project scaffolding directly into your agent.
[Set Up Agent Skills](https://img.ly/docs/cesdk/node-native/get-started/agent-skills-f7g8h9/)
### Using an AI-Powered IDE?
Connect your IDE to our **MCP Server** for real-time documentation search. Works with Claude Desktop, Cursor, VS Code Copilot, Windsurf and any MCP-compatible tool.
[Connect MCP Server](https://img.ly/docs/cesdk/node-native/get-started/mcp-server-fde71c/)
### Need Raw Documentation for AI?
Download our **LLMs.txt** files to manually load CE.SDK documentation into any AI tool. Available as a compact index or full documentation bundle.
[Download LLMs.txt](https://img.ly/docs/cesdk/node-native/llms-txt-eb9cc5/)
---
## Related Pages
- [Agent Skills](https://img.ly/docs/cesdk/node-native/get-started/agent-skills-f7g8h9/) - Install CE.SDK documentation and code generation skills for AI coding assistants.
- [MCP Server](https://img.ly/docs/cesdk/node-native/get-started/mcp-server-fde71c/) - Connect AI assistants to CE.SDK documentation using the Model Context Protocol (MCP) server.
- [LLMs.txt](https://img.ly/docs/cesdk/node-native/llms-txt-eb9cc5/) - Our documentation is available in LLMs.txt format
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Bun"
description: "Getting started with CE.SDK Engine in Node.js using Bun"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/bun-l3456c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Quickstart Bun](https://img.ly/docs/cesdk/node-native/get-started/bun-l3456c/)
---
This guide walks you through integrating the **CreativeEditor SDK (CE.SDK)
Engine** in a **Node.js environment using Bun** as the package manager and
runtime. By the end of this guide, you’ll have a working Node.js script that
**loads a scene, modifies it, and exports it as an image** using **Bun’s fast
runtime and bundling capabilities**.
## Who Is This Guide For?
This guide is for developers who:
- Need to **perform image editing and scene manipulation programmatically** in a **Node.js environment** using **Bun**.
- Want to use **CE.SDK’s Node.js package** for automation or backend processing **with a fast, modern runtime**.
## What You’ll Achieve
- Install and configure **CE.SDK Engine** for **Node.js using Bun**.
- Load and manipulate **design scenes** programmatically.
- Export designs as **PNG images** without requiring a UI.
## Prerequisites
Before getting started, ensure you have:
- [**Bun installed**](https://bun.sh/) (Download Bun).
- A valid **CE.SDK license key** ([Get a free trial](https://img.ly/forms/free-trial)).
> **Warning:** Node.js executable isn't capable of processing or exporting video.
## Step 1: Set Up Your Project
Initialize your project with Bun, selecting "Blank" for the template:
```bash
bun init my-cesdk-bun-project
```
Your project structure should look like this:
```
/my-cesdk-bun-project
├── node_modules
├── .gitignore
├── bun.lock
├── index.ts
├── package.json
├── README.md
└── tsconfig.json
```
## Step 2: Install CE.SDK for Node.js Using Bun
Navigate into your project folder and install the required packages:
```bash
# Install the CE.SDK Node.js package with Bun
bun add @cesdk/node
```
### index.ts (TypeScript with Bun)
Modify your `index.ts` file to initialize CE.SDK Engine and process a scene **using Bun**:
> **Warning:** **Important**: You must replace `'YOUR_LICENSE_KEY'` with your actual CE.SDK
> license key. The script fails with initialization errors without a valid
> license key. [Get a free trial license key](https://img.ly/forms/free-trial).
```tsx
import { writeFile } from 'fs/promises';
import CreativeEngine from '@cesdk/node';
const { MimeType } = CreativeEngine as any;
// Configuration for the engine
const config = {
// license: 'YOUR_CESDK_LICENSE_KEY', // Replace with your CE.SDK license key
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`,
};
// Initialize CE.SDK Engine
CreativeEngine.init(config).then(async engine => {
console.log('CE.SDK Engine initialized');
try {
// Load a scene from a URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene',
);
// Find the first page in the scene
const [page] = engine.block.findByType('page');
// Export the scene as a PNG image
const blob = await engine.block.export(page, {
mimeType: 'image/png',
});
const arrayBuffer = await blob.arrayBuffer();
// Save the exported image to the file system
await writeFile('./example-output.png', Buffer.from(arrayBuffer));
console.log('Export completed: example-output.png');
} catch (error) {
console.error('Error processing scene:', error);
} finally {
// Dispose of the engine to free resources
engine.dispose();
}
});
```
## Step 3: Run the Script Using Bun
Once everything is set up, run your script using:
```bash
bun run index.ts
```
This processes the scene and generates an image file named **`example-output.png`** in your project directory.
## Step 4: Optimize Your Bun Project
### 1. Using Bun’s Faster File System API
Bun includes a built-in **fs API** that performs better than Node.js. Replace your existing `index.ts` with this:
```tsx
import { write } from 'bun';
import CreativeEngine from '@cesdk/node';
const { MimeType } = CreativeEngine as any;
// Configuration for the engine
const config = {
// license: 'YOUR_CESDK_LICENSE_KEY', // Replace with your CE.SDK license key
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`,
};
// Initialize CE.SDK Engine
CreativeEngine.init(config).then(async engine => {
console.log('CE.SDK Engine initialized');
try {
// Load a scene from a URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene',
);
// Find the first page in the scene
const [page] = engine.block.findByType('page');
// Export the scene as a PNG image
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const arrayBuffer = await blob.arrayBuffer();
// Save the exported image to the file system
await write('./example-output.png', Buffer.from(arrayBuffer));
console.log('Export completed: example-output.png');
} catch (error) {
console.error('Error processing scene:', error);
} finally {
// Dispose of the engine to free resources
engine.dispose();
}
});
```
### 2. Running the Bun Optimized Script
```bash
bun run index.ts
```
## Troubleshooting & Common Errors
**❌ Error: `fetch is not defined`**
- **Bun natively supports `fetch`**, so this error **should not occur** unless using an outdated version.
**❌ Error: `Invalid license key`**
- Verify that your **license key** is correct and not expired.
**❌ Error: `Cannot find module '@cesdk/node'`**
- Ensure that **@cesdk/node** is installed correctly using `bun add @cesdk/node`.
**❌ Error: `SyntaxError: Unexpected token 'import'`**
- Ensure your script is named **index.ts** for **TypeScript** support in Bun.
## Next Steps
Congratulations! You’ve successfully integrated **CE.SDK Engine in Node.js using Bun**. Next, explore:
- [Using source sets](https://img.ly/docs/cesdk/node-native/import-media/source-sets-5679c8/)
- [Modifying text variables](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/)
- [Defining placeholders](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/)
- [Text styling](https://img.ly/docs/cesdk/node-native/text/styling-269c48/)
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Deno"
description: "Getting started with CE.SDK Engine in Node.js using Deno"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/deno-m2345b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Quickstart Deno](https://img.ly/docs/cesdk/node-native/get-started/deno-m2345b/)
---
This guide walks you through integrating the **CreativeEditor SDK (CE.SDK)
Engine** in a **Node.js-compatible environment using Deno**, the secure and
modern runtime for JavaScript and TypeScript. By the end of this guide, you’ll
have a Deno script that **loads a scene, modifies it, and exports it as an
image**—all without a UI.
## Who Is This Guide For?
This guide is for developers who:
- Want to **programmatically edit and export scenes** using CE.SDK in **Deno**.
- Prefer a **secure and modern runtime** with built-in TypeScript and native fetch support.
- Need to integrate CE.SDK into **server-side rendering, automation tools, or cloud functions** using Deno.
## What You’ll Achieve
- Install and configure **CE.SDK Engine** in a **Deno project**.
- Load and manipulate **design scenes** programmatically.
- Export scenes to **PNG images** without any UI.
## Prerequisites
Before getting started, ensure you have:
- **Deno installed**.
- A valid **CE.SDK license key** ([Get a free trial](https://img.ly/forms/free-trial)).
- **Deno v1.28 or later** to ensure compatibility with npm packages.
> **Note:** Node.js executable isn't capable of processing or exporting video.
## Step 1: Initialize Your Deno Project
Create your project structure:
```bash
deno init my_project
```
Resulting in the following project structure:
```
my_project
├── deno.json
├── main_test.ts
└── main.ts
```
## Step 2: Use CE.SDK Engine via npm Compatibility
In your `main.ts`, use Deno’s npm compatibility to load CE.SDK:
```tsx
// @deno-types="npm:@cesdk/node"
import CreativeEngine from 'npm:@cesdk/node';
import { writeFile } from 'node:fs/promises';
// Fix for TypeScript type resolution in Deno
const { MimeType } = CreativeEngine as any;
// CE.SDK configuration
const config = {
// license: 'YOUR_CESDK_LICENSE_KEY', // Enter your CE.SDK license key
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`,
};
try {
const engine = await CreativeEngine.init(config);
console.log('CE.SDK Engine initialized (Deno)');
try {
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene',
);
const [page] = engine.block.findByType('page');
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const arrayBuffer = await blob.arrayBuffer();
await writeFile('./example-output.png', new Uint8Array(arrayBuffer));
console.log('Export completed: example-output.png');
} catch (err) {
console.error('Error:', err);
} finally {
engine.dispose();
}
} catch (initError) {
console.error('Failed to initialize CE.SDK Engine:', initError);
}
```
> **Warning:** **Important**: You must replace `'YOUR_LICENSE_KEY'` with your actual CE.SDK
> license key. The script fails with initialization errors without a valid
> license key. [Get a free trial license key](https://img.ly/forms/free-trial).
## Step 3: Run the Script
Run the script using Deno’s Node.js/npm compatibility by adding the necessary flags:
```bash
deno run \
--allow-read \
--allow-write \
--allow-net \
--unstable-ffi \
main.ts
```
These flags are required to execute `main.ts` for the following reasons:
| Flag | Required | Why |
| ---------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `--allow-read` | Yes | Grants permission to read from the file system. Useful if the engine loads local assets or template files. |
| `--allow-write` | Yes | Allows CE.SDK to write the exported image file (`example-output.png`) to disk. |
| `--allow-net` | Yes | Required to fetch remote assets and scenes (e.g., from `cdn.img.ly`). |
| `--unstable-ffi` | Yes | Enables WebAssembly FFI, which CE.SDK Engine depends on for core rendering and export features. |
This processes the scene and generates an image file named `example-output.png` in your project directory.
## Troubleshooting & Common Errors
**❌ Error: `Cannot find module '@cesdk/node'`**
- Make sure you’re running with the `-unstable` flag and Deno v1.28 or later.
- Add `deno.json` or `import_map.json` if managing multiple npm dependencies.
**❌ Error: `Invalid license key`**
- Ensure your license key is correct and not expired.
**❌ TypeScript errors for `MimeType`**
- Use `CreativeEngine as any` or assert type manually to bypass missing type declarations.
## Next Steps
You’ve successfully integrated **CE.SDK Engine in Deno**. Now explore:
- [Using source sets](https://img.ly/docs/cesdk/node-native/import-media/source-sets-5679c8/)
- [Modifying text variables](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/)
- [Defining placeholders](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/)
- [Text styling](https://img.ly/docs/cesdk/node-native/text/styling-269c48/)
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "MCP Server"
description: "Connect AI assistants to CE.SDK documentation using the Model Context Protocol (MCP) server."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/mcp-server-fde71c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Build with AI](https://img.ly/docs/cesdk/node-native/get-started/build-with-ai-k7m9p2/) > [MCP Server](https://img.ly/docs/cesdk/node-native/get-started/mcp-server-fde71c/)
---
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?
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
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
| URL | Transport |
|-----|-----------|
| `https://mcp.img.ly/mcp` | Streamable HTTP |
No authentication is required.
## Setup Instructions
### Claude Code
Add the MCP server with a single command:
```bash
claude mcp add --transport http imgly_docs https://mcp.img.ly/mcp
```
### 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
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`
```json
{
"mcpServers": {
"imgly_docs": {
"url": "https://mcp.img.ly/mcp"
}
}
}
```
### VS Code
Add to your workspace configuration at `.vscode/mcp.json`:
```json
{
"servers": {
"imgly_docs": {
"type": "http",
"url": "https://mcp.img.ly/mcp"
}
}
}
```
### Windsurf
Add the following to your Windsurf MCP configuration at `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"imgly_docs": {
"serverUrl": "https://mcp.img.ly/mcp"
}
}
}
```
### 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
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.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Get Started"
description: "Start integrating CE.SDK into your application—from understanding the SDK to running your first editor."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/)
---
Everything you need to integrate CE.SDK into your application. Learn what the
SDK offers, get up and running with starter kits, explore AI-powered
workflows, and understand our licensing model.
---
## Related Pages
- [Node.js (Native) Creative Engine](https://img.ly/docs/cesdk/node-native/what-is-cesdk-2e7acd/) - Learn what CE.SDK is, how it works, and what you can build with its headless API, native C++ performance, and GPU-accelerated video export.
- [Node.js - New Project](https://img.ly/docs/cesdk/node-native/get-started/vanilla-n1234a/) - Getting started with CE.SDK Engine in Node.js using Vanilla JS
- [Node.js (Native) - New Project](https://img.ly/docs/cesdk/node-native/get-started/vanilla-nn1a2b/) - Getting started with CE.SDK Engine using the native Node.js bindings
- [Deno](https://img.ly/docs/cesdk/node-native/get-started/deno-m2345b/) - Getting started with CE.SDK Engine in Node.js using Deno
- [Bun](https://img.ly/docs/cesdk/node-native/get-started/bun-l3456c/) - Getting started with CE.SDK Engine in Node.js using Bun
- [Build with AI](https://img.ly/docs/cesdk/node-native/get-started/build-with-ai-k7m9p2/) - Give your AI coding assistant context about CE.SDK to generate accurate code and get instant answers.
- [Licensing](https://img.ly/docs/cesdk/node-native/licensing-8aa063/) - Understand CE.SDK’s flexible licensing, trial options, and how keys work across dev, staging, and production.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Node.js - AWS Lambda"
description: "Learn how to set up a scalable architecture on AWS to automatically generate images using the Creative Engine node package."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/vanilla-aws-lambda-fee18b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Quickstart Node.js](https://img.ly/docs/cesdk/node-native/get-started/vanilla-n1234a/)
---
This guide shows you how to create a simple API on **Amazon AWS** to dynamically generate image variants based on **CreativeEditor SDK (CE.SDK)** templates saved in an **S3** bucket by running the CE.SDK's Creative Engine on the server.
## Who is This Guide For?
- Are using **Node.js** and **Amazon AWS**.
- Want to create a **server-side API** for dynamically generating images or videos based on certain events.
- Need to implement a **scalable solution** for creative automation in your application.
## What You'll Achieve
- Implement CE.SDK service on AWS CDK App.
- Set up a table on DynamoDB to store image records.
- Create an image API implementing `create` and `get` endpoints.
- Create lambda functions for creating and querying image resources as well as interpolating parameters to render an image using CE.SDK.
## Prerequisites
Follow the steps in [this AWS tutorial.](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)
We are going to use AWS CDK to provision the infrastructure (API Gateway, Lambda Function, S3 buckets, DynamoDB and IAM Role) we need. You should have a basic understanding of the AWS CLI and [CloudFormation](https://aws.amazon.com/cloudformation/).
- [Install and configure](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) the AWS CLI.
- [Install the AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) npm package and bootstrap the necessary resources.
- Get a valid [CE.SDK license key](https://img.ly/forms/free-trial).
## Step 1: Create the AWS CDK App
Create the AWS CDK app, for this example, we’ll create an app called "cesdk-aws-lambda", but you’ll probably want to opt for a more descriptive name for your use case.
```bash
mkdir cesdk-aws-lambda
cd cesdk-aws-lambda
cdk init --language javascript
```
The main entry point for our app will be `bin/cesdk-aws-lambda` and the stack script we’ll use to provision our resources will be located in `lib/cesdk-aws-lambda-stack.js`.
Run the following command to test that everything is working correctly:
```bash
cdk synth
```
Since we haven’t added anything to our stack file this will synthesize an empty stack.
## Step 2: Create a CE.SDK service
Create a CDK service file under `lib/cesdk-service.js` to configure our stack. We’ll create the necessary resources for the images API endpoint first; a REST API using APIGateway that integrates with a lambda handler that in turns creates and queries the DynamoDB table:
```javascript
const { Construct } = require('constructs');
const apigateway = require('aws-cdk-lib/aws-apigateway');
const lambda = require('aws-cdk-lib/aws-lambda');
class CESDKService extends Construct {
constructor(scope, id) {
super(scope, id);
const tableName = 'ImagesTable';
// lambda function for images endpoint creating new images and returning images
const imagesHandler = new lambda.Function(this, 'ImagesHandler', {
runtime: lambda.Runtime.NODEJS_20_X,
code: lambda.Code.fromAsset('src'),
handler: 'images-handler.main',
environment: {
TABLE_NAME: tableName,
},
});
// Set up REST api for images
const api = new apigateway.RestApi(this, 'cesdk-api', {
restApiName: 'CESDK Service',
description: 'This service renders cesdk templates.',
});
const CESDKIntegration = new apigateway.LambdaIntegration(imagesHandler, {
requestTemplates: { 'application/json': '{ "statusCode": "200" }' },
});
const imagesResource = api.root.addResource('images');
imagesResource.addMethod('POST', CESDKIntegration); // POST /images
const imageResource = imagesResource.addResource('{id}');
imageResource.addMethod('GET', CESDKIntegration); // GET /images/{id}
}
}
module.exports = { CESDKService };
```
This service has to be added to the stack definition in the `lib/cesdk-aws-lambda-stack` file.
```javascript file=@cesdk_node_examples/cookbook-aws-lambda/lib/cesdk-aws-lambda-stack.js
const { Stack } = require("aws-cdk-lib");
const CESDKService = require("../lib/cesdk-service");
class CesdkAwsLambdaStack extends Stack {
/**
*
* @param {Construct} scope
* @param {string} id
* @param {StackProps=} props
*/
constructor(scope, id, props) {
super(scope, id, props);
// The code that defines your stack goes here
new CESDKService.CESDKService(this, "CESDK Service");
}
}
module.exports = { CesdkAwsLambdaStack };
```
Test that the app runs and synthesizes a stack with `cdk synth`.
## Step 3: Create a Lambda Function to Create Images
Create an `src` directory and run `npm init`.
We'll install all necessary dependencies inside the `src` directory, since only code specified via `code: lambda.Code.fromAsset("src")` in our service file will be made available to our lambda function.
```bash
yarn add @cesdk/node @aws-sdk/client-dynamodb uuid
```
Next initialize an `images-handler.js` file inside it. This lambda function handles the `POST` and `GET` image requests.
In the first case, we create a unique image id and a file name derived from it, then we add a new image record in our DynamoDB table storing the parameter from the request that should be interpolated, the id and file name as well as a creation status (initially `PENDING`) of the image that the client can query.
The second case takes the image id from the request and simply returns the record from the database.
```javascript file=@cesdk_node_examples/cookbook-aws-lambda/src/images-handler.js
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const {
DynamoDBDocumentClient,
GetCommand,
PutCommand,
} = require("@aws-sdk/lib-dynamodb");
const { v4: uuidv4 } = require("uuid");
const dynamoDBClient = new DynamoDBClient({});
const dynamoDBDocClient = DynamoDBDocumentClient.from(dynamoDBClient);
const tableName = process.env.TABLE_NAME;
exports.main = async function (event) {
const routeKey = `${event.httpMethod} ${event.resource}`;
try {
switch (routeKey) {
case "POST /images":
const id = uuidv4();
const filename = `awesome-headline-${id}.png`;
const requestBody = JSON.parse(event.body);
// Create a new item in the DB table
const putCommand = new PutCommand({
TableName: tableName,
Item: {
id,
filename,
interpolationParams: JSON.stringify({
headline: requestBody.headline,
}),
creationStatus: "PENDING",
url: "",
},
});
await dynamoDBDocClient.send(putCommand);
var body = { id };
break;
case "GET /images/{id}":
const getCommand = new GetCommand({
TableName: tableName,
Key: {
id: event.pathParameters.id,
},
});
const getResponse = await dynamoDBDocClient.send(getCommand);
body = getResponse.Item;
break;
}
return {
statusCode: 200,
headers: {},
body: JSON.stringify(body),
};
} catch (error) {
var body = error.stack || JSON.stringify(error, null, 2);
return {
statusCode: 400,
headers: {},
body: JSON.stringify(body),
};
}
};
```
After saving the function, you can run `cdk synth` again as sanity check that we’re still synthesizing an empty stack.
## Step 4: Integrating the Creative Engine
Now, we need another lambda function to perform the heavy lifting of running the Creative Engine and rendering the image.
For this add a `cesdk-handler.js` file in the `src` directory and create a lambda handler there.
We’ll initialize the `CreativeEngine` and load the template from the URL we’ll provide through an environment variable. Of course, in most use cases you will want to dynamically retrieve templates from file storage, but for illustration purposes we hardcode the template here.
```javascript
const CreativeEngine = require('@cesdk/node');
const templateURL = process.env.TEMPLATE_URL;
const config = {
// license: 'YOUR_CESDK_LICENSE_KEY',
};
exports.main = async function (event) {
try {
const engine = await CreativeEngine.init(config);
// load scene from remote template file
await engine.scene.loadFromURL(templateURL);
} catch (error) {
console.warn(error);
}
};
```
Now to access the newly created image record, we need to set the `cesdkHanlder` as a lambda trigger for our DynamoDB table.
Inside the `cesdk-service.js` we’ll first define the lambda function increasing the memory available to the function as well as the timeout to allow for more computation-heavy renderings.
Then we’ll grant the required permissions to perform updates to the images table and finally, we’ll add a DynamoDB event stream to the `cesdkHandler`. The complete `cesdk-service.js` file is as follows:
```javascript file=@cesdk_node_examples/cookbook-aws-lambda/lib/cesdk-service.js
const { Construct } = require("constructs");
const apigateway = require("aws-cdk-lib/aws-apigateway");
const lambda = require("aws-cdk-lib/aws-lambda");
const s3 = require("aws-cdk-lib/aws-s3");
const cdk = require("aws-cdk-lib");
const dynamodb = require("aws-cdk-lib/aws-dynamodb");
const iam = require("aws-cdk-lib/aws-iam");
const eventsource = require("aws-cdk-lib/aws-lambda-event-sources");
class CESDKService extends Construct {
constructor(scope, id) {
super(scope, id);
const tableName = "ImagesTable";
const bucket = new s3.Bucket(this, "CESDKStore");
// lambda function for images endpoint creating new images and returning images
const imagesHandler = new lambda.Function(this, "ImagesHandler", {
runtime: lambda.Runtime.NODEJS_20_X,
code: lambda.Code.fromAsset("src"),
handler: "images-handler.main",
environment: {
TABLE_NAME: tableName,
},
});
// lambda function running CE.SDK and rendering image
const cesdkHandler = new lambda.Function(this, "CESDKHandler", {
runtime: lambda.Runtime.NODEJS_20_X,
code: lambda.Code.fromAsset("src"),
handler: "cesdk-handler.main",
environment: {
BUCKET: bucket.bucketName,
TABLE_NAME: tableName,
TEMPLATE_URL:
"https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene",
},
timeout: cdk.Duration.minutes(5),
memorySize: 2048,
});
// Create dynamo db table for storing image objects
const imagesTable = new dynamodb.Table(this, "ImagesTable", {
tableName: "ImagesTable",
billingMode: dynamodb.BillingMode.PROVISIONED,
removalPolicy: cdk.RemovalPolicy.DESTROY,
partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
pointInTimeRecoverySpecification: {
PointInTimeRecoveryEnabled: true,
},
stream: dynamodb.StreamViewType.NEW_IMAGE,
});
// Configure lambda permissions for resources
bucket.grantReadWrite(cesdkHandler);
const imagesTablePermissionPolicy = new iam.PolicyStatement({
actions: [
"dynamodb:BatchGetItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
],
resources: [imagesTable.tableArn],
});
const imagesTablePermissions = new iam.Policy(
this,
`${this.appName}-ImagesTablePermissions`,
{
statements: [imagesTablePermissionPolicy],
}
);
imagesHandler.role?.attachInlinePolicy(imagesTablePermissions);
cesdkHandler.role?.attachInlinePolicy(imagesTablePermissions);
cesdkHandler.addEventSource(
new eventsource.DynamoEventSource(imagesTable, {
startingPosition: lambda.StartingPosition.LATEST,
})
);
// Set up REST api for images
const api = new apigateway.RestApi(this, "cesdk-api", {
restApiName: "CESDK Service",
description: "This service renders cesdk templates.",
});
const CESDKIntegration = new apigateway.LambdaIntegration(imagesHandler, {
requestTemplates: { "application/json": '{ "statusCode": "200" }' },
});
const imagesResource = api.root.addResource("images");
imagesResource.addMethod("POST", CESDKIntegration); // POST /images
const imageResource = imagesResource.addResource("{id}");
imageResource.addMethod("GET", CESDKIntegration); // GET /images/{id}
}
}
module.exports = { CESDKService };
```
The reason we are initializing the CE.SDK outside of our lambda handler is that this allows [resource sharing among lambda requests](https://docs.aws.amazon.com/lambda/latest/operatorguide/static-initialization.html) and decreases our response time.
To receive only events of new image records having been added to the table, we need to add the following config option to the `imagesTable` definition:
```javascript
stream: dynamodb.StreamViewType.NEW_IMAGE;
```
## Step 5: Filling a Template and Generating an Image
Now we can finally get to the meat of the matter and populate a CE.SDK template with data submitted via our API.
We receive the newly created image record via the event that is passed into the lambda handler, after interpolating the `headline` parameter and rendering the final image, we’ll store it in an S3 bucket and generate a signed URL to the image.
Lastly, we update the image with the URL and set the `creationStatus` to `FINISHED`. The complete handler file now looks as follows:
```javascript file=@cesdk_node_examples/cookbook-aws-lambda/src/cesdk-handler.js
const CreativeEngine = require("@cesdk/node");
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const {
DynamoDBDocumentClient,
UpdateCommand,
} = require("@aws-sdk/lib-dynamodb");
const { PutObjectCommand, S3Client } = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const s3Client = new S3Client({});
const dynamoDBClient = new DynamoDBClient({});
const dynamoDBDocClient = DynamoDBDocumentClient.from(dynamoDBClient);
const bucketName = process.env.BUCKET;
const templateURL = process.env.TEMPLATE_URL;
const tableName = process.env.TABLE_NAME;
const { MimeType } = CreativeEngine;
const config = {
license: "",
};
exports.main = async function (event) {
try {
const engine = await CreativeEngine.init(config);
// load scene from remote template file
await engine.scene.loadFromURL(templateURL);
for (const record of event.Records) {
const item = record.dynamodb.NewImage;
const filename = item.filename.S;
const id = item.id.S;
const interpolationParams = JSON.parse(item.interpolationParams.S);
// Interpolate the text content from request params
engine.block.setString(
engine.block.findByType("text")[0],
"text/text",
interpolationParams.headline
);
const [page] = engine.block.findByType("page");
const renderedImage = await engine.block.export(page, {
mimeType: "image/png",
});
const imageBuffer = await renderedImage.arrayBuffer();
const putObjectCommand = new PutObjectCommand({
Bucket: bucketName,
Body: Buffer.from(imageBuffer),
ContentType: "image/png",
Key: filename,
});
// Store rendered image in S3 bucket
await s3Client.send(putObjectCommand);
// Retrieve image url
const signedUrl = await getSignedUrl(s3Client, putObjectCommand, {
expiresIn: 3600,
});
// Update the item in DB with the signed URL and status
const updateCommand = new UpdateCommand({
TableName: tableName,
Key: { id },
UpdateExpression: "SET #status = :statusValue, #url = :signedUrl",
ExpressionAttributeNames: {
"#url": "url",
"#status": "creationStatus",
},
ExpressionAttributeValues: {
":signedUrl": signedUrl,
":statusValue": "FINISHED",
},
ReturnValues: "UPDATED_NEW",
});
await dynamoDBDocClient.send(updateCommand);
}
} catch (error) {
console.warn(error);
}
};
```
## Step 6: Test the Integration
1. Configure your AWS credentials via `aws configure` and entering your AWS Access Key ID, Secret Access Key, and region. These can usually be acquired on your AWS access portal.
2. Run `cdk deploy` to deploy the stack.
3. Access API Gateway in your AWS console and select `CESDK Service`. Under Resources menu, select to `/images` POST.

Then on 'Test' tab, test the API with a JSON body like this:
```json
{
"headline": "An Awesome Headline"
}
```
You'll receive a 200 response with the resulting image id. For the image rendering status to be `FINISHED`, you might need to wait a few seconds.
4. Now select `/images/{id}` GET and test the API with the image id you received from the previous step. You should receive a 200 response with a response body including `url` and `creationStatus`.

5. Check your S3 bucket for the newly created image, or test the image URL in your browser. You should see the rendered image with the headline you provided in the request.

## Troubleshooting & Common Errors
**❌ Error: `Invalid license key`**
- Verify that your license key is valid and not expired.
**❌ Error: `Need to perform AWS calls for account ..., but no credentials have been configured`**
- Make sure your AWS CDK Credentials are set up correctly and still valid before deploying the stack. You can re-configure the credentials by running `aws configure` and provide the valid ones.
**❌ Error: `Access Denied`**
- When you receive an `Access Denied` error while trying to access the image URL, ensure that you have set the correct permissions for the S3 bucket.
## Next Steps
We now have a simple API endpoint that renders and stores a CE.SDK template from a set of input parameters.
It’s easy to extrapolate this setup to any number of design automation use cases. We could load the template dynamically based on the request, provide images to replace template placeholders and provide more detailed specifications such as font, size and color of the text:
- [Images Source Sets](https://img.ly/docs/cesdk/node-native/import-media/source-sets-5679c8/)
- [Text Variables](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/text-variables-7ecb50/)
- [Placeholders](https://img.ly/docs/cesdk/node-native/create-templates/add-dynamic-content/placeholders-d9ba8a/)
- [Text Styling](https://img.ly/docs/cesdk/node-native/text/styling-269c48/)
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Node.js - Clone GitHub Project"
description: "Getting started with CE.SDK Engine in Node.js using Vanilla JS and a clone of the GitHub project."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/vanilla-clone-github-project-n1fe4a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Quickstart Node.js](https://img.ly/docs/cesdk/node-native/get-started/vanilla-n1234a/)
---
This guide walks you through **cloning and running** a pre-built
**CreativeEditor SDK (CE.SDK)** Node.js integration project from GitHub. It's
the fastest way to get started with CE.SDK in **Node.js** without having to
set everything up from scratch.
## Who Is This Guide For?
This guide is perfect for developers who:
- Want to use **CE.SDK on a server** without needing a custom setup.
- Prefer working with a ready-to-go **Node.js sample project**.
- Are comfortable using **Git** and **Node.js** to manage local development environments.
## What You’ll Achieve
- Clone the **CE.SDK Node.js integration project** from GitHub.
- Install the required dependencies and **run the project locally**.
- **Programmatically load a scene** with **CE.SDK CreativeEngine** and export as **PNG**.
> **Note:** Please note that the Node.js executable is not capable of processing or
> exporting video.
## Prerequisites
Before getting started, ensure you have the following:
- **Git** - Required to clone the project repository. [Download Git](https://git-scm.com/downloads).
- **The latest LTS version of Node.js and npm** - Necessary for installing dependencies and running the script. [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
Begin by cloning the CE.SDK examples repository from GitHub:
`bash git clone https://github.com/imgly/cesdk-web-examples.git `
`bash gh repo clone imgly/cesdk-web-examples `
> **Warning:** **Warning:** The full `cesdk-web-examples` repository has a size of
> approximately 1 GB. Ensure you have a reliable internet connection before
> cloning. Any download interruption may result in a `fatal: early EOF` error,
> and require restarting the clone process.
## Step 2: Install the Dependencies
Navigate to the example directory:
```bash
cd cesdk-web-examples/integrate-with-nodejs
```
Next, install the necessary dependencies using npm:
```bash
npm install
```
This downloads and installs all the required packages listed in the project’s `package.json` file.
## Step 3: Add Your License Key
Open `index.js` and replace the placeholder license key with your valid one.
```js
const config = {
// Replace with your CE.SDK license key
// license: 'YOUR_CESDK_LICENSE_KEY',
// ...
};
```
## Step 4: Run the Project
Run the Node.js script that contains your CreativeEngine logic with:
```bash
node index.js
```
This loads an existing scene and generates an image file named `example-output.png` in your project directory.
## Troubleshooting & Common Issues
**❌ Issue**: `Module not found` or missing packages
- Verify that `npm install` ran without any errors and all dependencies were installed correctly.
**❌ Error**: `Invalid license key`
- Confirm that the license key is correctly entered and valid.
## Next Steps
Congratulations! You’ve successfully integrated CE.SDK with Node.js. When you’re ready, dive deeper into the SDK and continue with the next steps:
- [Serve assets from your server](https://img.ly/docs/cesdk/node-native/serve-assets-b0827c/)
- [Import media](https://img.ly/docs/cesdk/node-native/import-media/concepts-5e6197/)
- [Automate creative workflows](https://img.ly/docs/cesdk/node-native/automation/overview-34d971/)
- [Running on AWS Lambda](https://img.ly/docs/cesdk/node-native/get-started/vanilla-aws-lambda-fee18b/)
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Node.js - New Project"
description: "Getting started with CE.SDK Engine in Node.js using Vanilla JS"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/vanilla-n1234a/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Quickstart Node.js](https://img.ly/docs/cesdk/node-native/get-started/vanilla-n1234a/)
---
This guide walks you through integrating the **CreativeEditor SDK (CE.SDK)
Engine** in a **Node.js environment using Vanilla JavaScript (CommonJS or ES
modules)**, enabling you to process images and designs programmatically. By
the end of this guide, you’ll have a working Node.js script that **loads a
scene, modifies it, and exports it as an image**.
## Who Is This Guide For?
This guide is for developers who:
- Need to perform **image editing and scene manipulation programmatically** in a **Node.js** environment using **plain JavaScript**.
- Want to use **CE.SDK’s Node.js package** for automation or backend processing **without TypeScript or additional frameworks**.
- Prefer **a script-based approach** for **design generation and image exports**.
## What You’ll Achieve
- Install and configure **CE.SDK Engine** for **Node.js**.
- Load and manipulate **design scenes** programmatically using **plain JavaScript**.
- Export designs as **PNG images** without requiring a UI.
> **Note:** Please note that the Node.js executable is not capable of processing or
> exporting video.
## Prerequisites
Before getting started, ensure you have:
- **Node.js v20 or later** installed. ([Download Node.js](https://nodejs.org/)).
- A valid **CE.SDK license key** - Required for engine initialization. [Start a free trial](https://img.ly/forms/free-trial) to get your license key.
## Step 1: Set Up Your Project
Create a new project folder and navigate into it:
```bash
mkdir my-cesdk-node-project
cd my-cesdk-node-project
```
Next, create a new `index.js` file manually or by running:
```bash
touch index.js
```
## Step 2: Install CE.SDK for Node.js
Run the following command to install the required packages:
```bash
npm install @cesdk/node
```
Your project structure should now look like this:
```
/my-cesdk-node-project
├── node_modules
├── index.js
└── package.json
```
For your `index.js`, you can either use ES modules or CommonJS. Both versions are shown below.
### index.js (ES Modules)
If your project is using **ES modules**, change the `package.json` file to include:
```json
{
"type": "module"
}
```
Then, modify **`index.js`** to use ES modules **(import syntax)**:
```js
import fs from 'fs/promises';
import CreativeEngine from '@cesdk/node';
// Configuration for the engine
const config = {
// license: 'YOUR_CESDK_LICENSE_KEY', // Replace with your CE.SDK license key
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`,
};
try {
// Initialize CE.SDK Engine
const engine = await CreativeEngine.init(config);
console.log('CE.SDK Engine initialized');
try {
// Load a scene from a URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene',
);
// Find the first page in the scene
const [page] = engine.block.findByType('page');
// Export the scene as a PNG image
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const arrayBuffer = await blob.arrayBuffer();
// Save the exported image to the file system
await fs.writeFile('./example-output.png', Buffer.from(arrayBuffer));
console.log('Export completed: example-output.png');
} catch (error) {
console.error('Error processing scene:', error);
} finally {
// Dispose of the engine to free resources
engine.dispose();
}
} catch (initError) {
console.error('Failed to initialize CE.SDK Engine:', initError.message);
process.exit(1);
}
```
### index.js (CommonJS)
Modify your `index.js` file to initialize CE.SDK Engine and process a scene using CommonJS **(require syntax)**:
```js
const fs = require('fs/promises');
const CreativeEngine = require('@cesdk/node');
const { MimeType } = CreativeEngine;
// Configuration for the engine
const config = {
// license: 'YOUR_CESDK_LICENSE_KEY', // Replace with your CE.SDK license key
baseURL: `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`,
};
// Initialize CE.SDK Engine
CreativeEngine.init(config)
.then(async engine => {
console.log('CE.SDK Engine initialized');
try {
// Load a scene from a URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene',
);
// Find the first page in the scene
const [page] = engine.block.findByType('page');
// Export the scene as a PNG image
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const arrayBuffer = await blob.arrayBuffer();
// Save the exported image to the file system
await fs.writeFile('./example-output.png', Buffer.from(arrayBuffer));
console.log('Export completed: example-output.png');
} catch (error) {
console.error('Error processing scene:', error);
} finally {
// Dispose of the engine to free resources
engine.dispose();
}
})
.catch(initError => {
console.error('Failed to initialize CE.SDK Engine:', initError.message);
process.exit(1);
});
```
## Step 3: Run the Script
Once everything is set up, run your script using:
```bash
node index.js
```
This code processes the scene and generates an image file named **`example-output.png`** in your project directory.
## Troubleshooting & Common Errors
**❌ Error: `fetch is not defined`**
- If using **Node.js v16 or lower**, this error can occur. We only support Node.js v20 and above, as those are the officially maintained versions.
**❌ Error: `Invalid license key`**
- Verify that your **license key** is correct and not expired.
**❌ Error: `SyntaxError: Cannot use import statement outside a module`**
- If using **ES modules**, ensure `"type": "module"` is set in `package.json`.
- If using **CommonJS**, ensure `require()` is used instead of `import`.
## Development Best Practices
- **Always handle initialization errors** - CE.SDK initialization can fail for various reasons. Common reasons are:
- Invalid license
- Network issues
- **Dispose resources** - Always call `engine.dispose()` in a `finally` block to free memory.
- **Use async/await** - The async/await pattern is generally cleaner than promise chains for CE.SDK operations.
## Next Steps
Congratulations! You’ve successfully integrated **CE.SDK Engine in Node.js** using **plain JavaScript**. Next, explore:
- [Serve assets from your server](https://img.ly/docs/cesdk/node-native/serve-assets-b0827c/)
- [Import media](https://img.ly/docs/cesdk/node-native/import-media/concepts-5e6197/)
- [Automating creative workflows](https://img.ly/docs/cesdk/node-native/automation/overview-34d971/)
- [Running on AWS Lambda](https://img.ly/docs/cesdk/node-native/get-started/vanilla-aws-lambda-fee18b/)
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Node.js (Native) - New Project"
description: "Getting started with CE.SDK Engine using the native Node.js bindings"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/get-started/vanilla-nn1a2b/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/node-native/get-started/overview-e18f40/) > [Quickstart Node.js (Native)](https://img.ly/docs/cesdk/node-native/get-started/vanilla-nn1a2b/)
---
This guide walks you through integrating the **CreativeEditor SDK (CE.SDK)
Engine** in a **Node.js environment using the native bindings**, enabling you
to process images, designs, and videos programmatically with native C++
performance. By the end of this guide, you'll have a working Node.js script
that **loads a scene, modifies it, and exports it as an image**.
## Who Is This Guide For?
This guide is for developers who:
- Need **native C++ performance** for image and video processing in a **Node.js** environment.
- Want to use **CE.SDK's native Node.js package** for automation or backend processing with GPU-accelerated video export.
- Prefer **a script-based approach** for **design generation, image exports, and video rendering**.
## What You'll Achieve
- Install and configure **CE.SDK Engine** using the **native Node.js bindings**.
- Load and manipulate **design scenes** programmatically.
- Export designs as **PNG images** without requiring a UI.
> **Tip:** Unlike the WASM-based `@cesdk/node` package, the native bindings support
> GPU-accelerated video export on supported platforms (macOS ARM/x64, Linux
> x64).
## Prerequisites
Before getting started, ensure you have:
- **Node.js v20 or later** installed. ([Download Node.js](https://nodejs.org/)).
- A valid **CE.SDK license key** - Required for engine initialization. [Start a free trial](https://img.ly/forms/free-trial) to get your license key.
- A supported platform: **macOS** (ARM or x64) or **Linux** (x64).
## Step 1: Set Up Your Project
Create a new project folder and navigate into it:
```bash
mkdir my-cesdk-native-project
cd my-cesdk-native-project
```
Next, create a new `index.mjs` file manually or by running:
```bash
touch index.mjs
```
## Step 2: Install CE.SDK Native for Node.js
Run the following command to install the native package:
```bash
npm install @cesdk/node-native
```
Your project structure should now look like this:
```
/my-cesdk-native-project
├── node_modules
├── index.mjs
└── package.json
```
## Step 3: Write Your Script
Modify **`index.mjs`** to initialize CE.SDK Engine and process a scene:
```js
import fs from 'fs/promises';
import CreativeEngine from '@cesdk/node-native';
async function main() {
// Configuration for the engine
const config = {
license: 'YOUR_CESDK_LICENSE_KEY',
};
// Initialize CE.SDK Engine
const engine = await CreativeEngine.init(config);
console.log('CE.SDK Engine initialized');
try {
// Load a scene from a URL
await engine.scene.loadFromURL(
'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene',
);
// Find the first page in the scene
const [page] = engine.block.findByType('page');
// Export the scene as a PNG image
const pngBuffer = await engine.block.export(page, 'image/png');
// Save the exported image to the file system
await fs.writeFile('./example-output.png', Buffer.from(pngBuffer));
console.log('Export completed: example-output.png');
} catch (error) {
console.error('Error processing scene:', error);
} finally {
// Dispose of the engine to free resources
engine.dispose();
}
}
main().catch(console.error);
```
## Step 4: Run the Script
Once everything is set up, run your script using:
```bash
node index.mjs
```
This code processes the scene and generates an image file named **`example-output.png`** in your project directory.
## Troubleshooting & Common Errors
**Error: `Cannot find module '@cesdk/node-native'`**
- Verify the package is installed: `npm ls @cesdk/node-native`.
- Ensure you're on a supported platform (macOS ARM/x64 or Linux x64).
**Error: `Invalid license key`**
- Verify that your **license key** is correct and not expired.
## Development Best Practices
- **Dispose resources** - Always call `engine.dispose()` in a `finally` block to free memory.
- **Use async/await** - Engine operations including initialization, loading scenes, and exporting are asynchronous.
## Next Steps
Congratulations! You've successfully integrated **CE.SDK Engine** using the **native Node.js bindings**. Next, explore:
- [Serve assets from your server](https://img.ly/docs/cesdk/node-native/serve-assets-b0827c/)
- [Import media](https://img.ly/docs/cesdk/node-native/import-media/concepts-5e6197/)
- [Automating creative workflows](https://img.ly/docs/cesdk/node-native/automation/overview-34d971/)
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Guides"
description: "Documentation for Guides"
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/guides-8d8b00/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/)
---
---
## Related Pages
- [Configuration](https://img.ly/docs/cesdk/node-native/configuration-2c1c3d/) - Learn how to configure CE.SDK to match your application's functional, visual, and performance requirements.
- [Settings](https://img.ly/docs/cesdk/node-native/settings-970c98/) - Explore all configurable editor settings and learn how to read, update, and observe them via the Settings API.
- [Serve Assets](https://img.ly/docs/cesdk/node-native/serve-assets-b0827c/) - Configure CE.SDK to load engine and content assets from your own servers instead of the IMG.LY CDN for production deployments.
- [Engine Interface](https://img.ly/docs/cesdk/node-native/engine-interface-6fb7cf/) - Understand CE.SDK's architecture and learn when to use direct Engine access for automation workflows
- [Automate Workflows](https://img.ly/docs/cesdk/node-native/automation-715209/) - Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale.
- [Open the Editor](https://img.ly/docs/cesdk/node-native/open-the-editor-23a1db/) - Learn how to load and create scenes, set the zoom level, and configure file proxies or URI resolvers.
- [Insert Media Into Scenes](https://img.ly/docs/cesdk/node-native/insert-media-a217f5/) - Understand how insertion works, how inserted media behave within scenes, and how to control them via UI or code.
- [Import Media](https://img.ly/docs/cesdk/node-native/import-media-4e3703/) - Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK.
- [Export](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) - Explore export options, supported formats, and configuration features for sharing or rendering output.
- [Save](https://img.ly/docs/cesdk/node-native/export-save-publish/save-c8b124/) - Save design progress locally or to a backend service to allow for later editing or publishing.
- [Store Custom Metadata](https://img.ly/docs/cesdk/node-native/export-save-publish/store-custom-metadata-337248/) - Attach, retrieve, and manage custom key-value metadata on design blocks in CE.SDK.
- [Edit Image](https://img.ly/docs/cesdk/node-native/edit-image-c64912/) - Use CE.SDK to crop, transform, annotate, or enhance images with editing tools and programmatic APIs.
- [Create Videos](https://img.ly/docs/cesdk/node-native/create-video-c41a08/) - Learn how to create and customize videos in CE.SDK using scenes, assets, and time-based editing.
- [Text](https://img.ly/docs/cesdk/node-native/text-8a993a/) - Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools.
- [Create and Edit Shapes](https://img.ly/docs/cesdk/node-native/shapes-9f1b2c/) - Draw custom vector shapes, combine them with boolean operations, and insert QR codes into your designs.
- [Create and Edit Stickers](https://img.ly/docs/cesdk/node-native/stickers-3d4e5f/) - Create and customize stickers using image fills for icons, logos, emoji, and multi-color graphics.
- [Create Compositions](https://img.ly/docs/cesdk/node-native/create-composition-db709c/) - Combine and arrange multiple elements to create complex, multi-page, or layered design compositions.
- [Create Templates](https://img.ly/docs/cesdk/node-native/create-templates-3aef79/) - Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK.
- [Colors](https://img.ly/docs/cesdk/node-native/colors-a9b79c/) - Manage color usage in your designs, from applying brand palettes to handling print and screen formats.
- [Fills](https://img.ly/docs/cesdk/node-native/fills-402ddc/) - Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements.
- [Outlines](https://img.ly/docs/cesdk/node-native/outlines-b7820c/) - Enhance design elements with strokes, shadows, and glow effects to improve contrast and visual appeal.
- [Filters and Effects](https://img.ly/docs/cesdk/node-native/filters-and-effects-6f88ac/) - Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying.
- [Animation](https://img.ly/docs/cesdk/node-native/animation-ce900c/) - Add motion to designs with support for keyframes, timeline editing, and programmatic animation control.
- [Rules](https://img.ly/docs/cesdk/node-native/rules-1427c0/) - Define and enforce layout, branding, and safety rules to ensure consistent and compliant designs.
- [Conversion](https://img.ly/docs/cesdk/node-native/conversion-c3fbb3/) - Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools.
- [Improve Performance](https://img.ly/docs/cesdk/node-native/performance-3c12eb/) - Optimize CE.SDK server integration with code splitting, memory monitoring, export timeouts, and lifecycle best practices for Node.js.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "For Audio Processing"
description: "Learn how to export audio in WAV or MP4 format from any block type in CE.SDK for Node.js server environments."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/guides/export-save-publish/export/audio-68de25/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/node-native/export-save-publish/export-82f968/) > [For Audio Processing](https://img.ly/docs/cesdk/node-native/guides/export-save-publish/export/audio-68de25/)
---
Export audio from pages, video blocks, audio blocks, and tracks to WAV or MP4 format for external processing, transcription, or analysis in server-side Node.js applications.
The `exportAudio` API allows you to extract audio from any block that contains audio content. This is particularly useful when building server-side workflows that integrate with external audio processing services like speech-to-text transcription, audio enhancement, or music analysis platforms.
Audio can be exported from multiple block types:
- **Page blocks** - Export the complete mixed audio composition
- **Video blocks** - Extract audio tracks from videos
- **Audio blocks** - Export standalone audio content
- **Track blocks** - Export audio from specific tracks
## Export Audio
Export audio from any block using the `exportAudio` API:
```javascript
const page = engine.scene.getCurrentPage();
const audioBuffer = await engine.block.exportAudio(page, {
mimeType: 'audio/wav',
sampleRate: 48000,
numberOfChannels: 2
});
console.log(`Exported ${audioBuffer.byteLength} bytes`);
```
### Export Options
Configure your audio export with these options:
- **`mimeType`** - `'audio/wav'` (uncompressed) or `'audio/mp4'` (compressed AAC)
- **`sampleRate`** - Audio quality in Hz (default: 48000)
- **`numberOfChannels`** - 1 for mono or 2 for stereo
- **`timeOffset`** - Start time in seconds (default: 0)
- **`duration`** - Length to export in seconds (0 = entire duration)
- **`onProgress`** - Callback receiving `(rendered, encoded, total)` for progress tracking
## Find Audio Sources
To find blocks with audio in your scene:
```javascript
// Find audio blocks
const audioBlocks = engine.block.findByType('audio');
// Find video blocks with audio
const videoFills = engine.block.findByType('//ly.img.ubq/fill/video');
const videosWithAudio = videoFills.filter(block => {
try {
return engine.block.getAudioInfoFromVideo(block).length > 0;
} catch {
return false;
}
});
```
## Working with Multi-Track Video Audio
Videos can contain multiple audio tracks (e.g., different languages). CE.SDK provides APIs to inspect and extract specific tracks.
### Check audio track count
```javascript
const videoFillId = engine.block.findByType('//ly.img.ubq/fill/video')[0];
const trackCount = engine.block.getAudioTrackCountFromVideo(videoFillId);
console.log(`Video has ${trackCount} audio track(s)`);
```
### Get track information
```javascript
const audioTracks = engine.block.getAudioInfoFromVideo(videoFillId);
audioTracks.forEach((track, index) => {
console.log(`Track ${index}:`, {
channels: track.channels,
sampleRate: track.sampleRate,
language: track.language || 'unknown',
label: track.label || `Track ${index}`
});
});
```
### Extract a specific track
```javascript
// Create audio block from track 0 (first track)
const audioBlockId = engine.block.createAudioFromVideo(videoFillId, 0);
// Export just this track's audio
const trackAudioBuffer = await engine.block.exportAudio(audioBlockId, {
mimeType: 'audio/wav'
});
```
### Extract all tracks
```javascript
// Create audio blocks for all tracks
const audioBlockIds = engine.block.createAudiosFromVideo(videoFillId);
// Export each track
for (let i = 0; i < audioBlockIds.length; i++) {
const trackBuffer = await engine.block.exportAudio(audioBlockIds[i]);
console.log(`Track ${i}: ${trackBuffer.byteLength} bytes`);
}
```
## Complete Workflow: Audio to Captions
A common workflow is to export audio, send it to a transcription service, and use the returned captions in your scene.
### Step 1: Export Audio
```javascript
const page = engine.scene.getCurrentPage();
const audioBuffer = await engine.block.exportAudio(page, {
mimeType: 'audio/wav',
sampleRate: 48000,
numberOfChannels: 2
});
```
### Step 2: Send to Transcription Service
Send the audio to a service that returns SubRip (SRT) format captions:
```javascript
import FormData from 'form-data';
import fetch from 'node-fetch';
async function transcribeAudio(audioBuffer) {
const formData = new FormData();
formData.append('audio', Buffer.from(audioBuffer), {
filename: 'audio.wav',
contentType: 'audio/wav'
});
formData.append('format', 'srt');
const response = await fetch('https://api.transcription-service.com/transcribe', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
...formData.getHeaders()
},
body: formData
});
// Returns SRT format text
return await response.text();
}
const srtContent = await transcribeAudio(audioBuffer);
```
### Step 3: Import Captions from SRT
Use the built-in API to create caption blocks from the SRT response:
```javascript
import fs from 'fs/promises';
import path from 'path';
// Save SRT to temporary file
const tempPath = path.join(process.cwd(), 'temp-captions.srt');
await fs.writeFile(tempPath, srtContent);
// Import captions from file URI
const uri = `file://${tempPath}`;
const captions = await engine.block.createCaptionsFromURI(uri);
// Clean up temporary file
await fs.unlink(tempPath);
// Add captions to page
const page = engine.scene.getCurrentPage();
const captionTrack = engine.block.create('//ly.img.ubq/captionTrack');
captions.forEach(caption => {
engine.block.appendChild(captionTrack, caption);
});
engine.block.appendChild(page, captionTrack);
// Center the first caption as a reference point
engine.block.alignHorizontally([captions[0]], 'Center');
engine.block.alignVertically([captions[0]], 'Center');
```
### Other Processing Services
Audio export also supports these workflows:
- **Audio enhancement** - Noise removal, normalization
- **Music analysis** - Tempo, key, beat detection
- **Language detection** - Identify spoken language
- **Speaker diarization** - Identify who spoke when
## Save to file
Save exported audio to the file system:
```javascript
import fs from 'fs/promises';
async function saveAudio(audioBuffer, fileName = 'exported_audio.wav') {
await fs.writeFile(fileName, Buffer.from(audioBuffer));
console.log(`Audio saved to: ${fileName}`);
}
```
## Next Steps
Now that you understand audio export, explore related audio and video features:
- [Add Captions](https://img.ly/docs/cesdk/node-native/edit-video/add-captions-f67565/) - Learn how to create and sync caption blocks with audio content
- [Control Audio and Video](https://img.ly/docs/cesdk/node-native/create-video/control-daba54/) - Master time offset, duration, and playback controls for audio blocks
- [Trim Video Clips](https://img.ly/docs/cesdk/node-native/edit-video/trim-4f688b/) - Apply the same trim concepts to isolate audio segments
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Import Media"
description: "Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/import-media-4e3703/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/node-native/import-media-4e3703/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/node-native/import-media/overview-84bb23/) - Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK.
- [Asset Concepts](https://img.ly/docs/cesdk/node-native/import-media/concepts-5e6197/) - This guide explains the foundational architecture of the CE.SDK asset system, including what asset sources are, how they organize content, and how they connect to the user interface.
- [Import From Remote Source](https://img.ly/docs/cesdk/node-native/import-media/from-remote-source-b65faf/) - Connect CE.SDK to external sources like servers or third-party platforms to import assets remotely.
- [Edit or Remove Assets](https://img.ly/docs/cesdk/node-native/import-media/edit-or-remove-assets-ce072c/) - Manage assets in local asset sources by updating metadata, removing individual assets, or deleting entire sources in CE.SDK.
- [Source Sets](https://img.ly/docs/cesdk/node-native/import-media/source-sets-5679c8/) - Provide multiple versions of images and videos at different resolutions for optimal performance and quality across editing and export workflows.
- [Using Default Assets](https://img.ly/docs/cesdk/node-native/import-media/default-assets-d2763d/) - Load shapes, stickers, images, and other built-in assets from IMG.LY's CDN to populate your CE.SDK editor using the Asset API.
- [Retrieve MIME Type](https://img.ly/docs/cesdk/node-native/import-media/retrieve-mimetype-ed13bf/) - Detect the MIME type of resources loaded in the engine to determine file formats for processing, export, or display.
- [Asset Content JSON Schema](https://img.ly/docs/cesdk/node-native/import-media/content-json-schema-a7b3d2/) - Understand the JSON schema structure for defining asset source content including version, metadata, and payload properties for images, videos, fonts, and templates.
- [Supported File Formats for Import (Node.js)](https://img.ly/docs/cesdk/node-native/import-media/file-format-support-8cdc84/) - Review the supported image, video, audio, and template formats for importing assets into CE.SDK in Node.js environments.
- [Size Limits](https://img.ly/docs/cesdk/node-native/import-media/size-limits-c32275/) - Learn about file size restrictions and how to optimize large assets for use in CE.SDK.
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Refresh Assets"
description: "Trigger asset reloads to ensure the library reflects newly uploaded or updated items."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/import-media/asset-panel/refresh-assets-382060/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
---
Learn how to refresh asset sources when external changes occur outside CE.SDK in a server environment.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-import-media-asset-library-refresh-assets-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-import-media-asset-library-refresh-assets-server-js)
CE.SDK automatically refreshes the asset library for built-in operations like uploads and deletions. However, when assets are modified outside of CE.SDK—through a custom CMS, cloud storage, or third-party upload widget—the asset source won't reflect these changes automatically. Use `engine.asset.assetSourceContentsChanged()` to notify the engine and trigger a refresh.
```typescript file=@cesdk_web_examples/guides-import-media-asset-library-refresh-assets-server-js/server-js.ts reference-only
import CreativeEngine, { AssetsQueryResult } from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Refresh Assets
*
* Demonstrates how to refresh asset sources after external changes:
* - Creating custom asset sources
* - Simulating external uploads
* - Triggering asset refresh with assetSourceContentsChanged()
* - Handling external modifications and deletions
*/
// Simulated external data store (represents Cloudinary, S3, or external CMS)
const externalAssets = [
{
id: 'cloud-1',
url: 'https://img.ly/static/ubq_samples/sample_1.jpg',
name: 'Mountain Landscape'
},
{
id: 'cloud-2',
url: 'https://img.ly/static/ubq_samples/sample_2.jpg',
name: 'Ocean Sunset'
}
];
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// ===== Section 1: Register a Custom Asset Source =====
// Register a custom asset source that fetches from an external system
// This source will need manual refresh when external changes occur
engine.asset.addSource({
id: 'cloudinary-images',
async findAssets(queryData): Promise {
// Fetch current assets from external data store
const filteredAssets = externalAssets.filter(
(asset) =>
!queryData.query ||
asset.name.toLowerCase().includes(queryData.query.toLowerCase())
);
return {
assets: filteredAssets.map((asset) => ({
id: asset.id,
label: asset.name,
meta: {
uri: asset.url,
thumbUri: asset.url,
blockType: '//ly.img.ubq/graphic'
}
})),
total: filteredAssets.length,
currentPage: queryData.page,
nextPage: undefined
};
}
});
console.log('✓ Created custom asset source: cloudinary-images');
// ===== Section 2: Query Initial Assets =====
const initialAssets = await engine.asset.findAssets('cloudinary-images', {
page: 0,
perPage: 100
});
console.log(`Initial assets: ${initialAssets.assets.length}`);
for (const asset of initialAssets.assets) {
console.log(` - ${asset.id}: ${asset.label}`);
}
// ===== Section 3: Simulate External Upload =====
// Simulate an external upload (e.g., from Cloudinary upload widget)
// In a real application, this would be triggered by webhook or polling
const newAsset = {
id: 'cloud-3',
url: 'https://img.ly/static/ubq_samples/sample_3.jpg',
name: 'Forest Path'
};
externalAssets.push(newAsset);
// Notify CE.SDK that the source contents have changed
engine.asset.assetSourceContentsChanged('cloudinary-images');
console.log('✓ External upload complete, asset source refreshed');
// Verify the new asset is available
const afterUpload = await engine.asset.findAssets('cloudinary-images', {
page: 0,
perPage: 100
});
console.log(`Assets after upload: ${afterUpload.assets.length}`);
// ===== Section 4: Simulate External Modification =====
// Simulate backend modifications (e.g., CMS updates, API changes)
externalAssets[0] = {
...externalAssets[0],
name: 'Modified: Mountain Landscape'
};
// Refresh the asset library to reflect changes
engine.asset.assetSourceContentsChanged('cloudinary-images');
console.log('✓ External modification complete, asset source refreshed');
// Verify the modification
const afterModification = await engine.asset.findAssets('cloudinary-images', {
page: 0,
perPage: 100
});
console.log(
`First asset after modification: ${afterModification.assets[0].label}`
);
// ===== Section 5: Simulate External Deletion =====
// Simulate asset deletion from external system
const removed = externalAssets.pop();
console.log(`Removed asset from external store: ${removed?.name}`);
// Refresh the asset library to reflect the deletion
engine.asset.assetSourceContentsChanged('cloudinary-images');
console.log('✓ External deletion complete, asset source refreshed');
// Verify the deletion
const afterDeletion = await engine.asset.findAssets('cloudinary-images', {
page: 0,
perPage: 100
});
console.log(`Assets after deletion: ${afterDeletion.assets.length}`);
// ===== Add an Image to the Scene =====
// Use an asset from the source to create a block
const imageBlock = engine.block.create('graphic');
engine.block.setShape(imageBlock, engine.block.createShape('rect'));
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setWidth(imageBlock, 400);
engine.block.setHeight(imageBlock, 300);
engine.block.setPositionX(imageBlock, 200);
engine.block.setPositionY(imageBlock, 150);
engine.block.appendChild(page, imageBlock);
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/refresh-assets-result.png`, buffer);
console.log('✓ Exported result to output/refresh-assets-result.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
This guide covers when manual refresh is needed, how to trigger refreshes programmatically, and integration patterns for server-side asset management.
## When to Use Asset Refresh
CE.SDK handles asset refresh automatically for built-in operations. Manual refresh is required when external systems modify asset source content.
**Automatic refresh** (no action needed):
- Uploads using built-in sources like `ly.img.upload.*`
- Deletions through default upload handlers
- Modifications made through CE.SDK's asset APIs
**Manual refresh required**:
- External uploads via third-party services (Cloudinary, S3)
- Backend modifications through CMS or API updates
- Sync with external storage (Azure Blob, Google Cloud Storage)
- Real-time collaboration when another process adds assets
## Registering a Custom Asset Source
Before refreshing assets, you need a custom asset source that fetches from your external system. The `findAssets` method queries your external data store each time assets are requested.
```typescript highlight-custom-source
// Register a custom asset source that fetches from an external system
// This source will need manual refresh when external changes occur
engine.asset.addSource({
id: 'cloudinary-images',
async findAssets(queryData): Promise {
// Fetch current assets from external data store
const filteredAssets = externalAssets.filter(
(asset) =>
!queryData.query ||
asset.name.toLowerCase().includes(queryData.query.toLowerCase())
);
return {
assets: filteredAssets.map((asset) => ({
id: asset.id,
label: asset.name,
meta: {
uri: asset.url,
thumbUri: asset.url,
blockType: '//ly.img.ubq/graphic'
}
})),
total: filteredAssets.length,
currentPage: queryData.page,
nextPage: undefined
};
}
});
console.log('✓ Created custom asset source: cloudinary-images');
```
This custom source fetches assets from an external data store (simulating Cloudinary, S3, or a CMS). When the external store changes, subsequent queries won't reflect updates until you call `assetSourceContentsChanged()`.
## Refreshing After External Uploads
When your backend receives new uploads from a third-party service, call `assetSourceContentsChanged()` to notify CE.SDK that the source contents have changed.
```typescript highlight-external-upload
// Simulate an external upload (e.g., from Cloudinary upload widget)
// In a real application, this would be triggered by webhook or polling
const newAsset = {
id: 'cloud-3',
url: 'https://img.ly/static/ubq_samples/sample_3.jpg',
name: 'Forest Path'
};
externalAssets.push(newAsset);
// Notify CE.SDK that the source contents have changed
engine.asset.assetSourceContentsChanged('cloudinary-images');
console.log('✓ External upload complete, asset source refreshed');
```
The key is calling `assetSourceContentsChanged('cloudinary-images')` after the external upload completes. This tells CE.SDK to re-fetch assets on the next query.
## Refreshing After External Modifications
When your backend modifies asset metadata—renaming files, updating tags, or changing thumbnails—call `assetSourceContentsChanged()` to sync the asset source.
```typescript highlight-external-modification
// Simulate backend modifications (e.g., CMS updates, API changes)
externalAssets[0] = {
...externalAssets[0],
name: 'Modified: Mountain Landscape'
};
// Refresh the asset library to reflect changes
engine.asset.assetSourceContentsChanged('cloudinary-images');
console.log('✓ External modification complete, asset source refreshed');
```
Any modification to assets in your external store requires a refresh. Without calling `assetSourceContentsChanged()`, subsequent queries may return stale data.
## Refreshing After External Deletions
When assets are deleted from your external system, call `assetSourceContentsChanged()` to ensure queries no longer return deleted assets.
```typescript highlight-external-deletion
// Simulate asset deletion from external system
const removed = externalAssets.pop();
console.log(`Removed asset from external store: ${removed?.name}`);
// Refresh the asset library to reflect the deletion
engine.asset.assetSourceContentsChanged('cloudinary-images');
console.log('✓ External deletion complete, asset source refreshed');
```
The refresh ensures deleted assets no longer appear in query results. If you skip this step, the engine may reference assets that no longer exist.
## Integration Patterns
### Webhook Handler
Handle webhooks from external services to trigger refreshes:
```typescript
app.post('/webhooks/cloudinary', (req, res) => {
const { notification_type, public_id } = req.body;
if (notification_type === 'upload' || notification_type === 'delete') {
engine.asset.assetSourceContentsChanged('cloudinary-images');
}
res.status(200).send('OK');
});
```
### Scheduled Sync
For periodic synchronization with external systems:
```typescript
import cron from 'node-cron';
cron.schedule('*/5 * * * *', () => {
// Refresh every 5 minutes
engine.asset.assetSourceContentsChanged('external-assets');
});
```
### Event-Driven Updates
Listen for database changes to trigger refreshes:
```typescript
database.on('assets:changed', (sourceId) => {
engine.asset.assetSourceContentsChanged(sourceId);
});
```
## Troubleshooting
**Assets not updating**:
Verify the source ID passed to `assetSourceContentsChanged()` matches the ID used when registering the source with `addSource()`. Source IDs are case-sensitive.
**Refresh not triggering**:
Ensure you call `assetSourceContentsChanged()` after the external operation completes. If called before the upload finishes, `findAssets()` may return stale data.
**Stale assets in queries**:
Check that your `findAssets` implementation fetches fresh data on each call. Avoid caching responses unless you invalidate the cache when calling `assetSourceContentsChanged()`.
**Memory management**:
Always dispose the engine when done processing to free resources. Use try/finally blocks to ensure cleanup happens even when errors occur.
## API Reference
| Method | Category | Purpose |
|--------|----------|---------|
| `engine.asset.assetSourceContentsChanged(sourceID)` | Asset API | Notify engine that asset source contents changed |
| `engine.asset.addSource(source)` | Asset API | Register custom asset source |
| `engine.asset.findAssets(sourceID, query)` | Asset API | Query assets from a source |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Asset Concepts"
description: "This guide explains the foundational architecture of the CE.SDK asset system, including what asset sources are, how they organize content, and how they connect to the user interface."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/import-media/concepts-5e6197/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/node-native/import-media-4e3703/) > [Concepts](https://img.ly/docs/cesdk/node-native/import-media/concepts-5e6197/)
---
Understand the foundational architecture of CE.SDK's asset system and how asset sources organize content across platforms.
Asset sources are CE.SDK's content delivery architecture. Instead of hardcoding asset knowledge into the engine, CE.SDK uses a modular system where any content can be provided through a standardized interface. This decouples what assets are available from how they're discovered and applied.
```
CROSS-PLATFORM ENGINE (engine.asset API)
┌─────────────────────────────────────────────────────────────────────────┐
│ │
│ findAssets() addSource() addLocalSource() apply() │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Custom │ │ Local │ │ JSON-Based │ │
│ │ Sources │ │ Sources │ │ Sources │ │
│ ├──────────────┤ ├──────────────┤ ├──────────────┤ │
│ │ Your API │ │ User Uploads │ │ Built-in │ │
│ │ Database │ │ Collections │ │ Asset Packs │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Identical across Web, iOS, Android, and server environments │
└─────────────────────────────────────────────────────────────────────────┘
```
This guide covers the foundational concepts of asset sources. For implementation details, see the linked guides at the end.
## Asset Source Fundamentals
An asset source provides content to the engine through a common interface. Every source has a unique identifier (e.g., `ly.img.image`, `ly.img.sticker`) and implements methods for discovering and applying assets.
Sources support:
- **Query-based discovery** with pagination and filtering
- **Optional grouping** (e.g., sticker groups: "emoji", "doodle", "hand")
- **Metadata** including credits, licenses, and format information
Sources are content-agnostic—images, fonts, templates, and custom content all use the same pattern.
## Content Organized as Asset Sources
Asset sources handle virtually all reusable creative content:
| Category | Examples |
| ---------- | ---------------------------------------- |
| Media | Images, videos, audio clips |
| Graphics | Stickers, shapes, vectors, icons |
| Typography | Fonts, typefaces, text presets |
| Colors | Color palettes, spot colors |
| Effects | Blur types, filters, LUT effects |
| Templates | Design templates, page presets |
| Custom | User uploads, remote APIs, your own data |
Built-in sources include `ly.img.image`, `ly.img.sticker`, `ly.img.template`, `ly.img.typeface`, `ly.img.filter.lut`, `ly.img.blur`, `ly.img.effect`, and more.
## Types of Asset Sources
There are three ways to provide assets to CE.SDK:
### Custom Sources
Implement the source interface to connect any backend—database, API, or custom system. Custom sources provide full control over discovery and application logic. Use custom sources when you need to:
- Connect to your existing content management system
- Implement custom search or filtering logic
- Control how assets are applied to the scene
### Local Sources
Managed by the engine with dynamic add/remove operations. Local sources are suitable for runtime asset management or collections that change during processing. The engine handles storage and retrieval.
### JSON-Based Sources
Pre-defined asset collections loaded from JSON files. All built-in asset packs use this approach. JSON sources are ideal for static content that doesn't change frequently.
## Server Environment Considerations
In server-side rendering scenarios, asset sources operate identically to browser environments at the engine API level. The `engine.asset` API provides the same methods for registering sources, querying assets, and applying them to scenes.
Server environments typically use asset sources for:
- **Batch processing** — Loading templates and applying assets programmatically
- **Dynamic content generation** — Populating designs with content from databases or APIs
- **Automated workflows** — Processing user-uploaded assets through custom sources
Since server environments are headless, there's no UI layer to configure. You work directly with the engine's asset API to register sources, query assets, and apply them to scenes programmatically.
## Cross-Platform Architecture
Asset sources use the `engine.asset` API consistently across all platforms (Web, iOS, Android) and server environments.
All platforms support:
- Custom source registration
- JSON-based asset loading
- Local asset management
- Group-based organization
- Event subscriptions (source added, removed, updated)
Code patterns transfer directly between platforms with only syntax changes.
## Asset Structure
Each asset contains:
- **ID** — Unique identifier within the source
- **Meta** — URI, thumbnail, MIME type, dimensions, block type hints
- **Label** — Localized display name
- **Tags** — Searchable keywords (localized)
- **Groups** — Category membership
- **Context** — Source reference for tracking origin
The engine uses metadata hints (`blockType`, `fillType`, `shapeType`) to determine what block type to create when applying an asset.
## Discovery and Application
Assets are discovered through queries supporting pagination, text search, tag/group filtering, and sorting. When applied, assets either create new blocks or modify existing ones. Sources can customize application behavior or use the engine's default implementation.
## Source Lifecycle Events
The engine emits events when sources change: added, removed, or contents updated. Subscribe to these events to keep processing logic synchronized with available content.
## Troubleshooting
Common conceptual misunderstandings:
- **Expecting sources to filter themselves** — Sources return all matching assets; your code determines what to process.
- **Mixing source types** — Custom sources (your code), local sources (engine-managed), and JSON sources (static files) serve different purposes. Choose based on whether you need dynamic backend connections, runtime asset management, or static asset packs.
- **Not disposing the engine** — In server environments, always call `engine.dispose()` when finished to free resources.
## API Reference
| Method | Category | Purpose |
| ------------------------------------- | ----------------- | ----------------------------------------------------------------- |
| `engine.asset.addSource()` | Source Management | Register a custom asset source with discovery and apply callbacks |
| `engine.asset.addLocalSource()` | Source Management | Create an engine-managed source for dynamic asset add/remove |
| `engine.asset.findAssets()` | Discovery | Query assets with pagination, search, filtering, and sorting |
| `engine.asset.apply()` | Application | Apply an asset to the active scene, creating a configured block |
| `engine.asset.onAssetSourceAdded()` | Events | Subscribe to source registration events |
| `engine.asset.onAssetSourceRemoved()` | Events | Subscribe to source removal events |
| `engine.asset.onAssetSourceUpdated()` | Events | Subscribe to source content change events |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Asset Content JSON Schema"
description: "Understand the JSON schema structure for defining asset source content including version, metadata, and payload properties for images, videos, fonts, and templates."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/import-media/content-json-schema-a7b3d2/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/node-native/import-media-4e3703/) > [Asset Content JSON Schema](https://img.ly/docs/cesdk/node-native/import-media/content-json-schema-a7b3d2/)
---
Reference documentation for the JSON schema structure used to define asset source content in CE.SDK.
Asset content JSON files define the structure and metadata for assets that CE.SDK loads into asset sources. This schema supports images, videos, audio, fonts, templates, colors, shapes, and effects.
## Manifest Structure
Every `content.json` file requires three top-level fields:
```json
{
"version": "2.0.0",
"id": "my.custom.source",
"assets": []
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `version` | `string` | Yes | Schema version |
| `id` | `string` | Yes | Unique identifier for the asset source |
| `assets` | `AssetDefinition[]` | Yes | Array of asset definitions |
## Asset Definition
Each asset in the `assets` array follows this structure:
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | `string` | Yes | Unique identifier within the source |
| `label` | `Record` | No | Localized display names for UI and tooltips |
| `tags` | `Record` | No | Localized keywords for search and filtering |
| `groups` | `string[]` | No | Categories for grouping assets in the UI |
| `meta` | `AssetMetaData` | No | Content-specific metadata |
| `payload` | `AssetPayload` | No | Structured data for specialized assets |
### Localization
Labels and tags use locale codes as keys (e.g., `"en"`, `"de"`, `"fr"`). CE.SDK selects the appropriate translation based on the user's locale.
```json
{
"id": "mountain-photo",
"label": {
"en": "Mountain Landscape",
"de": "Berglandschaft"
},
"tags": {
"en": ["nature", "mountain"],
"de": ["natur", "berg"]
},
"groups": ["landscapes", "nature"]
}
```
## Asset Metadata
The `meta` object contains content-specific information for loading and applying assets.
### Content Properties
Define URIs and file information for loading the asset content. The `uri` property points to the main asset file, while `thumbUri` and `previewUri` provide optimized versions for UI display.
```json
{
"meta": {
"uri": "{{base_url}}/images/photo.jpg",
"thumbUri": "{{base_url}}/thumbnails/photo-thumb.jpg",
"previewUri": "{{base_url}}/previews/photo-preview.jpg",
"filename": "photo.jpg",
"mimeType": "image/jpeg"
}
}
```
| Property | Type | Description |
|----------|------|-------------|
| `uri` | `string` | Primary content URI. Supports `{{base_url}}` placeholder |
| `thumbUri` | `string` | Thumbnail image URI for previews |
| `previewUri` | `string` | Higher-quality preview URI |
| `filename` | `string` | Original filename |
| `mimeType` | `string` | MIME type (e.g., `"image/jpeg"`, `"video/mp4"`) |
### Dimension Properties
Specify the pixel dimensions of the asset. CE.SDK uses these values for layout calculations and aspect ratio preservation when inserting assets into a design.
```json
{
"meta": {
"width": 1920,
"height": 1280
}
}
```
| Property | Type | Description |
|----------|------|-------------|
| `width` | `number` | Content width in pixels |
| `height` | `number` | Content height in pixels |
### Block Creation Properties
Control what design block CE.SDK creates when the asset is applied. These properties determine how the asset integrates into the design structure.
```json
{
"meta": {
"blockType": "//ly.img.ubq/graphic",
"fillType": "//ly.img.ubq/fill/image",
"shapeType": "//ly.img.ubq/shape/rect",
"kind": "image"
}
}
```
| Property | Type | Description |
|----------|------|-------------|
| `blockType` | `string` | Design block type to create |
| `fillType` | `string` | Fill type for the block |
| `shapeType` | `string` | Shape type for stickers/shapes |
| `kind` | `string` | Asset category hint (e.g., `"image"`, `"video"`, `"template"`) |
**Block Type Values:**
| Value | Use Case |
|-------|----------|
| `//ly.img.ubq/graphic` | Images, stickers, graphics |
| `//ly.img.ubq/text` | Text blocks |
| `//ly.img.ubq/audio` | Audio clips |
| `//ly.img.ubq/page` | Templates, pages |
| `//ly.img.ubq/group` | Grouped elements |
| `//ly.img.ubq/cutout` | Cutout shapes |
**Fill Type Values:**
| Value | Use Case |
|-------|----------|
| `//ly.img.ubq/fill/image` | Image fills |
| `//ly.img.ubq/fill/video` | Video fills |
| `//ly.img.ubq/fill/color` | Solid color fills |
| `//ly.img.ubq/fill/gradient/linear` | Linear gradients |
| `//ly.img.ubq/fill/gradient/radial` | Radial gradients |
| `//ly.img.ubq/fill/gradient/conical` | Conical gradients |
**Shape Type Values:**
| Value | Use Case |
|-------|----------|
| `//ly.img.ubq/shape/rect` | Rectangles |
| `//ly.img.ubq/shape/ellipse` | Circles, ovals |
| `//ly.img.ubq/shape/polygon` | Polygons |
| `//ly.img.ubq/shape/star` | Star shapes |
| `//ly.img.ubq/shape/line` | Lines |
| `//ly.img.ubq/shape/vector_path` | Custom vector paths |
### Media Properties
Configure playback behavior for time-based media like video and audio. Use `duration` to specify length and `looping` to enable repeat playback for background music or ambient video.
```json
{
"meta": {
"duration": "30",
"looping": true,
"vectorPath": "M10 10 L90 90"
}
}
```
| Property | Type | Description |
|----------|------|-------------|
| `duration` | `string` | Duration in seconds as a string (e.g., `"30"`, `"120"`) |
| `looping` | `boolean` | Whether media should loop continuously. Use for background music or ambient video |
| `vectorPath` | `string` | SVG path data for vector shapes |
### Effect Properties
Define visual effects that can be applied to design blocks. Effects include filters, blurs, and color adjustments.
```json
{
"meta": {
"effectType": "//ly.img.ubq/effect/lut_filter",
"blurType": "//ly.img.ubq/blur/uniform"
}
}
```
| Property | Type | Description |
|----------|------|-------------|
| `effectType` | `string` | Effect type (e.g., `"//ly.img.ubq/effect/lut_filter"`, `"//ly.img.ubq/effect/duotone_filter"`) |
| `blurType` | `string` | Blur type: `"//ly.img.ubq/blur/uniform"`, `"//ly.img.ubq/blur/linear"`, `"//ly.img.ubq/blur/mirrored"`, `"//ly.img.ubq/blur/radial"` |
### Responsive Sources
The `sourceSet` property defines multiple resolutions for responsive loading. This enables CE.SDK to load an appropriately sized image based on the display context, reducing bandwidth for thumbnails while providing full resolution when needed.
```json
{
"meta": {
"sourceSet": [
{ "uri": "{{base_url}}/small.jpg", "width": 640, "height": 480 },
{ "uri": "{{base_url}}/medium.jpg", "width": 1280, "height": 960 },
{ "uri": "{{base_url}}/large.jpg", "width": 1920, "height": 1440 }
]
}
}
```
When a user browses assets in the library panel, CE.SDK loads the smallest appropriate resolution. When the asset is added to the canvas and zoomed in, higher resolutions are loaded on demand. This pattern significantly improves initial load times for asset libraries with many items.
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `uri` | `string` | Yes | Source URI |
| `width` | `number` | Yes | Source width in pixels |
| `height` | `number` | Yes | Source height in pixels |
## Asset Payload
The `payload` object contains structured data for specialized asset types like colors, fonts, and presets.
| Property | Type | Description |
|----------|------|-------------|
| `color` | `AssetColor` | Color definition |
| `typeface` | `Typeface` | Font family definition |
| `transformPreset` | `AssetTransformPreset` | Page size or aspect ratio preset |
| `sourceSet` | `Source[]` | Responsive sources (same as meta.sourceSet) |
### Color Payload
Colors support three color spaces: sRGB, CMYK, and Spot Color. Use sRGB for screen-based designs, CMYK for print workflows, and Spot Color for brand-specific colors that require exact color matching.
**sRGB Color:**
```json
{
"payload": {
"color": {
"colorSpace": "sRGB",
"r": 0.2,
"g": 0.4,
"b": 0.8
}
}
}
```
sRGB is the standard color space for web and digital displays. Component values range from 0 to 1, where `{ r: 1, g: 0, b: 0 }` represents pure red.
| Property | Type | Range | Description |
|----------|------|-------|-------------|
| `colorSpace` | `"sRGB"` | — | Color space identifier |
| `r` | `number` | 0–1 | Red component |
| `g` | `number` | 0–1 | Green component |
| `b` | `number` | 0–1 | Blue component |
**CMYK Color:**
```json
{
"payload": {
"color": {
"colorSpace": "CMYK",
"c": 0.75,
"m": 0.25,
"y": 0.0,
"k": 0.1
}
}
}
```
CMYK is used for print production. Component values represent ink percentages from 0 to 1, where higher values mean more ink coverage.
| Property | Type | Range | Description |
|----------|------|-------|-------------|
| `colorSpace` | `"CMYK"` | — | Color space identifier |
| `c` | `number` | 0–1 | Cyan component |
| `m` | `number` | 0–1 | Magenta component |
| `y` | `number` | 0–1 | Yellow component |
| `k` | `number` | 0–1 | Black (key) component |
**Spot Color:**
```json
{
"payload": {
"color": {
"colorSpace": "SpotColor",
"name": "Pantone 286 C",
"externalReference": "pantone://286-c",
"representation": {
"colorSpace": "sRGB",
"r": 0.0,
"g": 0.22,
"b": 0.62
}
}
}
}
```
Spot colors reference named colors from systems like Pantone. The `representation` provides a screen preview while the actual color is defined by the external reference for accurate print reproduction.
| Property | Type | Description |
|----------|------|-------------|
| `colorSpace` | `"SpotColor"` | Color space identifier |
| `name` | `string` | Spot color name |
| `externalReference` | `string` | External reference URI |
| `representation` | `AssetRGBColor \| AssetCMYKColor` | Screen/print representation |
### Typeface Payload
Defines a font family with multiple font files for different weights and styles. This enables CE.SDK to load the correct font file when text formatting changes.
```json
{
"payload": {
"typeface": {
"name": "Roboto",
"fonts": [
{
"uri": "{{base_url}}/Roboto-Regular.ttf",
"subFamily": "Regular",
"weight": "normal",
"style": "normal"
},
{
"uri": "{{base_url}}/Roboto-Bold.ttf",
"subFamily": "Bold",
"weight": "bold",
"style": "normal"
},
{
"uri": "{{base_url}}/Roboto-Italic.ttf",
"subFamily": "Italic",
"weight": "normal",
"style": "italic"
}
]
}
}
}
```
Each font entry in the `fonts` array represents a single font file. When a user applies bold formatting, CE.SDK automatically selects the font entry with `weight: "bold"`. Include all weight and style combinations you want to support.
**Typeface Properties:**
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `name` | `string` | Yes | Typeface family name |
| `fonts` | `Font[]` | Yes | Array of font definitions |
**Font Properties:**
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `uri` | `string` | Yes | Font file URI (.ttf, .otf, .woff, .woff2) |
| `subFamily` | `string` | Yes | Font subfamily name (e.g., "Regular", "Bold Italic") |
| `weight` | `FontWeight` | No | Font weight |
| `style` | `FontStyle` | No | Font style |
**Font Weight Values:** `"thin"`, `"extraLight"`, `"light"`, `"normal"`, `"medium"`, `"semiBold"`, `"bold"`, `"extraBold"`, `"heavy"`
**Font Style Values:** `"normal"`, `"italic"`
### Transform Preset Payload
Defines page size or aspect ratio presets for templates, canvases, and crop tools. Use these to provide users with common format options like social media dimensions or print sizes.
**Fixed Size:**
```json
{
"payload": {
"transformPreset": {
"type": "FixedSize",
"width": 1080,
"height": 1920,
"designUnit": "Pixel"
}
}
}
```
Fixed size presets lock both width and height to specific values. Use `designUnit` to specify whether dimensions are in pixels (for digital), millimeters, or inches (for print).
| Property | Type | Description |
|----------|------|-------------|
| `type` | `"FixedSize"` | Preset type |
| `width` | `number` | Width value |
| `height` | `number` | Height value |
| `designUnit` | `string` | Unit: `"Pixel"`, `"Millimeter"`, or `"Inch"` |
**Fixed Aspect Ratio:**
```json
{
"payload": {
"transformPreset": {
"type": "FixedAspectRatio",
"width": 16,
"height": 9
}
}
}
```
Fixed aspect ratio presets maintain proportions while allowing flexible sizing. The width and height values represent the ratio components, not pixel dimensions.
| Property | Type | Description |
|----------|------|-------------|
| `type` | `"FixedAspectRatio"` | Preset type |
| `width` | `number` | Aspect ratio width component |
| `height` | `number` | Aspect ratio height component |
**Free Aspect Ratio:**
```json
{
"payload": {
"transformPreset": {
"type": "FreeAspectRatio"
}
}
}
```
Free aspect ratio presets allow unrestricted resizing without maintaining proportions.
## Base URL Placeholder
The `{{base_url}}` placeholder enables portable asset definitions. CE.SDK replaces this placeholder with the actual base path when loading:
- **From URL:** The parent directory of the JSON file becomes the base URL
- **From string:** You provide the base URL explicitly when loading
```json
{
"meta": {
"uri": "{{base_url}}/images/photo.jpg",
"thumbUri": "{{base_url}}/thumbnails/photo.jpg"
}
}
```
## Asset Type Examples
### Image Asset
Standard image assets are the most common type, used for photos, illustrations, and background images. They require a `blockType` of graphic with an image fill.
```json
{
"id": "photo-001",
"label": { "en": "Mountain Landscape" },
"tags": { "en": ["nature", "mountain"] },
"meta": {
"uri": "{{base_url}}/mountain.jpg",
"thumbUri": "{{base_url}}/mountain-thumb.jpg",
"mimeType": "image/jpeg",
"blockType": "//ly.img.ubq/graphic",
"fillType": "//ly.img.ubq/fill/image",
"width": 1920,
"height": 1280
}
}
```
### Video Asset
Video assets include duration information and use a video fill type. Set `looping` to `true` for videos that should repeat continuously.
```json
{
"id": "video-001",
"label": { "en": "Intro Animation" },
"meta": {
"uri": "{{base_url}}/intro.mp4",
"thumbUri": "{{base_url}}/intro-thumb.jpg",
"mimeType": "video/mp4",
"blockType": "//ly.img.ubq/graphic",
"fillType": "//ly.img.ubq/fill/video",
"width": 1920,
"height": 1080,
"duration": "5",
"looping": false
}
}
```
### Audio Asset
Audio assets use the audio block type and don't require visual dimensions. Set `looping` to `true` for background music that should repeat continuously throughout the design.
```json
{
"id": "audio-001",
"label": { "en": "Background Music" },
"meta": {
"uri": "{{base_url}}/music.mp3",
"mimeType": "audio/mpeg",
"blockType": "//ly.img.ubq/audio",
"duration": "120",
"looping": true
}
}
```
### Sticker Asset
Stickers are vector graphics that maintain quality at any size. They use the `vector_path` shape type and typically reference SVG files.
```json
{
"id": "sticker-001",
"label": { "en": "Star Badge" },
"meta": {
"uri": "{{base_url}}/star.svg",
"thumbUri": "{{base_url}}/star-thumb.png",
"mimeType": "image/svg+xml",
"blockType": "//ly.img.ubq/graphic",
"shapeType": "//ly.img.ubq/shape/vector_path",
"width": 200,
"height": 200
}
}
```
### Template Asset
Templates are complete design scenes that can be loaded as starting points. Use `kind: "template"` to identify them in the UI.
```json
{
"id": "template-001",
"label": { "en": "Social Media Story" },
"meta": {
"uri": "{{base_url}}/story-template.scene",
"thumbUri": "{{base_url}}/story-thumb.jpg",
"kind": "template",
"width": 1080,
"height": 1920
}
}
```
### Crop Preset Asset
Crop presets define aspect ratios for the crop tool. Use `transformPreset` in the payload to specify the ratio without fixed pixel dimensions.
```json
{
"id": "crop-square",
"label": { "en": "Square" },
"groups": ["social"],
"payload": {
"transformPreset": {
"type": "FixedAspectRatio",
"width": 1,
"height": 1
}
}
}
```
### Page Format Preset Asset
Page format presets define canvas sizes for new designs. Use `FixedSize` to specify exact dimensions in pixels, millimeters, or inches.
```json
{
"id": "format-instagram-story",
"label": { "en": "Instagram Story" },
"groups": ["social"],
"meta": {
"thumbUri": "{{base_url}}/instagram-story-thumb.jpg"
},
"payload": {
"transformPreset": {
"type": "FixedSize",
"width": 1080,
"height": 1920,
"designUnit": "Pixel"
}
}
}
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Assets not appearing | Verify `version`, `id`, and `assets` fields exist at the top level |
| Invalid asset | Ensure each asset has a unique `id` |
| Missing thumbnails | Check `thumbUri` points to accessible image URLs |
| Base URL not resolving | Use exact `{{base_url}}` syntax (double curly braces) |
| CORS errors | Configure server headers to allow cross-origin requests |
| Wrong block created | Verify `meta.blockType` matches the intended design block |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Using Default Assets"
description: "Load shapes, stickers, images, and other built-in assets from IMG.LY's CDN to populate your CE.SDK editor using the Asset API."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/import-media/default-assets-d2763d/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/node-native/import-media-4e3703/) > [Using Default Assets](https://img.ly/docs/cesdk/node-native/import-media/default-assets-d2763d/)
---
```typescript file=@cesdk_web_examples/guides-import-media-default-assets-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { writeFile } from 'fs/promises';
import { config } from 'dotenv';
config();
/**
* CE.SDK Server Guide: Using Default Assets
*
* Demonstrates loading all asset sources from IMG.LY's CDN using
* addLocalAssetSourceFromJSONURI and creating a scene with
* a star shape, sticker, and image.
*/
async function main(): Promise {
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
try {
// Versioned CDN URLs using the SDK package (recommended)
// For production, self-host these assets - see the Serve Assets guide
const PACKAGE_BASE = `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`;
const DEFAULT_ASSETS_URL = `${PACKAGE_BASE}/v4/`;
const DEMO_ASSETS_URL = `${PACKAGE_BASE}/demo/v3/`;
// Load default asset sources (core editor components)
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.sticker/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.vector.shape/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.color.palette/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.filter/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.effect/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.blur/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.typeface/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.crop.presets/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.page.presets/content.json`
);
// Load demo asset sources (sample content for testing)
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEMO_ASSETS_URL}ly.img.image/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEMO_ASSETS_URL}ly.img.video/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEMO_ASSETS_URL}ly.img.audio/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEMO_ASSETS_URL}ly.img.templates/content.json`
);
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEMO_ASSETS_URL}ly.img.text.components/content.json`
);
// List registered asset sources
const sources = engine.asset.findAllSources();
// eslint-disable-next-line no-console
console.log('Registered asset sources:', sources);
// Create a scene with a page
const PAGE_WIDTH = 800;
const PAGE_HEIGHT = 600;
engine.scene.create('VerticalStack', {
page: { size: { width: PAGE_WIDTH, height: PAGE_HEIGHT } }
});
const pages = engine.block.findByType('page');
const page = pages[0];
if (page == null) {
throw new Error('No page found in scene');
}
// Define the three assets to add: star shape, sticker, and image
const assetsToAdd = [
{
sourceId: 'ly.img.vector.shape',
assetId: '//ly.img.ubq/shapes/star/filled'
},
{
sourceId: 'ly.img.sticker',
assetId: '//ly.img.cesdk.stickers.emoticons/alien'
},
{
sourceId: 'ly.img.image',
assetId: 'ly.img.cesdk.images.samples/sample.1'
}
];
// Calculate layout for 3 centered blocks
const blockSize = Math.min(PAGE_WIDTH, PAGE_HEIGHT) * 0.2;
const spacing = blockSize * 0.3;
const totalWidth =
assetsToAdd.length * blockSize + (assetsToAdd.length - 1) * spacing;
const startX = (PAGE_WIDTH - totalWidth) / 2;
const centerY = (PAGE_HEIGHT - blockSize) / 2;
// Create and position each block
for (let i = 0; i < assetsToAdd.length; i++) {
const { sourceId, assetId } = assetsToAdd[i];
const asset = await engine.asset.fetchAsset(sourceId, assetId);
if (asset != null) {
const block = await engine.asset.apply(sourceId, asset);
if (block != null) {
engine.block.setWidth(block, blockSize);
engine.block.setHeight(block, blockSize);
engine.block.setPositionX(block, startX + i * (blockSize + spacing));
engine.block.setPositionY(block, centerY);
}
}
}
// Export the scene as PNG
const pngBlob = await engine.block.export(page, { mimeType: 'image/png' });
const pngBuffer = Buffer.from(await pngBlob.arrayBuffer());
await writeFile('output/scene.png', pngBuffer);
// eslint-disable-next-line no-console
console.log('Exported scene to output/scene.png');
} finally {
engine.dispose();
}
}
main().catch(console.error);
```
Load all asset sources from IMG.LY's CDN to populate your CE.SDK engine with shapes, stickers, filters, effects, fonts, images, and other media for server-side rendering.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-import-media-default-assets-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-import-media-default-assets-server-js)
CE.SDK provides built-in asset sources for shapes, stickers, filters, effects, fonts, and sample media. This guide demonstrates loading all available asset sources from IMG.LY's CDN and applying them to create a scene with a star shape, a sticker, and an image, then exporting to PNG.
> **Production Deployment:** The IMG.LY CDN is for development and prototyping only. For production, download and self-host assets from your own server. See the [Serve Assets](https://img.ly/docs/cesdk/node-native/serve-assets-b0827c/) guide for instructions.
## What Are Default and Demo Assets?
IMG.LY provides two categories of asset sources hosted on the IMG.LY CDN for development and prototyping:
**Default Assets** are core editor components:
| Source ID | Description |
|-----------|-------------|
| `ly.img.sticker` | Emojis, emoticons, decorations |
| `ly.img.vectorpath` | Shapes: stars, arrows, polygons |
| `ly.img.colors.defaultPalette` | Default color palette |
| `ly.img.filter.lut` | LUT-based color filters |
| `ly.img.filter.duotone` | Duotone color effects |
| `ly.img.effect` | Visual effects |
| `ly.img.blur` | Blur effects |
| `ly.img.typeface` | Font families |
| `ly.img.crop.presets` | Crop presets |
| `ly.img.page.presets` | Page size presets |
| `ly.img.page.presets.video` | Video page presets |
**Demo Assets** are sample content for development:
| Source ID | Description |
|-----------|-------------|
| `ly.img.image` | Sample images |
| `ly.img.video` | Sample videos |
| `ly.img.audio` | Sample audio tracks |
| `ly.img.template` | Design templates |
| `ly.img.video.template` | Video templates |
| `ly.img.textComponents` | Text component presets |
## Loading Assets from URL
Use `addLocalAssetSourceFromJSONURI()` to load an asset source directly from a JSON URL:
```typescript
const baseURL = `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets/v4/`;
await engine.asset.addLocalAssetSourceFromJSONURI(
`${baseURL}ly.img.vectorpath/content.json`
);
```
## Initialize the Engine
Create the Creative Engine instance:
```typescript highlight-init
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE
});
```
## Versioned CDN URLs
Use the SDK version to construct versioned CDN URLs. This ensures assets are compatible with your SDK version. For production deployments, see the [Serve Assets](https://img.ly/docs/cesdk/node-native/serve-assets-b0827c/) guide to self-host assets.
```typescript highlight-cdn-urls
// Versioned CDN URLs using the SDK package (recommended)
// For production, self-host these assets - see the Serve Assets guide
const PACKAGE_BASE = `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets`;
const DEFAULT_ASSETS_URL = `${PACKAGE_BASE}/v4/`;
const DEMO_ASSETS_URL = `${PACKAGE_BASE}/demo/v3/`;
```
## Loading Default Asset Sources
Load a default asset source from the CDN. Repeat this pattern for each source you need:
```typescript highlight-load-default-assets
// Load default asset sources (core editor components)
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEFAULT_ASSETS_URL}ly.img.sticker/content.json`
);
```
## Loading Demo Asset Sources
Load a demo asset source from the CDN. Repeat this pattern for each source you need:
```typescript highlight-load-demo-assets
// Load demo asset sources (sample content for testing)
await engine.asset.addLocalAssetSourceFromJSONURI(
`${DEMO_ASSETS_URL}ly.img.image/content.json`
);
```
## Exporting the Scene
Export the scene as a PNG file:
```typescript highlight-export
// Export the scene as PNG
const pngBlob = await engine.block.export(page, { mimeType: 'image/png' });
const pngBuffer = Buffer.from(await pngBlob.arrayBuffer());
await writeFile('output/scene.png', pngBuffer);
// eslint-disable-next-line no-console
console.log('Exported scene to output/scene.png');
```
## Cleanup
Always dispose of the engine when finished:
```typescript highlight-cleanup
engine.dispose();
```
## Filtering Assets with Matcher
Use the `matcher` option to load only specific assets from a source:
```typescript
const baseURL = `https://cdn.img.ly/packages/imgly/cesdk-node/${CreativeEngine.version}/assets/v4/`;
// Load only star and arrow shapes
await engine.asset.addLocalAssetSourceFromJSONURI(
`${baseURL}ly.img.vectorpath/content.json`,
{ matcher: ['*star*', '*arrow*'] }
);
// Load only emoji stickers
await engine.asset.addLocalAssetSourceFromJSONURI(
`${baseURL}ly.img.sticker/content.json`,
{ matcher: ['*emoji*'] }
);
```
An asset is included if it matches ANY pattern in the array. Patterns support `*` wildcards.
## API Reference
| Method | Description |
|--------|-------------|
| `engine.asset.addLocalAssetSourceFromJSONURI(contentURI, options?)` | Load an asset source from a JSON URL |
| `engine.asset.fetchAsset(sourceId, assetId)` | Fetch a specific asset by ID |
| `engine.asset.apply(sourceId, asset)` | Apply an asset to create a block |
**Parameters for `addLocalAssetSourceFromJSONURI`:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `contentURI` | `string` | Full URL to the content.json file |
| `options.matcher` | `string[]` | Optional wildcard patterns to filter assets |
**Returns:** `Promise` — The asset source ID from the JSON
## Next Steps
- [Serve Assets](https://img.ly/docs/cesdk/node-native/serve-assets-b0827c/) — Self-host assets for production deployments
- [Import From Remote Source](https://img.ly/docs/cesdk/node-native/import-media/from-remote-source-b65faf/) — Load assets from external URLs
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Edit or Remove Assets"
description: "Manage assets in local asset sources by updating metadata, removing individual assets, or deleting entire sources in CE.SDK."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/import-media/edit-or-remove-assets-ce072c/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/node-native/import-media-4e3703/) > [Edit or Remove Assets](https://img.ly/docs/cesdk/node-native/import-media/edit-or-remove-assets-ce072c/)
---
Manage assets in local asset sources programmatically on the server by updating metadata, removing individual assets, or deleting entire sources.
> **Reading time:** 10 minutes
>
> **Resources:**
>
> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)
>
> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-import-media-edit-or-remove-assets-server-js)
>
> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-import-media-edit-or-remove-assets-server-js)
Assets in local sources can be modified or removed programmatically in server-side workflows. CE.SDK provides methods to query, update, and remove assets, as well as entire asset sources. This guide demonstrates how to manage assets in headless mode for batch processing, cleanup tasks, and automated workflows.
```typescript file=@cesdk_web_examples/guides-import-media-edit-or-remove-assets-server-js/server-js.ts reference-only
import CreativeEngine from '@cesdk/node';
import { config } from 'dotenv';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
// Load environment variables
config();
/**
* CE.SDK Server Guide: Edit or Remove Assets
*
* Demonstrates how to manage assets in local asset sources:
* - Adding assets to local sources
* - Finding and querying assets
* - Updating asset metadata
* - Removing individual assets
* - Removing entire asset sources
* - Handling asset source events
*/
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
try {
// Create a design scene
engine.scene.create('VerticalStack', {
page: { size: { width: 800, height: 600 } }
});
const page = engine.block.findByType('page')[0];
// ===== Section 1: Create a Local Asset Source =====
// Create a local asset source to manage images
engine.asset.addLocalSource('server-uploads');
// Add sample assets to the source
engine.asset.addAssetToSource('server-uploads', {
id: 'asset-1',
label: { en: 'Sample Image 1' },
tags: { en: ['sample', 'image'] },
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_1.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
engine.asset.addAssetToSource('server-uploads', {
id: 'asset-2',
label: { en: 'Sample Image 2' },
tags: { en: ['sample', 'photo'] },
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_2.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
engine.asset.addAssetToSource('server-uploads', {
id: 'asset-3',
label: { en: 'Sample Image 3' },
tags: { en: ['sample', 'landscape'] },
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_3.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
console.log('✓ Created local source with 3 assets');
// ===== Section 2: Find Assets in a Source =====
// Query assets from the source to find specific assets
const result = await engine.asset.findAssets('server-uploads', {
query: 'sample',
page: 0,
perPage: 100
});
console.log('Found assets:', result.assets.length);
for (const asset of result.assets) {
console.log(` - ${asset.id}: ${asset.label}`);
}
// ===== Section 3: Update Asset Metadata =====
// To update an asset's metadata, remove it and add an updated version
engine.asset.removeAssetFromSource('server-uploads', 'asset-1');
// Add the updated version with new metadata
engine.asset.addAssetToSource('server-uploads', {
id: 'asset-1',
label: { en: 'Updated Sample Image' }, // New label
tags: { en: ['sample', 'image', 'updated'] }, // New tags
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_1.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
console.log('✓ Updated asset-1 metadata');
// ===== Section 4: Remove an Asset from a Source =====
// Remove a single asset from the source
// The asset is permanently deleted from the source
engine.asset.removeAssetFromSource('server-uploads', 'asset-2');
console.log('✓ Removed asset-2 from server-uploads');
// Verify the asset was removed
const afterRemove = await engine.asset.findAssets('server-uploads', {
page: 0,
perPage: 100
});
console.log('Remaining assets:', afterRemove.assets.length);
// ===== Section 5: Notify UI of Changes =====
// After modifying assets, notify any connected UI components
// In server context, this ensures any connected clients are updated
engine.asset.assetSourceContentsChanged('server-uploads');
console.log('✓ Notified UI of source changes');
// ===== Section 6: Create a Temporary Source =====
// Create a temporary source that will be removed
engine.asset.addLocalSource('temp-source');
engine.asset.addAssetToSource('temp-source', {
id: 'temp-asset',
label: { en: 'Temporary Asset' },
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_4.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
console.log('✓ Created temporary source with 1 asset');
// ===== Section 7: Remove an Entire Asset Source =====
// Remove a complete asset source and all its assets
engine.asset.removeSource('temp-source');
console.log('✓ Removed temp-source and all its assets');
// ===== Section 8: Listen to Asset Source Events =====
// Subscribe to lifecycle events for asset sources
const unsubscribeAdded = engine.asset.onAssetSourceAdded((sourceId) => {
console.log(`Event: Source added - ${sourceId}`);
});
const unsubscribeRemoved = engine.asset.onAssetSourceRemoved((sourceId) => {
console.log(`Event: Source removed - ${sourceId}`);
});
const unsubscribeUpdated = engine.asset.onAssetSourceUpdated((sourceId) => {
console.log(`Event: Source updated - ${sourceId}`);
});
// Demonstrate events
engine.asset.addLocalSource('event-demo');
engine.asset.assetSourceContentsChanged('event-demo');
engine.asset.removeSource('event-demo');
// Clean up subscriptions
unsubscribeAdded();
unsubscribeRemoved();
unsubscribeUpdated();
// ===== Add an Image to the Scene =====
// Use an asset from the remaining source to create a block
const imageBlock = engine.block.create('graphic');
engine.block.setShape(imageBlock, engine.block.createShape('rect'));
const imageFill = engine.block.createFill('image');
engine.block.setString(
imageFill,
'fill/image/imageFileURI',
'https://img.ly/static/ubq_samples/sample_1.jpg'
);
engine.block.setFill(imageBlock, imageFill);
engine.block.setWidth(imageBlock, 400);
engine.block.setHeight(imageBlock, 300);
engine.block.setPositionX(imageBlock, 200);
engine.block.setPositionY(imageBlock, 150);
engine.block.appendChild(page, imageBlock);
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/edit-or-remove-assets-result.png`, buffer);
console.log('✓ Exported result to output/edit-or-remove-assets-result.png');
} finally {
// Always dispose the engine to free resources
engine.dispose();
}
```
## Setup
Initialize the headless CE.SDK engine for server-side processing. The engine runs without a UI, making it suitable for automated asset management.
```typescript highlight=highlight-setup
// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});
```
## Creating a Local Asset Source
Create a local source and populate it with assets. Use `engine.asset.addLocalSource()` to create the source, then `engine.asset.addAssetToSource()` to add assets.
```typescript highlight=highlight-create-source
// Create a local asset source to manage images
engine.asset.addLocalSource('server-uploads');
// Add sample assets to the source
engine.asset.addAssetToSource('server-uploads', {
id: 'asset-1',
label: { en: 'Sample Image 1' },
tags: { en: ['sample', 'image'] },
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_1.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
engine.asset.addAssetToSource('server-uploads', {
id: 'asset-2',
label: { en: 'Sample Image 2' },
tags: { en: ['sample', 'photo'] },
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_2.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
engine.asset.addAssetToSource('server-uploads', {
id: 'asset-3',
label: { en: 'Sample Image 3' },
tags: { en: ['sample', 'landscape'] },
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_3.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
console.log('✓ Created local source with 3 assets');
```
Each asset has a unique `id` for identification, a `label` and `tags` for searchability, and `meta` properties containing the asset `uri` and `blockType`.
## Finding Assets in a Source
Query assets from a source using `engine.asset.findAssets()`. This method supports search queries and pagination for handling large asset collections.
```typescript highlight=highlight-find-assets
// Query assets from the source to find specific assets
const result = await engine.asset.findAssets('server-uploads', {
query: 'sample',
page: 0,
perPage: 100
});
console.log('Found assets:', result.assets.length);
for (const asset of result.assets) {
console.log(` - ${asset.id}: ${asset.label}`);
}
```
The `findAssets()` method returns an object with the `assets` array, `total` count, and pagination information. Use this to locate specific assets before modification or removal.
## Updating Asset Metadata
To update an asset's metadata, remove the existing asset and add a new version with the same ID. CE.SDK does not provide a direct update method.
```typescript highlight=highlight-update-metadata
// To update an asset's metadata, remove it and add an updated version
engine.asset.removeAssetFromSource('server-uploads', 'asset-1');
// Add the updated version with new metadata
engine.asset.addAssetToSource('server-uploads', {
id: 'asset-1',
label: { en: 'Updated Sample Image' }, // New label
tags: { en: ['sample', 'image', 'updated'] }, // New tags
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_1.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
console.log('✓ Updated asset-1 metadata');
```
This pattern preserves the asset ID while updating labels, tags, URIs, or other metadata. Any references to the asset ID remain valid after the update.
## Removing an Asset from a Source
Remove individual assets using `engine.asset.removeAssetFromSource()`. The asset is permanently deleted from the source.
```typescript highlight=highlight-remove-asset
// Remove a single asset from the source
// The asset is permanently deleted from the source
engine.asset.removeAssetFromSource('server-uploads', 'asset-2');
console.log('✓ Removed asset-2 from server-uploads');
// Verify the asset was removed
const afterRemove = await engine.asset.findAssets('server-uploads', {
page: 0,
perPage: 100
});
console.log('Remaining assets:', afterRemove.assets.length);
```
In server-side workflows, verify the removal by querying the source again. This is useful for automated cleanup or user-initiated deletions.
## Signaling Source Changes
After modifying assets, you can call `engine.asset.assetSourceContentsChanged()` to signal that the source contents have changed. This is primarily useful when synchronizing state with connected browser clients. For standalone batch processing, this call is optional.
```typescript highlight=highlight-notify-ui
// After modifying assets, notify any connected UI components
// In server context, this ensures any connected clients are updated
engine.asset.assetSourceContentsChanged('server-uploads');
console.log('✓ Notified UI of source changes');
```
## Creating Temporary Sources
Create sources for temporary or session-specific assets that can be removed when no longer needed.
```typescript highlight=highlight-create-temp-source
// Create a temporary source that will be removed
engine.asset.addLocalSource('temp-source');
engine.asset.addAssetToSource('temp-source', {
id: 'temp-asset',
label: { en: 'Temporary Asset' },
meta: {
uri: 'https://img.ly/static/ubq_samples/sample_4.jpg',
blockType: '//ly.img.ubq/graphic'
}
});
console.log('✓ Created temporary source with 1 asset');
```
## Removing an Entire Asset Source
Remove a complete source and all its assets using `engine.asset.removeSource()`. This is useful for cleanup after processing or when a batch operation completes.
```typescript highlight=highlight-remove-source
// Remove a complete asset source and all its assets
engine.asset.removeSource('temp-source');
console.log('✓ Removed temp-source and all its assets');
```
## Listening to Asset Source Events
Subscribe to lifecycle events to track source changes. This is useful for logging, analytics, or triggering downstream processes.
```typescript highlight=highlight-events
// Subscribe to lifecycle events for asset sources
const unsubscribeAdded = engine.asset.onAssetSourceAdded((sourceId) => {
console.log(`Event: Source added - ${sourceId}`);
});
const unsubscribeRemoved = engine.asset.onAssetSourceRemoved((sourceId) => {
console.log(`Event: Source removed - ${sourceId}`);
});
const unsubscribeUpdated = engine.asset.onAssetSourceUpdated((sourceId) => {
console.log(`Event: Source updated - ${sourceId}`);
});
// Demonstrate events
engine.asset.addLocalSource('event-demo');
engine.asset.assetSourceContentsChanged('event-demo');
engine.asset.removeSource('event-demo');
// Clean up subscriptions
unsubscribeAdded();
unsubscribeRemoved();
unsubscribeUpdated();
```
Each subscription returns an unsubscribe function. Call it when monitoring is no longer needed.
## Exporting Results
After processing, export the scene to verify your changes or produce output files.
```typescript highlight=highlight-export
// Export the result to PNG
const outputDir = './output';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const blob = await engine.block.export(page, { mimeType: 'image/png' });
const buffer = Buffer.from(await blob.arrayBuffer());
writeFileSync(`${outputDir}/edit-or-remove-assets-result.png`, buffer);
console.log('✓ Exported result to output/edit-or-remove-assets-result.png');
```
## Cleanup
Always dispose the engine when processing is complete to free resources.
```typescript highlight=highlight-cleanup
// Always dispose the engine to free resources
engine.dispose();
```
## Batch Processing Patterns
For processing multiple designs or assets:
1. **Initialize once**: Create the engine once and reuse it for multiple operations
2. **Process in batches**: Group related operations to minimize API calls
3. **Handle errors gracefully**: Wrap operations in try-catch to continue processing after individual failures
4. **Dispose at the end**: Only dispose the engine after all processing is complete
## Best Practices
- **Query before modifying**: Verify assets exist before attempting removal to avoid silent failures
- **Log operations**: Record asset additions, updates, and removals for debugging and audit trails
- **Use unique IDs**: Generate consistent, unique asset IDs for reliable lookups
- **Clean up temporary sources**: Remove sources created for batch operations when finished
- **Handle concurrent access**: In multi-process environments, coordinate asset modifications to prevent conflicts
## Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| Asset not found | ID mismatch or already removed | Query the source first to verify asset exists |
| Operation fails silently | Source is not a local source | Only local sources support `removeAssetFromSource()` |
| Memory issues | Engine not disposed | Always call `engine.dispose()` in a finally block |
| Event handlers not firing | Subscription created after operation | Subscribe before performing operations |
## API Reference
| Method | Category | Purpose |
|--------|----------|---------|
| `engine.asset.findAssets()` | Asset Discovery | Query assets from a source with filtering and pagination |
| `engine.asset.addAssetToSource()` | Asset Lifecycle | Add or update an asset in a local source |
| `engine.asset.removeAssetFromSource()` | Asset Lifecycle | Remove a single asset from a local source |
| `engine.asset.removeSource()` | Source Management | Remove an entire asset source and all its assets |
| `engine.asset.assetSourceContentsChanged()` | UI Notification | Notify connected clients that source contents changed |
| `engine.asset.onAssetSourceAdded()` | Event Subscriptions | Subscribe to source addition events |
| `engine.asset.onAssetSourceRemoved()` | Event Subscriptions | Subscribe to source removal events |
| `engine.asset.onAssetSourceUpdated()` | Event Subscriptions | Subscribe to source content change events |
---
## More Resources
- **[Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md)** - Browse all Node.js (Native) documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/node-native/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/node-native/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Supported File Formats for Import (Node.js)"
description: "Review the supported image, video, audio, and template formats for importing assets into CE.SDK in Node.js environments."
platform: node-native
url: "https://img.ly/docs/cesdk/node-native/import-media/file-format-support-8cdc84/"
---
> This is one page of the CE.SDK Node.js (Native) documentation. For a complete overview, see the [Node.js (Native) Documentation Index](https://img.ly/docs/cesdk/node-native.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/node-native/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/node-native/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/node-native/import-media-4e3703/) > [File Format Support](https://img.ly/docs/cesdk/node-native/import-media/file-format-support-8cdc84/)
---
When building server-side creative applications with CE.SDK in Node.js, understanding which file formats you can import is essential for processing user content at scale. CE.SDK's Node.js package supports the same comprehensive range of modern media formats as the browser version.
This guide provides a complete reference of supported file formats for importing media, templates, and fonts into CE.SDK on Node.js.
## Supported Import Formats
CE.SDK for Node.js supports importing the following media 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) |
| **Templates** | `.idml` (InDesign), `.psd` (Photoshop), `.scene` (CE.SDK Native) |
> **Note:** 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. Contact our support team to learn more about implementing
> custom importers.
## Video and Audio Codecs
While container formats (`.mp4`, `.mov`, `.webm`) define how media is packaged, codecs determine how the content is compressed. CE.SDK for Node.js supports the following codecs:
### Video Codecs
- **H.264 / AVC** (in `.mp4` or `.mov`) – Universally supported with software decoding
- **H.265 / HEVC** (in `.mp4` or `.mov`) – Requires platform-specific support; availability varies by system libraries
- **VP8, VP9, AV1** (in `.webm`) – Modern codecs supported through system media libraries
### Audio Codecs
- **MP3** (in `.mp3` files or within `.mp4`/`.mov` containers)
- **AAC** (in `.m4a`, `.mp4`, or `.mov` containers)
> **Note:** In Node.js environments, video and audio codec support depends on the system's
> installed media libraries and codecs. Ensure your deployment environment has
> the necessary codecs installed for your target formats.
## Size Limits and Constraints
CE.SDK for Node.js processes media using the system's available resources. Server environments typically have more memory and CPU resources than browsers, but you should still be mindful of limits:
### Image Resolution Limits
| Constraint | Recommendation / Limit |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Input Resolution** | Maximum input resolution is **4096×4096 pixels** by default. Images from external sources are resized to this size before rendering. You can modify this value using the `maxImageSize` setting. |
| **Output Resolution** | There is no enforced output resolution limit. The editor theoretically supports output sizes up to **16,384×16,384 pixels**. Practical limits depend on available system memory and CPU resources. |
All image processing in CE.SDK for Node.js is handled server-side using CPU-based rendering (no GPU required). The default limit of 4096×4096 ensures reasonable processing times and memory usage. Higher resolutions will work but may require more memory and longer processing times.
> **Note:** For server deployments, monitor memory usage when processing high-resolution
> images. Consider implementing request queuing or resource pooling for
> production workloads.
### Video Resolution and Duration Limits
| Constraint | Recommendation / Limit |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Resolution** | Up to **4K UHD** is supported for processing, depending on available system resources. Maximum video size is bounded by the **32-bit address space of WebAssembly (wasm32)** and **available system memory**. |
| **Frame Rate** | 30 FPS at 1080p is recommended; 60 FPS works but increases processing time |
| **Duration** | Stories and reels of up to **2 minutes** process efficiently. Longer videos up to **10 minutes** are supported, with processing time scaling proportionally with duration. |
> **Warning:** Server-side video processing is CPU-intensive. For production deployments,
> consider implementing job queues, progress tracking, and timeout handling for
> long-running video operations.
## Format-Specific Considerations
### 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
- 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
- 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
The following SVG elements are not supported:
- Animation elements (``)
- Foreign object (``)
- Text-related elements (``, ``, ``)
- Script elements (`