---
title: Composition properties
slug: cms-saas/docs/composition-properties
createdAt: 2026-10-13T18:02:44.000Z
---

Composition properties give a single [Visual Builder](docId\:fw42GyhbrcH5AIDDT18VZ) experience several independent layouts, such as a main canvas and an aside. Content managers edit each layout separately.

A composition holds the layout of an experience or section. It stores the structure of the layout's nodes, the display templates applied to them, and the components they contain. Every experience and section content type has one built-in composition. Experience content types can also declare additional composition properties.

This topic describes how to configure compositions through the [CMS (SaaS) REST API](docId\:UBbVVvgyfOQzyLOTn4KMH). It covers a composition's layout, the content types it can contain, and how to read and write its value. For the structure of the composition value itself, see [Configure Visual Builder](docId:0o9L75GP0gCgPCxNO1CvS).

## Prerequisites

Before you start, make sure you have the following:

- **A REST API token** – Create an API key in **Settings** > **API Keys**, and use it to request a token. See [API keys](docId:1a70jiCnu-fn8T-XQaNZ6) and [Authentication and authorization](docId:2uVhCnJr4BdL9e5UNkN4L).
- **The right scopes, if your token is restricted** – Configuring compositions on a content type requires the `contenttypes:admin` scope. Writing composition values requires the `content:admin` scope.

## Built-in and custom compositions

Compositions come in two kinds, and you configure each kind in a different place on the content type.

| Aspect                         | Built-in composition                           | Custom composition property                                         |
| ------------------------------ | ---------------------------------------------- | ------------------------------------------------------------------- |
| Base types                     | `_experience` and `_section`                   | `_experience` only                                                  |
| Number per content type        | Exactly one, always present                    | Any number, opt-in                                                  |
| Configured through             | The `composition` field on the content type    | A property of type `composition` in the content type's `properties` |
| Value read and written through | The `composition` field on the content version | `properties.PROPERTY_KEY.value` on the content version              |
| Editing surface                | The main Visual Builder canvas                 | A separate canvas the content manager switches to                   |

Built-in and custom compositions use the same value shape: a tree of composition nodes. CMS (SaaS) indexes each one to [Optimizely Graph](docId:1bX4s5MQJssocEnQERGi7) as a `CompositionStructureNode`. Nothing in the value distinguishes one kind from the other.

:::hint{type="info"}
**Note**
Only experience content types support custom composition properties. A section content type can use only its built-in composition. A composition property also cannot be an item in an `array` property. The key `composition` is reserved for the built-in composition, so you cannot declare a property with that key.
:::

## Layout types

A composition's layout type determines which nodes it can contain.

- `outline` – A flat, ordered list of sections or section-enabled components. The default for experiences.
- `grid` – A hierarchical structure of rows, columns, and elements. The default for sections.
- `form` – The structure of a form: steps, rows, columns, and form elements. Available when you activate Optimizely Forms. See [Activate forms](docId\:HcnXoFfbjtj0xLEqE2WQ9).

Set the layout type with the `format` field on the content type's `composition` field or on a `composition` property. If you omit `format`, the composition uses the default layout type for the base type.

The layout type also decides the shape of the value:

- With `outline`, the root node holds a flat, ordered list of section nodes and section-enabled component nodes. A section node carries its own composition, which is where its rows, columns, and components live.
- With `grid`, the root node holds rows directly, and each row holds columns, which hold components.

:::hint{type="warning"}
**Important**
You cannot change the `format` of a composition after content that uses it exists. Such a request fails with `The layout type of 'PROPERTY_KEY' cannot be changed from 'CURRENT_LAYOUT_TYPE' to 'NEW_LAYOUT_TYPE' because the property already has content.`
:::

## Add a custom composition property

Declare the property in the content type's `properties` with the data type `composition`. The following example creates an experience content type with two compositions. The built-in composition uses `outline`, and an extra `sidebar` composition uses `grid`.

```http
POST https://api.cms.optimizely.com/v1/contenttypes
Content-Type: application/json
```

```json
{
    "key": "landingPage",
    "baseType": "_experience",
    "displayName": "Landing page",
    "composition": {
        "format": "outline"
    },
    "properties": {
        "sidebar": {
            "type": "composition",
            "format": "grid",
            "displayName": "Sidebar",
            "description": "A secondary canvas rendered beside the main content.",
            "sortOrder": 10
        }
    }
}
```

A `GET` of the content type returns the property in the same shape. The built-in composition is a system property, so it never appears under `properties`. The content type's `composition` field reports its settings instead. The response omits that field when the content type does not override the built-in composition.

See [Create content type API reference](docId\:WFhEMMql5Y0x5-zT_t6df).

## Configure the built-in composition

Use the content type's `composition` field to configure the built-in composition. Use it to override the layout type and restrict which content types the composition can contain. It applies only to content types with base type `_experience` or `_section`.

The following request changes the built-in composition of `landingPage` to `grid` and limits it to `heroBlock` and element content types.

```http
PATCH https://api.cms.optimizely.com/v1/contenttypes/landingPage
Content-Type: application/merge-patch+json
```

```json
{
    "composition": {
        "format": "grid",
        "allowedTypes": [ "heroBlock", "_element" ]
    }
}
```

Set `composition` to `null` to remove the override and fall back to the default layout type for the base type.

See [Update content type API reference](docId\:BJ7oMWSDd-DJV0G7U7J2y).

## Restrict the types a composition can contain

Restrict a composition's content types so content managers can add only the content types that belong in that layout. Built-in compositions and custom composition properties support `allowedTypes` and `restrictedTypes`. These are the same validation fields that `content` and `contentReference` properties use. See [Validation fields](docId\:IIrc48HRxDZe6C7wiVBVT).

- `allowedTypes` – Content types that the composition can contain. An empty or omitted array means no allow list.
- `restrictedTypes` – Content types that the composition must not contain. It takes priority over `allowedTypes`.

Each entry is a content type key or a base type, such as `_component` or `_section`. To allow every content type that fulfills a [contract](docId\:lZMH0gXdg1ukN34zOWid0), list the contract.

The following example allows `ctaElement` and `imageElement` in the `sidebar` composition and restricts `videoElement`.

```json
{
    "key": "landingPage",
    "baseType": "_experience",
    "displayName": "Landing page",
    "properties": {
        "sidebar": {
            "type": "composition",
            "format": "grid",
            "allowedTypes": [ "ctaElement", "imageElement" ],
            "restrictedTypes": [ "videoElement" ]
        }
    }
}
```

CMS (SaaS) checks the restriction against the section and component nodes that the composition contains directly. This check covers inline nodes and nodes that use a content reference. A section node is a boundary. CMS (SaaS) checks the section itself against the composition's types, but not the section's contents. The section's own composition constrains those contents, and CMS (SaaS) validates them when the section is saved.

:::hint{type="info"}
**Note**
CMS (SaaS) validates every item in a composition on every save, not only the items you changed. If you tighten `allowedTypes` after content exists, you can still read that content. The next save fails until you remove the items that are no longer allowed.
:::

### Layout compatibility

CMS (SaaS) rejects an allow list whose types can never appear in the configured layout. For example, only sections and section-enabled components can appear in an `outline`. The following request allows `_page` in an `outline` composition, so it fails. The error path is `properties.sidebar.allowedTypes`, and the message is `The type '_page' cannot be used in a 'outline' layout composition.`

```json
{
    "properties": {
        "sidebar": {
            "type": "composition",
            "format": "outline",
            "allowedTypes": [ "_page" ]
        }
    }
}
```

To let a component appear in a composition, declare the matching composition behavior on its content type:

- `sectionEnabled` – For the section level of an outline.
- `elementEnabled` – For the element level of a grid.

See [Standard fields](docId\:IIrc48HRxDZe6C7wiVBVT).

## Set a composition value

Write the built-in composition through the `composition` field of the content version. Write a custom composition property through `properties.PROPERTY_KEY.value`. Both fields take the same node tree.

The following request creates a `landingPage` experience with a value for each composition.

```http
POST https://api.cms.optimizely.com/v1/content
Content-Type: application/json
```

```json
{
    "contentType": "landingPage",
    "container": "43f936c99b234ea397b261c538ad07c9",
    "initialVersion": {
        "displayName": "Autumn campaign",
        "locale": "en",
        "composition": {
            "nodeType": "experience",
            "layoutType": "outline",
            "nodes": [
                {
                    "displayName": "Hero",
                    "nodeType": "section",
                    "layoutType": "grid",
                    "component": {
                        "contentType": "BlankSection"
                    }
                }
            ]
        },
        "properties": {
            "sidebar": {
                "value": {
                    "nodeType": "experience",
                    "layoutType": "grid",
                    "nodes": [
                        {
                            "displayName": "A row",
                            "nodeType": "row",
                            "nodes": [
                                {
                                    "displayName": "A column",
                                    "nodeType": "column",
                                    "nodes": [
                                        {
                                            "displayName": "A call to action",
                                            "nodeType": "component",
                                            "component": {
                                                "contentType": "ctaElement",
                                                "properties": {
                                                    "heading": {
                                                        "value": "Book a demo"
                                                    }
                                                }
                                            }
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            }
        }
    }
}
```

The root node of a composition reflects the content that owns it. Because `sidebar` belongs to an experience, its root node type is `experience`, even though its layout type is `grid`. Its rows sit directly under the root, with no section level in between.

See [Create content API reference](docId\:p9XgSvFtr8nSot5JGYOq0).

## Update a single composition

CMS (SaaS) stores each composition independently, so a partial update of one leaves the others untouched. The following request replaces the `sidebar` composition without affecting the built-in composition or any other composition property.

```http
PATCH https://api.cms.optimizely.com/v1/content/6946107a8ad6414f8f1786364dab1ec2/versions/1253
Content-Type: application/merge-patch+json
```

```json
{
    "properties": {
        "sidebar": {
            "value": {
                "nodeType": "experience",
                "layoutType": "grid",
                "nodes": [ ]
            }
        }
    }
}
```

## Query a composition property

CMS (SaaS) indexes each custom composition property to Optimizely Graph as a field named after the property key. The field has the type `CompositionStructureNode`, the same type as the built-in `composition` field. Query both in the same request and reuse the same fragment for each. See [Visual Builder queries](docId\:c9SHk7bXcUedwuW2icb72).

## Composition error messages

The following table lists the errors specific to compositions. The error path points at `composition` for the built-in composition and at `properties.PROPERTY_KEY` for a custom composition property.

| Error path                                                         | Message                                                                                                                                                                                                    | Cause                                                                                      |
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `composition`                                                      | `The 'composition' field is only supported on content types with base type '_experience' or '_section'.`                                                                                                   | The request sets the `composition` field on another base type.                             |
| `properties.PROPERTY_KEY`                                          | `Custom properties of type 'PropertyComposition' such as 'PROPERTY_KEY' are only supported on Experience content types. Section content types may use only the inherited built-in 'Composition' property.` | The request declares a `composition` property on a content type that is not an experience. |
| `composition.format`, `properties.PROPERTY_KEY.format`             | `The layout type 'LAYOUT_TYPE' is not a registered layout type.`                                                                                                                                           | `format` is not one of the available layout types.                                         |
| `composition.format`, `properties.PROPERTY_KEY.format`             | `The layout type of 'PROPERTY_KEY' cannot be changed from 'CURRENT_LAYOUT_TYPE' to 'NEW_LAYOUT_TYPE' because the property already has content.`                                                            | The request changes the layout type after content uses the composition.                    |
| `composition.allowedTypes`, `properties.PROPERTY_KEY.allowedTypes` | `The allowedTypes field contains one or more invalid content types: 'CONTENT_TYPE_KEY'.`                                                                                                                   | An entry does not match a content type key or base type.                                   |
| `composition.allowedTypes`, `properties.PROPERTY_KEY.allowedTypes` | `The type 'CONTENT_TYPE_KEY' cannot be used in a 'LAYOUT_TYPE' layout composition.`                                                                                                                        | An allowed type can never appear in the configured layout type.                            |
