1. Home
  2. Docs
  3. docs
  4. Customizing Static CMS
  5. Creating Custom Previews

Creating Custom Previews

Static CMS exposes a window.CMS global object that you can use to register custom previews for an entire collection (or file within a file collection) via registerPreviewTemplate (editor view) and registerPreviewCard / registerFieldPreview (collection view).

React Components Inline

registerPreviewTemplate, registerPreviewCard and registerFieldPreview require 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).

Editor Preview

registerPreviewTemplate allows you to create a template that overrides the entire editor preview for a given collection.

Params

Param

Type

Description

name

string

The name of the collection (or file for file collections) which this preview component will be used for
Folder collections: Use the name of the collectionFile collections: Use the name of the file

react_component

React Function Component

A React functional component that renders the collection data.

The following parameters will be passed to your react_component during render:

Param

Type

Description

entry

object

Object with a data field that contains the current value of all widgets in the editor

collection

object

Collection configuration

fields

object

The fields for the given collection

document

Document

The document object the preview is within. If rendered with a frame, it will be the frame’s document

window

Window

The window object the preview is within. If rendered with a frame, it will be the frame’s window

widgetFor

Function

Given a field name, returns the rendered preview of that field’s widget and value

widgetsFor

Function

Given a field name, returns the rendered previews of that field’s nested child widgets and values

Example

const PostPreview = ({ widgetFor, entry, collection, fields }) => {
  const imageField = useMemo(() => fields.find(field => field.name === 'image'), [fields]);
  const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);

  return h(
    'div',
    {},
    h('h1', {}, entry.data.title),
    h('img', { src: imageUrl }),
    h('div', { className: 'text' }, widgetFor('body')),
  );
};

CMS.registerPreviewTemplate('posts', PostPreview);
import CMS, { useMediaAsset } from '@staticcms/core';

const PostPreview = ({ widgetFor, entry, collection, fields }) => {
  const imageField = useMemo(() => fields.find(field => field.name === 'image'), [fields]);
  const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);

  return (
    <div>
      <h1>{entry.data.title}</h1>
      <img src={imageUrl} />
      <div className="text">{widgetFor('body')}</div>
    </div>
  );
};

CMS.registerPreviewTemplate('posts', PostPreview);
import CMS, { useMediaAsset } from '@staticcms/core';

import type { TemplatePreviewProps } from '@staticcms/core';

/**
 * The type for 'entry.data'
 */
interface Post {
  image: string;
  title: string;
  body: string;
}

const PostPreview = ({ widgetFor, entry, collection, fields }: TemplatePreviewProps<Post>) => {
  const imageField = useMemo(() => fields.find(field => field.name === 'image'), [fields]);
  const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);

  return (
    <div>
      <h1>{entry.data.title}</h1>
      <img src={imageUrl} />
      <div className="text">{widgetFor('body')}</div>
    </div>
  );
};

CMS.registerPreviewTemplate('posts', PostPreview);

Lists and Objects

The API for accessing the individual fields of list- and object-type entries is similar to the API for accessing fields in standard entries, but there are a few key differences. Access to these nested fields is facilitated through the widgetsFor function, which is passed to the preview template component during render.

List Template Example

For list fields, the widgetFor function returns an array of objects that you can map over in your template. If your field is a list of authors containing two entries, with fields name and description, the return value of widgetsFor would look like this:

[{
  data: { title: 'Mathias', description: 'Co-Founder'},
  widgets: { title: <WidgetComponent>, description: <WidgetComponent>}
},
{
  data: { title: 'Chris', description: 'Co-Founder'},
  widgets: { title: <WidgetComponent>, description: <WidgetComponent>}
}]

const AuthorsPreview = ({ widgetsFor }) => {
  return h(
    'div',
    {},

    // This is a static header that would only be rendered once for the entire list
    h('h1', {}, 'Authors'),

    // Here we provide a simple mapping function that will be applied to each
    // object in the array of authors
    widgetsFor('authors').map(function (author, index) {
      return h(
        'div',
        { key: index },
        h('hr', {}),
        h('strong', {}, author.data.name),
        author.widgets.description,
      );
    }),
  );
};

CMS.registerPreviewTemplate('authors', AuthorsPreview);
import CMS from '@staticcms/core';

const AuthorsPreview = ({ widgetsFor }) => {
  return (
    <div>
      {/* This is a static header that would only be rendered once for the entire list */}
      <h1>Authors</h1>
      {/* Here we provide a simple mapping function that will be applied to each object in the array of authors */}
      {widgetsFor('authors').map((author, index) => (
        <div key={index}>
          <hr />
          <strong>{author.data.name}</strong>
          {author.widgets.description}
        </div>
      ))}
    </div>
  );
};

CMS.registerPreviewTemplate('authors', AuthorsPreview);
import CMS from '@staticcms/core';

import type { TemplatePreviewProps } from '@staticcms/core';

interface Author {
  name: string;
  description: string;
}

/**
 * The type for 'entry.data'
 */
interface Authors {
  authors: Author[];
}

const AuthorsPreview = ({ widgetsFor }: TemplatePreviewProps<Authors>) => {
  return (
    <div>
      {/* This is a static header that would only be rendered once for the entire list */}
      <h1>Authors</h1>
      {/* Here we provide a simple mapping function that will be applied to each object in the array of authors */}
      {widgetsFor('authors').map((author, index) => (
        <div key={index}>
          <hr />
          <strong>{author.data.name}</strong>
          {author.widgets.description}
        </div>
      ))}
    </div>
  );
};

CMS.registerPreviewTemplate('authors', AuthorsPreview);

Object Example

Object fields are simpler than lists – instead of widgetsFor returning an array of objects, it returns a single object. Accessing the shape of that object is the same as the shape of objects returned for list fields:

{
  data: { front_limit: 0, author: 'Chris' },
  widgets: { front_limit: <WidgetComponent>, author: <WidgetComponent>}
}

const GeneralPreview = ({ entry, widgetsFor }) => {
  const title = entry.data.site_title;
  const posts = entry.data.posts;

  return h(
    'div',
    {},
    h('h1', {}, title),
    h(
      'dl',
      {},
      h('dt', {}, 'Posts on Frontpage'),
      h('dd', {}, widgetsFor('posts').widgets.front_limit || 0),

      h('dt', {}, 'Default Author'),
      h('dd', {}, widgetsFor('posts').data.author || 'None'),
    ),
  );
};

CMS.registerPreviewTemplate('general', GeneralPreview);
import CMS from '@staticcms/core';

const GeneralPreview = ({ entry, widgetsFor }) => {
  const title = entry.data.site_title;
  const posts = entry.data.posts;

  return (
    <div>
      <h1>{title}</h1>
      <dl>
        <dt>Posts on Frontpage</dt>
        <dd>{widgetsFor('posts').widgets.front_limit || 0)}</dd>
        <dt>Default Author</dt>
        <dd>{widgetsFor('posts').data.author || 'None')}</dd>
      </dl>
    </div>
  );
};

CMS.registerPreviewTemplate('general', GeneralPreview);
import CMS from '@staticcms/core';

import type { TemplatePreviewProps } from '@staticcms/core';

interface Posts {
  front_limit?: number;
  author?: string;
}

/**
 * The type for 'entry.data'
 */
interface GeneralSettings {
  site_title: string;
  posts: Posts;
}

const GeneralPreview = ({ entry, widgetsFor }: TemplatePreviewProps<GeneralSettings>) => {
  const title = entry.data.site_title;
  const posts = entry.data.posts;

  return (
    <div>
      <h1>{title}</h1>
      <dl>
        <dt>Posts on Frontpage</dt>
        <dd>{widgetsFor('posts').widgets.front_limit || 0)}</dd>
        <dt>Default Author</dt>
        <dd>{widgetsFor('posts').data.author || 'None')}</dd>
      </dl>
    </div>
  );
};

CMS.registerPreviewTemplate('general', GeneralPreview);

Editor Preview Styles

Register a custom stylesheet to use on the preview pane.

CMS.registerPreviewStyle(url);

Raw Styles

If you want to provide a raw CSS string instead of a url, you can pass { raw: true } as the second parameter.

CMS.registerPreviewStyle('.main { color: blue; border: 1px solid gree; }', { raw: true });

Collection Card Preview

registerPreviewCard allows you to create a card template that overrides the cards displayed in the collection view.

Params

Param

Type

Description

name

string

The name of the collection (or file for file collections) which this preview component will be used for
Folder collections: Use the name of the collectionFile collections: Use the name of the file

component

React Function Component

A React functional component that renders a preview card for a given entry in your collection

getHeight

function

A function that returns the height for your cards. An object containing the current collection and entry are passed into the function at render. If no getHeight function is provided, the height will be 204 if the collection has an image field or 64 if the collection does not have an image field

The following parameters will be passed to your react_component during render:

Param

Type

Description

entry

object

Object with a data field that contains the current value of all widgets in the editor

widgetFor

Function

Given a field name, returns the rendered preview of that field’s widget and value

widgetsFor

Function

Given a field name, returns the rendered previews of that field’s nested child widgets and values

hasLocalBackup

boolean

Whether the current entry has a local backup

Example

const PostPreviewCard = ({ entry, widgetFor }) => {
  return h(
    'div',
    { style: { width: '100%' } },
    widgetFor('image'),
    h(
      'div',
      { style: { padding: '16px', width: '100%' } },
      h(
        'div',
        {
          style: {
            display: 'flex',
            width: '100%',
            justifyContent: 'space-between',
            alignItems: 'start',
          },
        },
        h(
          'div',
          {
            style: {
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'baseline',
              gap: '8px',
            },
          },
          h('strong', { style: { fontSize: '24px' } }, entry.data.title),
          h('span', { style: { fontSize: '16px' } }, entry.data.date),
        ),
        h(
          'div',
          {
            style: {
              backgroundColor: entry.data.draft === true ? 'blue' : 'green',
              color: 'white',
              border: 'none',
              padding: '4px 8px',
              textAlign: 'center',
              textDecoration: 'none',
              display: 'inline-block',
              cursor: 'pointer',
              borderRadius: '4px',
            },
          },
          entry.data.draft === true ? 'Draft' : 'Published',
        ),
      ),
    ),
  );
};

CMS.registerPreviewCard('posts', PostPreviewCard, () => 240);
import CMS from '@staticcms/core';

const PostPreviewCard = ({ entry, widgetFor }) => {
  return (
    <div style={{ width: '100%' }}>
      {widgetFor('image')}
      <div style={{ padding: '16px', width: '100%' }}>
        <div
          style={{
            display: 'flex',
            width: '100%',
            justifyContent: 'space-between',
            alignItems: 'start',
          }}
        >
          <div
            style={{
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'baseline',
              gap: '8px',
            }}
          >
            <strong style={{ fontSize: '24px' }}>{entry.data.title}</strong>
            <span style={{ fontSize: '16px' }}>{entry.data.date}</span>
          </div>
          <div
            style={{
              backgroundColor: entry.data.draft === true ? 'blue' : 'green',
              color: 'white',
              border: 'none',
              padding: '4px 8px',
              textAlign: 'center',
              textDecoration: 'none',
              display: 'inline-block',
              cursor: 'pointer',
              borderRadius: '4px',
            }}
          >
            {entry.data.draft === true ? 'Draft' : 'Published'}
          </div>
        </div>
      </div>
    </div>
  );
};

CMS.registerPreviewCard('posts', PostPreviewCard, () => 240);
import CMS from '@staticcms/core';

import type { TemplatePreviewCardProps } from '@staticcms/core';

/**
 * The type for 'entry.data'
 */
interface Post {
  image: string;
  title: string;
  date: string;
  body: string;
  draft: boolean;
}

const PostPreviewCard = ({ entry, widgetFor }: TemplatePreviewCardProps<Post>) => {
  return (
    <div style={{ width: '100%' }}>
      {widgetFor('image')}
      <div style={{ padding: '16px', width: '100%' }}>
        <div
          style={{
            display: 'flex',
            width: '100%',
            justifyContent: 'space-between',
            alignItems: 'start',
          }}
        >
          <div
            style={{
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'baseline',
              gap: '8px',
            }}
          >
            <strong style={{ fontSize: '24px' }}>{entry.data?.title}</strong>
            <span style={{ fontSize: '16px' }}>{entry.data?.date}</span>
          </div>
          <div
            style={{
              backgroundColor: entry.data?.draft === true ? 'blue' : 'green',
              color: 'white',
              border: 'none',
              padding: '4px 8px',
              textAlign: 'center',
              textDecoration: 'none',
              display: 'inline-block',
              cursor: 'pointer',
              borderRadius: '4px',
            }}
          >
            {entry.data?.draft === true ? 'Draft' : 'Published'}
          </div>
        </div>
      </div>
    </div>
  );
};

CMS.registerPreviewCard('posts', PostPreviewCard, () => 240);

Grid View

Collection Field Preview

registerFieldPreview allows you to create a custom preview for a specific field in the table view for collections.

Params

Param

Type

Description

collectionName

string

The name of the collection (or file for file collections) which this preview component will be used for
Folder collections: Use the name of the collectionFile collections: Use the name of the file

fieldName

string

The name of the field

component

React Function Component

A React functional component that renders a preview of the field for a given entry in your collection

The following parameters will be passed to your component during render:

Param

Type

Description

collection

object

Collection configuration

field

object

Field configuration

value

Function

The current value of the field for the given entry

Example

const PostDraftFieldPreview = ({ value }) => {
  return h(
    'div',
    {
      style: {
        backgroundColor: value === true ? 'rgb(37 99 235)' : 'rgb(22 163 74)',
        color: 'white',
        border: 'none',
        padding: '2px 6px',
        textAlign: 'center',
        textDecoration: 'none',
        display: 'inline-block',
        cursor: 'pointer',
        borderRadius: '4px',
        fontSize: '14px',
      },
    },
    value === true ? 'Draft' : 'Published',
  );
};

CMS.registerFieldPreview('posts', 'draft', PostDraftFieldPreview);
import CMS from '@staticcms/core';

const PostDraftFieldPreview = ({ value }) => {
  return h(
    'div',
    {
      style: {
        backgroundColor: value === true ? 'rgb(37 99 235)' : 'rgb(22 163 74)',
        color: 'white',
        border: 'none',
        padding: '2px 6px',
        textAlign: 'center',
        textDecoration: 'none',
        display: 'inline-block',
        cursor: 'pointer',
        borderRadius: '4px',
        fontSize: '14px',
      },
    },
    value === true ? 'Draft' : 'Published',
  );
};

CMS.registerFieldPreview('posts', 'draft', PostDraftFieldPreview);
import CMS from '@staticcms/core';

import type { FieldPreviewProps } from '@staticcms/core';

const CMS = ({ value }: FieldPreviewProps<boolean>) => {
  return h(
    'div',
    {
      style: {
        backgroundColor: value === true ? 'rgb(37 99 235)' : 'rgb(22 163 74)',
        color: 'white',
        border: 'none',
        padding: '2px 6px',
        textAlign: 'center',
        textDecoration: 'none',
        display: 'inline-block',
        cursor: 'pointer',
        borderRadius: '4px',
        fontSize: '14px',
      },
    },
    value === true ? 'Draft' : 'Published',
  );
};

CMS.registerFieldPreview('posts', 'draft', PostDraftFieldPreview);

Table View