Configuring CKEditor 4 and Alloy Editor
Liferay DXP 2026.Q1+
The sample workspace’s liferay-sample-editor-config-contributor-1 client extension configures Liferay’s legacy text editors. It adds an Insert Video button to Alloy Editor’s floating toolbar and a Create AI Content button to CKEditor 4’s toolbar. If your instance still runs these editors, you can use the sample as a template for your own toolbar changes. See Using an Editor Config Contributor Client Extension to learn more about this client extension type.
Alloy Editor and CKEditor 4 are Liferay’s legacy text editors, and Alloy Editor builds on CKEditor 4. These editors are deprecated in Liferay DXP 2026.Q2 and you must upgrade to CKEditor 5.
The LPD-11235 feature flag’s behavior depends on your version. In Liferay DXP 2026.Q1, it’s a release feature flag named Enhanced Rich Text Editor, and activating it switches to CKEditor 5. In Liferay DXP 2026.Q2+, CKEditor 5 is the default and LPD-11235 is a deprecation feature flag, so activating it switches back to CKEditor 4 while you plan your upgrade.
See Upgrading to CKEditor 5 and 2026.Q2 Maintenance Mode and Deprecations for more information.
Prerequisites
To work with Editor Config Contributor client extensions, follow these steps:
-
Install a supported version of Java.
NoteCheck the compatibility matrix for supported JDKs, databases, and environments. See JVM Configuration for recommended JVM settings.
-
Download and unzip the sample workspace:
curl -o com.liferay.sample.workspace-latest.zip https://repository.liferay.com/nexus/service/local/artifact/maven/content\?r\=liferay-public-releases\&g\=com.liferay.workspace\&a\=com.liferay.sample.workspace\&\v\=LATEST\&p\=zipunzip com.liferay.sample.workspace-latest.zip
Now you can examine and modify the sample Editor Config Contributor client extensions.
Examine the Client Extension Definition
The Editor Config Contributor example is in the sample workspace’s client-extensions/liferay-sample-editor-config-contributor-1/ folder. Its client-extension.yaml file defines these details:
assemble:
- from: build
into: static
liferay-sample-editor-config-contributor-1:
editorConfigKeys:
- description
- fragmentEntryLinkRichTextEditor
- sampleAlloyEditor
- sampleClassicEditor
- sampleLegacyEditor
- sampleReactClassicEditor
name: Liferay Sample Editor Config Contributor 1
type: editorConfigContributor
url: index.js
The client extension declares its ID (liferay-sample-editor-config-contributor-1), its type (editorConfigContributor), and the editors it applies to (editorConfigKeys). Its url property points to index.js, the bundled JavaScript file the build produces. The assemble block packages the entire build/ folder into the client extension’s .zip file as static resources, including the index.js file. See the Editor Config Contributor YAML Configuration Reference for more information.
This sample targets the editor for rich text elements in page fragments (fragmentEntryLinkRichTextEditor) and several sample editor applications not included out-of-the-box.
The sample’s only dependency is @liferay/js-api version 0.8.0, which provides the editor transformer types. The build bundles src/index.ts into build/index.js with a single esbuild command:
esbuild src/index.ts --outdir=build --bundle --format=esm
Examine the Transformer Code
The src/index.ts file defines an editorConfigTransformer function, wraps it in an EditorTransformer object, and exports that object as the module’s default export:
import {
EditorConfigTransformer,
EditorTransformer,
} from '@liferay/js-api/editor';
const editorConfigTransformer: EditorConfigTransformer<any> = (config) => {
// ...
};
const editorTransformer: EditorTransformer<any> = {
editorConfigTransformer,
};
export default editorTransformer;
Liferay calls editorConfigTransformer with the configuration object of each editor matched by the extension’s editorConfigKeys and applies the returned configuration. Because those keys also match CKEditor 5 instances, the function body handles all three editor types in order: Alloy Editor, CKEditor 5, and CKEditor 4.
Alloy Editor
The function first handles Alloy Editor, identifying it by its toolbars configuration object:
// Alloy Editor
const toolbars: any = config.toolbars;
if (typeof toolbars === 'object') {
interface ISelection {
buttons: Array<string>;
name: string;
}
const textSelection: ISelection = toolbars.styles?.selections?.find(
(selection: ISelection) => selection.name === 'text'
);
if (textSelection.buttons) {
textSelection.buttons.push('video');
return {
...config,
toolbars,
};
}
}
The branch finds the text selection in config.toolbars.styles.selections and pushes a video button onto its button list. When you select text in an Alloy Editor field, the floating toolbar shows an Insert Video button.
CKEditor 5
The second branch handles CKEditor 5:
// CKEditor 5
if (config.editorType === 'ckeditor5') {
return {
...config,
toolbar: [
'accessibilityHelp',
'|',
'undo',
'redo',
'|',
'alignment',
],
};
}
The config.editorType check matters because the transformer receives configurations for both CKEditor 4 and CKEditor 5 editors. When the type is ckeditor5, the branch replaces the toolbar with the accessibilityHelp, undo, redo, and alignment controls and then returns the configuration. Without this branch, a CKEditor 5 configuration reaches the CKEditor 4 handler below and comes back with a toolbar the fragment editor can’t render.
CKEditor 4
Any configuration the earlier branches don’t return falls through to the CKEditor 4 handler:
// CKEditor
const toolbar: string | [string[]] = config.toolbar;
const buttonName = 'AICreator';
let transformedConfig: any;
if (typeof toolbar === 'string') {
const activeToolbar = config[`toolbar_${toolbar}`];
activeToolbar.push([buttonName]);
transformedConfig = {
...config,
[`toolbar_${toolbar}`]: activeToolbar,
};
}
else if (Array.isArray(toolbar)) {
toolbar.push([buttonName]);
transformedConfig = {
...config,
toolbar,
};
}
const extraPlugins: string = config.extraPlugins;
return {
...transformedConfig,
extraPlugins: extraPlugins ? `${extraPlugins},aicreator` : 'aicreator',
};
CKEditor 4 expresses its toolbar in two forms, and the branch handles both. When config.toolbar is a string, it names a toolbar_<name> array in the configuration, and the branch pushes an AICreator button onto that array. When config.toolbar is an array, the branch pushes the button onto it directly. The returned configuration also appends aicreator to extraPlugins, preserving any plugins already listed there. In the editor, the button appears as Create AI Content at the end of the toolbar.
Deploy the Client Extension
Start a new Liferay DXP instance by running
docker run -it -m 8g -p 8080:8080 liferay/dxp:2026.q1.9-lts
Sign in to Liferay at http://localhost:8080 using the email address test@liferay.com and the password test. When prompted, change the password to learn.
Once Liferay starts, open a new terminal and run this command from the client-extensions/liferay-sample-editor-config-contributor-1/ folder in the sample workspace:
../../gradlew clean deploy -Ddeploy.docker.container.id=$(docker ps -lq)
This builds the client extension and deploys the zip to Liferay’s deploy/ folder. For the complete deployment workflow, including deploying to Liferay SaaS, see Deploy the Client Extension to Liferay.
Confirm deployment is successful in your Liferay instance’s console:
STARTED liferaysampleeditorconfigcontributor1_...
Verify the Editor Changes
To verify your changes are applied, begin editing a fragment’s rich text element. This editor is targeted by the client extension’s editorConfigKeys value (fragmentEntryLinkRichTextEditor).
With CKEditor 5 active, that field takes the CKEditor 5 branch, and its toolbar carries the accessibility help, undo, redo, and alignment controls the branch sets.
Activating the LPD-11235 deprecation feature flag switches the instance to CKEditor 4. Liferay renders this field with Alloy Editor, so the Alloy Editor branch handles it instead. Select text in the field to see the Insert Video button on the floating toolbar.
To see the CKEditor 4 branch run, list the config key of a CKEditor 4 editor in editorConfigKeys. The branch adds a Create AI Content button to that editor’s toolbar.