How to create an inverse relationships plugin on DatoCMS
By: Irene Oppo
Tags: Headless Cms, Plugins, Coding, Building, Web Development

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

DatoCMS supports three types of plugin: field editors, field-addons and sidebar widgets.

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

DatoCMS plugins need to be assigned to a field in one of your models to come alive, so while creating new plugins you need to declare which kind of fields your plugin supports.

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:

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 datoCmsApitToken global parameter we configured earlier:

import { SiteClient } from ‘datocms-client; window.DatoCmsPlugin.init((plugin) => {

plugin.startAutoResizer();



// …



const client = new SiteClient(plugin.parameters.global.datoCmsApiToken);

});

Building up the query

The next thing to do is building the right query to get what you need, in this case all the Post records that are linked to a specific author.

The plugin object holds a couple of properties that are essential here. The first one is plugin.itemTypes that returns all the project’s models. We can use it to get our Post model:

const postItemType = Object.values(plugin.itemTypes).find(

itemType => itemType.attributes.api_key === plugin.parameters.instance.itemTypeApiKey

);

The second is plugin.itemId , which is the ID of the current author record we’re seeing. Looks we’ve got everything to build the query now!

const linkFieldApiKey = plugin.parameters.instance.fieldApiKey; const query = {

‘filter[type]’: postItemType.id,

[`filter[fields][${linkFieldApiKey}][is][]`]: plugin.itemId,

‘page[limit]’: 10,

version: ‘current’,

}; client.items.all(query)

This returns all the blog posts that have the current record as its author. For each one of them we can create a link element and then append it to the iframe:

dato.items.all(query).then((items) => {

items.forEach((item) => {

const link = document.createElement(‘a’);

link.textContent = “title placeholder”;

const url = `#`;

link.href = url;

link.target = ‘_top’;

link.classList.add(‘link’);

container.appendChild(link);

});

})

.catch((error) => {

console.log(error);

});

We now need to find out which is the title field for the Post model:

const titleFieldId = postItemType.relationships.title_field.data.id;

const titleField = plugin.fields[titleFieldId];

Now we can build the url and show the appropriate title. Of course our item could be localized, so in this case we should get the title in the main locale:

if (titleField.attributes.localized) {

const mainLocale = plugin.site.attributes.locales[0];

link.textContent = item[titleField.attributes.api_key][mainLocale];

} else {

link.textContent = item[titleField.attributes.api_key];

} const url = `/editor/item_types/${postItemType.id}/items/${item.id}/edit`;

Finally you should be able to see something like this:

Add your favourite CSS styling and there you have your first custom plugin!

Share your plugin to the world

It is now time to share your work with the community!

Open your package.json and README.md and make sure all the info is correct following our documentation.

Consider adding a nice cover picture and a preview GIF image so that when the plugin will be displayed our plugin catalog it will be easier to decide that this is the plugin we want.