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 17449_878c65-a0> |
Type 17449_9cc520-ff> |
Description 17449_c17ec6-e8> |
|---|---|---|
|
name 17449_2f38af-9a> |
string 17449_bb930a-82> |
Widget name, allows this widget to be used via the field |
|
control 17449_1c9d5e-d5> |
React Function Component |
|
|
preview 17449_efce56-47> | 17449_2dcc76-e1> |
Optional. Renders the widget preview. See Preview Component 17449_1afb92-fe> |
|
options 17449_1003e1-29> |
object 17449_9ac756-c8> |
Optional. Widget options. See Options 17449_748cd8-26> |
Control Component
The react component that renders the control. It receives the following props:
|
Param 17449_915b4b-91> |
Type 17449_e32c96-b9> |
Description 17449_90b9d2-b3> |
|---|---|---|
|
label 17449_21878b-ba> |
string 17449_4825fb-ea> |
The label for the widget 17449_769449-6a> |
|
value 17449_137e96-78> |
An valid widget value 17449_6bbfc5-10> |
The current value of the widget 17449_a00c32-3e> |
|
onChange 17449_d1f48a-42> |
function 17449_c30dac-40> |
Function to be called when the value changes. Accepts a valid widget value 17449_143a70-eb> |
|
clearChildValidation 17449_50d821-4b> |
function 17449_5f63a6-64> |
Clears all validation errors for children of the widget 17449_d40316-3d> |
|
field 17449_dc9944-c9> |
object 17449_1a0e86-08> |
The field configuration for the current widget. See Widget Options 17449_17314f-5c> |
|
collection 17449_0418bc-a5> |
object 17449_ef8e31-84> |
The collection configuration for the current widget. See Collections 17449_163c90-c3> |
|
collectionFile 17449_d84031-93> |
object 17449_4bada3-7b> |
The collection file configuration for the current widget if entry is part of a File Collection 17449_8750b2-0d> |
|
config 17449_3f265f-8a> |
object 17449_5c6dd4-1e> |
The current Static CMS config. See configuration options 17449_c72075-80> |
|
entry 17449_0806a3-f3> |
object 17449_651dbd-21> |
Object with a |
|
path 17449_f16a96-5d> |
string 17449_de66a5-86> |
|
|
hasErrors 17449_4011cc-f7> |
boolean 17449_201f18-f6> |
Specifies if there are validation errors with the current widget 17449_8c4586-3b> |
|
fieldsErrors 17449_0e86bf-50> |
object 17449_a57f06-33> |
Key/value object of field names mapping to validation errors 17449_1d2b24-80> |
|
disabled 17449_ca959e-7f> |
boolean 17449_c05aac-4b> |
Specifies if the widget control should be disabled 17449_7d9ee7-ea> |
|
submitted 17449_a41145-38> |
boolean 17449_90d2b9-ac> |
Specifies if a save attempt has been made in the editor session 17449_8a9e9e-5b> |
|
forList 17449_ce687f-fb> |
boolean 17449_5b9116-78> |
Specifies if the widget is within a |
|
listItemPath 17449_c178ac-52> |
string |
|
|
forSingleList 17449_682f16-82> |
boolean 17449_3d7db9-40> |
Specifies if the widget is within a singleton |
|
duplicate 17449_2f028b-d4> |
function 17449_7708a2-f5> |
Specifies if that field is an i18n duplicate 17449_15d967-51> |
|
hidden 17449_60efea-8b> |
function 17449_2ea286-12> |
Specifies if that field should be hidden 17449_fe2d13-04> |
|
locale 17449_28c772-ac> |
string |
The current locale of the editor 17449_905c65-2b> |
|
query 17449_2220a0-8b> |
function 17449_9f2f18-ee> |
Runs a search on another collection. See Query 17449_20408b-bc> |
|
i18n 17449_734c2b-80> |
object 17449_fbb003-4b> |
The current i18n settings 17449_f986ee-b6> |
|
t 17449_b9b299-2c> |
function 17449_e6bd2f-d9> |
Translates a given key to the current locale 17449_07f4f7-34> |
Query
query allows you to search the entries of a given collection. It accepts the following props:
|
Param 17449_a4d944-98> |
Type 17449_58ccf6-a0> |
Default 17449_14e3ee-14> |
Description 17449_41865b-17> |
|---|---|---|---|
|
namespace 17449_1d5d29-d6> |
string 17449_d5128b-6f> | 17449_3a207f-a2> |
Unique identifier for search 17449_c20ede-4b> |
|
collectionName 17449_249c2d-be> |
string 17449_d304c7-65> | 17449_4e192c-87> |
The collection to be searched 17449_15e40c-1c> |
|
searchFields 17449_381418-3c> |
list of strings 17449_d9707c-27> | 17449_72149a-fb> |
The Fields to be searched within the target collection 17449_42cea8-c4> |
|
searchTerm 17449_6feb52-89> |
string 17449_3a2226-60> | 17449_bc99e1-c4> |
The term to search with 17449_6ad378-6e> |
|
file 17449_e96b61-11> |
string 17449_727777-4b> | 17449_3aed36-77> |
Optional The file in a file collection to search. Ignored on folder collections 17449_03cd86-a1> |
|
limit 17449_da0233-bb> |
string 17449_9f1c66-3f> | 17449_18b44e-e6> |
Optional The number of results to return. If not specified, all results are returned 17449_7e1293-e6> |
Preview Component
The react component that renders the preview. It receives the following props:
|
Param 17449_14097b-3b> |
Type 17449_33bca6-1d> |
Description 17449_118e70-e6> |
|---|---|---|
|
value 17449_61ea0c-0a> |
An valid widget value 17449_2afd8d-45> |
The current value of the widget 17449_9785e4-6e> |
|
field 17449_00a333-31> |
object 17449_2f465a-1b> |
The field configuration for the current widget. See Widget Options 17449_a3a4ab-8e> |
|
collection 17449_b64662-3b> |
object 17449_ce7498-d6> |
The collection configuration for the current widget. See Collections 17449_439c3c-35> |
|
config 17449_c1a2d9-9e> |
object 17449_ab5172-13> |
The current Static CMS config. See configuration options 17449_03166f-72> |
|
entry 17449_8b1db4-40> |
object 17449_6ec7de-81> |
Object with a |
Options
Register widget takes an optional object of options. These options include:
|
Param 17449_380b56-39> |
Type 17449_c3a345-0c> |
Description 17449_bec1b9-36> |
|---|---|---|
|
validator 17449_03e7a8-d9> |
function 17449_5a3e4f-e5> |
Optional. Validates the value of the widget 17449_c22564-ce> |
|
getValidValue 17449_f23666-53> |
string 17449_d2c0bb-d9> |
Optional. Given the current value, returns a valid value. See Advanced field validation 17449_b4b7e4-eb> |
|
schema 17449_c86e54-d8> |
JSON Schema object 17449_8d113a-0f> |
Optional. Enforces a schema for the widget’s field configuration 17449_988c03-88> |
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 });import CMS from '@staticcms/core';
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 (
<div>
<label for="inputId">{label}</label>
<input
id="inputId"
type="text"
value={value ? value.join(separator) : ''}
onChange={handleChange}
/>
</div>
);
};
const CategoriesPreview = ({ value }) => {
return (
<ul>
{value.map((val, index) => {
return <li key={index}>{value}</li>;
})}
</ul>
);
};
const schema = {
properties: {
separator: { type: 'string' },
},
};
CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema });import CMS from '@staticcms/core';
import type { WidgetControlProps, WidgetPreviewProps } from '@staticcms/core';
interface CategoriesField {
widget: 'categories';
}
const CategoriesControl = ({
label,
value,
field,
onChange,
}: WidgetControlProps<string[], CategoriesField>) => {
const separator = useMemo(() => field.separator ?? ', ', [field.separator]);
const handleChange = useCallback(
e => {
onChange(e.target.value.split(separator).map(e => e.trim()));
},
[separator, onChange],
);
return (
<div>
<label for="inputId">{label}</label>
<input
id="inputId"
type="text"
value={value ? value.join(separator) : ''}
onChange={handleChange}
/>
</div>
);
};
const CategoriesPreview = ({ value }: WidgetPreviewProps<string[], CategoriesField>) => {
return (
<ul>
{value.map((val, index) => {
return <li key={index}>{value}</li>;
})}
</ul>
);
};
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: __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 }),
];
};import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
const FileControl = ({ collection, field, value, entry, onChange }) => {
const handleChange = ({ path }) => {
onChange(path);
};
const handleOpenMediaLibrary = useMediaInsert(
value,
{ collection, field, controlID },
handleChange,
);
const assetSource = useMediaAsset(value, collection, field, entry);
return (
<>
<button type="button" onClick={handleOpenMediaLibrary}>
Upload
</button>
<img role="presentation" src={assetSource} />
</>
);
};import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
import type { WidgetControlProps, MediaPath } from '@staticcms/core/interface';
import type { FC } from 'react';
const FileControl: FC<WidgetControlProps<string, MyField>> = ({
collection,
field,
value,
entry,
onChange,
}) => {
const handleChange = ({ path }: MediaPath) => {
onChange(path);
};
const handleOpenMediaLibrary = useMediaInsert(
internalValue,
{ collection, field, controlID },
onChange,
);
const assetSource = useMediaAsset(value, collection, field, entry);
return (
<>
<button type="button" onClick={handleOpenMediaLibrary}>
Upload
</button>
<img role="presentation" src={assetSource} />
</>
);
};