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