Search
Loading...
Skip to content

Versioning of Assets

Manage how CE.SDK stores and resolves asset URLs in saved designs, ensuring designs remain functional when assets are updated or moved.

10 mins
estimated time
Download
StackBlitz
GitHub

CE.SDK references assets via URIs rather than embedding files directly into designs. When you save a design with engine.scene.saveToString(), asset URLs are stored as strings. On load, CE.SDK fetches assets from those URLs. This approach keeps saved designs small but means URL changes can break existing designs. This guide explains how CE.SDK stores asset references and strategies for managing asset URLs in server-side applications.

This guide covers how to inspect asset URLs stored in designs, the difference between scene serialization and archive export, how to programmatically update asset URLs, and strategies for versioned URL schemes.

Setup#

Initialize the headless CE.SDK engine for server-side processing. The engine runs without a UI, making it suitable for automated workflows.

// Initialize CE.SDK engine in headless mode
const engine = await CreativeEngine.init({
// license: process.env.CESDK_LICENSE, // Optional (trial mode available)
});

How Asset URLs Are Stored#

Assets in a scene are blocks with fill properties containing URI strings. When you add an image to a design, CE.SDK creates a fill block that stores the source URL. We use engine.block.getFill() to get the fill block and engine.block.getString() to inspect the stored URI.

// Create an image block with a remote URL
const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg';
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', imageUri);
engine.block.setFill(imageBlock, imageFill);
engine.block.setWidth(imageBlock, 300);
engine.block.setHeight(imageBlock, 200);
engine.block.setPositionX(imageBlock, 50);
engine.block.setPositionY(imageBlock, 50);
engine.block.appendChild(page, imageBlock);
// Get the fill block that contains the image URI
const fill = engine.block.getFill(imageBlock);
// Inspect the stored URI - this is exactly what gets saved in the scene
const storedUri = engine.block.getString(fill, 'fill/image/imageFileURI');
console.log('Stored image URI:', storedUri);

The fill/image/imageFileURI property contains exactly what gets written to the saved scene. CE.SDK doesn’t transform or normalize these URLs—they’re stored and loaded as-is.

Scene Serialization vs Archive Export#

CE.SDK provides two approaches for saving designs, each with different trade-offs for asset handling.

Saving as a Scene String#

The saveToString() method serializes the scene structure while keeping asset references as URLs. This produces small files that load quickly, but requires the original assets to remain available at their URLs.

// Save the scene to a string - URLs are preserved as references
const sceneString = await engine.scene.saveToString();
console.log('Scene saved to string, length:', sceneString.length);
// The scene string contains the URL reference, not the image data itself
// This keeps the saved scene small and loads quickly

Use scene strings when:

  • Assets are hosted on a stable CDN with reliable URLs
  • You want to keep storage costs low
  • Designs need to load quickly
  • You can guarantee asset availability

Saving as an Archive#

The saveToArchive() method bundles the scene with all referenced assets into a ZIP file. This creates a self-contained package that works without network access.

// Alternatively, save as an archive with embedded assets
const archiveBlob = await engine.scene.saveToArchive();
console.log('Archive created, size:', archiveBlob.size, 'bytes');
// Archives are self-contained - they include all asset data
// Use archives when designs need to work offline or across environments

Use archives when:

  • Designs need to work offline
  • You’re migrating designs between environments
  • You can’t guarantee long-term URL availability
  • Portability is more important than file size
ApproachMethodAssetsFile SizePortability
ScenesaveToString()Referenced by URLSmallRequires URL availability
ArchivesaveToArchive()Embedded in ZIPLargerSelf-contained

What Happens When URLs Change#

When a design is loaded and an asset URL returns a 404 or is otherwise unavailable, the block appears empty or shows an error state. In server-side processing, this may cause export failures or incomplete renders.

CE.SDK doesn’t provide automatic fallbacks or retries for failed asset loads. If some assets fail while others succeed, the design loads partially. To prevent broken designs, ensure assets remain available at their original URLs or migrate designs when URLs change.

Updating Asset URLs Programmatically#

When you need to migrate assets to a new location, you can load existing scenes, update the URLs, and save the modified scene. We use engine.block.setString() to update the fill property.

// Programmatically update an asset URL (e.g., for CDN migration)
const newUri = 'https://img.ly/static/ubq_samples/sample_2.jpg';
engine.block.setString(fill, 'fill/image/imageFileURI', newUri);
// Verify the change
const updatedUri = engine.block.getString(fill, 'fill/image/imageFileURI');
console.log('Updated image URI:', updatedUri);

For batch updates, iterate through all blocks of a given type and update their fills.

// Find all graphic blocks to batch update their asset URLs
const graphicBlocks = engine.block.findByType('graphic');
console.log('Found graphic blocks:', graphicBlocks.length);
// Iterate through blocks to inspect or update their fills
for (const blockId of graphicBlocks) {
const blockFill = engine.block.getFill(blockId);
const fillType = engine.block.getType(blockFill);
if (fillType === '//ly.img.ubq/fill/image') {
const uri = engine.block.getString(blockFill, 'fill/image/imageFileURI');
console.log('Image block found with URI:', uri);
// Example: migrate from old CDN to new CDN
if (uri.includes('old-cdn.example.com')) {
const migratedUri = uri.replace(
'old-cdn.example.com',
'new-cdn.example.com'
);
engine.block.setString(
blockFill,
'fill/image/imageFileURI',
migratedUri
);
}
}
}

This pattern is useful for CDN migrations or restructuring asset directories.

Strategies for Versioned Asset URLs#

Designing your URL scheme to support versioning prevents accidental overwrites and makes migrations easier. We recommend three approaches.

// Demonstrate versioned URL patterns
// Path-based versioning: include version in the URL path
const pathVersionedUrl = 'https://cdn.example.com/assets/v2/logo.png';
console.log('Path-versioned URL:', pathVersionedUrl);
// Hash-based versioning: include content hash in filename
const hashVersionedUrl = 'https://cdn.example.com/assets/logo-a1b2c3d4.png';
console.log('Hash-versioned URL:', hashVersionedUrl);
// Query parameter versioning: append version as query string
const queryVersionedUrl = 'https://cdn.example.com/assets/logo.png?v=2';
console.log('Query-versioned URL:', queryVersionedUrl);

Path-Based Versioning#

Include version in the URL path: https://cdn.example.com/assets/v2/logo.png. When you update assets, increment the version directory. Old designs reference old paths while new designs use new paths. Both versions can coexist on the same CDN.

Hash-Based Filenames#

Use content hashes in filenames: logo-a1b2c3d4.png. The URL changes whenever content changes, ensuring automatic cache invalidation. Build tools like Webpack and Vite generate these automatically. This pattern works well for content-addressable storage.

Query Parameter Versioning#

Append version as query parameter: logo.png?v=2. The base URL stays the same but the version parameter forces cache invalidation. Note that some CDNs ignore query parameters for caching—verify your CDN configuration before relying on this approach.

Exporting Results#

After processing designs, export the result to your desired format. The headless engine supports all export formats including PNG, PDF, and more.

// 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}/asset-versioning-result.png`, buffer);
// Also save the scene string for demonstration
writeFileSync(`${outputDir}/scene.txt`, sceneString);
console.log('✓ Exported result to output/asset-versioning-result.png');
console.log('✓ Saved scene string to output/scene.txt');

Cleanup#

Always dispose the engine when processing is complete to free resources.

// Always dispose the engine to free resources
engine.dispose();

Best Practices#

When managing asset URLs in production:

  • Use immutable URLs: Content-addressed or versioned paths prevent accidental overwrites
  • Keep old assets available: Don’t delete assets that may be referenced by saved designs
  • Use archives for portability: Export as archive when designs need to work offline or across environments
  • Plan CDN migrations carefully: Update saved designs before decommissioning old URLs
  • Batch process efficiently: Load the engine once and process multiple designs before disposing
  • Handle errors gracefully: Implement retry logic for transient network failures

Troubleshooting#

IssueCauseSolution
Asset not loadingURL changed or deletedVerify URL accessibility, update scene
Design partially loadsSome assets unavailableCheck all asset URLs, consider archive export
Archive too largeMany/large embedded assetsOptimize assets before archiving
Export failsMissing or broken assetsVerify all asset URLs before export
Memory issuesEngine not disposedAlways call engine.dispose() in finally block

Next Steps#