Skip to content
Logo Theodo

4 ways to build a custom Strapi Admin Panel

Chloé Mouret7 min read

Strapi V4 is live !

Few months ago, I started a project as a software engineer in which we were using Strapi V3.

For those who are not familiar with Strapi, it is an open-source, headless CMS (Content Management System) which allows users to create, manage and expose data thanks to a built-in Admin Panel.

On my project, we quickly began to customize the Strapi’s Admin Panel by directly overriding the files in our project: we just had to reproduce the path to the specific file we wanted to modify.

You can think of it as if you wanted to eject a specific file which needs to be modified. For example, if I want to change the color of a the “Publish/Unpublish” button from blue to green, I have to:

  1. Find where the button’s color is handled in the Github repository of Strapi V3. It is in the file : packages/strapi-plugin-content-manager/admin/src/containers/EditView/Header/index.js. (This is the file I want to eject)
  2. Create the folder relative to the package in the extensions folder, /extensions/content-manager/
  3. Recreate the path = create the index.js file: extensions/content-manager/admin/src/containers/EditView/Header/index.js
  4. Copy paste the old file (from the repository or the node modules)
  5. Replace “primary” by “success” on line 13

Then, when building the Strapi Admin Panel, Strapi would know that this file had been modified and would use this one instead of the old one. And so, the Publish/Unpublish button is green.

Then Strapi V4 was live ! And in Strapi V4, this functionnality is not available anymore.

So I quickly wondered if it would still be possible to customize our Strapi’s interface. In this article, I am going to present you four ways to customize your Strapi Admin Panel, starting with the easiest one: the use of an already existing plugin. Then the configuration options technique, followed by the injection file method, in order to finish with the more complex one: the creation of your own plugin.

harry potter gif

To illustrate, I am taking the example of a media: the writers need a CMS to write, edit and publish their articles and they chose Strapi V4.

Existing plugin

The first and easiest thing to do when you want to customize something in your Strapi’s interface is to try and find a plugin that does what you want.

To find plugins you can look into:

In my case, the editors are not huge fans of the markdown syntax used in the RichText Object of Strapi V4, they would appreciate something more realistic to see directly the result without having to use the preview mode.

I found the plugin “strapi-plugin-editorjs” which is an EditorJS plugin for Strapi to replace the RichText field offered by Strapi. The documentation is very clear on how to install and use the plugin.

The only thing to do in this case is:

yarn add strapi-plugin-react-editorjs

And after this, the RichText object in the Strapi Admin Panel uses the EditorJS one:

The evolution of Strapi's RichText Object with the strapi-plugin-editorjs plugin

What if the plugin I want does not exist ?

Configuration options

First, you can very easily modify the Strapi Admin Panel thanks to the configuration options: this is used for the translations, the logo, the favicon, the available locales, the theme, the tutorials and even the notification (see the documentation here).

I am going to quickly show you how to use it with the translations file and how to change all the wordings of your Strapi’s interface.

You have to find the translation you want to change: for example if I want to change the title of the homepage from ”Welcome 👋” to “Hi Chloé!”:

💡 To easily find the translation file in the github folder: you can replace the .com by .dev in the Github URL which opens a VisualStudio tab in your internet tab. And then, find directly the translation to change thanks to the old message 💡

// src/admin/app.js

export default {
  config: {
    translations: {
      en: {
        "app.components.HomePage.welcome.again": "Hi Chloé!",
  bootstrap() {},

You will see:

The result of the config file modification

💡Use yarn develop —watch-admin in order to see directly the modifications you are applying without having to relaunch your server. 💡

Injection zone

It is also possible to customize the Admin Panel using the injection zones API without having to generate a plugin with injectContentManagerComponent.

I am going to take a simple example to show you easily how to add a custom component in your Strapi’s interface: I want to add a button on the Strapi’s interface which is printing “Hello World”

First create the /extensions folder in src/admin and then you can create your component:

// src/admin/extensions/components/HelloWorldButton/index.js

import React from "react";
import { Button } from "@strapi/design-system/Button";
import Up from "@strapi/icons/Up";

const HelloWorldButton = () => {
  return (
      startIcon={<Up />}
      onClick={() => alert("Hello World")}
      Hello World

export default HelloWorldButton;

And then use the injectContentManagerComponent in the app.js file to inject your new component directly into your app:

// src/admin/app.js

import HelloWorldButton from "./extensions/components/HelloWorldButton";

export default {
  bootstrap(app) {
    app.injectContentManagerComponent("listView", "actions", {
      name: "HelloWorldButton",
      Component: HelloWorldButton,

Which is doing that:

The result of the implementation of the custom HelloWorld button

You can choose where to put your component thanks to the two first parameters of the injectContentManagerComponent:

The different options to inject a component in Strapi

This example of injection may seem a little useless so I want to present you a use case in which I used it.

In my case, I have a homepage which highlights some articles. These articles are chosen by my editors thanks to a relation with my “Article” collection.

I only want to see in my homepage articles that are published and not the ones that are drafted. Unfortunately, the selector used to do the relation with the “Article” collection allows users to choose between all the articles (even the drafted ones). My writers think that it can be dangerous because even if there is a little point of color indicating that it is not yet published, it is possible that one day someone makes a mistake and puts on the homepage a draft article.

The native Strapi selector

In my project, I have created a component that uses the native selector of Strapi but I have added a check in order to allow the user to select only the published articles.

NB: You can also use this method to inject a component of a specific plugin into another one by using the method injectComponent() which works the same way as injectContentManagerComponent() (documentation here)

New plugin ?

If what you want to do is more complex than translations or add components into the Strapi’s interface, you will probably have to create your own plugin. I recommand you read this article or see this video which are basically tutos on how to create your own plugin.

This solution is more complex and will take much more time than the two other solutions. If you can it is better to use the injection zone in order to save some time.


Even if it was globally easier and quicker to customize the Strapi Admin Panel using Strapi V3, it was not very stable. This method can be seen as a file ejection: it is basically to take some code from Strapi and to rewrite it in your own codebase which creates a strong relation between an extern library (Strapi) and your own code. At each new version of Strapi if a file changed you had to change also your code and find where to do it and how to do it. It was not very maintainable and it could break a lot of things if you had done a lot of modifications.

In Strapi V4, there is still a connection between Strapi and your codebase if, for example, you are reusing Strapi’s component (as I did in my injection example). But the connection is much smaller and your modifications are more stable. The choice of Strapi to remove the ejection method seems pragmatic in order to maintain your app more easily.

Liked this article?