Creating Field Plugins

February 17, 2020

By Kendall Strautman

In the previous post, we learned how to create a custom field component and register it to the sidebar. With that baseline, let's go full circle on the topic of custom fields in TinaCMS. In this short but sweet post 🧁, we’ll cover how to turn a field component into a field plugin.

Field Plugin vs. Field Component

Plugins extend functionality in the CMS; field plugins allow us to create and register custom fields. There are a few reasons why you may want to create a field plugin. With a custom field, you can completely control the editing experience and functionality. If the primary fields provided by Tina don't fit your use case, you can fill the gaps.

A field component is just one piece of a field plugin (more on this later). A custom field component can achieve the same functionality as a plugin. But if you plan on reusing the custom field on different forms, it is recommended to take the extra steps to make a plugin 🔌.

Creating field plugins helps confine complex logic to a single module. This makes it easier to update or swap out custom field functionality later down the line. Using the plugin API makes our 'higher-level' code more reusable and contained, keeping the fields independent from the core CMS.

For those who want to dig deeper, this approach seeks to embody the Dependency Inversion Principle.

Getting Started 👏

To follow along, you should have a custom field component set-up with a Tina form. If not, you can get some more context from the previous post: how to create a custom field component. In the following examples, I am using the same llama-filters 🦙 demo from our previous post.

There are two steps to adding a field plugin to the CMS. First, we'll define the field component object and register it with the CMS. Next, we'll use the field plugin in a form definition so we can edit content in the sidebar with our fancy custom field plugin.

Want to see a working example? Check out the Authors Field Plugin from the Tina Grande Starter.

1. Add the Field Plugin to the CMS

To register the custom field as a plugin with the CMS, we’ll need to head to the file where we can access the CMS instance. In the Next.js demo, we’ll look at _app.js.

// _app.js

import React from 'react'
import App from 'next/app'
import { Tina, TinaCMS } from 'tinacms'
import { GitClient } from '@tinacms/git-client'
/*
 ** 1. Import the custom field component
 */
import RangeInput from '../components/RangeInput'

/*
 ** 2. Define the field plugin
 */
const customFieldPlugin = {
  name: 'range-input',
  Component: RangeInput,
}

export default class Site extends App {
  constructor() {
    super()
    this.cms = new TinaCMS({
      apis: {
        git: new GitClient('http://localhost:3000/___tina'),
      },
      sidebar: {
        position: 'overlay',
        hidden: process.env.NODE_ENV === 'production',
      },
    })
    /*
     ** 3. Register the plugin with the cms
     */
    this.cms.fields.add(customFieldPlugin)
  }

  render() {
    //...
  }
}

You’ll want to import the custom field component and then register the plugin with the CMS directly. Notice how we import the RangeInput component created in the previous post. This is the custom field component that we're now attaching to a field plugin.

If you’re working with Gatsby, this looks slightly different. Hint: you’ll head to the gatsby-browser.js file to access the CMS instance.

Field Plugin Interface

Let's break down the field plugin further. The interface below should provide some insight into all that can go into creating a field plugin. When you register a field plugin with Tina, it expects an object with a similar shape.

interface FieldPlugin {
  name: string
  Component: React.FC<any>
  type?: string
  validate?(
    value: any,
    allValues: any,
    meta: any,
    field: Field
  ): string | object | undefined
  parse?: (value: any, name: string, field: Field) => any
  format?: (value: any, name: string, field: Field) => any
  defaultValue?: any
}

At a minimum, field plugins require a name and a component. The name is used to reference the custom field in form definitions (more on this later). The Component is what is actually rendered in the sidebar.

You can see that there are additional configuration functions and options. Note that the properties with a question mark are optional. These options are incredibly useful for creating fields that require validation, parsing, or formatting.

To see a more complex example, checkout the documentation on creating an email field.

2. Use the custom field in a form

Now that the plugin is registered with the CMS, we can use it in any form definition. Going back to the llama-filters demo, let’s head to index.js where the Tina form is configured. We need to update the form options for our image saturation field to reference the field plugin name, as opposed to calling the component directly.

/*
** 1. Remove the import of the custom field component
*/
- import RangeInput from '../components/RangeInput'
import React from 'react'
import { useLocalJsonForm } from 'next-tinacms-json'

export default function Index(props) {
  //...
}

const formOptions = {
 fields: [
   /*
   ** 2. Reference the field plugin `name` instead
   **    of passing the custom component directly
   */
   {
     label: 'Image Saturation',
     name: 'saturation',
-    component: RangeInput
+    component: 'range-input'
   }
 ]
}

Index.getInitialProps = async function() {
  //...
}

That's it! With the plugin defined and registered with the CMS, you can reuse this field ad infinitum. In my opinion, creating a field plugin helps maintain a consistent interface for defining forms. This way, the custom field works behind the scenes as if it were a native Tina field, which is pretty slick.

Short and sweet, as promised 🍰

This post, combined with the former, should give you all the building blocks to start making your own field plugins. Feel free to dive into the documentation on fields or plugins. Make sure to share your groovy custom fields with us @tina_cms 🖖. Or, if you feel there is a fundamental field missing from Tina, open up a PR to contribute your custom field!