How to create an inverse relationship plugin on DatoCMS

Posted on December 20th, 2018 by Stefano Verna

Just a couple of weeks ago we released the Plugins SDK, that enable the developers community to fully customize every project's administrative area.

Let's say we have a project that has, for example, an Author model and a Post model and each post is linked to an author by a single link field. In DatoCMS we can see which author created the post by navigating into the single post... but it is not possible to see which posts each author has written!

Being able to see inverse relationships it's a pretty common need: thanksfully we can build a plugin for that, let's see how!

Run the project generator

To start straight away with the development and avoid writing boilerplate code, we've prepared a super-handy Yeoman generator.

Let's create the folder that will contain the skeleton of our plugin and, once inside the folder, let's run the generator:

mkdir inverse-relationships

cd inverse-relationships

npx -p yo -p generator-datocms-plugin -c 'yo datocms-plugin'

Here's a quick video of the tool in action:

As you can see, a number of questions will be prompted. Some of them are quite obvious (ie. the plugin name), others require a little more understanding of what's going on, so let's see them in detail.

Plugin type

While the first two types of plugin operate on top of a particular field replacing/enhancing the default editor that DatoCMS provides, sidebar widget are rendered.. duh, on the sidebar of the record editor.

Since this plugin applies to an entire record instead of a single field, we'll pick the Sidebar widget option.

Field types the plugin is compatible with

While it's quite obvious that you need to assign field editors plugins and field-addons plugins to a field, it might be less clear why this also applies to sidebar widgets, right? Well, the reason is that the sidebar widget might want to use the field's value to store some data (and yes, in case you're wondering, the underlying field won't be shown on the interface).

In any case, our sidebar widget plugin won't read/store any data, so we can just choose any field type (ie. JSON) and move on to the next question.

Configuration parameters

Parameters allow the final user to customize the plugin, and you as the developer are in charge of deciding to which degree you want your plugin to be customizable.

Global parameters are set just once for the whole project. In this case we need to ask the final user the API token to use to query the DatoCMS API and get the actual inverse relationships.

Instance variables, on the other hand, allow the final user to insert different settings every time the plugin gets applied on a field.

Our plugin requires two instance variables: the first one will store the name of the model you want to connect to (ie. post), the other will store the field that hosts the inverse relation between the two (ie. author).

Parameters need to be expressed in JSON. The format is pretty straight-forward, but feel free to take a look at our documentation for all the nitty gritty details:

"hint":"The model you want linked records to show up (ie. <code>post</code>)"

},

{

"id":"fieldApiKey",

"label":"Field ID",

"type":"string",

"required":true,

"hint":"The single-link field to use as foreign key (ie. <code>author</code>)"

}

]

}

Alright, I'm done configuring stuff!

If everything went right, you should see in your DatoCMS project an already configured plugin similar to this one:

As you can see, in this page you can already insert the only global configuration parameter we have, the DatoCMS API token. Let's do so and hit the Save button.

Assign the plugin to a JSON field

Now that the plugin is installed in the project, you need to apply it to a JSON field to see it working.

Navigate to the Author model (Settings > Models > Author) and add the JSON field that will host the plugin. You will notice that in the presentation tab you can select our plugin as the Field editor option and fill-in the instance configuration parameters:

See the plugin working

Technically speaking, plugins are small HTML5 applications that exist in a sandboxed <iframe> and interact with the main DatoCMS webapp.

As you can see, our generator already setup the Entry point of our plugin so that it points to a tunnel connected to our local project. That is, using the raw localhost URL would give us some mixed content errors due to the fact that the local development server runs in HTTP while the main DatoCMS webapp runs in HTTPS.

Anyway, just start your local webpack development server with:

yarn start

Once the server starts, go to one of your authors.. and voilà! You should see a glorious "Hello, world!" awaiting for you!

Write some code

Alright, the setup part is finished, it is time to get to the coding! Let's take a look at src/index.js file:

import'./style.sass';

window.DatoCmsPlugin.init((plugin)=>{

plugin.startAutoResizer();

const container =document.createElement('div');

container.classList.add('container');

container.textContent='Hello, world!';

document.body.appendChild(container);

});

The project already includes our Plugin SDK, which exposes the DatoCmsPlugin.init() method. Its callback will be called when the cross-window communication between our plugin and the DatoCMS main webapp is in place, so all the code needs to be executed inside of it. The plugin.startAutoResizer() call will make sure the iframe will automatically resize whenever the content of the page changes.

Alright, let's start adding a title to the widget:

const container =document.createElement('div');

container.classList.add('container');

const title =document.createElement('h4');

title.textContent='Linked content';

document.body.appendChild(container);

container.appendChild(title);

Great, now we can add to our project the datocms-client package, so that we'll be able to query the DatoCMS API to get the inverse relationships:

yarn add datocms-client

Once installed, we can require the package and instanciate the DatoCMS SiteClient using the datoCmsApiToken global parameter we configured earlier: