Today we will be looking at a Headless CMS called Strapi. To keep this post on topic I am going to assume some prior knowledge on Strapi. If you're new to the technology and would like some intro guides, let me know and I will make a short series on this. Generally speaking though their documentation should be enough to get you up to speed quickly, I've leaned on them heavily to learn the platform.

What I would like to address today however is a multi-environment setup for this tool, specifically around UI customization in the admin panel and persisting these changes across each environment.


strapi-persist-2

If you've ever run Strapi in a multi-environment setup you will notice that whenever you spin up a new instance of your site you lose a lot of the UI customization in your application. By this I mean the "list" and "edit" views in your admin panel for a particular content type are set back to their default structure. It would be tempting to just set these back up manually in each environment because "It only needs to be done once". While this is true (and honestly probably not a deal breaker) it does present a possibilty for configuration drift between environments and just generally adds more potential places for there to be an error or misconfiguration.

While there are no out-of-the-box solutions to persist this customization it can easily be achieved by using the bootstrap functionality baked into Strapi. Let's disect that one below.


The Initial Content Type

For the example today we are going to stick with the blog format where we create a Post content type. This type will have the basic building blocks that we need to render our posts on whatever website is consuming our Strapi API. A basic blog content type might look like below.

Post Content Type

post-content-type

Post List View

post-list-view

Post Edit View

post-edit-view

Now we have the option to go in and manually configure some of these views, for example in the Post Edit View we can clearly see the button on the right marked "Configure the view".

Let's do that now so we can change the order of some of the items, perhaps move the featured image to the top (to mimic the actual blog post structure) and rename some labels or add descriptions.

Configured Post Edit View

post-config-edit-view

As you can see we have changed Featured_image to read Featured Image and moved it's position. We have also supplied an arbitrary description to the Slug field. That's great and all but what happens when I spin up a new environment? I would have to do that all again manually. In this instance for two fields I would say that's probably OK, for a complicated data type that has additional logic or a heirarchy of information this isn't an overly acceptable solution.

Thankfully Strapi saves this configuration in the database and we can pull it out as JSON, let's do that now!

UI Customiztion via JSON

  1. Connect to your local database instance. I am using Azure Data Studio with their PostgreSQL plugin. Feel free to use whatever tools you're comfortable with.
  2. Select all entries from core_store table.
  3. Look for a row labelled plugin_content_manager_configuration_content_types::application::{content_type}.{content_type}. In this instance plugin_content_manager_configuration_content_types::application::post.post
  4. Copy the value field. It should look something like below.

The data structure should be fairly self explanatory, we can make some tweaks the the settings of this content type, whether it is filterable or searchable for example. Under metadatas we can change details about each value in the content type. For example you can see the change we have made in featured_image to change the label to Featured Image when in the edit view, though you can see that same change hasn't propogated over to the list view.

The layouts key shows the heirarchy of our edit view, I find that making these changes is easier in the UI and then exporting out the associated JSON however once you have the file the first time you can definitely move these around in code. Lastly the list key under layouts specifies which keys, and the order of the keys gets shown.

Tweaking the UI Customization

Now we have our JSON file and understand the structure we can go ahead and make some changes.

  • We are going to make sure that the created_at key says Created At in the list view as well as the edit view.
  • We are going to make the featured_image media picker take up the full width of the admin panel (a change you seem to only be able to make via JSON)
  • We are going to remove id and featured_image from the list view and replace it a created_at column instead.

The differences are minor but can be seen below.

In order to test these changes the simplest way to it do (for now) is to edit the database directly and replace the current string with your current data and restart the server.

The Final List View

post-final-list-view

As you can see our changes have worked, id and featured_image have been removed and created_at added in place with the correct label.

The Final Edit View

post-final-edit-view

As you can see our image picker now takes up the full width of the admin panel even though we only had the option to move it in the editor.

Persisting the Changes

Now we know how to edit our UI customization file lets persist it.

  1. Save that JSON file somewhere in your project so that it stays in source control and is easily referenced later.
  2. Create a file under config/functions/bootstrap.js as per their docs on Functions.
  3. In the bootstrap function call some code to update the corresponding database row with the file you have saved in your project. Here is an example utility function I have written that lets me pass in a json file location that corresponds to <contentType>.json.

As you can see I am using knex for this because I am familiar with it. That being said you could use the bookshelf or any other adaptor, or the suitable connector for your database type.


Closing Thoughts

Now whenever your application starts (or hot reload fires in your dev environment) your UI customization is updated in the database and now your UI is configured across multiple environments.

With some extra tweaks you can do the same with components that are within a more complex data type. The syntax is slightly different so I suggest a cool extension to this post, if you want to dig into it, is to work out how to persist UI Customization in components too. I do this in my current project and it's really handy.

For those wondering when spinning up an entirely fresh environment the bootstrap code gets run AFTER strapi does a bunch of set up. That means your content type, even if it is "brand new" will still be in the database and your SQL query will run so you shouldn't need to do any complicated checks for rows or do some weird double-deploy nonsense. I have seen some back and forth on the Strapi Issue Tracker as the whether bootstrap should run before or after Strapi initializes, howevever given it provides us access to the database connector (and makes sure our data types are set up correctly) I am all for after as the default, as it currently is.

Now you can worry less about your UI customization and more about the logic required to run your application. What's great is this method can be used whenever, so if you have to make changes through the UI you can easily pull out the updated files as required and persist them in your database. You could even find ways to automate that process, or pull your customization from a master database that it used for this purpose. Whatever your setup, by storing your UI customization in the database you have a wealth of opportunities to streamline your multi-environment customization.