What you'll learn
- How to create a new content model field plugin
- How a plugin stores and retrieves data
A detailed and more in-depth explanation and how-to for a Webiny Headless CMS field plugins. As an example, in this tutorial you will create a new field plugin that uses Google Maps API to retrieve a location or an address. In this tutorial we assume that you know how Webiny plugins work.
Tutorial example files are located in our example repository.
You need a field that gives you a functionality to search for an address via Google Maps API. When you select the address you want, from the list the Google Maps API provided, it is set to the form data. Also, you do not want to index that field, and you want to encrypt it before saving into the storage.
By all means, you can create plugins where ever you want - but try to stick to some structure.
apps directories, so put your plugins in those, according to a plugin type.
Our suggestion would be something like this:
[UI]plugins folder -
[API]plugins folder -
Your field type is a name of the directory containing all the plugins for that type. Remember, there are multiple types of plugins for a single field type in both UI and API side.
Files we are creating are:
addressFieldPlugin.ts- API field definition
addressFieldStoragePlugin.ts- API storage modifications
addressFieldIndexPlugin.ts- API indexing modifications
addressFieldPlugin.tsx- UI field definition and display when creating a model
addressFieldRendererPlugin.tsx- UI display of the field when creating or updating the entry
For quick info on required plugins check our How-to Guide: Create a Webiny Headless CMS field plugin.
First we need to create a field definition plugin. The base for the plugin is:
And in the
createField() function we return the field data:
Note that renderer name is left blank so code automatically determines which renderer to use. You can put the name of the renderer, but for our tutorial we leave it blank and use it later.
Now we can create the second UI plugin, a renderer for the field we just created. A base for the renderer plugin is:
Remember that we left the
renderer.name property blank when we created the field? Now in the
canUse() function you put the condition that determines if that particular renderer is for a given field:
render() function you put what is actually displayed when the field rendering is called. We can create a
onSelect() function that actually triggers the field change -
Now let's go solve the API plugins. First we need to create a plugin of type
This is the base of that plugin:
In the read definition, for the read and preview API, we can have a user-friendly GraphQL field:
And the resolver for that field:
Since we do not want the field to be filtered, we do not need to define
The manage side of the API can be exactly the same as the read one, but in our case it is a bit different. Since we save plain JSON value, this part is quite simple:
Now we have everything required to create and save the field. Next thing we need is to prevent the indexing of the field. By default, if a field is not searchable it is removed from the index. But for this tutorial we can create our own plugin. This is the base of that plugin:
toIndex we must remove the field from
values and put it into non-indexable
fromIndex we must revert that action:
This plugin now does what we want - disables the field indexing. Of course, we could pack the field with
jsonpack or compress it in the
toIndex method to take less space. The choice is all yours, just remember to revert what ever action you do.
All we have left to write is the plugin for storage. As we said, we want to encrypt the data for the storage. You can what ever library you want, it is all up to you. For this tutorial we can use some custom
decrypt functions. The base of the plugin looks like this:
First, we encrypt the data. Our suggestion is to encrypt the data and return an object with encrypted value and method of encryption:
Of course if you want to just return the encrypted value, feel free, the choice is yours.
And then comes the decryption. Because we expect our value to have some structure, it is easy to check if decryption is necessary. Or if we need to throw an error due to something we expect is not there.
You should now have a functioning field, apart the
AddressSearch component and
decrypt functions - it is up to you to created them.
Remember that you need to import the plugins.
Default import file location for API is
api/code/headlessCMS/src/index.ts and for UI it is
If you changed that, import in these locations.
Do not forget to rebuild, redeploy and rerun your application.