Templates define a reusable design pattern—text regions, image placeholders, and locked brand elements that your app can populate at runtime. This guide walks you through creating a template from scratch in Android using Kotlin, enabling variable bindings, and saving the result as a string or archive for reuse.
What You’ll Learn#
- Differences between templates and scenes.
- Programmatically build a template scene.
- Enable variable bindings for dynamic text.
- Save templates to string or archive.
- Store basic metadata for library use.
When to Use It#
Choose this guide when you need to author templates programmatically for things such as:
- Automation pipelines
- Unit tests
- Code‑generated layouts.
Prefer the web-based CE.SDK editors if your goal is to let designers craft rich templates visually including:
- Marking placeholders.
- Locking styles.
- Setting edit permissions.
Templates vs Scenes#
- Scene: a complete document (pages, blocks, assets). Edit and export it directly.
- Template: a reusable pattern applied to scenes; often includes placeholders and variables to control what’s editable versus locked.
Create Templates Programmatically#
The web-based CE.SDK editors include built-in template logic and UI. You can use them to:
- Mark blocks as placeholders
- Bind variables
- Assign granular edit permissions.
For most teams, this is the recommended path to author templates.
This guide shows how to achieve similar results in Android/Kotlin, which is useful for code‑driven generation, CI pipelines, or dynamic authoring.
In the code below:
- You’ll create a scene.
- Add a page.
- Insert a text block bound to a variable
- Add an image block.
import ly.img.engine.DesignBlockTypeimport ly.img.engine.Engineimport ly.img.engine.FillTypeimport ly.img.engine.ShapeType
val scene = engine.scene.create()val page = engine.block.create(DesignBlockType.Page)engine.block.appendChild(parent = scene, child = page)
// Text block bound to a variable (e.g., {{name}})val text = engine.block.create(DesignBlockType.Text)engine.block.setString(text, property = "text/text", value = "{{name}}")engine.block.setPositionX(text, value = 0.1F)engine.block.setPositionY(text, value = 0.1F)engine.block.appendChild(parent = page, child = text)
// Image block for dynamic contentval image = engine.block.create(DesignBlockType.Graphic)val shape = engine.block.createShape(ShapeType.Rect)engine.block.setShape(image, shape = shape)
val imageFill = engine.block.createFill(FillType.Image)engine.block.setFill(image, fill = imageFill)
engine.block.setWidth(image, value = 300F)engine.block.setHeight(image, value = 200F)engine.block.setPositionX(image, value = 0.1F)engine.block.setPositionY(image, value = 0.3F)engine.block.appendChild(parent = page, child = image)Binding Variables#
- Use variables for text substitution .
- Use named blocks or image fills for media that users swap at runtime.
Define a variable in text using curly brackets. The variable can be the entire string or part of a string, such as "Hello, {{guest_name}}".
To populate variables at runtime:
import ly.img.engine.Engine
// Populate the template with actual dataengine.variable.set(key = "name", value = "John Smith")engine.variable.set(key = "guest_name", value = "Alice")For image replacement in templates, use named blocks:
import ly.img.engine.Engine
// Give the image block a name for easy lookupengine.block.setString(imageBlock, property = "name", value = "profile-photo")
// Later, find and replace the image fillval blocks = engine.block.findByType(DesignBlockType.Graphic)for (block in blocks) { val name = engine.block.getString(block, property = "name") if (name == "profile-photo") { val fill = engine.block.getFill(block) engine.block.setString(fill, property = "fill/image/imageFileURI", value = "https://example.com/photo.jpg") }}Saving Templates#
Templates are scenes with some extra settings. Save templates:
- Use the same logic as for scenes.
- Save as a string for a lightweight file: the template needs to be able to resolve all asset URLs at runtime.
- Save as an archive for a self-contained, portable file: bundles the assets into the file.
Save as String#
import ly.img.engine.Engine
val sceneAsString = engine.scene.saveToString(scene = scene)// Persist to your DB or send to a backendSave as Archive#
import android.content.Contextimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.withContextimport ly.img.engine.Engineimport java.io.File
suspend fun saveTemplateAsArchive( engine: Engine, context: Context, scene: Int): File { val blob = engine.scene.saveToArchive(scene = scene)
// Save to file val file = File(context.filesDir, "template_${System.currentTimeMillis()}.cesdk") withContext(Dispatchers.IO) { file.outputStream().channel.use { channel -> channel.write(blob) } }
return file}Once you’ve created the string or data blob, use standard methods to persist it.
Complete Example#
Here’s a complete example that creates a template from scratch:
import android.content.Contextimport kotlinx.coroutines.CoroutineScopeimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.launchimport kotlinx.coroutines.withContextimport ly.img.engine.Colorimport ly.img.engine.DesignBlockTypeimport ly.img.engine.Engineimport ly.img.engine.FillTypeimport ly.img.engine.ShapeTypeimport ly.img.engine.SizeModeimport java.io.File
fun createTemplateFromScratch( context: Context, license: String, userId: String) = CoroutineScope(Dispatchers.Main).launch { val engine = Engine.getInstance(id = "ly.img.engine.template") engine.start(license = license, userId = userId) engine.bindOffscreen(width = 1080, height = 1920)
try { // Create scene and page val scene = engine.scene.create() val page = engine.block.create(DesignBlockType.Page) engine.block.appendChild(parent = scene, child = page) engine.block.setWidth(page, value = 1080F) engine.block.setHeight(page, value = 1920F)
// Background (brand color that stays locked) val background = engine.block.create(DesignBlockType.Graphic) val bgShape = engine.block.createShape(ShapeType.Rect) engine.block.setShape(background, shape = bgShape)
val bgFill = engine.block.createFill(FillType.Color) val bgColor = Color.fromRGBA(r = 0.95F, g = 0.95F, b = 0.98F, a = 1.0F) engine.block.setColor(bgFill, property = "fill/color/value", color = bgColor) engine.block.setFill(background, fill = bgFill)
engine.block.appendChild(parent = page, child = background) engine.block.fillParent(background) engine.block.sendToBack(background)
// Title text with variable val title = engine.block.create(DesignBlockType.Text) engine.block.setString(title, property = "text/text", value = "{{product_name}}") engine.block.setTextFontSize(title, fontSize = 48F) engine.block.setWidthMode(title, mode = SizeMode.AUTO) engine.block.setHeightMode(title, mode = SizeMode.AUTO) engine.block.setPositionX(title, value = 100F) engine.block.setPositionY(title, value = 200F) engine.block.appendChild(parent = page, child = title)
// Description text with variable val description = engine.block.create(DesignBlockType.Text) engine.block.setString(description, property = "text/text", value = "{{description}}") engine.block.setTextFontSize(description, fontSize = 24F) engine.block.setWidthMode(description, mode = SizeMode.AUTO) engine.block.setHeightMode(description, mode = SizeMode.AUTO) engine.block.setPositionX(description, value = 100F) engine.block.setPositionY(description, value = 300F) engine.block.appendChild(parent = page, child = description)
// Product image placeholder (named for easy replacement) val productImage = engine.block.create(DesignBlockType.Graphic) val imageShape = engine.block.createShape(ShapeType.Rect) engine.block.setShape(productImage, shape = imageShape)
val imageFill = engine.block.createFill(FillType.Image) // Set a placeholder image URL engine.block.setString( imageFill, property = "fill/image/imageFileURI", value = "https://img.ly/static/ubq_samples/sample_1.jpg" ) engine.block.setFill(productImage, fill = imageFill)
// Name it for easy lookup later engine.block.setString(productImage, property = "name", value = "product-image")
engine.block.setWidth(productImage, value = 600F) engine.block.setHeight(productImage, value = 600F) engine.block.setPositionX(productImage, value = 240F) engine.block.setPositionY(productImage, value = 600F) engine.block.appendChild(parent = page, child = productImage)
// Price text with variable val price = engine.block.create(DesignBlockType.Text) engine.block.setString(price, property = "text/text", value = "${{price}}") engine.block.setTextFontSize(price, fontSize = 36F) engine.block.setTextColor( price, color = Color.fromRGBA(r = 0.2F, g = 0.6F, b = 0.2F, a = 1.0F) ) engine.block.setWidthMode(price, mode = SizeMode.AUTO) engine.block.setHeightMode(price, mode = SizeMode.AUTO) engine.block.setPositionX(price, value = 100F) engine.block.setPositionY(price, value = 1350F) engine.block.appendChild(parent = page, child = price)
// Save as string val templateString = engine.scene.saveToString(scene = scene) println("Template saved as string (${templateString.length} characters)")
// Save to file (for demonstration) val stringFile = File(context.filesDir, "template_string.scene") withContext(Dispatchers.IO) { stringFile.writeText(templateString) }
// Save as archive (includes all assets) val archiveBlob = engine.scene.saveToArchive(scene = scene) val archiveFile = File(context.filesDir, "template_archive.cesdk") withContext(Dispatchers.IO) { archiveFile.outputStream().channel.use { channel -> channel.write(archiveBlob) } } println("Template saved as archive: ${archiveFile.absolutePath}")
// Example: Populate the template with actual data engine.variable.set(key = "product_name", value = "Premium Coffee Mug") engine.variable.set(key = "description", value = "Hand-crafted ceramic, dishwasher safe") engine.variable.set(key = "price", value = "24.99")
println("Template created and saved successfully!")
} finally { // Note: Don't stop the engine here if you want to keep using it // engine.stop() }}Add Template Metadata#
Like other assets, you can:
- Load templates into the asset library .
- Store metadata in your CMS or local database.
- Use the saved metadata later when you register the template as an
AssetDefinitionin anAssetSource.
That way the UI can display names, thumbnails, and categories.
Example metadata structure:
data class TemplateMetadata( val id: String, val name: String, val description: String, val thumbnailUrl: String, val category: String, val tags: List<String>, val variables: List<String>, val createdAt: Long, val updatedAt: Long)Lock Template Properties#
Templates can restrict editing at runtime so that users don’t edit any part of the design that should remain static. To protect integrity, you can lock properties such as:
- Position
- Size
- Color
- Fill
The guide for locking templates provides details on which properties are lockable and how to set up editor and adopter rules.
Example of locking a block:
import ly.img.engine.Engine
// Lock the background so users can't move or resize itengine.block.setScopeEnabled(background, key = "layer/move", enabled = false)engine.block.setScopeEnabled(background, key = "layer/resize", enabled = false)engine.block.setScopeEnabled(background, key = "fill/change", enabled = false)Load and Use Templates#
Once you’ve created and saved a template, load it back and populate with data:
Load from String#
import android.net.Uriimport ly.img.engine.Engine
// Load template from stringval scene = engine.scene.load(scene = templateString)
// Populate with dataengine.variable.set(key = "product_name", value = "Wireless Headphones")engine.variable.set(key = "description", value = "Premium sound quality")engine.variable.set(key = "price", value = "149.99")
// Find and replace the product imageval blocks = engine.block.findByType(DesignBlockType.Graphic)for (block in blocks) { val name = engine.block.getString(block, property = "name") if (name == "product-image") { val fill = engine.block.getFill(block) engine.block.setString( fill, property = "fill/image/imageFileURI", value = "https://example.com/headphones.jpg" ) }}Load from Archive#
Archives need to be unzipped before loading. Here’s how to load a template from an archive file:
import android.content.Contextimport android.net.Uriimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.withContextimport ly.img.engine.Engineimport java.io.Fileimport java.io.FileOutputStreamimport java.util.zip.ZipInputStream
suspend fun loadTemplateFromArchive( engine: Engine, context: Context, archiveFile: File): Int { // Unzip archive to cache directory val extractDir = File(context.cacheDir, "template_${System.currentTimeMillis()}") extractDir.mkdirs()
withContext(Dispatchers.IO) { ZipInputStream(archiveFile.inputStream()).use { zipInputStream -> var entry = zipInputStream.nextEntry while (entry != null) { val file = File(extractDir, entry.name) if (entry.isDirectory) { file.mkdirs() } else { file.parentFile?.mkdirs() FileOutputStream(file).use { outputStream -> zipInputStream.copyTo(outputStream) } } zipInputStream.closeEntry() entry = zipInputStream.nextEntry } } }
// Load the scene.scene file from the extracted archive val sceneFile = File(extractDir, "scene.scene") val sceneUri = Uri.fromFile(sceneFile) return engine.scene.load(sceneUri = sceneUri)}Troubleshooting#
❌ Variables not populating:
- Confirm the variable syntax uses double curly brackets:
{{variable_name}} - Verify that
engine.variable.set()is called with the correct key. - Check that the scene is loaded before setting variables.
❌ Named blocks not found:
- Use
engine.block.getString(block, property = "name")to verify block names. - Make sure the name was set during template creation.
- Search within the correct block type using
engine.block.findByType().
❌ Missing fonts/images at runtime:
- Use an archive save to embed assets into a template for portability.
- Ensure that the asset URIs are reachable and stable.
- For local files, use
file:///android_asset/for assets in the app’s assets folder.
❌ Template won’t load:
- Verify the template string or archive file is not corrupted.
- Check that all required assets are accessible at the specified URIs.
- Ensure the CE.SDK version used to create the template matches the version loading it.
Next Steps#
Now that you can create templates, some related topics you may find helpful are:
- Generate scenes with templates as the source.
- Apply templates to existing scenes.
- Batch Processing — automate template population at scale.
- Multi-Image Generation — generate multiple variants from a single template.