Set up a two-surface integration where template creators have full editing access while template adopters can only modify designated areas.
Many integrations need two different editing experiences: one for designers who build templates, and one for end users who customize them. The Creator and Adopter roles make this possible—same CE.SDK, different permissions based on who’s using it. For detailed scope configuration patterns, see Lock Content.
The example builds a brand template with a logo and a headline, marks only the headline as editable, then switches between the Creator and Adopter roles to show how the effective permissions change.
Understanding the Two-Surface Pattern#
Template-based workflows typically involve two distinct user groups with different needs:
| Surface | Users | Role | What they can do |
|---|---|---|---|
| Creator Surface | Designers, admins | Creator |
Full editing—build templates, set locks |
| Adopter Surface | End users, marketers | Adopter |
Restricted editing—only modify unlocked areas |
This separation protects design intent while enabling customization. Each role installs its own global scope defaults: the Creator role sets every global scope to .allow, so block-level locks are never consulted. The Adopter role defers the global scopes to each block’s own settings, so the locks configured by creators decide what users can modify.
Setting Up the Creator Surface#
The Creator surface is where templates are built. Call engine.editor.setRole(_:) with "Creator" to give designers unrestricted access. New engine instances already start in the Creator role; the explicit call documents the surface’s intent. Read the active role back with engine.editor.getRole().
try engine.editor.setRole("Creator")
let activeRole = try engine.editor.getRole()print("Active role:", activeRole) // CreatorUnder the Creator role’s defaults, every operation is permitted regardless of block-level scope settings. This is where designers build the template layout, configure which elements should be editable with engine.block.setScopeEnabled(_:key:enabled:), and save the template for distribution.
Configuring What Users Can Edit#
The scope system controls what Adopters can modify. While in the Creator role, enable specific scopes on the blocks that should stay editable. Verify a block-level setting with engine.block.isScopeEnabled(_:key:).
try engine.block.setScopeEnabled(headline, key: "editor/select", enabled: true)try engine.block.setScopeEnabled(headline, key: "text/edit", enabled: true)
let headlineIsSelectable = try engine.block.isScopeEnabled(headline, key: "editor/select")print("Headline editor/select enabled:", headlineIsSelectable) // trueWhen Adopters load this template, they can edit the headline text but nothing else. The editor/select scope must be enabled for users to interact with a block at all. The logo keeps its defaults—blocks created under the Creator role start with every block-level scope disabled—so it stays locked for Adopters. For comprehensive scope configuration patterns, see Lock Content.
Setting Up the Adopter Surface#
The Adopter surface is where templates are used. Call engine.editor.setRole(_:) with "Adopter" to apply the restrictions configured by creators. Check the resulting permissions with engine.block.isAllowedByScope(_:key:), which evaluates the global and block-level settings together.
try engine.editor.setRole("Adopter")
let canEditHeadline = try engine.block.isAllowedByScope(headline, key: "text/edit")let canSelectLogo = try engine.block.isAllowedByScope(logo, key: "editor/select")let canMoveLogo = try engine.block.isAllowedByScope(logo, key: "layer/move")print("Adopter can edit the headline:", canEditHeadline) // trueprint("Adopter can select the logo:", canSelectLogo) // falseprint("Adopter can move the logo:", canMoveLogo) // falseThe Adopter role sets every global scope to .defer—except editor/add, which stays .allow so users can still add their own content. Blocks an Adopter creates start with all block-level scopes enabled, leaving users in full control of content they add themselves.
Scopes describe what users may do: query the effective permission with isAllowedByScope(_:key:) and use it to gate the editing controls you expose to users. Engine API calls from your own code aren’t blocked by scopes.
When to Use This Pattern#
This two-surface approach works well for:
- Brand template systems: Marketing teams customize approved templates
- Design approval workflows: Creators build, reviewers can’t accidentally modify
- Self-service customization: End users personalize within guardrails
- White-label products: Customers can only edit designated areas
For simpler use cases where all users have the same permissions, you may not need separate surfaces. For preview or approval surfaces that should allow no editing at all, set the "Viewer" role—it sets every global scope to .deny.
Switching Roles at Runtime#
The same engine instance can switch roles at any time, for example when a designer previews the Adopter experience. Each setRole(_:) call re-applies that role’s global scope defaults and overwrites any manual engine.editor.setGlobalScope(key:value:) customizations. Block-level scopes are untouched, so the locks configured in the Creator role persist across switches.
try engine.editor.setRole("Creator")
let creatorCanMoveLogo = try engine.block.isAllowedByScope(logo, key: "layer/move")let headlineStillEditable = try engine.block.isScopeEnabled(headline, key: "text/edit")print("Creator can move the logo:", creatorCanMoveLogo) // trueprint("Headline text/edit survived the switch:", headlineStillEditable) // trueTroubleshooting#
| Issue | Cause | Solution |
|---|---|---|
| Adopter can edit everything | The role is still Creator |
Call setRole(_:) with "Adopter" on the adopter surface |
| Adopter can’t edit anything | editor/select scope not enabled |
Enable editor/select on blocks users should interact with |
| Manual global scope settings disappear | setRole(_:) re-applies the role’s global defaults |
Re-apply setGlobalScope(key:value:) customizations after switching roles |
| Changes not persisting | Template not saved after scope changes | Save the template after configuring scopes in the Creator role |
API Reference#
| Method | Description |
|---|---|
engine.editor.setRole(_:) |
Set the editing role: "Creator", "Adopter" or "Viewer" |
engine.editor.getRole() |
Get the current editing role |
engine.block.setScopeEnabled(_:key:enabled:) |
Enable or disable a scope on a block |
engine.block.isScopeEnabled(_:key:) |
Check if a scope is enabled on a block |
engine.block.isAllowedByScope(_:key:) |
Check the effective permission after evaluating global and block-level settings |
Common Scopes#
| Scope | Description |
|---|---|
editor/select |
Allow selecting the block (required for any interaction) |
fill/change |
Allow changing the block’s fill (images, colors) |
text/edit |
Allow editing text content |
text/character |
Allow changing text formatting (font, size, color) |
layer/move |
Allow moving the block |
layer/resize |
Allow resizing the block |
layer/rotate |
Allow rotating the block |
layer/crop |
Allow cropping the block |
lifecycle/destroy |
Allow deleting the block |
Next Steps#
- Lock Content - Configure scope-based permissions to lock design elements
- Set Editing Constraints - Fine-tune what users can modify
- Placeholders - Mark editable image, video, or text areas within a locked template layout