Editor Interfaces
An editor interface represents the look and feel of the entry editor in the web app. They are tightly coupled to a content type and define which widgets are rendered in both content fields and the sidebar.
For example, the following content type describes a typical blog post data structure. It has a title, a body, and a category that can be either "General", "iOS" or "Android".
{
"fields": [
{ "id": "title", "name": "Title", "type": "Symbol" },
{ "id": "body", "name": "Body", "type": "Text" },
{
"id": "category",
"name": "Category",
"type": "Symbol",
"validations": [{ "in": ["General", "iOS", "Android"] }]
}
]
}
An editor interface could, for example, define that the title
field should be rendered as an input field (the widget ID is singleLine
and it comes from a builtin
widget the web app provides). It could then define that the body
should be a normal text area (the widget id is multipleLine
), and that the category
should be rendered as a dropdown field (the widget id is dropdown
).
The editor interface would look like this:
{
"controls": [
{
"fieldId": "title",
"widgetNamespace": "builtin",
"widgetId": "singleLine"
},
{
"fieldId": "body",
"widgetNamespace": "builtin",
"widgetId": "multipleLine"
},
{
"fieldId": "category",
"widgetNamespace": "builtin",
"widgetId": "dropdown"
}
]
}
There are sets of compatible builtin
widgets per content type field type:
Widget ID | Applicable field types | Description |
---|---|---|
assetLinkEditor | Asset | Search, attach, and preview an asset. |
assetLinksEditor | Asset (array) | Search, attach, reorder, and preview multiple assets. |
assetGalleryEditor | Asset (array) | Search, attach, reorder, and preview multiple assets in a gallery layout |
boolean | Boolean | Radio buttons with customizable labels. |
datePicker | Date | Select date, time, and timezon. |
entryLinkEditor | Entry | Search and attach another entry. |
entryLinksEditor | Entry (array) | Search and attach multiple entries. |
entryCardEditor | Entry | Search, attach, and preview another entry. |
entryCardsEditor | Entry (array) | Search, attach and preview multiple entries. |
numberEditor | Integer, Number | A simple input for numbers. |
rating | Integer, Number | Uses stars to select a number. |
locationEditor | Location | A map to select or find coordinates from an address. |
objectEditor | Object | A code editor for JSON |
urlEditor | Symbol | A text input that also shows a preview of the given URL. |
slugEditor | Symbol | Automatically generates a slug and validates its uniqueness across entries. |
listInput | Symbol (array) | Text input that splits values on , and stores them as an array. |
checkbox | Symbol (array) | A group of checkboxes. One for each value from the in validation on the content type field |
tagEditor | Symbol (array) | A text input to add a string to the list. Shows the items as tags and allows to remove them. |
multipleLine | Text | A simple <textarea> input |
markdown | Text | A full-fledged markdown editor |
singleLine | Text, Symbol | A simple text input field |
dropdown | Text, Symbol, Integer, Number | A <input type="select"> element. It uses the values from an in validation on the content type field as options. |
radio | Text, Symbol, Integer, Number | A group of radio buttons. One for each value from the in validation on the content type field |
richTextEditor | RichText | A default rich text editor |
Field control settings
You can pass custom settings to a field control that change the behavior or presentation of a widget.
The entry for a field of type Boolean
, for example, would look like this.
{
"fieldId": "isFeatured",
"widgetNamespace": "builtin",
"widgetId": "boolean",
"settings": {
"helpText": "Feature this post on the homepage?",
"trueLabel": "yes",
"falseLabel": "no"
}
}
If present, the settings
object of a control must be an object. All widgets accept the helpText
setting and use it to render extra information with the widget. Other settings are widget specific.
Widget ID | Setting name | Description |
---|---|---|
boolean | trueLabel |
Shows this text next to the radio button that sets this value to true . Defaults to “Yes”. |
boolean | falseLabel |
Shows this text next to the radio button that sets this value to false . Defaults to “No”. |
rating | stars |
Number of stars to select from. Defaults to 5. |
datePicker | format |
One of “dateonly”, “time”, “timeZ” (default). Specifies whether to show the clock and/or timezone inputs. |
datePicker | ampm |
Specifies which type of clock to use. Must be one of the stringss “12” or “24” (default). |
Custom field control widgets
Editor interfaces can be used to assign custom widgets to a field, sidebar or the entire entry editor. Currently there are two types of custom widgets: Apps and UI Extension. To do so just use one of app
or extension
namespaces and provide the widget ID:
{
"controls": [
{
"fieldId": "title",
"widgetNamespace": "extension",
"widgetId": "myextension",
"settings": {
"devMode": false
}
},
{
"fieldId": "image",
"widgetNamespace": "app",
"widgetId": "myapp"
}
]
}
Field groups
Field groups allow to organize how fields are displayed in Compose page editor. Groups are defined with two properties editorLayout
and groupControls
, like in the following example:
{
...
"editorLayout": [
{
"groupId": "content",
"name": "Content",
"items": [
{ "fieldId": "title" },
{ "fieldId": "body" },
]
},
{
"groupId": "settings",
"name": "Settings",
"items": [
{
"groupId": "seo",
"name": "Seo",
"items": [{"fieldId": "slug"}]
}
]
}
],
"groupControls": [
{
"groupId": "content",
"widgetId": "topLevelTab",
"widgetNamespace": "builtin",
},
{
"groupId": "settings",
"widgetId": "topLevelTab",
"widgetNamespace": "builtin",
}
]
}
Editor Layout
editorLayout
describes the hierarchical organization of fields and groups. Groups are containers of fields or nested groups.
Items in editorLayout
are of two types: group items or field items. These can, respecting some constraints,
be positioned in the layout as needed. When it's present, editorLayout
redefines the order in which fields are rendered by Compose. Leaf field items
are displayed in the order they appear in the hierarchy.
Fields that are not assigned to a group will be automatically added to the first group defined in editorLayout
.
Group Item | ||
---|---|---|
Property Name | Type | Description |
groupId | string |
Unique identifier of the group |
name | string |
The display name of the group |
items | object[] |
Group children, can either be group items or field items |
Field Item | ||
---|---|---|
Property Name | Type | Description |
fieldId | string |
Unique identifier of the field, it must be a field id in the related content type |
Group controls
Similar to field controls, groupControls
defines the widgets that render the groups present in editorLayout
.
Widget ID | Description |
---|---|
fieldSet | Renders the group as an expandable and collapsible section. This is the default widget of a group |
topLevelTab | Renders the group as a dedicated tab, separated horizontally from other groups |
At the moment custom group widgets are not supported.
Like field controls, group controls can specify widget settings. The helpText
property in settings
contains textual information displayed in the group heading. The collapsedByDefault
property controls whether the field set is collapsed at page load, false
by default.
{
"groupId": "seo",
"widgetNamespace": "builtin",
"widgetId": "fieldSet",
"settings": {
"helpText": "Fields to configure seo metadata",
"collapsedByDefault": true
}
}
Groups constraints
- Top level items in
editorLayout
must be group items, they must map to a group control with topLevelTabwidgetId
. - Non top level groups must not map to to a group control with topLevelTab
widgetId
. - Groups cannot be nested more than 1 level, top level group can have groups as children, but those can only contain fields.
Sidebar
By default the web app renders a set of predefined widgets (like "Status" or "Preview") in the entry editor sidebar. Using the editor interface, it's possible to alter the default sidebar by hiding, rearranging built-in widgets and/or adding custom widgets.
{
"sidebar": [
{
"widgetNamespace": "sidebar-builtin",
"widgetId": "content-preview-widget"
},
{
"widgetNamespace": "app",
"widgetId": "myapp"
}
]
}
Sidebar items are rendered in exactly the same order as they are defined in the sidebar
property. The sidebar above will render two widgets, the first being the builtin preview widget and the other one using an app.
Builtin sidebar widgets (widgets in the sidebar-builtin
namespace) have the following IDs:
- Status (publish button):
publication-widget
- Entry tasks:
content-workflows-tasks-widget
- Content preview:
content-preview-widget
- Incoming links:
incoming-links-widget
- Translations (locale selection):
translation-widget
- Entry-level versioning:
versions-widget
If the sidebar is in its default state and therefore only displays our built-in widgets, the sidebar
property is left out from the editor interface. You can download the JSON array of the default state here.
Custom editor
It is possible to add an editor or even completely replace the default entry editor provided by the web app with a custom editor implemented as an App or a UI Extension.
Once you've built your custom editor, you can use it by setting an optional editors
property with your custom editor. This will render both the custom editor and the default editor. You can also disable the default editor, by including it in the list and marking it as disabled.
{
"editors": [
{
"widgetNamespace": "extension",
"widgetId": "mycustomeditor",
"settings": {
"devMode": true
}
},
{
"widgetNamespace": "editor-builtin",
"widgetId": "default-editor",
"disabled": true
}
]
}
Similarly to field and sidebar configuration, instance parameters for an app or UI Extension can be passed as settings
.
If the editor is in its default state and therefore only displays the default entry editor, the editor
property is left out from the editor interface. You can download the JSON array of the default state here.
Default configuration
In the EditorInterface
entity, editors
, sidebar
and controls
are optional fields.
Therefore, general defaults are defined which will be rendered if none of them is specified.
Groups properties editorLayout
and groupControls
are conditionally required, they are optional, but if one the two is part of an editor interface
then the other has to be too.
These defaults are defined and provided by the contentful-management.js library. To get these defaults, you can import them from the library as follows:
import { editorInterfaceDefaults } from 'contentful-management';
// sidebar defaults for entries
const sidebarEntryDefaults = editorInterfaceDefaults.SidebarEntryConfiguration;
// sidebar defaults for assets
const sidebarAssetDefaults = editorInterfaceDefaults.SidebarAssetConfiguration;
// entry configuration defaults
const entryDefaults = editorInterfaceDefaults.EntryConfiguration;
The default for controls
are not constants, because they depend on which fields are defined. To solve this, contentful-management.js provides
a function that returns the default control for a given field.
import { editorInterfaceDefaults } from 'contentful-management';
const field = {
apiName: '<field-name>',
id: '<field-id>',
name: '<field-name>',
type: 'Symbol',
};
// sidebar defaults for entries
const control = editorInterfaceDefaults.getDefaultControlOfField(field);
// {
// "fieldId": "title",
// "widgetNamespace": "builtin",
// "widgetId": "singleLine"
// "settings": {
// "helpText": null,
// }
// }
console.log(control);
Configuring editor interfaces with apps
Apps are able to edit editor interfaces as part of their configuration process. Details on how this works can be in our article about target state.
Using the plain client to access and modify editor interfaces
Using Contentful's plain JavaScript client you can access and modify editor interfaces. The following example shows how to retrieve an editor interface for a content type.
const contentful = require('contentful-management');
const plainClient = contentful.createClient(
{
// This is the access token for this space. Normally you get the token in the Contentful web app
accessToken: 'YOUR_ACCESS_TOKEN',
},
{ type: 'plain' }
);
// use plainClient to access the Contentful Management API and get editor interface for a content type
const editorInterface = await plainClient.editorInterface.get({
spaceId: 'YOUR_SPACE_ID',
environmentId: 'YOUR_ENVIRONMENT_ID',
contentTypeId: 'YOUR_CONTENT_TYPE_ID',
});
You can also modify editor interfaces using the plain client. The following example shows how to add (and then remove) a sidebar widget to an editor interface.
// Add a sidebar widget to the editor interface (builtin translation widget)
await plainClient.editorInterface.update(
{
spaceId: 'YOUR_SPACE_ID',
environmentId: 'YOUR_ENVIRONMENT_ID',
contentTypeId: 'YOUR_CONTENT_TYPE_ID',
},
{
...updatedEditorInterfaces,
sidebar: [
...updatedEditorInterfaces.sidebar,
{
widgetId: 'translation-widget',
widgetNamespace: 'sidebar-builtin',
settings: {
defaultLocale: 'en-US',
},
},
],
}
);
// Delete the builtin translation widget from the editor interface
await plainClient.editorInterface.update(
{ spaceId: 'b1jn4as0c5ht', environmentId: 'master', contentTypeId: 'item' },
{
...editorInterface,
sidebar: editorInterface.sidebar.filter(
(entry) => entry.widgetId !== 'translation-widget'
),
}
);
Custom apps can also be added to editor interfaces using the plain client. The following example shows how to add a custom app to an editor interface.
// Add a custom app to the editor interface
await plainClient.editorInterface.update(
{
spaceId: 'YOUR_SPACE_ID',
environmentId: 'YOUR_ENVIRONMENT_ID',
contentTypeId: 'YOUR_CONTENT_TYPE_ID',
},
{
...updatedEditorInterfaces,
sidebar: [
...updatedEditorInterfaces.sidebar,
{
widgetId: 'YOUR_CUSTOM_WIDGET_ID',
widgetNamespace: 'app',
settings: {
// any app settings
},
},
],
}
);