Search Docs
Loading...
Skip to content

Editing Workflow

CE.SDK controls editing access through roles and scopes, enabling template workflows where designers create locked layouts and end-users customize only permitted elements.

5 mins
estimated time
GitHub

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:

RolePurposeDefault Access
CreatorDesigners building templatesFull access to all operations
AdopterEnd-users customizing templatesLimited by block-level scopes
ViewerStatic preview without interactionRead-only, no playback controls
PresenterPresenting slideshows or playing videosRead-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 role
try engine.editor.setRole("Adopter")
print("New role:", try engine.editor.getRole()) // "Adopter"
// Switch back to Creator for the rest of the guide
try 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 effect
try 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 value
let moveScope = try engine.editor.getGlobalScope(key: "layer/move")
print("Global 'layer/move' scope:", moveScope) // .defer
// List all available scopes
let 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 it
try 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 scope
let canMove = try engine.block.isScopeEnabled(block, key: "layer/move")
print("Block 'layer/move' enabled:", canMove) // false

Permission Resolution#

Permissions resolve in this order:

  1. Role defaults — Each role has preset global scope values
  2. Global scope — If .allow or .deny, this is the final answer
  3. 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 apply
try 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 restored
try engine.editor.setRole("Creator")
let isAllowedAsCreator = try engine.block.isAllowedByScope(block, key: "layer/move")
print("Moving allowed as Creator:", isAllowedAsCreator) // true

Customizing 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 changes
Task {
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:

  1. Designer (Creator) creates the template layout
  2. Designer locks brand elements using block scopes
  3. Designer keeps personalization fields editable
  4. End-user (Adopter) opens the template
  5. End-user edits only permitted elements
  6. 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