Create a template library where templates are stored, managed, and applied programmatically through a custom asset source.
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, so users can browse thumbnails and apply templates to their designs. This guide covers saving scenes as templates, creating a template asset source, adding templates with metadata, and managing them.
Saving Templates#
With a design open in the engine, export the current scene as a template file. Scenes serialize in two formats.
String Format#
Use engine.scene.saveToString() to serialize the scene to a base64 string. This lightweight format references assets by URL, so it is ideal for templates whose assets are hosted on a CDN. Persist the string wherever you store templates — a database, or a .scene file as shown here.
let templateString = try await engine.scene.saveToString()let stringURL = FileManager.default.temporaryDirectory .appendingPathComponent("template-\(UUID().uuidString).scene")try templateString.write(to: stringURL, atomically: true, encoding: .utf8)Archive Format#
For self-contained templates that bundle all assets, use engine.scene.saveToArchive(). It returns Data (the Blob typealias) holding a .zip archive with every asset embedded, making the template portable without external dependencies.
let templateArchive = try await engine.scene.saveToArchive()let archiveURL = FileManager.default.temporaryDirectory .appendingPathComponent("template-\(UUID().uuidString).zip")try templateArchive.write(to: archiveURL)Creating a Template Asset Source#
Register a local asset source with engine.asset.addLocalSource(sourceID:applyAsset:). The applyAsset closure runs when a template is selected: it reads the asset’s meta["uri"] and calls engine.scene.applyTemplate(from:), which keeps the current scene’s page dimensions and design unit while resizing the template’s content to fit them. Return nil because applying a template mutates the current scene rather than creating a new block.
Capture engine weakly: the source retains this callback for its lifetime, so a strong capture would risk a retain cycle.
try engine.asset.addLocalSource(sourceID: "my-templates", applyAsset: { [weak engine] asset in guard let engine, let uri = asset.meta?["uri"], let url = URL(string: uri) else { return nil } try await engine.scene.applyTemplate(from: url) return nil})applyTemplate(from:) accepts a serialized scene string or a URL to a .scene file — not a .zip archive. To start from an archive, load it with engine.scene.loadArchive(from:), which replaces the current scene instead of merging a template into it.
Adding Templates to the Source#
Register templates with engine.asset.addAsset(to:asset:), passing an AssetDefinition that carries the display metadata and load URLs. Build each uri and thumbUri from the base URL where you host your assets.
let templates = [ AssetDefinition( id: "template-business-card", meta: [ "uri": baseURL.appendingPathComponent("ly.img.templates/templates/cesdk_business_card_1.scene").absoluteString, "thumbUri": baseURL.appendingPathComponent("ly.img.templates/thumbnails/cesdk_business_card_1.jpg") .absoluteString, ], label: ["en": "Business Card"], ), AssetDefinition( id: "template-blank", meta: [ "uri": baseURL.appendingPathComponent("ly.img.templates/templates/cesdk_blank_1.scene").absoluteString, "thumbUri": baseURL.appendingPathComponent("ly.img.templates/thumbnails/cesdk_blank_1.png").absoluteString, ], label: ["en": "Blank"], ), AssetDefinition( id: "template-postcard", meta: [ "uri": baseURL.appendingPathComponent("ly.img.templates/templates/cesdk_postcard_1.scene").absoluteString, "thumbUri": baseURL.appendingPathComponent("ly.img.templates/thumbnails/cesdk_postcard_1.jpg").absoluteString, ], label: ["en": "Postcard"], ),]for template in templates { try engine.asset.addAsset(to: "my-templates", asset: template)}Each template asset requires:
id— Unique identifier for the templatelabel— Localized display name shown in the template library.IMGLYEngine.Localeis aStringtypealias, so use plain language keys such as["en": "Business Card"]meta["uri"]— URL to the.scenefile or archive loaded when the template is appliedmeta["thumbUri"]— URL to a preview image displayed in the template library grid
Managing Templates#
After the initial setup, manage templates programmatically.
let sources = engine.asset.findAllSources()print("Registered sources:", sources)
let results = try await engine.asset.findAssets( sourceID: "my-templates", query: .init(query: nil, page: 0, perPage: 10),)print("Templates in library:", results.total)
try engine.asset.removeAsset(from: "my-templates", assetID: "template-postcard")try engine.asset.assetSourceContentsChanged(sourceID: "my-templates")List registered source IDs with engine.asset.findAllSources(), and page through a source’s templates with engine.asset.findAssets(sourceID:query:) — results.total reports the count. Remove a template with engine.asset.removeAsset(from:assetID:), then call engine.asset.assetSourceContentsChanged(sourceID:) to notify any connected UI that the source changed.
Troubleshooting#
| Issue | Cause | Solution |
|---|---|---|
| Template fails to load | Incorrect URI in the asset’s meta |
Verify meta["uri"] points to a valid .scene file or archive |
| Apply callback not triggered | applyAsset closure not provided to addLocalSource |
Pass the applyAsset: closure when creating the source |
| Empty query results | Templates queried before they were added | Call addAsset(to:asset:) before findAssets(sourceID:query:) |
API Reference#
| Method | Description |
|---|---|
engine.asset.addLocalSource(sourceID:applyAsset:) |
Register a local asset source with an apply callback |
engine.asset.addAsset(to:asset:) |
Add an asset to a registered source |
engine.asset.removeAsset(from:assetID:) |
Remove an asset from a source by ID |
engine.asset.findAllSources() |
List registered asset source IDs |
engine.asset.findAssets(sourceID:query:) |
Query a source’s assets with paging |
engine.asset.assetSourceContentsChanged(sourceID:) |
Notify the UI that a source’s contents changed |
engine.scene.saveToString() |
Serialize the scene to a base64 string |
engine.scene.saveToArchive() |
Save the scene as a self-contained archive (Data) |
engine.scene.applyTemplate(from:) |
Apply a template to the current scene |
Next Steps#
- Create From Scratch — Build reusable templates programmatically with the Engine API
- Text Variables — Add dynamic text content to templates
- Placeholders — Create editable image and video areas
- Customize Asset Library — Organize and present asset sources in the library