Editing architecture

Help us improve this documentation

The framework-agnostic documentation is under development. If you have suggestions for improving the content, let us know by sharing your Feedback at the bottom of this page.

The SitecoreAI Page builder provides content authors with a WYSIWYG canvas for editing pages, but it doesn't render your site pages. Instead, it delegates rendering to your app by loading the app inside an iframe. When you enable visual editing, your app renders the page HTML, embeds structural metadata into the HTML, and returns the result to the Page builder.

Handshake endpoints

For your app and the Page builder to securely communicate, your app must expose two editing API endpoints that the Page builder will call:

  • /api/editing/config - the Page builder makes GET and OPTIONS requests first to confirm your app supports metadata editing mode and to get the list of registered components.
  • /api/editing/render - for every page view inside the editor, the Page builder loads the editing canvas by setting the iframe src to this endpoint with the relevant parameters in the URL. The browser makes the GET request, and your app uses those parameters to fetch the editing layout data from Sitecore, renders the full page HTML with metadata code blocks embedded, and returns the complete HTML document to the browser.

Note that the /api/editing/config and /api/editing/render paths shown in this documentation are conventional defaults, not fixed values:

  • The URL the Page builder calls for the config endpoint is what is stored in the Server side rendering engine application URL field on the corresponding item under /sitecore/system/Settings/Services/Rendering Hosts/*. Most implementations don't need to change this.
  • Unlike the config endpoint, the render endpoint's path is not configurable on the Rendering Host item.

Both endpoints are protected by a shared secret, typically named SITECORE_EDITING_SECRET.

Important

In addition to validating the shared secret, check the request's Origin header and restrict CORS responses to Sitecore's Page builder domains (for example, https://pages.sitecorecloud.io and https://app.sitecorecloud.io). Configure your Content-Security-Policy frame-ancestors directive the same way so that only the Page builder can load your app in an iframe. For an example implementation, see the CORS handling shown in the Enable visual editing walkthrough.

How editing works

When a content author opens a page in the Page builder, the following happens:

  1. The Page builder calls your app's /api/editing/config endpoint the first time it connects to an editing host. For example, when the Page builder first loads, or when you switch editing hosts. Your app validates the secret and returns a JSON document declaring "editMode": "metadata" and the component names your app can render. This documentation only covers metadata mode, the modern editing mode that replaces the legacy chromes mode.
  2. The Page builder opens the canvas by loading an iframe. The iframe's src points to your app's /api/editing/render endpoint, with query parameters that identify the page, site, language, and version being edited. Note that the render endpoint always returns a complete HTML document, not a fragment.
  3. Using the parameters, your app retrieves the editing layout data from the Preview GraphQL API. The response includes field values, the component tree, and editing-specific metadata for each field.
  4. Your app renders a complete HTML document, embedding <code> metadata blocks around every placeholder, component, and field value, along with the Sitecore-provided canvas scripts in <head>. The <head> must also include your app's own CSS and JavaScript assets to ensure the page renders with the correct styles and scripts in the editing canvas, just as it does on the published site. After the browser loads this HTML in the iframe, the Sitecore-provided canvas scripts activate editing overlays and scan the <code> markers to map on-screen elements to their Sitecore items.

The config response

Your /api/editing/config endpoint must return a JSON document with the following shape:

{
  "editMode": "metadata",
  "components": ["Promo", "Title", "RichText", "..."],
  "packages": {}
}
PropertyDescription
editModeMust be "metadata" to use metadata editing mode.
componentsThe list of Sitecore component (rendering) names your app can render. Each name must match the name of the corresponding rendering item in Sitecore.
packagesAn optional map of package versions. Leave this an empty object for custom apps.

The Page builder uses components to determine which renderings it can add to a page from your app. A rendering that isn't in this list can still exist in Sitecore, but content authors won't be able to add it to a page through the Page builder while editing with your app.

The editing GraphQL query

When rendering content in your app, you retrieve layout data by route path using the layout query. The editing query differs in two ways:

  • It looks up the page by item ID, not by route path.
  • It sends two additional HTTP headers.

Item ID

The Page builder identifies pages by their Sitecore item ID (GUID), not by route path. The editing query uses item(path: $itemId) and passes the GUID directly. It also retrieves the site dictionary alongside the layout data, so that any translation keys your components use are available when rendering the editing HTML:

query EditingQuery(
  $siteName: String!
  $itemId:   String!
  $language: String!
  $version:  String
  $pageSize: Int = 1000
  $after:    String
) {
  item(path: $itemId, language: $language, version: $version) {
    rendered
  }
  site {
    siteInfo(site: $siteName) {
      dictionary(language: $language, first: $pageSize, after: $after) {
        results { key value }
        pageInfo { endCursor hasNext }
      }
    }
  }
}

The rendered field returns layout data in the same JSON shape as the layout query used for content rendering. The $pageSize and $after variables support cursor-based pagination for sites with large dictionaries.

The site dictionary is a flat key-value store of translation strings managed in Sitecore. For example, button labels, form placeholders, and other UI text that content authors translate independently of component field content. The dictionary is returned with the layout data and is available for apps where component renderers need to resolve translation keys at render time. The sample apps in the Enable visual editing walkthrough retrieve the dictionary but don't consume it because their components don't contain translatable UI strings.

HTTP headers

The editing query sends two HTTP headers in addition to the standard x-sitecore-contextid header:

HeaderDescription
sc_editModeWhen "true", the Preview GraphQL API returns draft (unpublished) content and adds a metadata property to every field object in the layout data.

Set this value to "true" when the Page builder opens a page in edit mode, and "false" for preview mode.
sc_layoutKindThe layout type to return, which can be shared or final.

The default is final.

The metadata property that sc_editMode: "true" adds to each field object makes inline editing possible. The Page builder uses it to identify which field a content author is interacting with. Without this header, metadata is absent and field chromes cannot be activated.

For the complete query implementation in your framework, see Enable visual editing.

Metadata editing mode

Metadata editing mode is Sitecore's modern editor integration strategy. It uses metadata provided by the layout service for placeholders, renderings, and fields. The Page builder uses this metadata to identify editable layout elements and enable component selection and reordering.

In metadata editing mode, your app wraps every placeholder, component, and field in a pair of <code> elements. The Page builder's JavaScript reads these to identify which parts of the rendered HTML correspond to which Sitecore items and fields. This lets the Page builder build a map of editable regions. That map is what lets content authors click a component, see its selection handles, drag it to a new position, and open the component's fields panel.

Here's an example of what the rendered HTML looks like inside the editor:

<!-- Placeholder open -->
<code type="text/sitecore" chrometype="placeholder" class="scpm" kind="open" id="headless-main_<ROUTE_UID>"></code>

<div data-placeholder="headless-main">
    <!-- Component open -->
    <code type="text/sitecore" chrometype="rendering" class="scpm" kind="open" id="<UNIQUE_COMPONENT_ID>"></code>
    <div>
        <!-- Field open -->
        <code type="text/sitecore" chrometype="field" class="scpm" kind="open">{"type":"field render","fieldId":"...","fieldType":"Single-Line Text",...}</code>
        <h1>Hello World</h1>
        <!-- Field close -->
        <code type="text/sitecore" chrometype="field" class="scpm" kind="close"></code>
    </div>
    <!-- Component close -->
    <code type="text/sitecore" chrometype="rendering" class="scpm" kind="close"></code>
</div>

<!-- Placeholder close -->
<code type="text/sitecore" chrometype="placeholder" class="scpm" kind="close"></code>

Note the following about the id attributes:

  • The placeholder id attribute is <placeholderKey>_<parentUID>, where parentUID is the uid of the parent rendering or route item that owns the placeholder.
  • The component id attribute is the component's own uid from the layout data.

The Page builder uses these IDs to connect each on-screen element to a specific Sitecore item so it knows what to update when a content author makes a change.

Field chromes

Component-level <code> blocks let the Page builder select and reorder components. To let content authors also edit field values inline, for example, clicking directly on a headline or paragraph and typing, you must also wrap each individual field value in its own pair of <code> blocks. This additional layer activates the content-editable surface so a content author can click on text and type.

When sc_editMode: "true" is set in the GraphQL request, the layout data includes a metadata object on each field. The opening code block serializes that metadata as its text content. When the Page builder loads the page, it reads these blocks to activate a content-editable surface over each field value:

<!-- Field open - serialized field metadata is the text content -->
<code type="text/sitecore" chrometype="field" class="scpm" kind="open">{"type":"field render","fieldId":"...","fieldType":"Single-Line Text",...}</code>
<span>Hello World</span>
<!-- Field close -->
<code type="text/sitecore" chrometype="field" class="scpm" kind="close"></code>

Without field chromes, components are selectable in the canvas, but content authors cannot edit any fields inline. In other words, they can highlight text, but typing has no effect.

Editing scripts

Component and field chromes tell the Page builder what is on the page. The editing scripts tell it how to interact with the page.

When sc_editMode is "true", the sitecore.context object in the editing layout data response includes fields that the Page builder uses to enable editing interactions in the canvas. Your /api/editing/render endpoint must embed these fields in the HTML that it returns to the Page builder, in addition to your app's own CSS and JavaScript assets:

  • clientScripts - an array of JavaScript URLs. The Page builder loads these scripts into the canvas iframe to attach editing overlays and interaction handlers. Without them, the canvas loads your page but has no way to receive or send editing events.
  • clientData - a map of two values your app must write into <script> tags with specific IDs:
    • hrz-canvas-state - a JSON object that tells the editing scripts which item, site, language, and layout mode the canvas is displaying.
    • hrz-canvas-verification-token - a token that authenticates postMessage messages between the iframe and the Page builder. Without it, the Page builder ignores signals from the iframe.

The HTML your app renders must embed these in the <head> and <script> tags:

<!-- JavaScript bundles provided by Sitecore (exact URLs vary per environment) -->
<script type="text/javascript" src="https://..."></script>

<!-- Canvas state that the Page builder reads on load -->
<script id="hrz-canvas-state" type="application/json">{"itemId":"...","siteName":"...",...}</script>

<!-- Verification token for the canvas communication channel -->
<script id="hrz-canvas-verification-token" type="application/json">TOKEN</script>

The HTML your app renders must also include a <script> element with id="jss-hrz-editing" in the <body>. The Page builder checks for this element to confirm the app is responding in metadata editing mode:

<script id="jss-hrz-editing" type="application/json">{}</script>

Query parameters for the render endpoint

When the Page builder loads the editing canvas, it sets the iframe src to your app's /api/editing/render URL. The browser then makes a GET request with the following query parameters:

Query parameterDescription
secretRequired.

Must match SITECORE_EDITING_SECRET.
sc_siteRequired.

The site name.

Example: my-site
sc_itemidRequired.

The Sitecore item ID (GUID) of the page being edited.
sc_langRequired.

The page's language code.

Example: en
routeRequired.

The route path to the page.

Example: / and /products
modeRequired.

edit for the editing canvas, or preview for preview mode.
sc_versionOptional.

Item version. Use a numeric version number, or latest to explicitly request the most recent version. If unset, the latest version of the item is retrieved.
sc_variantOptional.

Personalization variant ID.
sc_layoutKindOptional.

The layout type, which can be shared or final.

The default is final.

Example request

Here's an example GET request that the Page builder makes to the render endpoint:

/api/editing/render?secret=<SITECORE_EDITING_SECRET>&sc_site=my-site&sc_itemid=54C8E9B5-0B2C-5363-8FA6-D32A3A302F51&sc_lang=en&route=/&mode=edit&sc_version=latest

The sc_version, sc_variant, and sc_layoutKind parameters are all optional and may be omitted.

If you have suggestions for improving this article, let us know!