Static CMS
Star StaticJsCMS/static-cms on GitHub
DocsContributingCommunity

Creating Custom Widgets

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.

React Components Inline

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.

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).

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

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
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
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
isDisabledbooleanSpecifies if the widget control should be disabled
submittedbooleanSpecifies if a save attempt has been made in the editor session
forListbooleanSpecifices if the widget is within a list widget
isFieldDuplicatefunctionFunction that given a field configuration, returns if that field is a duplicate
isFieldHiddenfunctionFunction that given a field configuration, returns if that field is hidden
getAssetAsync functionFunction that given a url returns (as a promise) a loaded asset
localestring
| undefined
The current locale of the editor
mediaPathsobjectKey/value object of control IDs (passed to the media library) mapping to media paths
clearMediaControlfunctionClears a control ID's value from the internal store
openMediaLibraryfunctionOpens the media library popup. See Open Media Library
removeInsertedMediafunctionRemoves draft media for a give control ID
removeMediaControlfunctionClears a control ID completely from the internal store
queryfunctionRuns a search on another collection. See Query
i18nobjectThe current i18n settings
tfunctionTranslates 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:

ParamTypeDefaultDescription
controlIDstringOptional A unique identifier to which the uploaded media will be linked
forImagebooleanfalseOptional If true, restricts upload to image files only
valuestring
list of strings
Optional The current selected media value
allowMultiplebooleanOptional Allow multiple files or images to be uploaded at once. Only used on media libraries that support multi upload
replaceIndexnumberOptional The index of the image in an list. Ignored if allowMultiple is false
configobjectOptional Media library config options. Available options depend on the media library being used
fieldobjectOptional The current field configuration

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
getAssetAsync functionFunction that given a url returns (as a promise) a loaded asset

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

admin/index.html

<script src="https://unpkg.com/@staticcms/app@^1.0.0/dist/static-cms-app.js"></script>
<script>
  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,
      })
    });
  };

  const CategoriesPreview = ({ value }) => {
    return h(
      'ul',
      {},
      value.map(function (val, index) {
        return h('li', { key: index }, val);
      }),
    );
  };

  const schema = {
    properties: {
      separator: { type: 'string' },
    },
  };

  CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema });
</script>

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.
  • useMediaAsset - Transforms your stored url into a usable url for displaying as a preview.
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';

const FileControl = ({ collection, field, value, entry, onChange }) => {
  const handleOpenMediaLibrary = useMediaInsert(
    value,
    { field, controlID },
    onChange,
  );

  const assetSource = useMediaAsset(value, collection, field, entry);

  return (
    <>
      <button type="button" onClick={handleOpenMediaLibrary}>Upload</button>
      <img role="presentation" src={assetSource} />
    </>
  );
};