Plugin SDK > Field extensions

    Field extensions

    By creating what we call 'field extensions', plugins can change the way in which the fields of a record are presented to the final editor, going beyond the appearance configurations that DatoCMS offers by default.

    There are different types of field extensions that can be created, depending on requirements:

    "Field editor" extensions

    They operate on top of a particular field, replacing the default field editor that DatoCMS provides with custom code:

    The use cases are varied, and many examples are already on our marketplace, ready to be installed on your project:

    • The Shopify product plugin can be hooked into string fields and completely changes the interface to allow you to browse the products in your Shopify store, then save the ID of the selected product in the string field itself;

    • The Hidden field plugin simply hides a specific field from the editor's eyes, while the Conditional fields plugin shows/hides a number of fields when you toggle a particular checkbox field.

    Field editors as sidebar panels

    It is also possible to move editor extensions in the right-hand sidebar, giving it the appearance of a collapsible panel. The difference between this mode and a sidebar panel is that this controls a specific field of the record and can use it as a "storage unit" to save internal information, while a sidebar panel is not associated to any particular field.

    As an example, the Sidebar notes plugin uses this mode to turn a JSON field into a kind of notepad where you can add virtual post-it notes.

    "Field addon" field extensions

    As the name suggests, addons do not change the way a field is edited, but they add functionality, or provide additional information, directly below the field editor. While only one editor can be set up for each field, it is possible to have several addons per field, each providing its own different functionality:

    As examples of use, Yandex Translate adds a button below your localisable text/string fields to automatically translate its content from one locale to another, while Sanitize HTML allows you to clean up the HTML code present in a text field according to various preferences.

    Two sides of the same coin

    Editors and addons are both field extensions, so they have access to exactly the same methods and information. The difference between the two is simply semantics: editors are for editing the field, while addons offer extra functionality.

    How to hook field extensions to a field

    The SDK provides an overrideFieldExtensions hook that can be implemented to declare the intention to take part in the rendering of any field within the form, either by setting its editor, or by adding some addons, or both.

    In this example, we are forcing the use of a custom starRating editor for all integer fields that have an ID of rating:

    import { connect, Field, FieldIntentCtx } from 'datocms-plugin-sdk';
    connect({
    overrideFieldExtensions(field: Field, ctx: FieldIntentCtx) {
    if (
    field.attributes.field_type === 'integer' &&
    field.attributes.api_key === 'rating'
    ) {
    return {
    editor: { id: 'starRating' },
    };
    }
    },
    });

    Similarly, we can also add an addon extension called loremIpsumGenerator below all the text fields:

    overrideFieldExtensions(field: Field, ctx: FieldIntentCtx) {
    if (field.attributes.field_type === 'text') {
    return {
    addons: [
    { id: 'loremIpsumGenerator' },
    ],
    };
    }
    }

    Rendering the field extension

    At this point, we need to actually render the field extensions by implementing the renderFieldExtension hook.

    Inside of this hook we can implement a simple "router" that will present a different React component depending on the field extension that we've requested to render inside the iframe.

    We also make sure to pass down as a prop the second ctx argument, which provides a series of information and methods for interacting with the main application:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import { connect, RenderFieldExtensionCtx } from 'datocms-plugin-sdk';
    function render(component: React.ReactNode) {
    ReactDOM.render(
    <React.StrictMode>{component}</React.StrictMode>,
    document.getElementById('root'),
    );
    }
    connect({
    renderFieldExtension(fieldExtensionId: string, ctx: RenderFieldExtensionCtx) {
    switch (fieldExtensionId) {
    case 'starRating':
    return render(<StarRatingEditor ctx={ctx} />);
    case 'loremIpsumGenerator':
    return render(<LoremIpsumGenerator ctx={ctx} />);
    }
    },
    });

    The implementation of the Lorem Ipsum component is pretty straightforward: we simply use the ctx.setFieldValue function to change the value of the field into a randomly generated string:

    import { Canvas, Button } from 'datocms-react-ui';
    import { loremIpsum } from 'lorem-ipsum';
    type PropTypes = {
    ctx: RenderFieldExtensionCtx;
    };
    function LoremIpsumGenerator({ ctx }: PropTypes) {
    const insertLoremIpsum = () => {
    ctx.setFieldValue(ctx.fieldPath, loremIpsum({ format: 'plain' }));
    };
    return (
    <Canvas ctx={ctx}>
    <Button type="button" onClick={insertLoremIpsum} buttonSize="xxs">
    Add lorem ipsum
    </Button>
    </Canvas>
    );
    }

    It is important to wrap the content inside the Canvas component, so that the iframe will continuously auto-adjust its size based on the content we're rendering, and to give our app the look and feel of the DatoCMS web app.

    The Star Rating component is quite similar. We get the current field value from ctx.formValues and the disabled state from ctx.disabled. When the user interacts with the component and changes its value, we call ctx.setFieldValue to propagate the change to the main DatoCMS application:

    import ReactStars from 'react-rating-stars-component';
    import get from 'lodash/get';
    import { Canvas } from 'datocms-react-ui';
    import { RenderFieldExtensionCtx } from 'datocms-plugin-sdk';
    type PropTypes = {
    ctx: RenderFieldExtensionCtx;
    };
    function StarRatingEditor({ ctx }: PropTypes) {
    const currentValue = get(ctx.formValues, ctx.fieldPath);
    const handleChange = (newValue: number) => {
    ctx.setFieldValue(ctx.fieldPath, newValue);
    };
    return (
    <Canvas ctx={ctx}>
    <ReactStars
    size={32}
    isHalf={false}
    edit={!ctx.disabled}
    value={currentValue || 0}
    onChange={handleChange}
    />
    </Canvas>
    );
    }

    Here's the final result:

    Adding user-defined settings into the mix

    You might have noticed that our plugin is currently hardcoding some choices, namely:

    • the rules that decide when to apply both our "star rating" and "lorem ipsum" extensions;

    • the maximum number of stars to show;

    • the length of the "lorem ipsum" text we're generating;

    If we want, we could make these settings configurable by the user, either by implementing some global plugin settings, or by transforming our field extensions into "manual" extensions.

    When to use one strategy or the other is completely up to you, and each has its own advantages/disadvanges.

    • Manual field extensions are, well, manually hooked by the end-user on each field, and for each installation different configuration options can be specified. Given that our star rating extension will most likely be used in a few specific places rather than in all integer fields of the project, manual fields might be the best choice.

    • On the other hand, our Lorem Ipsum generator may be convenient in all text fields, so requiring the end user to manually install it everywhere would be unnecessarily tedious. In this case, the choice to force the addon on all fields with the overrideFieldExtensions hook is probably the right one.

    In the next section we're going to take a much more detailed look at manual field extensions, and we're going to convert our star rating editor into a manual extension.


    overrideFieldExtensions

    Use this function to automatically force one or more field extensions to a particular field.

    Return value

    The function must return an object with the following structure:

    Properties available in context

    The following information and methods are available:

    renderFieldExtension

    This function will be called when the plugin needs to render a field extension (see the manualFieldExtensions and overrideFieldExtensions functions).

    Properties available in context

    The following information and methods are available:

    Methods available in context

    The following information and methods are available: