This guide shows you how to add a Collage feature in your web app with the help of the CE.SDK. A collage allows you to reuse images and assets as you change the layout of the scene.
Layouts in CE.SDK are predefined templates that dictate how to arrange images and assets within a single composition. They allow you to create visually appealing collages by specifying each image’s:
- Position
- Size
- Orientation relative to other assets on the page
Layouts instantly create visuals and allow you to skip manually arranging each asset.
What You’ll Learn#
In this guide, you will learn how to:
- Set up the layout panel in the CE.SDK UI.
- Use a layout file for collages.
- Use TypeScript to apply layouts in your custom UI.
When to Use Layouts#
The CE.SDK Layout feature is ideal for:
- Photo collages
- Grid layouts
- Magazine spreads
- Social media posts
Difference Between Layouts and Templates#
Layouts are custom assets that differ from templates:
- Templates: Load new assets and replace the existing scene.
- Layouts: Keep existing assets but change their arrangement on the page.
Preserving assets while changing the layout isn’t a native CE.SDK feature. The following sections explain how to leverage the CE.SDK to do it in your app using JavaScript.
How Collages Work#
When you choose a collage layout, the app needs to:
- Load a new layout file.
- Extract content from your current design.
- Map content to new layout positions.
- Preserve images and text in visual order.
You trigger this workflow when a user selects a layout from the Layouts panel in the CE.SDK UI.
Add a Layouts Panel to the CE.SDK UI#
The CE.SDK allows you to add custom panels to the UI . To add a Layouts panel for collage selection, you need to create it and load custom assets to configure its appearance and behavior.
For this action, you need:
- A layout file defining the collage structure (use this one to get started).
- Thumbnail images for preview (find a collection here).
1. Add a Layouts Option to the CE.SDK Menu#
To customize the CE.SDK UI and create a Layouts panel, use the CE.SDK UserInterfaceAPI. Add a Layout button with ui.setDockOrder(), using the following properties:
idkeylabeliconentries
For example:
cesdk.ui.setDockOrder([ { id: 'ly.img.assetLibrary.dock', key: 'layouts-dock-entry', label: 'Layouts', icon: '@imgly/Layout', entries: ['layouts'], },]);
2. Customize the Layouts Panel Appearance#
To configure how the Layouts panel looks and behaves, use the ui.addAssetLibraryEntry() helper with the following options:
id: Unique identifier for the panel.sourceIds: Which assets to display.previewLength: How many preview items to display in the panel.gridColumns: How many columns to contain the previews in the panel.gridItemHeight: The shape of each tile in the panel grid.previewBackgroundType: How to fit the previews inside their tiles (cover/contain).gridBackgroundType: How to display the panel background (cover/contain).
For example, the demo uses this configuration:
instance.ui.addAssetLibraryEntry({id: 'ly.img.layouts', // Referenced in the dock entry in Step 1sourceIds: ['ly.img.layouts'], // Points to our custom layout asset sourcepreviewLength: 2, // Number of preview items int the compact panelgridColumns: 2, // Organize tiles in 2 columnsgridItemHeight: 'square', // Square tilespreviewBackgroundType: 'contain', // Fit compact panel backgroundgridBackgroundType: 'contain' // Fit panel background});
To learn more about panel customization, check the Panel Customization guide .
3. Load Custom Assets#
The demo uses a helper function to load a custom layout asset source from a JSON file. This file defines the available layouts and their metadata. You can reuse this logic by defining both:
- A
ContentJSONobject (likeCustomLayouts.json) - A
baseURLfor your custom assets that replaces the{{base_url}}.
import { createApplyLayoutAsset } from './lib/createApplyLayoutAsset';import loadAssetSourceFromContentJSON from './lib/loadAssetSourceFromContentJSON';
// ...const caseAssetPath = (path, caseId = 'layouts') => `${process.env.NEXT_PUBLIC_URL_HOSTNAME}${process.env.NEXT_PUBLIC_URL}/cases/${caseId}${path}`;
// Call the helper to load the layout assets loadAssetSourceFromContentJSON( instance.engine, // Pss the CE.SDK engine to load assets into LAYOUT_ASSETS, // Pass the JSON bundle caseAssetPath(''), // Base URL for assets createApplyLayoutAsset(instance.engine) // Callback to createApplyLayoutAsset.js helper ); await instance.loadFromURL(caseAssetPath('/custom-layouts.scene')); // Load the scene// ...In the previous example, the helper accepts an optional applyAsset callback, so:
- A user picks a layout from the library.
- The engine invokes the callback to apply it.
- The engine replaces the current page’s structure with the layout while keeping the user’s images/text.
Asset preservation isn’t a CE.SDK native feature. It’s handled in the createApplyLayoutAsset.js helper, which you can find in the repository.
Apply the Collage#
When applying a collage, the following actions need to be implemented:
- Changing the structure of the design.
- Transferring the existing content to the new structure.
- Deleting the previous scene.
You can find this workflow in the createApplyLayoutAsset() helper from the demo. Follow these steps to replicate it:
1. Prepare and Allow changes#
- Allow block deletion with
editor.setGlobalScope('lifecycle/destroy', 'Allow'). - Clear the selected blocks with
block.setSelected(block, false).
const scopeBefore = engine.editor.getGlobalScope('lifecycle/destroy');engine.editor.setGlobalScope('lifecycle/destroy', 'Allow');const page = engine.scene.getCurrentPage();engine.block.findAllSelected().forEach((block) => engine.block.setSelected(block, false));2. Load the New Layout#
- Load the new layout with
block.loadFromString(). - Return the first page from the loaded blocks as the layout page.
const sceneString = await fetch(asset.meta.uri).then((response) => response.text());const blocks = await engine.block.loadFromString(sceneString);const layoutPage = blocks[0];3. Backup Current Page#
- Duplicate the current page with
block.duplicate()to keep a backup of the existing content. - The CE.SDK uses this backup to transfer images and text to the new layout.
- Clear the current page structure by destroying all its children.
const oldPage = engine.block.duplicate(page);engine.block.getChildren(page).forEach((child) => { engine.block.destroy(child);});engine.block.getChildren(layoutPage).forEach((child) => { engine.block.insertChild(page, child, engine.block.getChildren(page).length);});4. Transfer Content#
Copy user text and images onto the new layout:
copyAssets(engine, oldPage, page);5. Sort Blocks Visually and Pair Content#
Grab text and image blocks from both pages in visual order (top to bottom, left to right):
const fromChildren = visuallySortBlocks(engine, getChildrenTree(engine, fromPageId).flat());const textsOnFromPage = fromChildren.filter((childId) => engine.block.getType(childId).includes('text'));const imagesOnFromPage = fromChildren.filter((childId) => engine.block.getKind(childId) === 'image');// same for toPageId -> textsOnToPage, imagesOnToPageThen apply the content from the old page to the new layout by looping through the blocks:
const fromText = engine.block.getString(fromBlock, 'text/text');const fromFontFileUri = engine.block.getString(fromBlock, 'text/fontFileUri');const fromTypeface = engine.block.getTypeface(fromBlock);engine.block.setFont(toBlock, fromFontFileUri, fromTypeface);const fromTextFillColor = engine.block.getColor(fromBlock, 'fill/solid/color');engine.block.setString(toBlock, 'text/text', fromText);engine.block.setColor(toBlock, 'fill/solid/color', fromTextFillColor);const fromImageFill = engine.block.getFill(fromBlock);const toImageFill = engine.block.getFill(toBlock);const fromImageFileUri = engine.block.getString(fromImageFill, 'fill/image/imageFileURI');engine.block.setString(toImageFill, 'fill/image/imageFileURI', fromImageFileUri);const fromImageSourceSets = engine.block.getSourceSet(fromImageFill, 'fill/image/sourceSet');engine.block.setSourceSet(toImageFill, 'fill/image/sourceSet', fromImageSourceSets);if (engine.block.supportsPlaceholderBehavior(fromBlock)) { engine.block.setPlaceholderBehaviorEnabled( toBlock, engine.block.isPlaceholderBehaviorEnabled(fromBlock) );}engine.block.resetCrop(toBlock);6. Cleanup and Restore State#
Cleanup temporary blocks with block.destroy() and restore global scope:
engine.block.destroy(oldPage);engine.block.destroy(layoutPage);engine.editor.setGlobalScope('lifecycle/destroy', scopeBefore);if (config.addUndoStep) { engine.editor.addUndoStep();}return page;This keeps the editor stable and predictable after the layout changes and prevents:
- Stray selections.
- Ghost placeholders.
- Unused assets left at the scene.
Advanced Collage Techniques#
The CE.SDK BlockAPI eases the transfer of placeholder behavior when moving images between blocks. You can use it to:
- Checks if the block supports placeholder behavior with
supportsPlaceholderBehavior. - Apply the same setting to the target image block with:
isPlaceholderBehaviorEnabled()setPlaceholderBehaviorEnabled()
if (engine.block.supportsPlaceholderBehavior(fromBlock)) { engine.block.setPlaceholderBehaviorEnabled( toBlock, engine.block.isPlaceholderBehaviorEnabled(fromBlock), );}This maintains the behavior of current placeholders after the layout swap.
To ensure the CE.SDK uses all the existing images when applying a layout, you can:
- Handle overflow when more images than slots:
for ( let index = 0; index < imagesOnToPage.length && index < imagesOnFromPage.length; index++) { ... }- Fill empty slots with defaults or placeholders:
// loop condition ends when sources run out, leaving remaining target slots unchangedindex < imagesOnToPage.length && index < imagesOnFromPage.length;- List content by importance or metadata:
const visuallySortBlocks = (engine, blocks) => { const blocksWithCoordinates = blocks .map((block) => ({ block, coordinates: [ Math.round(engine.block.getPositionX(block)), Math.round(engine.block.getPositionY(block)) ] })) .sort(({ coordinates: [X1, Y1] }, { coordinates: [X2, Y2] }) => { if (Y1 === Y2) return X1 - X2; return Y1 - Y2; }); return blocksWithCoordinates.map(({ block }) => block);};For a better user experience, consider adding animations when applying layouts:
const LoadingSpinner = () => { return <div className={styles.spinner} data-cy={'loading-spinner'}></div>;};You can code more customizations to incorporate these effects into collage transitions:
- Fading
- Sliding
- Morphing
Optimize the Layout Workflow#
For a better user experience when creating collages, consider these optimizations:
| Topic | Strategies |
|---|---|
| Asset Loading | ・ Lazy load thumbnails and cache scene files ・ Preload common layouts and minimize file sizes ・ Use CDN for efficient asset delivery |
| Content Transfer | ・ Batch block operations to reduce engine calls ・ Optimize sorting algorithms and memory usage ・ Handle large page structures efficiently |
| Visual Cues | ・ Show loading states and support undo/redo ・ Add error recovery and prevent accidental changes |
Troubleshooting#
| ❌ Issue | ✅ Solutions |
|---|---|
| Layout not applying | ・ Verify asset source registration and callback connection ・ Check browser console for scene files or CORS errors ・ Check the validity of scene file URLs |
| Content lost during layout change | ・ Debug visual sorting with console logs ・ Verify block type filtering is correct ・ Test with different content settings |
| Incorrect visual order | ・ Adjust coordinate rounding tolerance in sorting ・ Flatten complex hierarchies before sorting ・ Test with well-defined layout structures |
| Performance degradation | ・ Optimize scene file sizes ・ Batch engine operations ・ Profile and optimize content transfer |
| Undo not working | ・ Ensure addUndoStep() called after all changes complete・ Verify undo configuration in engine setup |
Next Steps#
Now that know how to create collages with layouts, explore these related guides to expand your CE.SDK knowledge:
- Apply Templates - Work with templates instead of layouts
- Create Custom Asset Sources - Import custom assets
- Customize UI Panels - Advanced UI customization
- Work with Images - Manage image blocks and fills
- Scene Management - Load and save scenes