Manage how CE.SDK stores and resolves asset URLs in saved designs, keeping designs functional when assets are updated or moved.
CE.SDK references assets via URLs 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
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 over time.
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.
How Asset URLs Are Stored#
An asset reference lives on a fill block, not on the graphic block itself. When
you add an image to a design, an image fill stores the source URL in its
fill/image/imageFileURI property. Use engine.block.getFill() to reach the
fill and engine.block.getUri() to inspect the stored URL. Responsive image
variants are stored separately in fill/image/sourceSet, so include that
property when a fill uses source sets.
val fill = engine.block.getFill(imageBlock)val storedUri = engine.block.getUri( block = fill, property = "fill/image/imageFileURI",)The fill/image/imageFileURI property contains exactly what gets written to
the saved scene. CE.SDK does not transform or normalize these URLs; they are
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 a small String that loads quickly, but
requires the original assets to remain available at their URLs. Persist the
returned value wherever you store designs.
val sceneString = engine.scene.saveToString(scene = scene)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 together with all referenced
assets into a self-contained archive. Android returns the archive as a
ByteBuffer, which your app can write to storage or upload. Archives work
without network access because every accessible asset is embedded.
val archiveBuffer = engine.scene.saveToArchive(scene = scene)Archives are useful when:
- Designs need to work offline.
- You migrate designs between environments.
- You cannot guarantee long-term URL availability.
- Portability matters more than file size.
| Approach | Method | Assets | File Size | Portability |
|---|---|---|---|---|
| Scene | saveToString() |
Referenced by URL | Small | Requires URL availability |
| Archive | saveToArchive() |
Embedded in archive | Larger | Self-contained |
What Happens When URLs Change#
When a design is loaded and an asset URL returns a 404 or is otherwise unavailable, the affected block appears empty or shows an error state. Other blocks can still load, so a design may appear partially complete.
CE.SDK does not provide automatic fallbacks or retries for failed asset loads. A previously fetched copy of the asset may still appear until caches expire, which can temporarily mask a broken URL.
Updating Asset URLs Programmatically#
When you need to migrate assets to a new location, load the existing scene,
update the URLs, and save the modified scene. Use engine.block.setUri() to
point a fill at a new asset.
val loadedScene = engine.scene.load(scene = sceneString, waitForResources = true)val loadedPage = engine.scene.getPages().first()val loadedImageBlock = engine.block.getChildren(loadedPage).first()val loadedFill = engine.block.getFill(loadedImageBlock)val updatedUri = Uri.parse("https://img.ly/static/ubq_samples/sample_2.jpg")
engine.block.setUri( block = loadedFill, property = "fill/image/imageFileURI", value = updatedUri,)
val currentUri = engine.block.getUri( block = loadedFill, property = "fill/image/imageFileURI",)This pattern is useful when a known asset moves to a new permanent location. The updated scene string now points at the replacement URL.
For batch migrations, scan graphic blocks, keep only image fills, replace the old CDN prefix in both the image file URL and any source-set entries, then save the migrated scene.
val oldAssetPrefix = "https://img.ly/static/ubq_samples"val newAssetPrefix = "https://cdn.example.com/assets/v2"val migratedUris = mutableListOf<Uri>()val migratedSourceSetUris = mutableListOf<Uri>()
engine.block.findByType(DesignBlockType.Graphic).forEach { block -> if (!engine.block.supportsFill(block)) return@forEach
val blockFill = engine.block.getFill(block) if (engine.block.getType(blockFill) != FillType.Image.key) return@forEach
val previousUri = engine.block.getUri( block = blockFill, property = "fill/image/imageFileURI", ) if (previousUri.toString().startsWith(oldAssetPrefix)) { val migratedUri = Uri.parse( previousUri.toString().replace( oldValue = oldAssetPrefix, newValue = newAssetPrefix, ), ) engine.block.setUri( block = blockFill, property = "fill/image/imageFileURI", value = migratedUri, ) migratedUris += migratedUri }
var sourceSetChanged = false val migratedSourceSet = engine.block.getSourceSet( block = blockFill, property = "fill/image/sourceSet", ).map { source -> if (!source.uri.toString().startsWith(oldAssetPrefix)) return@map source
sourceSetChanged = true val migratedSourceUri = Uri.parse( source.uri.toString().replace( oldValue = oldAssetPrefix, newValue = newAssetPrefix, ), ) migratedSourceSetUris += migratedSourceUri source.copy(uri = migratedSourceUri) }
if (sourceSetChanged) { engine.block.setSourceSet( block = blockFill, property = "fill/image/sourceSet", sourceSet = migratedSourceSet, ) }}
val migratedSceneString = engine.scene.saveToString(scene = loadedScene)Run this kind of migration before decommissioning the old asset host. Keep the old URLs available until every saved design that depends on them has been migrated.
Strategies for Versioned Asset URLs#
Designing your URL scheme to support versioning prevents accidental overwrites and makes migrations easier.
val pathVersionedUrl = "https://cdn.example.com/assets/v2/logo.png"val hashVersionedUrl = "https://cdn.example.com/assets/logo-a1b2c3d4.png"val queryVersionedUrl = "https://cdn.example.com/assets/logo.png?v=2"Path-Based Versioning#
Put the version in the path, such as https://cdn.example.com/assets/v2/logo.png. Old designs can keep using the v1 path while new designs point to v2.
Hash-Based Filenames#
Put a content hash in the filename, such as logo-a1b2c3d4.png. The URL changes whenever the file changes, which makes the URL safe to cache for a long time.
Query Parameter Versioning#
Add the version as a query parameter, such as logo.png?v=2. This keeps the path stable, but you must verify that your CDN includes query parameters in its cache key.
Best Practices#
When managing asset URLs in production:
- Use immutable URLs: Content-addressed or versioned paths prevent accidental overwrites.
- Keep old assets available: Do not delete assets that may be referenced by saved designs.
- Use archives for portability: Export as an archive when designs need to work offline or across environments.
- Plan CDN migrations carefully: Update saved designs before decommissioning old URLs.
- Set appropriate cache headers: Balance performance with freshness requirements.
- Document your URL scheme: Make the versioning strategy clear for your team.
Troubleshooting#
| Issue | Cause | Fix |
|---|---|---|
| Asset shows an old version | Cached copy of the asset | Use a cache-busting or versioned URL |
| Asset does not load | URL changed or deleted | Verify URL accessibility, update the scene |
| Design partially loads | Some assets unavailable | Check all asset URLs, consider an archive export |
| Archive is large | Archive embeds referenced assets | Optimize media before archiving or use scene strings |
API Reference#
| Method | Description |
|---|---|
engine.block.getFill(block=_) |
Get the fill block that stores the media URL |
engine.block.getUri(block=_, property="fill/image/imageFileURI") |
Read an image fill URL |
engine.block.setUri(block=_, property="fill/image/imageFileURI", value=_) |
Replace an image fill URL |
engine.block.getSourceSet(block=_, property="fill/image/sourceSet") |
Read responsive image source URLs |
engine.block.setSourceSet(block=_, property="fill/image/sourceSet", sourceSet=_) |
Replace responsive image source URLs |
engine.block.getChildren(block=_) |
Read child blocks from the loaded page |
engine.block.findByType(type=_) |
Find blocks to inspect or migrate |
engine.block.supportsFill(block=_) |
Skip blocks that cannot have fills |
engine.block.getType(block=_) |
Check whether a fill is an image fill |
engine.scene.saveToString(scene=_) |
Save a scene with URI references |
engine.scene.load(scene=_, waitForResources=_) |
Load a saved scene string |
engine.scene.getPages() |
Get pages from the loaded scene |
engine.scene.saveToArchive(scene=_) |
Save a self-contained archive as a ByteBuffer |
engine.scene.loadArchive(archiveUri=_, waitForResources=_) |
Load an archive saved to a URI |
Next Steps#
- Serve Assets From Your Server - Configure self-hosted assets.
- Save Designs - Save and serialize designs.
- Export Overview - Review export options including archives.