Persisting UI Customizations in Strapi
Tags: node, webdev, strapi
Published: 2021-03-15 23:01:57

Today we will be looking at a Headless CMS called [Strapi](https://strapi.io/). 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.

---

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8s6kd73w4wc40rk2ekx.jpg)
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
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jftlm1776jl6vzzz5hrq.png)

### Post List View
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uy2678vb4868l3q947nh.png)

### Post Edit View
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6it7227hz74cg9z0eb52.png)

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
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ltlq6dh4s5djiafwynp9.png)

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 hierarchy 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.

{% gist https://gist.github.com/iamscottcab/f7882e53e21391e1c831dbd14064352d %}

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.

{% gist https://gist.github.com/iamscottcab/e8f7d615cf49a8460aecc77b3e93ef76 %}

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
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dy420zjq5cq9lk5mfsvv.png)

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
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9o3u3oqlzcfajzci1fvm.png)

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](https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/configurations.html#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`.

{% gist https://gist.github.com/iamscottcab/b127c450220fd64ee972c9ae39ce64a0 %}

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.