Home / Tech Posts

My experiences with customizing the Strapi CMS

8th April, 2022
Experiences from tailor fitting the Strapi CMS

A CMS solution that is built in-house from the ground-up offers maximum flexibility. But, it also requires a substantial initial time-to-build. Starting with a customizable open source CMS framework can reduce this initial time-to-build. But, there is always a danger of the ready-to-customize CMS not being customizable to certain future business requirements.

I mean, imagine if the business decides to go multilingual but the ready-to-customize CMS cannot support it without completely re-architecting the solution. To avoid such deadlock situations, it is critical to evaluate the customization capabilities of any ready-to-customize CMS.

I was in a similar situation ~6 months ago when I had to evaluate Node based CMS frameworks for one of my client’s website content management requirements. After the initial comparison of the available frameworks, I undertook a month long proof-of-concept customizing the Strapi CMS to the provided requirements. This was followed up with an implementation of the CMS solution that has now been in production for 3 months.

Based on this experience, I have detailed the kind of CMS customizations that were straight forward to achieve with Strapi. I have also listed the customizations are the non-trivial to implement. The objective of this post is to help someone evaluate Strapi customization capabilities to make an informed decision.

Note: The below detailed customizations were performed on Strapi 3.6.6

Straightforward strapi customizations

Below is the list of customizations that I found uncomplicated to achieve with the Strapi CMS.

Custom UI validations with lifecycle hooks:

Our content team uses the Strapi admin to add content to the system. Hereby, we wanted the capability to write custom UI validation functions for this data entry.

Strapi allows us to execute custom code on operations such as find, create, update, delete, count, etc. We used this capability to execute our custom input validations on create and update. This also allowed us to display custom error messages.

While the above solution has worked well for us, it would have been ideal to validate a field’s inputs as the user tabs out of the fields. But, this does not seem to be feasible with the available lifecycle hooks mechanism. It would require overriding the strapi admin source code. From my experience with extending or overriding strapi admin functionalities, this would be non-trivial to setup.

Activity log with middleware:

One of the client requirements was to record an audit trail of all the activities performed via the Strapi admin UI.

With Strapi, we could write middlewares to achieve this. These would be custom functions that can be executed on every request Strapi receives. We could also set up the sequence of execution of such custom functions via middleware load configuration. We were also able to control if we want our custom code to execute before or after Strapi CMS performs the database operation (via the call await next();). Additionally, the middleware has access to the strapi object to query the database via strapi.services['object-name'].create(), strapi.services['object-name'].find(), etc calls.

So, we wrote an audit trail middleware that would record the just performed task into a separate Strapi table that was not editable for regular Strapi admin users.

Custom media uploads with plugins:

The client team wanted to be able to upload images into the CMS setup to be used by the website. But, they also wanted to disallow uploads with certain file names, extensions, mime-types and sizes.

Strapi’s plugin mechanism allows us to extend the core Strapi functionality. In fact, the wonderful strapi-provider-upload-aws-s3-plus-cdn plugin mostly covered our requirements.

To achieve the specific upload validations, we created our own plugin extension with code similar to this single file source code from the plugin. We added our custom input validations to the upload function. I found creating local plugins within Strapi fairly straight forward.

Custom field types in the Strapi admin UI with plugin and lifecycle hooks:

One of the client requirements that would test Strapi’s customization capabilities was to build a custom input field. This drop-down field was expected to contain the name of all the published single-types. Our content team should be able to select a single-type from the drop-down field within the Strapi admin UI. Strapi has an inbuilt enumeration type, but it can only contain a static list of values.

To solve this, we firstly created a Content Type called single-types that could store the names of all the published single types. Now, we added a lifecycle hook on create and update for our single types to add an entry to this single-types table when published or un-published. Next, we created a plugin as described here to add a custom field type to the Strapi admin UI. Every time the custom field type would load, it would query our single-types to get the list of items to display in the drop-down.

In this way, any field that can be represented via a React component can be added to the Strapi admin UI. This could be a WYSIGYG editor, color picker, maps location selector, image with annotations or anything else.

Sitemap, email notifications and other customizations with middleware:

We are required to do a lot of things whenever a certain content is published. Some of these are:

We have generally found middleware to be the right place to setup such actions. However, not adding the right checks at the outset can make the CMS very slow. As a result, most of our middlewares customization code is within check blocks such as the following:

//Perform tasks only if this 
//is a page 
//type create or update
if (isContentTypeX && isPublished 
  && (actionType == "CREATE" 
  || actionType == "UPDATE"))
{
  //Do things only if the page 
  //has Enviornment and 
  //Name values available
  if (ctx.response.body 
    && ctx.response.body.Environment 
    && ctx.response.body.name)
  {
    ...
    ...
  }
}

CMS customizations that can get complicated with Strapi

Below are some of the changes that I experienced to be non-trivial to implement with the Strapi CMS.

Custom content publishing workflow:

The content publishing workflow at my client’s was simple and mirrored Strapi admin’s single-step publishing workflow (Save as Draft -> Publish).

But, I observed that there is no easy way to customize the content publishing workflow (eg : Save as Draft -> Under Review -> Approved -> Publish). By overriding the Strapi admin code, I was able to add additional buttons to mirror a custom publishing workflow. But, I do not know if there is a way to add custom stages, hooks for the custom stages for the content, access control around the custom workflow.

Non cosmetic Strapi admin UI modifications:

I was able to achieve cosmetic admin UI modifications (like changing the logo image or welcome message) with ease. But, changes beyond such cosmetic alterations required a thorough understanding of the Strapi admin UI code, for which no adequate documentation is available. This made implementing such changes cumbersome and error-prone.

For example, modifying how the Strapi admin allows to re-order components within a dynamic zone (as raised here)can be complicated because it requires understanding how the Strapi admin code performs this and then overriding / adding to the admin UI functionality.

Ordering components within a dynamic zone

Ordering components within a dynamic zone

Handling schema updates

When the model for a Content Type or a Single Type is altered by adding or removing the fields to it, there is no in-build mechanism or hook to specify how the schema change needs to be handled for the already existing data.

This required us to plan the model changes and write custom scripts to handle the changes for the existing data. We also backed-up our data before and end of the custom-scripts execution. I suspect this could become a potential concern as our setup grows older and contains more legacy data.

Building the CMS solution on top of the existing data

Unlike many of the CMS solutions, Strapi maintains the data schema in-code. How this in-code schema is represented within the database is handled by the Strapi framework. This means, Strapi cannot directly be setup on top of an existing CMS database.

For our implementation, the amount of data in the legacy CMS database was limited. So, we migrated it manually by creating new records via the Strapi CMS admin UI. In case if the legacy CMS data would be large in quantity, we would have written custom scripts to read the rows of data and invoke Strapi CMS APIs to insert it into the Strapi CMS system.

Strapi version upgrade

Since we started our implementation, Strapi has released a major upgrade with Strapi CMS 4. However, we found upgrading from v3.6.6 to v4.0 isn’t straight forward. It breaks our admin UI specific customizations as well as the custom plugins we wrote.

We plan to upgrade in the near future by carefully handling the breaking changes. But, this experience leads me to conclude that the future Strapi major version upgrades may result in breaking changes and will not be straight forward to execute.

Conclusion

Strapi offers a very lucrative mix of ready-to-use features and customization capabilities via hooks, middlewares and plugins. That stated, any customization that cannot be setup via these capabilities can be complicated to implement. This makes it imperative to evaluate one’s must-have CMS requirements against Strapi’s customization capabilities.

You can follow me on my twitter where I tweet about my entrepreneurship journey, site speed and web dev experiences.