Skip to main content

Create a Webiny Headless CMS Field Plugin

What you'll learn
  • field plugin types for UI and API
  • which plugins are required and which are optional

Overview#

This guide should help you understand how to create Headless CMS field plugins and how to use them in the API and the UI. By going through this guide you can see how to create at least two plugins for your field - UI plugin and API plugin. There are extra API field type plugins that can help you with indexing (Elasticsearch) and storage of the field.

Also, in this guide, we assume that you know how Webiny plugins work.

Plugin Types#

There are a few plugins types that you need to have in mind when creating a new field. For the UI part of the application, there are CmsEditorFieldTypePlugin and CmsEditorFieldRendererPlugin. For the API part of the application, there are CmsModelFieldToGraphQLPlugin, CmsModelFieldToStoragePlugin and CmsModelFieldToElasticSearchPlugin. It is required only to have CmsModelFieldToGraphQLPlugin to make the field work.

UI plugins#

CmsEditorFieldTypePlugin#

Plugins of this type are defining the field for the UI part of the application. Here is a simple example of the plugin:

export default (): CmsEditorFieldTypePlugin => ({
type: "cms-editor-field-type",
name: "cms-editor-field-type-textEntry",
field: {
type: "largeText",
label: `Large text`,
description: `A text that can be extremely large, probably HTML or JSON.`,
icon: <Icon />,
allowMultipleValues: true,
allowPredefinedValues: false,
multipleValuesLabel: `Large texts`,
createField() {
return {
type: "largeText",
settings: {},
validation: [],
renderer: {
// we leave this empty so renderer it is automatically chosen according to canUse() in the renderer plugin
name: ""
}
};
}
}
});

This example adds a largeText type field to the UI which can be set to accept multiple values.

For more advanced examples, check either text field type or ref field type.

CmsEditorFieldRendererPlugin#

Plugins of this type are used to render a field in the create or update entry form in the UI side of the application. A simple example of the renderer meant to render a largeText type field when set to allow multiple values.

export default (): CmsEditorFieldRendererPlugin => {
type: "cms-editor-field-renderer",
name: "cms-editor-field-renderer-largeText-multiple",
renderer: {
rendererName: "largeTextRenderer",
name: `Large texts`,
description: `Renders multiple large text inputs.`,
canUse({ field }) {
return field.type === "largeText" && field.multipleValues;
},
render(props) {
return (
<>
<LargeText />
<LargeText />
<AddMore />
</>
);
}
}
};

You also must create a renderer for a field type largeText when not set to allow multiple values.

API plugins#

CmsModelFieldToGraphQLPlugin#

Plugins of this type define GraphQL part for a field type in the API side of the application. A simple example of the plugin:

export default (): CmsModelFieldToGraphQLPlugin => ({
type: "cms-model-field-to-graphql",
name: "cms-model-field-to-graphql-largeText",
fieldType: "largeText",
isSortable: false,
isSearchable: false,
manage: {
createInputField({ field }) {
if (field.multipleValues) {
return field.fieldId + ": [String]";
}
return field.fieldId + ": String";
},
createResolver({ field }) {
return instance => {
return instance[field.fieldId];
};
},
createTypeField({ field }) {
if (field.multipleValues) {
return `${field.fieldId}: [String]`;
}
return `${field.fieldId}: String`;
}
},
read: {
createResolver({ field }) {
return instance => {
return instance[field.fieldId];
};
},
createTypeField({ field }) {
if (field.multipleValues) {
return `${field.fieldId}: [String]`;
}
return `${field.fieldId}: String`;
}
}
});

Note that there can be differences in management and read definitions. That is because of a case that administration is working with data directly and the user on the read side expects a resolved field.

When you created CmsEditorFieldTypePlugin, CmsEditorFieldRendererPlugin and CmsModelFieldToGraphQLPlugin plugin types, your field is ready to be used. Just import them in the application.

CmsModelFieldToElasticSearchPlugin#

Plugins of this type define transformations that are run on the field of a type before saving into Elasticsearch. A kinda, simple example of the plugin:

export default (): CmsModelFieldToElasticSearchPlugin => ({
type: "cms-model-field-to-elastic-search",
name: "cms-model-field-to-elastic-search-largeText",
fieldType: "largeText",
toIndex({ toIndexEntry, storageEntry, originalEntry, field }) {
const value = toIndexEntry.values[field.fieldId];
delete toIndexEntry.values[field.fieldId];
return {
values: toIndexEntry.values,
rawValues: {
...toIndexEntry.rawValues,
[field.fieldId]: JSON.stringify(value)
}
};
},
fromIndex({ entry, field }) {
const value = entry.rawValues[field.fieldId];
delete entry.rawValues[field.fieldId];
return {
values: {
...entry.values,
[field.fieldId]: JSON.parse(value)
},
rawValues: entry.rawValues
};
}
});

Be aware that you must return top-level entry properties, for example, values or rawValues, merged with existing ones. Otherwise, you lose data. You can check the richTextIndexing plugin for more reference.

Inside the toIndex method, you can get the original entry (originalEntry), the one that was prepared for storage (storageEntry), and the one that is inserted into the Elasticsearch (toIndexEntry). You should not change them, but you can. Inside the fromIndex method, you always get an entry (entry) that has possibly been modified by some previous Elasticsearch plugin.

CmsModelFieldToStoragePlugin#

Plugins of this type are like the CmsModelFieldToElasticSearchPlugin plugin type, they just handle to and from storage transformations. A simple example to store field as compressed value:

export default (): CmsModelFieldToStoragePlugin => ({
type: "cms-model-field-to-storage",
name: "cms-model-field-to-storage-largeText",
fieldType: "largeText",
async fromStorage({ value }): Promise<any> {
return ungzip(value);
},
async toStorage({ value }): Promise<string> {
return gzip(value);
}
});

You can check the plugin for storing richText as string packed with jsonpack library as another example.

Learn more#

Tutorial: Create a Webiny Headless CMS address field plugin#

Last updated on by Adrian Smijulj