2022-09-30 11:39:35 -04:00
---
2022-11-04 17:41:12 -04:00
group: Customization
2022-09-30 11:39:35 -04:00
title: Creating Custom Widgets
2022-11-04 17:41:12 -04:00
weight: 40
2022-09-30 11:39:35 -04:00
---
2022-11-04 17:41:12 -04:00
The 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.
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
### React Components Inline
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
The `registerPreviewTemplate` 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.
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
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`).
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
**NOTE**: `createClass` is still provided, allowing for the creation of react class components. However it has now been deprecated and will be removed in `v2.0.0`.
## Register Widget
2022-09-30 11:39:35 -04:00
Register a custom widget.
```js
// Using global window object
CMS.registerWidget(name, control, [preview], [schema]);
// Using npm module import
2022-10-02 20:06:20 -04:00
import CMS from '@staticcms/core';
2022-09-30 11:39:35 -04:00
CMS.registerWidget(name, control, [preview], [schema]);
```
2022-11-04 17:41:12 -04:00
### 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](https://reactjs.org/docs/components-and-props.html)<br />\| string | <ul><li>`React Function Component` - The react component that renders the control. See [Control Component](#control-component)</li><li>`string` - Name of a registered widget whose control should be used (includes built in widgets).</li></ul> |
| preview | [React Function Component](https://reactjs.org/docs/components-and-props.html) | _Optional_. Renders the widget preview. See [Preview Component](#preview-component) |
| options | object | _Optional_. Widget options. See [Options](#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 |
| field | object | The field configuration for the current widget. See [Widget Options](/docs/widgets#common-widget-options) |
| collection | object | The collection configuration for the current widget. See [Collections](/docs/collection-overview) |
| config | object | The current Static CMS config. See [configuration options](/docs/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 |
| isDisabled | 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 | Specifices if the widget is within a `list` widget |
| isFieldDuplicate | function | Function that given a field configuration, returns if that field is a duplicate |
| isFieldHidden | function | Function that given a field configuration, returns if that field is hidden |
| getAsset | Async function | Function that given a url returns (as a promise) a loaded asset |
| locale | string<br />\| undefined | The current locale of the editor |
| mediaPaths | object | Key/value object of control IDs (passed to the media library) mapping to media paths |
| clearMediaControl | function | Clears a control ID's value from the internal store |
| openMediaLibrary | function | Opens the media library popup. See [Open Media Library](#open-media-library) |
| removeInsertedMedia | function | Removes draft media for a give control ID |
| removeMediaControl | function | Clears a control ID completely from the internal store |
| query | function | Runs a search on another collection. See [Query](#query) |
| i18n | object | The current i18n settings |
| t | function | Translates a given key to the current locale |
#### Open Media Library
`openMediaLibrary` allows you to open up the media library popup. It accepts the following props:
| Param | Type | Default | Description |
| ------------- | --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
| controlID | string | | _Optional_ A unique identifier to which the uploaded media will be linked |
| forImage | boolean | `false` | _Optional_ If `true`, restricts upload to image files only |
| value | string<br />list of strings | | _Optional_ The current selected media value |
| allowMultiple | boolean | | _Optional_ Allow multiple files or images to be uploaded at once. Only used on media libraries that support multi upload |
| replaceIndex | number | | _Optional_ The index of the image in an list. Ignored if ` allowMultiple` is `false` |
| config | object | | _Optional_ Media library config options. Available options depend on the media library being used |
| field | object | | _Optional_ The current field configuration |
#### 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](/docs/widgets#common-widget-options) |
| collection | object | The collection configuration for the current widget. See [Collections](/docs/collection-overview) |
| config | object | The current Static CMS config. See [configuration options](/docs/configuration-options) |
| entry | object | Object with a `data` field that contains the current value of all widgets in the editor |
| getAsset | Async function | Function that given a url returns (as a promise) a loaded asset |
### 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](#advanced-field-validation) |
| schema | JSON Schema object | _Optional_. Enforces a schema for the widget's field configuration |
### Example
2022-09-30 11:39:35 -04:00
`admin/index.html`
```html
2022-11-04 17:41:12 -04:00
<script src="https://unpkg.com/@staticcms/core@%5E1.0.0/dist/static-cms-core.js"></script>
2022-09-30 11:39:35 -04:00
<script>
2022-11-04 17:41:12 -04:00
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: this.handleChange,
2022-09-30 11:39:35 -04:00
})
2022-11-04 17:41:12 -04:00
});
};
const CategoriesPreview = ({ value }) => {
return h(
'ul',
{},
value.map(function (val, index) {
return h('li', { key: index }, val);
}),
2022-09-30 11:39:35 -04:00
);
2022-11-04 17:41:12 -04:00
};
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
const schema = {
properties: {
separator: { type: 'string' },
},
};
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, options: { schema });
2022-09-30 11:39:35 -04:00
</script>
```
2022-11-07 10:27:58 -05:00
`admin/config.yml` (or `admin/config.js`)
2022-09-30 11:39:35 -04:00
2022-11-07 10:27:58 -05:00
<CodeTabs>
```yaml
2022-09-30 11:39:35 -04:00
collections:
2022-11-04 17:41:12 -04:00
- name: posts
2022-09-30 11:39:35 -04:00
label: Posts
folder: content/posts
fields:
2022-11-04 17:41:12 -04:00
- name: title
2022-09-30 11:39:35 -04:00
label: Title
widget: string
2022-11-04 17:41:12 -04:00
- name: categories
2022-09-30 11:39:35 -04:00
label: Categories
widget: categories
separator: __
```
2022-11-07 10:27:58 -05:00
```js
collections: [
{
name: 'posts',
label: 'Posts',
folder: 'content/posts',
fields: [
{
name: 'title'
label: 'Title'
widget: 'string'
},
{
name: 'categories'
label: 'Categories'
widget: 'categories'
separator: '__'
}
]
}
]
```
</CodeTabs>
2022-09-30 11:39:35 -04:00
## Advanced field validation
2022-10-25 09:18:18 -04:00
All widget fields, including those for built-in widgets, [include basic validation](/docs/widgets/#common-widget-options) capability using the `required` and `pattern` options.
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
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.
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
### Examples
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
#### No Errors
2022-09-30 11:39:35 -04:00
```javascript
2022-11-04 17:41:12 -04:00
const validator = () => {
// Do internal validation
return true;
2022-09-30 11:39:35 -04:00
};
```
2022-11-04 17:41:12 -04:00
#### Has Error
2022-09-30 11:39:35 -04:00
```javascript
2022-11-04 17:41:12 -04:00
const validator = () => {
// Do internal validation
return false;
};
2022-09-30 11:39:35 -04:00
```
2022-11-04 17:41:12 -04:00
#### Error With Type
2022-09-30 11:39:35 -04:00
```javascript
2022-11-04 17:41:12 -04:00
const validator = () => {
// Do internal validation
return { type: 'custom-error' };
};
2022-09-30 11:39:35 -04:00
```
2022-11-04 17:41:12 -04:00
#### Error With Type and Message
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
_Useful for returning custom error messages_
2022-09-30 11:39:35 -04:00
```javascript
2022-11-04 17:41:12 -04:00
const validator = () => {
// Do internal validation
return { type: 'custom-error', message: 'Your error message.' };
};
2022-09-30 11:39:35 -04:00
```
2022-11-04 17:41:12 -04:00
#### Promise
2022-09-30 11:39:35 -04:00
2022-11-04 17:41:12 -04:00
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.
2022-09-30 11:39:35 -04:00
```javascript
2022-11-04 17:41:12 -04:00
const validator = () => {
return this.existingPromise;
};
2022-09-30 11:39:35 -04:00
```