Replacing WYSIWYG in v4

Making an attempt to either migrate an existing plugin, or create a new one to replace the markdown WYSIWYG editor.

All the examples I can find use the v3 syntax of calling registerField to actually replace the editor.

I have noticed in the new documentation that it specifically calls out using the bootstrap lifecycle to customize this. However, there isn’t a concrete example, and I’m having trouble actually getting this to work.

Does anyone have a working example of replacing that particular component, or some tips on how to utilize the bootstrap calls to make this replacement?

Thank you!


Same here. I also have wysisyg and colorpicker plugin setup. But unable to do it for V4 due to insufficient documentation for V4. I tried it using addField but its not working.
Any help is appreciated.

This is my solution with TinyMCE, it supports media selection too:

  • generate a new plugin with “npx strapi generate
  • enable the plugin in config/plugin.js
  • npm install @tinymce/tinymce-react tinymce
  • in your plugin folder create the component admin/src/components/Editor.js and paste the following code
import React, { useState, useRef } from 'react'
import { Field, Stack, Flex, FieldLabel } from '@strapi/design-system';

import tinymce from 'tinymce';
import 'tinymce/icons/default';
import 'tinymce/themes/silver';
import 'tinymce/plugins/code';
import 'tinymce/plugins/colorpicker';
import 'tinymce/plugins/textcolor';
import 'tinymce/skins/ui/oxide/skin.css';
import { Editor as TinyMCE } from "@tinymce/tinymce-react";
import { useLibrary, prefixFileUrlWithBackendUrl } from '@strapi/helper-plugin';

export default function Editor({ name, value, onChange, ...props }) {

    const { components } = useLibrary();
    const editorRef = useRef(null);

    const [showMediaLibrary, setShowMediaLibrary] = useState(false)

    const MediaLibraryDialog = components['media-library'];

    const handleToggleMediaLib = () => {

    const handleSelectAssets = files => {
        const formattedFiles = => ({
            alt: f.alternativeText ||,
            url: prefixFileUrlWithBackendUrl(f.url),
            mime: f.mime,

        const imgs = => `<img src='${img.url}' alt='${img.alt}'>`).join();
            target: {
                name: name,
                value: value + imgs


    const initEditor = {
        width: '100%',
        branding: false,
        height: '400px',
        toolbar: 'customInsertButton code',
        plugins: 'code ',
        setup: function (editor) {
            editor.ui.registry.addButton('customInsertButton', {
                text: 'Add Media',
                onAction: function (_) {

    return <Field name={name}>
        <Stack size={1}>
            <Flex cols="auto auto 1fr" gap={1}>


                <TinyMCE init={initEditor} ref={editorRef} value={value} onEditorChange={
                    (editorContent) => onChange({
                        target: {
                            name: name,
                            value: editorContent
                } />
        {showMediaLibrary && <MediaLibraryDialog onClose={handleToggleMediaLib} onSelectAssets={handleSelectAssets} />}

  • in your plugin folder edit admin/src/index.js

import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import Initializer from './components/Initializer';
import Editor from './components/Editor';
const name =;

export default {
  register(app) {
      id: pluginId,
      initializer: Initializer,
      isReady: false,


  • start strapi with yarn develop --watch-admin and you should be able to see the new editor

Awesome! That is the solutión,

now we should use:


insted of


Thank you!

Thank you! Unfortunately we ran out of time and reverted back to v3 for an initial deployment, but glad this was able to help others. Hopefully we will bump things up here soon.

Were you able to make this approach work? I added the field and then in schema.json place the

  "attributes": {
    "fieldName": {
      "type": "customType"

In UI it seems to work but later after save data is not being record in db and just getting null.

I followed your example exactly as described but for one or another reason the plugin doesn’t show up and Strapi keeps loading it’s own editor.

Steps i did:

  1. Created a new project with npx create-strapi-app@latest Strapi --quickstart and installed it, when completed i stopped it and got into the project folder.
  2. npx strapi generate choose the plugin option and named it wysiwyg
  3. Copied the result it gives into project_root/config/plugins.js (file didn’t exist).
module.exports = {
  wysiwyg: {
    enabled: true,
    resolve: "./src/plugins/wysiwyg",
  1. Installed tinyMCE, my package.json looks like this now:
  "dependencies": {
    "@strapi/plugin-i18n": "4.0.0",
    "@strapi/plugin-users-permissions": "4.0.0",
    "@strapi/strapi": "4.0.0",
    "@tinymce/tinymce-react": "^3.13.0",
    "sqlite3": "5.0.2",
    "tinymce": "^5.10.2"
  1. Created the editor file in project_root/src/plugins/wysiwyg/admin/src/components/Editor.js
  2. Modified the index.js file in project_root/src/plugins/wysiwyg/admin/src/index.js
  3. Restarted Strapi with yarn develop --watch-admin

You would expect to see the new plugin also at the plugin list within the admin… but it doesn’t. Someone who can help me a bit further with this one?

I am getting a following error:

How do we define tinymce in global namespace?
I tried using webpack config, but ran into issues again.

I have installed tinymce already.

The easy way to add CKEditor5

Go in you strapi directory


npm i strapi-plugin-ckeditor5@latest

after the installation of the plugin is completed rebuild strapi

npm run strapi build

That’s it :slight_smile:

More info on the their GitHub

1 Like