CE.SDK controls editing access through roles and scopes, enabling template workflows where designers create locked layouts and end-users customize only permitted elements.
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"let role = try engine.editor.getRole()print("Current role:", role) // "Creator"
// Switch to a different roletry engine.editor.setRole("Adopter")print("New role:", try engine.editor.getRole()) // "Adopter"
// Switch back to Creator for the rest of the guidetry 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:
.allow— Always permit the operation.deny— Always block the operation.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 .defer.
// Set global scopes to 'Defer' so block-level settings take effecttry engine.editor.setGlobalScope(key: "editor/select", value: .defer)try engine.editor.setGlobalScope(key: "layer/move", value: .defer)try engine.editor.setGlobalScope(key: "text/edit", value: .defer)try engine.editor.setGlobalScope(key: "lifecycle/destroy", value: .defer)
// Query a global scope valuelet moveScope = try engine.editor.getGlobalScope(key: "layer/move")print("Global 'layer/move' scope:", moveScope) // .defer
// List all available scopeslet allScopes = try engine.editor.findAllScopes()print("Available scopes:", allScopes.count)To lock a specific block, disable its scopes:
// Lock the block — Adopters cannot select, move, or delete ittry engine.block.setScopeEnabled(block, key: "editor/select", enabled: false)try engine.block.setScopeEnabled(block, key: "layer/move", enabled: false)try engine.block.setScopeEnabled(block, key: "lifecycle/destroy", enabled: false)
// Query a block-level scopelet canMove = try engine.block.isScopeEnabled(block, key: "layer/move")print("Block 'layer/move' enabled:", canMove) // falsePermission Resolution#
Permissions resolve in this order:
- Role defaults — Each role has preset global scope values
- Global scope — If
.allowor.deny, this is the final answer - Block-level scope — If global is
.defer, check the block’s settings
Use isAllowedByScope(_:key:) to check the final computed permission for any block and scope combination:
// Check the final resolved permission (role + global + block scopes)let isAllowed = try engine.block.isAllowedByScope(block, key: "layer/move")print("Moving allowed:", isAllowed) // false (global is .defer, block is disabled)Switching Roles#
Change roles at runtime with setRole(_:). When switching to Adopter, block-level restrictions take effect. Switching back to Creator restores full access.
// Switch to Adopter — restrictions now applytry engine.editor.setRole("Adopter")
let isAllowedAsAdopter = try engine.block.isAllowedByScope(block, key: "layer/move")print("Moving allowed as Adopter:", isAllowedAsAdopter) // false
// Switch back to Creator — full access restoredtry engine.editor.setRole("Creator")
let isAllowedAsCreator = try engine.block.isAllowedByScope(block, key: "layer/move")print("Moving allowed as Creator:", isAllowedAsCreator) // trueCustomizing Role Behavior#
The onRoleChanged property provides an AsyncStream<String> that fires after role defaults are applied. Use it to customize scopes per role:
// Subscribe to role changesTask { for await role in engine.editor.onRoleChanged { if role == "Adopter" { // Enable filters for adopters even though normally restricted try engine.editor.setGlobalScope(key: "appearance/filter", value: .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.
Implementation Guides#
For detailed implementation, see this guide:
Lock Design Elements — Step-by-step instructions for locking specific elements in templates