Generate multiple product variants — different colors, sizes or copy — from a single design template using the CE.SDK Engine API in Swift.
What You’ll Learn#
- Define a data model to describe product attribute combinations.
- Load a design template and discover its available variables.
- Replace text placeholders and swap product images for each variant.
- Export each variation as JPEG.
When to Use It#
Use product variations when a single product needs multiple visual representations — for example, a t-shirt in five colors or a sneaker in three sizes. Each variant shares the same layout but differs in text, images or styling.
For generating many images from different data records, see Batch Processing. For producing multiple layout formats from one record, see Multi-Image Generation.
Define the Data Model#
Start by modeling your product variants. Each entry represents one combination of attributes to apply to the template.
struct ProductVariant { let color: String let size: String let price: String let imageURL: URL}
let variants: [ProductVariant] = [ ProductVariant( color: "Midnight Black", size: "M", price: "$29.99", imageURL: URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!, ), ProductVariant( color: "Ocean Blue", size: "L", price: "$34.99", imageURL: URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!, ),]The imageURL points to the product photo for that color variant. In production, you’d load these from an API or database.
Create a Template#
Product variation templates use text variables (wrapped in {{double braces}}) and named image blocks as placeholders. You can create templates in the Web CE.SDK editor and save them as archives, or build them on any platform programmatically (see example below).
let scene = try engine.scene.create()let page = try engine.block.create(.page)try engine.block.appendChild(to: scene, child: page)try engine.block.setWidth(page, value: 500)try engine.block.setHeight(page, value: 500)
let text = try engine.block.create(.text)try engine.block.appendChild(to: page, child: text)try engine.block.setWidth(text, value: 400)try engine.block.setHeight(text, value: 50)try engine.block.setPositionX(text, value: 50)try engine.block.setPositionY(text, value: 50)try engine.block.replaceText(text, text: "{{ProductName}} – {{ProductColor}}")
let priceText = try engine.block.create(.text)try engine.block.appendChild(to: page, child: priceText)try engine.block.setWidth(priceText, value: 200)try engine.block.setHeight(priceText, value: 40)try engine.block.setPositionX(priceText, value: 50)try engine.block.setPositionY(priceText, value: 120)try engine.block.replaceText(priceText, text: "{{ProductPrice}}")
let imageBlock = try engine.block.create(.graphic)try engine.block.appendChild(to: page, child: imageBlock)try engine.block.setWidth(imageBlock, value: 300)try engine.block.setHeight(imageBlock, value: 300)try engine.block.setPositionX(imageBlock, value: 100)try engine.block.setPositionY(imageBlock, value: 180)try engine.block.setName(imageBlock, name: "ProductImage")let imageFill = try engine.block.createFill(.image)try engine.block.setFill(imageBlock, fill: imageFill)try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/imgly_logo.jpg",)
// Save template as a string for further exportlet templateString = try await engine.scene.saveToString()Text blocks containing {{ProductName}}, {{ProductColor}} and {{ProductPrice}} automatically register as variables. Named image blocks like "ProductImage" let you swap fills by name.
Discover Template Variables#
Before processing, verify which variables the template exposes. This is useful for validating data against the template at runtime.
let variableKeys = engine.variable.findAll()print("Template variables: \(variableKeys)")// Expected: ["ProductName", "ProductColor", "ProductPrice"]findAll() returns the keys of all registered text variables. Use this to confirm your data model covers every placeholder before starting a batch run.
Generate Variations#
Loop through each variant, reload the template, populate variables, then export.
for variant in variants { // Reload the template for each variant try await engine.scene.load(from: templateString)
// Set text variables for this variant try engine.variable.set(key: "ProductName", value: "Classic Tee") try engine.variable.set(key: "ProductColor", value: variant.color) try engine.variable.set(key: "ProductPrice", value: variant.price)
// Replace the product image by finding the block by name if let block = engine.block.find(byName: "ProductImage").first { let fill = try engine.block.getFill(block) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: variant.imageURL.absoluteString, ) }
// Export the current variation guard let exportPage = try engine.block.find(byType: .page).first else { continue } let blob = try await engine.block.export(exportPage, mimeType: .jpeg)
// Save to a file let dir = FileManager.default.temporaryDirectory let fileName = "product-\(variant.color.lowercased().replacingOccurrences(of: " ", with: "-"))-\(variant.size).jpg" let fileURL = dir.appendingPathComponent(fileName) try blob.write(to: fileURL)}Set Text Variables#
Use engine.variable.set(key:value:) to replace each placeholder with the variant’s data:
// Set text variables for this varianttry engine.variable.set(key: "ProductName", value: "Classic Tee")try engine.variable.set(key: "ProductColor", value: variant.color)try engine.variable.set(key: "ProductPrice", value: variant.price)All variable values are strings. Convert numbers or prices to their display format before setting them.
Replace the Product Image#
Find the image block by its name and update its fill URI:
// Replace the product image by finding the block by nameif let block = engine.block.find(byName: "ProductImage").first { let fill = try engine.block.getFill(block) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: variant.imageURL.absoluteString, )}The block name "ProductImage" was assigned when the template was created. Using names keeps automation readable compared to referencing block IDs directly.
Export the Variant#
Export the populated page as JPEG and write it to disk:
// Export the current variationguard let exportPage = try engine.block.find(byType: .page).first else { continue }let blob = try await engine.block.export(exportPage, mimeType: .jpeg)
// Save to a filelet dir = FileManager.default.temporaryDirectorylet fileName = "product-\(variant.color.lowercased().replacingOccurrences(of: " ", with: "-"))-\(variant.size).jpg"let fileURL = dir.appendingPathComponent(fileName)try blob.write(to: fileURL)You can export as PNG, PDF or other formats by changing the mimeType parameter. See the Export guide for all available options.
Next Steps#
Product variations are one pattern for automating design output. Explore related guides:
- Batch Processing — process many data records at once.
- Multi-Image Generation — create multiple layout formats from one record.
- Text Variables — deep dive into the variable system.
- Placeholders — work with placeholder blocks.