Home / Tech Posts

Strapi customizations options : What to use when?

26th July, 2022

Right instrument at the right moment = musical harmony

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.

Strapi’s Customization Options

Before I talk about mapping requirements to Strapi customization options, let’s look at some customization possibilities that come with Strapi:

  • life-cycle hooks - functions that get triggered when the data is fetched from or modified into the CMS.
  • middlewares - functions that can be attached to any kind of request into or response from the CMS.
  • custom routes - Rest APIs that can be built to serve custom requirements.
  • admin panel extensions - React UI code that can override the Strapi admin or core plugins UI screens.
  • custom plugin - code to extend the API and Strapi admin UI functionality.

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.

Altering the default Rest API Behavior

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:

  • Custom input validation during insert / update of data.
  • Custom access control when retrieving the data.
  • Autopopulate certain data (calculated fields, access logs, etc) when certain APIs are consumed.
  • Notify external systems (email, third-party systems) when certain actions are undertaken.

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:

    • Prefer creating a route middlware whenever possible.
    • If the required change doesn’t fit into a middleware, extend the controller.
    • I have never extended a service because service methods can be called from various parts of the CMS and I may inadvertantly break something. I would rather create a custom route-controller.

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

  • Also, whenever I extend a controller, I prefer modifying only the input parameters or output data and not replace the core function call. This is to keep the functioning of the Strapi Rest API parameters (sort, filter, pagination, population, etc) intact.
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.

Adding Custom Rest APIs

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:

  • Provide secure hash values.
  • Decrypt and provide a certain content from an upstream server where it is stored encrypted.
  • Obtain data / status / flag from a third-party provider.
  • Batch update large number of records based on a certain condition.

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.
  },
}));

Adding features that trigger at every CMS request

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:

  • Set up a server-side API caching layer.
  • Set up a custom logger.
  • Set up an activity log to track all insert / update / deletes made within the CMS via the admin UI and Rest APIs.
  • Set up CORS header to be sent with every response.

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.

Altering the admin UI

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:

  • Change of fields in the admin sign-up / login form.
  • UI changes to the Content Manager form to add / edit content.
  • Changes to the logic for API user’s registration, login, forgot-password, etc behavior.

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.

Extending the Strapi Admin Functionalities

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:

  • Addition of field-types beyond the ones provided by Strapi’s content manager (Google map location selector, select control with pagination and autocomplete, etc).
  • Admin form the change the status of records in batches.
  • Custom admin UI functionality such as send emails or review alerts.
  • Dynamic admin form where the values inputted into one field change the fields and options in the form.

When creating a Strapi plugin, we can:

  • Add React UI code within the src/plugins/<plugin-name>/admin/src folder to build any kind of custom UI around the data managed within the Strapi CMS.
  • Create custom routes and controllers within 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.

Conclusion

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.

You can follow me on my twitter. I mostly tweet about my web development, site speed and entrepreneurship experiences.
Copyright (c) 2017-2022 Tezify All Rights Reserved. Created in India. GSTIN : 24BBQPS3732P1ZW.