Static CMS
Star StaticJsCMS/static-cms on GitHub
v4.3.0DocsExamplesDemoCommunity

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

ParamTypeDescription
namestringWidget name, allows this widget to be used via the field widget property in config
controlReact Function Component
| string
  • React Function Component - The react component that renders the control. See Control Component
  • string - Name of a registered widget whose control should be used (includes built in widgets).
previewReact Function ComponentOptional. Renders the widget preview. See Preview Component
optionsobjectOptional. Widget options. See Options

Control Component

The react component that renders the control. It receives the following props:

ParamTypeDescription
labelstringThe label for the widget
valueAn valid widget valueThe current value of the widget
onChangefunctionFunction to be called when the value changes. Accepts a valid widget value
clearChildValidationfunctionClears all validation errors for children of the widget
fieldobjectThe field configuration for the current widget. See Widget Options
collectionobjectThe collection configuration for the current widget. See Collections
collectionFileobjectThe collection file configuration for the current widget if entry is part of a File Collection
configobjectThe current Static CMS config. See configuration options
entryobjectObject with a data field that contains the current value of all widgets in the editor
pathstring. separated string donating the path to the current widget within the entry
hasErrorsbooleanSpecifies if there are validation errors with the current widget
fieldsErrorsobjectKey/value object of field names mapping to validation errors
disabledbooleanSpecifies if the widget control should be disabled
submittedbooleanSpecifies if a save attempt has been made in the editor session
forListbooleanSpecifies if the widget is within a list widget
listItemPathstring
| undefined
. separated string donating the path to the closet parent list item within the entry
forSingleListbooleanSpecifies if the widget is within a singleton list widget (string array, number array, etc)
duplicatefunctionSpecifies if that field is an i18n duplicate
hiddenfunctionSpecifies if that field should be hidden
localestring
| undefined
The current locale of the editor
queryfunctionRuns a search on another collection. See Query
i18nobjectThe current i18n settings
tfunctionTranslates a given key to the current locale

Query

query allows you to search the entries of a given collection. It accepts the following props:

ParamTypeDefaultDescription
namespacestringUnique identifier for search
collectionNamestringThe collection to be searched
searchFieldslist of stringsThe Fields to be searched within the target collection
searchTermstringThe term to search with
filestringOptional The file in a file collection to search. Ignored on folder collections
limitstringOptional 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:

ParamTypeDescription
valueAn valid widget valueThe current value of the widget
fieldobjectThe field configuration for the current widget. See Widget Options
collectionobjectThe collection configuration for the current widget. See Collections
configobjectThe current Static CMS config. See configuration options
entryobjectObject 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:

ParamTypeDescription
validatorfunctionOptional. Validates the value of the widget
getValidValuestringOptional. Given the current value, returns a valid value. See Advanced field validation
schemaJSON Schema objectOptional. 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 the forFolder 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 }),
  ];
};