Right instrument at the right moment = musical harmony
On its homepage, Strapi is described as a fully customizable and developer-first headless CMS. A large number of customization options is one of the reasons behind Strapi’s popularity. However, about a year ago, when I first started to dabble with Strapi - I found the large number of customization options overwhelming. At that time, my biggest challenge was:
Given a certain CMS customization requirement, which of Strapi’s customization options would be the most suitable to implement the requirement?
Now, having undertaken a few Strapi CMS implementations, I want to document the answer to this for future self-reference and for others who may go through a similar experience.
Note : This post is based on the experiences with Strapi 4.x.x. Due to the large differences between Strapi 4.x and the earlier versions, the content here may not hold true for the earlier versions.
Before I talk about mapping requirements to Strapi customization options, let’s look at some customization possibilities that come with Strapi:
Note : Above is not meant to be an exhaustive list. There are other Strapi options like scheduled jobs, webhooks, policies, GraphQL resolvers, etc that aren’t covered here.
One of the most common customization requirements is to modify the behavior of the built-in Rest APIs. These are the APIs to query the data from or add the data into the system.
Some of such requirements are:
Strapi provides multiple options to implement the above. And, from a maintainability perspective, it helps to pick the right option:
If our customization has to execute for data handling via both - Rest APIs and admin UI, it is ideal to leverage the life-cycle hooks. I have found this to be useful for custom input validations or to populate data within the computed fields.
If our customization shall execute for the Rest APIs but not for the admin UI actions, these changes could be coded as route middlewares, extending controllers or extending services. My approach to pick one of these is the following:
Some requirements where I have written route middlewares are to code custom access control for data retrieval via the Rest APIs. Or, to notify external systems (like emailing someone when a record is updated).
async find(ctx) {
// Add custom code here to deal with input parameters
const { data, meta } = await super.find(ctx); //Do not replace this.
// Add custom code here to deal with outputted data
return { data, meta };
}
Whenever I feel the need to override the core function call, I prefer creating a custom route and controller instead.
Note: Strapi’s controller actions and services are not triggered when making GraphQL requests (v4 onwards). So, the above does not apply if we are leveraging GraphQL with Strapi.
If we need to provide APIs that function beyond the regular CRUD (create, read, update, delete) operations, these can be added as custom routes to our Strapi implementation. It is better to add custom routes instead of overriding the behavior of the built-in Strapi controllers.
Below are some cases when I have written custom routes:
A custom route can be declared as following:
//src/api/secure/routes/custom-routes.js
module.exports = {
routes: [
{
method: "GET",
path: "/secure/get-hash",
handler: "secure.getHash",
},
],
};
And, a custom controller for the custom route can be defined as below:
// src/api/secure/controllers/secure.js
"use strict";
const { createCoreController } = require("@strapi/strapi").factories;
module.exports = createCoreController("api::secure.secure", ({ strapi }) => ({
async getHash(ctx) {
//custom code for hash generation goes in here.
},
}));
If a certain change is expected to work not just for a few Rest APIs, but across the entire Strapi server application, it is ideal to implement it as application-level middleware.
Below are some of the requirements that I have implemented with CMS-wide Strapi middlewares:
These need to be defined within the src/middlewares
folder and then need to be added to the config/middlewares.js
to be invoked at the right time. The order of the middlewares listed within config/middlewares.js
controls the order of execution of the middlewares.
Note: Strapi comes with a lot of built-in middlewares. Often, what we may need is to configure one of the available built-in middlewares.
At a lot of the places, Strapi’s admin UI is used by the organizations to handle the CMS content. In such setups, the admin UI may require modifications to suit the team’s requirements.
Below are the requirements I have found suitable to implement by extending the core plugins or the admin UI code:
Changes such as above can be implemented by extending specific code and then re-building the Strapi admin (strapi build
):
If, what we seek to customize is core Strapi admin UI functionality, we need to copy the code from node_modules/@strapi/admin/admin/src
to src/admin/ui
and then modify the specific files that need to be changed (more details here).
If, what we seek to customize belongs to one of Strapi’s core plugins (content-manager, users-permissions, upload), we can extend these by adding changes to src/extensions/<plugin-name>
. For example, to customize the user registration logic (residing in the users-permissions plugin’s server/controller/auth.js
), we have to copy the contents of node_modules/@strapi/plugin-users-permissions/
to src/extensions/users-permissions/
and then modify the register()
function definition within src/extensions/users-permissions/server/controller/auth.js
.
If the customization requirement does not fit into any of the categories covered so far, creating our own Strapi plugin to implement them may be the way out.
Following are some of the requirements I preferred to implement by creating a Strapi plugin:
When creating a Strapi plugin, we can:
src/plugins/<plugin-name>/admin/src
folder to build any kind of custom UI around the data managed within the Strapi CMS.src/plugins/<plugin-name>/server
folder. These can then be accessed from within the rest of the Strapi admin.When building our custom admin UI, we can leverage Strapi’s design system to make it look in sync with the rest of the Strapi admin UI.
Note: Strapi marketplace lists various plugins developed by third-party developers & vendors. Sometimes, our custom requirement may just require using one of these plugins.
Implementing a requirement with the right customization option can go a long way in helping with the project’s maintainability and extensibility.
At the same time, there’s always a possibility of having a customization requirement that may not be straight-forward to implement with Strapi. Strapi’s features roadmap is one of the best places keep a watch for the such possible requirements.