First-Party Plugins
A guide to enabling a plugin’s plugins.
Base Module #
1. Create the module in modules/ckeditor/Module.php #
<?php
namespace modules\ckeditor;
use craft\ckeditor\Field as CkeditorField;
use craft\ckeditor\helpers\CkeditorConfig;
use craft\htmlfield\events\ModifyPurifierConfigEvent;
use yii\base\Event;
use yii\base\Module as BaseModule;
class Module extends BaseModule
{
public function init(): void
{
parent::init();
// Add registration lines here
Event::on(
CkeditorField::class,
CkeditorField::EVENT_MODIFY_PURIFIER_CONFIG,
static function(ModifyPurifierConfigEvent $event): void {
$definition = $event->config->getDefinition('HTML', true);
if ($definition === null) {
return;
}
// Add purifier rules here
}
);
}
}
2. Register it in config/app.php #
'modules' => [
'ckeditor' => [
'class' => \modules\ckeditor\Module::class,
],
],
'bootstrap' => ['ckeditor'],
3. Add PSR-4 autoloading to composer.json #
This is only needed if you don’t already have "modules\\": “modules/".
"autoload": {
"psr-4": {
"modules\\ckeditor\\": "modules/ckeditor/"
}
},
3. Run dump-autoload #
ddev composer dump-autoload
The Plugins #
Highlight #
// Registration line
CkeditorConfig::registerFirstPartyPackage(['Highlight'], ['highlight']);
// Purifier rule
$definition->addElement('mark', 'Inline', 'Inline', 'Common');
Marker and pen colours. Emits <mark class="marker-yellow">. Without the rule the highlight vanishes on save. Six defaults: marker-yellow, marker-green, marker-pink, marker-blue, pen-red, pen-green. Needs front-end CSS, since CKEditor's styles are CP-only. Restrict the palette with a highlight.options array in the field's config JSON.
Special Characters #
CkeditorConfig::registerFirstPartyPackage(['SpecialCharacters', 'SpecialCharactersEssentials'], ['specialCharacters']);
Character picker. Inserts plain characters, so no purifier rule. Always pair with SpecialCharactersEssentials, the base plugin alone gives you an empty panel. For a narrower picker, swap Essentials for individual sets: SpecialCharactersArrows, SpecialCharactersCurrency, SpecialCharactersLatin, SpecialCharactersMathematical, SpecialCharactersText.
Emoji #
CkeditorConfig::registerFirstPartyPackage(['Emoji'], ['emoji']);
Picker plus :shortcode autocomplete. Plain unicode output, no rule needed. Verify your DB collation is utf8mb4 or emoji will mangle on save. That’s a Craft-install thing, not a CKEditor one.
Show Blocks #
CkeditorConfig::registerFirstPartyPackage(['ShowBlocks'], ['showBlocks']);
Toggles block outlines in the editor. Pure editing aid, no output, no rule.
Accessibility Help #
CkeditorConfig::registerFirstPartyPackage(['AccessibilityHelp'], ['accessibilityHelp']);
Alt+0 dialog listing keyboard shortcuts. No output. The cheapest win here.
Text Transformation #
CkeditorConfig::registerFirstPartyPackage(['TextTransformation']);
Autocorrect as editors type - smart quotes, em dashes, ellipses. No button, so it applies to every field with no per-field opt-out. Output is plain characters, no rule. Configure or disable specific transformations with a typing.transformations object in the field's config JSON. On a site where editors paste code samples, you'll want that!
Mention #
CkeditorConfig::registerFirstPartyPackage(['Mention']);
$definition->addAttribute('span', 'data-mention', 'Text');
No button. Emits <span class="mention" data-mention="@ben">. Does nothing until you give it a feed, and a feed is a function, which the CP's JSON box can't hold. Craft wraps .js config files in an IIFE returning a config object, so put it in config/ckeditor/Mention.js:
const items = [
{ id: '@ben', name: 'Ben', link: 'https://bensomething.com' },
];
return {
mention: {
feeds: [{
marker: '@',
minimumCharacters: 1,
feed: (query) => items.filter((item) =>
item.id.toLowerCase().includes(query.toLowerCase())
),
}],
},
};
Select it under the field's Config Options → use a config file. That replaces the field's JSON config entirely, so merge any existing options in. For real data, swap items for a fetch() against a controller returning [{id, name, link}].
Table Column Resize #
CkeditorConfig::registerFirstPartyPackage(['TableColumnResize']);
$definition->addElement('colgroup', 'Block', 'Optional: col', 'Common');
$definition->addElement('col', 'Block', 'Empty', 'Common', ['span' => 'Number']);
Drag handles on table columns. No button of its own, it extends the existing table feature, so the toolbar needs insertTable. Widths ride on <colgroup><col style="width:40%">. <col> escapes html-field's inline-style stripper, so the widths survive.
Image Resize #
CkeditorConfig::registerFirstPartyPackage(['ImageResize']);
$definition->addAttribute('img', 'style', 'Text');
Resize handles. Buttons live in the image toolbar, not the main one. The rule isn't sufficient on its own: html-field runs a second pass that strips inline styles from img unless the field's "Remove inline styles" setting is off, so widths can still vanish on save. Turn that setting off on any field using this.