
Install CKEditor on Strapi, In this guide, we will see how you can create a new Field for your administration panel
Step1
Generate a plugin
cd your-projectfolder
yarn strapi generate:plugin wysiwyg
Step2
Install the needed dependencies
cd plugins/wysiwyg
yarn add @ckeditor/ckeditor5-react @ckeditor/ckeditor5-build-classic
Step3
# Go back to strapi root folder
it will work only yarn, not npm. watch-admin help for project rebuilding automatically
cd ../..
yarn develop --watch-admin
Step4
Creating the MediaLib
Make folder components/MediaLib/index.js under ./plugins/wysiwyg/admin/src/
The path looks like that
./plugins/wysiwyg/admin/src/components/MediaLib/index.js
Then Paste the bellow code
import React, { useEffect, useState } from ‘react’;
import { useStrapi, prefixFileUrlWithBackendUrl } from ‘strapi-helper-plugin’;
import PropTypes from ‘prop-types’;
const MediaLib = ({ isOpen, onChange, onToggle }) => {
const {
strapi: {
componentApi: { getComponent },
},
} = useStrapi();
const [data, setData] = useState(null);
const [isDisplayed, setIsDisplayed] = useState(false);
useEffect(() => {
if (isOpen) {
setIsDisplayed(true);
}
}, [isOpen]);
const Component = getComponent(‘media-library’).Component;
const handleInputChange = data => {
if (data) {
const { url } = data;
setData({ …data, url: prefixFileUrlWithBackendUrl(url) });
}
};
const handleClosed = () => {
if (data) {
onChange(data);
}
setData(null);
setIsDisplayed(false);
};
if (Component && isDisplayed) {
return (
<Component
allowedTypes={[‘images’, ‘videos’, ‘files’]}
isOpen={isOpen}
multiple={false}
noNavigation
onClosed={handleClosed}
onInputMediaChange={handleInputChange}
onToggle={onToggle}
/>
);
}
return null;
};
MediaLib.defaultProps = {
isOpen: false,
onChange: () => { },
onToggle: () => { },
};
MediaLib.propTypes = {
isOpen: PropTypes.bool,
onChange: PropTypes.func,
onToggle: PropTypes.func,
};
export default MediaLib;
Step5
Creating the WYSIWYG Wrapper
Path ./plugins/wysiwyg/admin/src/components/Wysiwyg/index.js
Paste the below code in index.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { isEmpty } from 'lodash';
import { Button } from '@buffetjs/core';
import { Label, InputDescription, InputErrors } from 'strapi-helper-plugin';
import Editor from '../CKEditor';
import MediaLib from '../MediaLib';
const Wysiwyg = ({
inputDescription,
errors,
label,
name,
noErrorsDescription,
onChange,
value,
}) => {
const [isOpen, setIsOpen] = useState(false);
let spacer = !isEmpty(inputDescription) ? <div style={{ height: '.4rem' }} /> : <div />;
if (!noErrorsDescription && !isEmpty(errors)) {
spacer = <div />;
}
const handleChange = data => {
if (data.mime.includes('image')) {
const imgTag = `<p><img src="${data.url}" caption="${data.caption}" alt="${data.alternativeText}"></img></p>`;
const newValue = value ? `${value}${imgTag}` : imgTag;
onChange({ target: { name, value: newValue } });
}
// Handle videos and other type of files by adding some code
};
const handleToggle = () => setIsOpen(prev => !prev);
return (
<div
style={{
marginBottom: '1.6rem',
fontSize: '1.3rem',
fontFamily: 'Lato',
}}
>
<Label htmlFor={name} message={label} style={{ marginBottom: 10 }} />
<div>
<Button color="primary" onClick={handleToggle}>
MediaLib
</Button>
</div>
<Editor name={name} onChange={onChange} value={value} />
<InputDescription
message={inputDescription}
style={!isEmpty(inputDescription) ? { marginTop: '1.4rem' } : {}}
/>
<InputErrors errors={(!noErrorsDescription && errors) || []} name={name} />
{spacer}
<MediaLib onToggle={handleToggle} isOpen={isOpen} onChange={handleChange} />
</div>
);
};
Wysiwyg.defaultProps = {
errors: [],
inputDescription: null,
label: '',
noErrorsDescription: false,
value: '',
};
Wysiwyg.propTypes = {
errors: PropTypes.array,
inputDescription: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.shape({
id: PropTypes.string,
params: PropTypes.object,
}),
]),
label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.shape({
id: PropTypes.string,
params: PropTypes.object,
}),
]),
name: PropTypes.string.isRequired,
noErrorsDescription: PropTypes.bool,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
};
export default Wysiwyg;
Step6
Implementing CKEditor
Path ./plugins/wysiwyg/admin/src/components/CKEditor/index.js
Paste the below code
import React from 'react';
import PropTypes from 'prop-types';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import styled from 'styled-components';
const Wrapper = styled.div`
.ck-editor__main {
min-height: 200px;
> div {
min-height: 200px;
}
}
`;
const configuration = {
toolbar: [
'heading',
'|',
'bold',
'italic',
'link',
'bulletedList',
'numberedList',
'|',
'indent',
'outdent',
'|',
'blockQuote',
'insertTable',
'mediaEmbed',
'undo',
'redo',
],
};
const Editor = ({ onChange, name, value }) => {
return (
<Wrapper>
<CKEditor
editor={ClassicEditor}
config={configuration}
data={value}
onReady={editor => editor.setData(value)}
onChange={(event, editor) => {
const data = editor.getData();
onChange({ target: { name, value: data } });
}}
/>
</Wrapper>
);
};
Editor.propTypes = {
onChange: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string,
};
export default Editor;
At this point, we have simply created a new plugin that is mounted in our project but our custom Field has not been
registered yet.
Registering a our new Field
Path — ./plugins/wysiwyg/admin/src/index.js
import pluginPkg from '../../package.json';
import Wysiwyg from './components/Wysiwyg';
import pluginId from './pluginId';
import App from './containers/App';
import Initializer from './containers/Initializer';
import lifecycles from './lifecycles';
import trads from './translations';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon,
id: pluginId,
initializer: Initializer,
injectedComponents: [],
isReady: false,
isRequired: pluginPkg.strapi.required || false,
layout: null,
lifecycles,
mainComponent: null,
name,
preventComponentRendering: false,
trads,
// menu: {
// pluginsSectionLinks: [
// {
// destination: `/plugins/${pluginId}`,
// icon,
// label: {
// id: `${pluginId}.plugin.name`,
// defaultMessage: name,
// },
// name,
// permissions: [
// // Uncomment to set the permissions of the plugin here
// // {
// // action: '', // the action name should be plugins::plugin-name.actionType
// // subject: null,
// // },
// ],
// },
// ],
//},
};
strapi.registerField({ type: 'wysiwyg', Component: Wysiwyg });
return strapi.registerPlugin(plugin);
};
Finally, you will have to rebuild strapi so the new plugin is loaded correctly
yarn build
If you want to remove WYSIWYG from your plugin folder then just remove the below code from .plugins/wysiwyg/admin/src/index.js
// menu: {
// pluginsSectionLinks: [
// {
// destination: `/plugins/${pluginId}`,
// icon,
// label: {
// id: `${pluginId}.plugin.name`,
// defaultMessage: name,
// },
// name,
// permissions: [
// // Uncomment to set the permissions of the plugin here
// // {
// // action: '', // the action name should be plugins::plugin-name.actionType
// // subject: null,
// // },
// ],
// },
// ],
//},