DEV Community

Cover image for DrupalCon Chicago 2026: How I vibe-coded my first custom Drupal module extending CKEditor
Ondřej Chrastina for CKEditor

Posted on

DrupalCon Chicago 2026: How I vibe-coded my first custom Drupal module extending CKEditor

I was at DrupalCon Chicago 2026, juggling booth duty, an AI Summit talk, and a Lightning Talk about CKEditor's contrib modules. But it's those hallway conversations that stick with you – and this time, the same question kept coming up.

  • "How do I customize CKEditor just a little bit?"
  • "Can I add my own button?"
  • "I want something specific to our workflow, but where do I even start?"

People knew what they wanted. They just didn't know how to get there.

So I grabbed Wojtek Kukowski – the main maintainer of the CKEditor Drupal modules (CKEditor 5 Plugin Pack and CKEditor 5 Premium Features), and asked him:

"What's actually required to make a CKEditor plugin extension for Drupal?"

He briefed me on the essentials: create a Drupal module that wraps a CKEditor plugin. And I thought: "Let me try to translate this into a prompt for Claude Code and include the official docs and see what happens."

The experiment

Setup

  • Drupal: 11.3.3.
  • Tool: Claude Code.
  • Goal: Create a Drupal module with a CKEditor 5 plugin from scratch.

The prompt (almost too simple)

I kept it minimal:

I would like to register a new module for Drupal based on the https://www.drupal.org/docs/develop/creating-modules information. I would like the module to be a CKEditor plugin https://ckeditor.com/docs/ckeditor5/latest/framework/tutorials/creating-simple-plugin-timestamp.html

If you are not sure, ask me.

That's it. Two documentation links and an invitation to ask questions.

What happened

  1. Claude explored the codebase – I was prompting in the local project with CKEditor AI running. It found existing CKEditor modules (CKEditor 5 Plugin Pack and CKEditor 5 Premium Features in my case) and learned from their patterns.

  2. Claude asked clarifying questions:

    • What should the plugin do? (Insert timestamp.)
    • What should the module be called? (ckeditor5_timestamp)
    • Toolbar button or context menu? (Toolbar button.)
  3. Claude created all the files – module metadata, CKEditor configuration, JavaScript plugin, PHP class. Everything.

  4. Time: A couple of minutes.

Let me show you what it generated.

The files Claude created

All in ~/web/modules/custom/ckeditor5_timestamp.

Module metadata

The standard .info.yml that tells Drupal about your module:

name: CKEditor 5 Timestamp
type: module
description: "Adds a CKEditor 5 plugin that inserts the current timestamp at the cursor position."
package: CKEditor
core_version_requirement: "^10.1 || ^11"
Enter fullscreen mode Exit fullscreen mode

CKEditor plugin definition

This is where the magic happens – the .ckeditor5.yml file that registers your plugin with Drupal's CKEditor integration:

ckeditor5_timestamp_timestamp:
  ckeditor5:
    plugins:
      - timestamp.Timestamp
  drupal:
    label: Timestamp
    library: ckeditor5_timestamp/timestamp
    toolbar_items:
      timestamp:
        label: Timestamp
    elements: false
Enter fullscreen mode Exit fullscreen mode

JavaScript plugin

The actual CKEditor 5 plugin that creates the toolbar button and handles the timestamp insertion:

import { Plugin } from "ckeditor5/src/core";
import { ButtonView } from "ckeditor5/src/ui";

class Timestamp extends Plugin {
  init() {
    const editor = this.editor;

    editor.ui.componentFactory.add("timestamp", (locale) => {
      const button = new ButtonView(locale);

      button.set({
        label: "Timestamp",
        withText: true,
        tooltip: true,
      });

      button.on("execute", () => {
        const now = new Date();
        editor.model.change((writer) => {
          editor.model.insertContent(writer.createText(now.toString()));
        });
        editor.editing.view.focus();
      });

      return button;
    });
  }

  static get pluginName() {
    return "Timestamp";
  }
}

export default { Timestamp };
Enter fullscreen mode Exit fullscreen mode

PHP plugin class

A minimal PHP class that extends Drupal's CKEditor plugin system:

<?php

declare(strict_types=1);

namespace Drupal\ckeditor5_timestamp\Plugin\CKEditor5Plugin;

use Drupal\ckeditor5\Plugin\CKEditor5PluginDefault;

/**
 * CKEditor 5 Timestamp plugin.
 */
class Timestamp extends CKEditor5PluginDefault {

}
Enter fullscreen mode Exit fullscreen mode

The gotcha (there's always one)

I enabled the module, cleared the cache, added the button to a text format... and hit this:

CKEditorError: plugincollection-plugin-not-found {"plugin":null}
Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-plugincollection-plugin-not-found
Enter fullscreen mode Exit fullscreen mode

console with error

The problem

Drupal's CKEditor 5 uses a DLL (Dynamic Link Library) pattern – webpack-specific builds that register plugins to the global CKEditor5 variable. Raw ES modules don't work – plugins must be bundled and exported to the CKEditor5.[pluginname] namespace.

Drupal core is discussing an Import Maps API that might let browsers resolve ES module imports natively – no bundling to UMD required. If import maps land in Drupal core, you'll be able to write clean import { Plugin } from 'ckeditor5/src/core' statements and have the browser figure out the rest. Browser support for import maps is getting there too: Chrome 133+ and Safari already support multiple import maps, with Firefox ESR 153 expected around July 2026.

The fix

Claude created a properly bundled version in js/build/timestamp.js and updated the library configuration to point to it:

timestamp:
  js:
    js/build/timestamp.js: { minified: true }
  dependencies:
    - core/ckeditor5
Enter fullscreen mode Exit fullscreen mode

Total additional time: About 2 minutes.

Voila! The timestamp button appeared in the toolbar, and clicking it inserted the current date and time.

Timestamp plugin showcase

Timestamp diagram flow

What you get

Two files that can help you extend CKEditor:

  1. SKILL.md – A comprehensive guide teaching Claude Code how to:

    • Explore existing CKEditor implementations.
    • Ask the right clarifying questions.
    • Create all necessary files.
    • Handle the DLL bundling requirement.
    • Troubleshoot common issues.
  2. PROMPT.md – A template you can customize:

    • Replace the plugin requirements with yours.
    • Specify your module name.
    • Define the UI type you need.

How to use it

  1. Install Claude Code: See the official documentation.

  2. Put the files in your Drupal project.

  3. Adjust PROMPT.md with your requirements:

    • Plugin function: What should it do?
    • Module name: your_module_name.
    • UI type: Toolbar button, context menu, etc.
  4. Ask Claude to run it:

    Please create a CKEditor 5 plugin for Drupal following the SKILL.md approach using the requirements in PROMPT.md

  5. Enable and configure:

   drush en your_module_name
   drush cr
Enter fullscreen mode Exit fullscreen mode

Then configure the text format in Drupal admin to add your new button.

Try it yourself

My plugin was straightforward – a simple timestamp inserter. I'm curious how it works for you with more complex requirements.

  • Did it work on the first try?
  • What gotchas did you encounter?
  • How complex was your plugin?

You can check out the files like PROMPT.md and SKILL.md as well as the module files in the GitHub repository.

Beyond the plugin

How the SKILL.md and PROMPT.md came to be

The same "reverse engineering" approach I used for the webpack configuration also created the SKILL.md and PROMPT.md files. After Claude successfully built the plugin, I backtracked through its actions – what did it explore? What questions did it ask? What files did it create? – and distilled that into reusable instructions.

I do this whenever I need repeatable context. Instead of relying solely on a project-wide CLAUDE.md file, I create focused instruction files for specific tasks. The SKILL.md teaches Claude how to build CKEditor plugins; the PROMPT.md provides the what – the specific requirements for each new plugin.

CI for the build

Similarly, I asked Claude to generate a GitHub Actions workflow that builds the JavaScript on every push to main. If the bundled output changes, the action commits it automatically. This keeps the repository's js/build/timestamp.js in sync without manual intervention – useful for contributors who might not have Node.js set up locally.

Making it community-friendly

To make this repository truly accessible as a starting point, more work would be needed: proper documentation, contribution guidelines, issue templates, and licensing clarity. I maintain a template for this at os-guidelines – a checklist covering everything from repository naming to CI automation across different tech stacks. If you're publishing your own CKEditor plugin module, consider using it as a starting point.


Want to learn more?

  • Create your own plugin: Check out the CKEditor 5 plugin development guide for the full tutorial on building custom plugins from scratch. Once you're comfortable with the basics, explore more capabilities – toolbar customization, editor behavior hooks, and custom commands.

  • Try CKEditor AI: The AI capabilities have been in CKEditor since last year and are gaining adoption across the ecosystem. Drupal support landed just a week before DrupalCon – perfect timing! You can try the demo right in your browser, or grab a free 14-day trial to test it in your own Drupal installation.

  • Grab the code: All the files from this article – SKILL.md, PROMPT.md, the complete module, webpack config, and GitHub Actions workflow – are available in the GitHub repository.

Enjoy! And if you have any question - the comments section is yours, or find me up on Drupal Slack! 🥑

Top comments (0)