CE.SDK controls editing access through roles and scopes, enabling template workflows where designers create locked layouts and end-users customize only the permitted parts.
The Kotlin snippets below assume you already have an Engine instance on the main thread.
CE.SDK uses a two-tier permission system: roles define user types with preset permissions, while scopes control specific capabilities. This enables workflows where templates can be prepared by designers and safely customized by end-users.
This guide covers:
- The four user roles and their purposes
- How scopes control editing capabilities
- The permission resolution hierarchy
- Common template workflow patterns
Roles#
Roles define user types with different default permissions:
| Role | Purpose | Default Access |
|---|---|---|
| Creator | Designers building templates | Full access to all operations |
| Adopter | End-users customizing templates | Limited by block-level scopes |
| Viewer | Static preview without interaction | Read-only, no playback controls |
| Presenter | Presenting slideshows or playing videos | Read-only with playback and navigation |
Creators set the block-level scopes that constrain what Adopters can do. This separation enables brand consistency while allowing personalization.
// Roles define user types: "Creator", "Adopter", "Viewer", "Presenter".val role = engine.editor.getRole()println("Current role: $role") // "Creator"
engine.editor.setRole("Adopter")val adopterRole = engine.editor.getRole()println("Preview role: $adopterRole") // "Adopter"
engine.editor.setRole("Creator")Scopes#
Scopes define specific capabilities organized into categories:
- Text: Editing content and character formatting
- Fill/Stroke: Changing colors and shapes
- Layer: Moving, resizing, rotating, cropping
- Appearance: Filters, effects, shadows, animations
- Lifecycle: Deleting and duplicating elements
- Editor: Adding new elements and selecting
Global vs Block-Level Scopes#
Global scopes apply editor-wide and determine whether block-level settings are checked:
GlobalScope.ALLOW— Always permit the operationGlobalScope.DENY— Always block the operationGlobalScope.DEFER— Check block-level scope settings
Block-level scopes control permissions on individual blocks. These settings only take effect when the corresponding global scope is set to GlobalScope.DEFER.
// Defer to the block-level settings so the template controls the Adopter experience.engine.editor.setGlobalScope(key = "editor/select", globalScope = GlobalScope.DEFER)engine.editor.setGlobalScope(key = "layer/move", globalScope = GlobalScope.DEFER)engine.editor.setGlobalScope(key = "text/edit", globalScope = GlobalScope.DEFER)engine.editor.setGlobalScope(key = "text/character", globalScope = GlobalScope.DEFER)engine.editor.setGlobalScope(key = "lifecycle/destroy", globalScope = GlobalScope.DEFER)
val moveScope = engine.editor.getGlobalScope(key = "layer/move")val allScopes = engine.editor.findAllScopes()println("Global 'layer/move' scope: $moveScope")println("Available scopes: ${allScopes.count()}")To lock a specific block, disable its scopes:
engine.block.setScopeEnabled(page, key = "editor/select", enabled = false)engine.block.setScopeEnabled(page, key = "layer/move", enabled = false)engine.block.setScopeEnabled(page, key = "lifecycle/destroy", enabled = false)
engine.block.setScopeEnabled(background, key = "editor/select", enabled = false)engine.block.setScopeEnabled(background, key = "layer/move", enabled = false)engine.block.setScopeEnabled(background, key = "lifecycle/destroy", enabled = false)
// Locked brand elements stay fixed for Adopters.engine.block.setScopeEnabled(brandBanner, key = "editor/select", enabled = false)engine.block.setScopeEnabled(brandBanner, key = "layer/move", enabled = false)engine.block.setScopeEnabled(brandBanner, key = "lifecycle/destroy", enabled = false)
engine.block.setScopeEnabled(companyName, key = "editor/select", enabled = false)engine.block.setScopeEnabled(companyName, key = "layer/move", enabled = false)engine.block.setScopeEnabled(companyName, key = "text/edit", enabled = false)engine.block.setScopeEnabled(companyName, key = "text/character", enabled = false)engine.block.setScopeEnabled(companyName, key = "lifecycle/destroy", enabled = false)
// Keep the attendee name editable but fixed in place.engine.block.setScopeEnabled(attendeeName, key = "editor/select", enabled = true)engine.block.setScopeEnabled(attendeeName, key = "layer/move", enabled = false)engine.block.setScopeEnabled(attendeeName, key = "text/edit", enabled = true)engine.block.setScopeEnabled(attendeeName, key = "text/character", enabled = false)engine.block.setScopeEnabled(attendeeName, key = "lifecycle/destroy", enabled = false)In the example template, the brand banner and company name are locked while the attendee name keeps editor/select and text/edit enabled so adopters can personalize the template without moving or deleting anything.
Permission Resolution#
Permissions resolve in this order:
- Role defaults — Each role has preset global scope values
- Global scope — If
GlobalScope.ALLOWorGlobalScope.DENY, this is the final answer - Block-level scope — If global is
GlobalScope.DEFER, check the block’s settings
Use isAllowedByScope() to check the final computed permission for any block and scope combination:
engine.editor.setRole("Creator")
val creatorCanSelectBrandBanner = engine.block.isAllowedByScope(brandBanner, key = "editor/select")val creatorCanEditAttendeeName = engine.block.isAllowedByScope(attendeeName, key = "text/edit")
println("Creator can select the banner: $creatorCanSelectBrandBanner") // trueprintln("Creator can edit the attendee name: $creatorCanEditAttendeeName") // trueSwitching Roles#
Change roles at runtime with setRole(). When switching to Adopter, block-level restrictions take effect. Switching back to Creator restores full access.
engine.editor.setRole("Adopter")
val adopterCanSelectBrandBanner = engine.block.isAllowedByScope(brandBanner, key = "editor/select")val adopterCanEditAttendeeName = engine.block.isAllowedByScope(attendeeName, key = "text/edit")
println("Adopter can select the banner: $adopterCanSelectBrandBanner") // falseprintln("Adopter can edit the attendee name: $adopterCanEditAttendeeName") // true
engine.editor.setRole("Creator")Customizing Role Behavior#
onRoleChanged() returns a Flow<String> that emits after role defaults are applied. Collect it before switching roles when you need to override selected scopes for a role:
fun customizeEditingWorkflowRoles( engine: Engine, scope: CoroutineScope,): Job = scope.launch(start = CoroutineStart.UNDISPATCHED) { engine.editor.onRoleChanged().collect { role -> if (role == "Adopter") { engine.editor.setGlobalScope(key = "appearance/filter", globalScope = GlobalScope.ALLOW) engine.editor.setGlobalScope(key = "appearance/effect", globalScope = GlobalScope.ALLOW) } }}Template Workflow Pattern#
A typical template workflow:
- Designer (Creator) creates the template layout
- Designer locks brand elements using block scopes
- Designer keeps personalization fields editable
- End-user (Adopter) opens the template
- End-user edits only permitted elements
- End-user exports the personalized result
This pattern ensures brand consistency while enabling personalization. The Android example creates a small template with locked brand elements and one editable personalization field:
val brandBanner = engine.block.create(DesignBlockType.Graphic)engine.block.setName(brandBanner, BRAND_BANNER_NAME)engine.block.setShape(brandBanner, shape = engine.block.createShape(ShapeType.Rect))engine.block.setWidth(brandBanner, value = 640F)engine.block.setHeight(brandBanner, value = 220F)engine.block.setPositionX(brandBanner, value = 40F)engine.block.setPositionY(brandBanner, value = 48F)engine.block.setFill(brandBanner, fill = engine.block.createFill(FillType.Color))engine.block.setColor( block = engine.block.getFill(brandBanner), property = "fill/color/value", value = Color.fromHex("#FF0B1220"),)engine.block.appendChild(parent = page, child = brandBanner)
val companyName = engine.block.create(DesignBlockType.Text)engine.block.setName(companyName, COMPANY_NAME)engine.block.setWidthMode(companyName, mode = SizeMode.AUTO)engine.block.setHeightMode(companyName, mode = SizeMode.AUTO)engine.block.setPositionX(companyName, value = 88F)engine.block.setPositionY(companyName, value = 122F)engine.block.replaceText(companyName, text = "IMGLY Labs")engine.block.setTextColor(companyName, color = Color.fromHex("#FFFFFFFF"))engine.block.appendChild(parent = page, child = companyName)
val attendeeName = engine.block.create(DesignBlockType.Text)engine.block.setName(attendeeName, ATTENDEE_NAME)engine.block.setWidthMode(attendeeName, mode = SizeMode.AUTO)engine.block.setHeightMode(attendeeName, mode = SizeMode.AUTO)engine.block.setPositionX(attendeeName, value = 88F)engine.block.setPositionY(attendeeName, value = 404F)engine.block.replaceText(attendeeName, text = "Alex Morgan")engine.block.setTextColor(attendeeName, color = Color.fromHex("#FF0B1220"))engine.block.setBackgroundColor(attendeeName, color = Color.fromRGBA(231, 240, 255, 255))engine.block.setBackgroundColorEnabled(attendeeName, enabled = true)engine.block.setFloat(attendeeName, property = "backgroundColor/paddingLeft", value = 24F)engine.block.setFloat(attendeeName, property = "backgroundColor/paddingTop", value = 20F)engine.block.setFloat(attendeeName, property = "backgroundColor/paddingRight", value = 24F)engine.block.setFloat(attendeeName, property = "backgroundColor/paddingBottom", value = 20F)engine.block.setFloat(attendeeName, property = "backgroundColor/cornerRadius", value = 18F)engine.block.appendChild(parent = page, child = attendeeName)Troubleshooting#
- Block-level restrictions do not apply — Set the matching global scope to
GlobalScope.DEFER;GlobalScope.ALLOWandGlobalScope.DENYbypass block settings. - Role-specific overrides disappear after switching roles — Apply custom scope changes from
onRoleChanged()because role defaults are applied during the role switch. - A block cannot be selected at all — Check
editor/select; disabling that scope prevents interaction before other scope checks can matter.
API Reference#
| API | Purpose |
|---|---|
engine.editor.setRole(role=_) | Set the active user role. |
engine.editor.getRole() | Read the active user role. |
engine.editor.onRoleChanged() | Collect role changes after role defaults are applied. |
engine.editor.setGlobalScope(key="layer/move",globalScope=_) | Allow, deny, or defer an operation globally. |
engine.editor.getGlobalScope(key="layer/move") | Read the global state for one scope. |
engine.editor.findAllScopes() | List all scope keys supported by the engine. |
engine.block.setScopeEnabled(block=_,key="text/edit",enabled=_) | Enable or disable one scope on a block. |
engine.block.isScopeEnabled(block=_,key="text/edit") | Read the block-level scope flag. |
engine.block.isAllowedByScope(block=_,key="text/edit") | Check the final resolved permission for a block. |
Next Steps#
- Lock Design Elements — Step-by-step instructions for locking specific elements in templates