Creating Custom Widgets
Static CMS exposes a window.CMS
global object that you can use to register custom widgets via registerWidget
. The same object is also the default export if you import Static CMS as an npm module.
React Components Inline
The registerWidget
requires you to provide a React component. If you have a build process in place for your project, it is possible to integrate with this build process.
However, although possible, it may be cumbersome or even impractical to add a React build phase. For this reason, Static CMS exposes some constructs globally to allow you to create components inline: h
(alias for React.createElement) as well some basic hooks (useState
, useMemo
, useEffect
, useCallback
).
Register Widget
Register a custom widget.
// Using global window object
CMS.registerWidget(name, control, [preview], [{ schema }]);
// Using npm module import
import CMS from '@staticcms/core';
CMS.registerWidget(name, control, [preview], [{ schema }]);
Params
Param | Type | Description |
---|---|---|
name | string | Widget name, allows this widget to be used via the field widget property in config |
control | React Function Component | string |
|
preview | React Function Component | Optional. Renders the widget preview. See Preview Component |
options | object | Optional. Widget options. See Options |
Control Component
The react component that renders the control. It receives the following props:
Param | Type | Description |
---|---|---|
label | string | The label for the widget |
value | An valid widget value | The current value of the widget |
onChange | function | Function to be called when the value changes. Accepts a valid widget value |
clearChildValidation | function | Clears all validation errors for children of the widget |
field | object | The field configuration for the current widget. See Widget Options |
collection | object | The collection configuration for the current widget. See Collections |
collectionFile | object | The collection file configuration for the current widget if entry is part of a File Collection |
config | object | The current Static CMS config. See configuration options |
entry | object | Object with a data field that contains the current value of all widgets in the editor |
path | string | . separated string donating the path to the current widget within the entry |
hasErrors | boolean | Specifies if there are validation errors with the current widget |
fieldsErrors | object | Key/value object of field names mapping to validation errors |
disabled | boolean | Specifies if the widget control should be disabled |
submitted | boolean | Specifies if a save attempt has been made in the editor session |
forList | boolean | Specifies if the widget is within a list widget |
listItemPath | string | undefined | . separated string donating the path to the closet parent list item within the entry |
forSingleList | boolean | Specifies if the widget is within a singleton list widget (string array, number array, etc) |
duplicate | function | Specifies if that field is an i18n duplicate |
hidden | function | Specifies if that field should be hidden |
locale | string | undefined | The current locale of the editor |
query | function | Runs a search on another collection. See Query |
i18n | object | The current i18n settings |
t | function | Translates a given key to the current locale |
Query
query
allows you to search the entries of a given collection. It accepts the following props:
Param | Type | Default | Description |
---|---|---|---|
namespace | string | Unique identifier for search | |
collectionName | string | The collection to be searched | |
searchFields | list of strings | The Fields to be searched within the target collection | |
searchTerm | string | The term to search with | |
file | string | Optional The file in a file collection to search. Ignored on folder collections | |
limit | string | Optional The number of results to return. If not specified, all results are returned |
Preview Component
The react component that renders the preview. It receives the following props:
Param | Type | Description |
---|---|---|
value | An valid widget value | The current value of the widget |
field | object | The field configuration for the current widget. See Widget Options |
collection | object | The collection configuration for the current widget. See Collections |
config | object | The current Static CMS config. See configuration options |
entry | object | Object with a data field that contains the current value of all widgets in the editor |
Options
Register widget takes an optional object of options. These options include:
Param | Type | Description |
---|---|---|
validator | function | Optional. Validates the value of the widget |
getValidValue | string | Optional. Given the current value, returns a valid value. See Advanced field validation |
schema | JSON Schema object | Optional. Enforces a schema for the widget's field configuration |
Example
const CategoriesControl = ({ label, value, field, onChange }) => {
const separator = useMemo(() => field.separator ?? ', ', [field.separator]);
const handleChange = useCallback((e) => {
onChange(e.target.value.split(separator).map(e => e.trim()));
}, [separator, onChange]);
return h('div', {}, {
h('label', { for: 'inputId' }, label),
h('input', {
id: 'inputId',
type: 'text',
value: value ? value.join(separator) : '',
onChange: handleChange,
})
});
};
const CategoriesPreview = ({ value }) => {
return h(
'ul',
{},
value.map((val, index) => {
return h('li', { key: index }, val);
}),
);
};
const schema = {
properties: {
separator: { type: 'string' },
},
};
CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema });
admin/config.yml
(or admin/config.js
)
collections:
- name: posts
label: Posts
folder: content/posts
fields:
- name: title
label: Title
widget: string
- name: categories
label: Categories
widget: categories
separator: __
Advanced field validation
All widget fields, including those for built-in widgets, include basic validation capability using the required
and pattern
options.
With custom widgets, the widget can also optionally pass in a validator
method to perform custom validations, in addition to presence and pattern. The validator
function will be automatically called, and it can return either a boolean
value, an object
with a type and error message or a promise.
Examples
No Errors
const validator = () => {
// Do internal validation
return true;
};
Has Error
const validator = () => {
// Do internal validation
return false;
};
Error With Type
const validator = () => {
// Do internal validation
return { type: 'custom-error' };
};
Error With Type and Message
Useful for returning custom error messages
const validator = () => {
// Do internal validation
return { type: 'custom-error', message: 'Your error message.' };
};
Promise
You can also return a promise from validator
. The promise can return boolean
value, an object
with a type and error message or a promise.
const validator = () => {
return this.existingPromise;
};
Interacting With The Media Library
If you want to use the media library in your custom widget you will need to use the useMediaInsert
and useMediaAsset
hooks.
useMediaInsert
- Takes the current url to your media, details about your field (including a unique ID) and a callback method for when new media is uploaded. If you want to select folders instead of files, set theforFolder
variable in options.useMediaAsset
- Transforms your stored url into a usable url for displaying as a preview.
const FileControl = ({ collection, field, value, entry, onChange }) => {
const handleChange = ({ path }) => {
onChange(path);
};
const handleOpenMediaLibrary = useMediaInsert(value, { collection, field, controlID }, onChange);
const assetSource = useMediaAsset(value, collection, field, entry);
return [
h('button', { type: 'button', onClick: handleOpenMediaLibrary }, 'Upload'),
h('img', { role: 'presentation', src: assetSource }),
];
};