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 theforFoldervariable 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 }),
];
};