← Back to Articles

CKEditor5 Phoenix Integration Guide

By ravi.suraj110@gmail.com January 19, 2026

This document explains step-by-step how ckeditor5_phoenix was integrated into this Phoenix LiveView blog application.

Overview

ckeditor5_phoenix is an Elixir package that provides seamless integration between CKEditor 5 (a rich text editor) and Phoenix Framework. It handles the complex JavaScript initialization, LiveView synchronization, and provides Elixir components for easy use in templates.
 

Step 1: Add Dependency to mix.exs

File: mix.exs

defp deps do
  [
    # ... other dependencies
    {:ckeditor5_phoenix, "~> 1.15.8"}
  ]
end

Explanation:- 

  • This adds the `ckeditor5_phoenix` package to your project's dependencies
  • The package version `~> 1.15.8` means it will use version 1.15.8 or any compatible newer version
  • After adding this, you need to run `mix deps.get` to download the package


 

Why it's needed:-

- The package provides Elixir modules, JavaScript hooks, and configuration utilities needed for CKEditor 5 integration.

 

Step 2: Install CKEditor 5 Assets

Command:-

mix ckeditor5.install

What it does:-

  •  Downloads CKEditor 5 JavaScript and CSS files to `deps/ckeditor5/`
  • Installs the specific version (47.3.0) of CKEditor 5
  • Makes CKEditor 5 assets available for bundling with your application


 

Why it's needed:-

  • CKEditor 5 is a JavaScript library that needs to be available in the browser
  • This command ensures the correct version is installed and accessible


 

Step 3: Register JavaScript Hooks

File:-assets/js/app.js

import { Hooks } from "ckeditor5_phoenix"

let liveSocket = new LiveSocket("/live", Socket, {
  longPollFallbackMs: 2500,
  params: {_csrf_token: csrfToken},
  hooks: Hooks  // CKEditor 5 hooks are registered here
})

Explanation:-

  • - Hooks from ckeditor5_phoenix contains JavaScript hooks that Phoenix LiveView needs
  • - These hooks handle:
  1.   - Initializing CKEditor 5 when the component mounts
  2.   - Synchronizing content between the editor and LiveView
  3.   - Handling editor lifecycle (mount, update, destroy)
  4.   - Managing editor state during LiveView updates


 

Why it's needed:-

- Phoenix LiveView uses hooks to bridge JavaScript and Elixir

- The hooks automatically initialize CKEditor 5 when the component appears in the DOM

- They handle the two-way data binding between the editor and your LiveView

 

Step 4: Import CKEditor 5 Styles

File:-assets/css/app.css

#Added:
@import "../../deps/ckeditor5/dist/ckeditor5.css";


 Explanation:-

- This imports CKEditor 5's default CSS styles

- The path `../../deps/ckeditor5/` points to where `mix ckeditor5.install` placed the files

- These styles provide the editor's visual appearance (toolbar, buttons, editor area)


 

Why it's needed:-

- Without these styles, the editor would be functional but unstyled

- The CSS includes styles for the toolbar, editor area, and all UI components


 

## Step 5: Import CKEditor5 Module in blogWeb


 

**File:**`lib/myblog_web.ex`

#Added to `live_view` macro:

def live_view do
  quote do
    use Phoenix.LiveView,
      layout: {MyblogWeb.Layouts, :app}
    use CKEditor5  # <-- Added this

    unquote(html_helpers())
  end
end


 

#Added to `html` macro:**

def html do
  quote do
    use Phoenix.Component
    use CKEditor5  # <-- Added this

    # ... rest of the macro
  end
end
 

Explanation:-

- `use CKEditor5` is an Elixir macro that imports the `ckeditor/1` component function

- This makes the `<.ckeditor>` component available in all LiveView and HTML templates

- The macro creates a `defdelegate` that forwards calls to `CKEditor5.Components.Editor.render/1`


 

Why it's needed:-

- Without this, you couldn't use `<.ckeditor>` in your templates

- It provides the Elixir-side component that renders the HTML structure CKEditor 5 needs


 

**What `use CKEditor5` does internally:**

defmacro __using__(_opts) do
  quote do
    defdelegate ckeditor(assigns), to: CKEditor5.Components.Editor, as: :render
  end
end


 

Step 6: Use CKEditor Component

File:-lib/myblog_web/live/article_live/new.ex

<.ckeditor
  id="article_content"
  name="article[content]"
  type="classic"
  value={@form[:content].value || ""}
  editable_height="400px"
  change_event={true}
/>
 

Explanation:-

- <.ckeditor> is a Phoenix component that renders the CKEditor 5 editor

- Attributes:-

  - `id`: Unique identifier for the editor instance

  - `name`: Form field name (used when submitting the form)

  - `type="classic"`: Editor type (classic has toolbar above editor)

  - `value`: Initial content to display in the editor

  - `editable_height`: Height of the editable area

  - `change_event={true}`: Enables automatic content sync with LiveView


 

Why it's needed:-

- The component handles all the complex HTML structure and data attributes

- It automatically creates the hidden input field for form submission

- It sets up the `phx-hook="CKEditor5"` attribute that triggers the JavaScript hooks


 

**What the component renders:**

<div
  id="article_content"
  phx-hook="CKEditor5"
  phx-update="ignore"
  cke-preset="..."
  cke-editable-height="400px"
  cke-initial-value="..."
  cke-change-event="true"
>

  <div id="article_content_editor"></div>
  <input type="hidden" id="article_content_input" name="article[content]" />
</div>
 

 

Step 7: Add Event Handlers for Content Sync

File:-lib/myblog_web/live/article_live/new.ex


 

Added Events - 

@impl true

def handle_event("ckeditor5:change", %{"data" => %{"main" => content}}, socket) do
  changeset =
    %Article{}
    |> Blog.change_article(%{"content" => content})
    |> Map.put(:action, :validate)
 
  {:noreply, assign(socket, :form, to_form(changeset))}
end


 Explanation:-

- When `change_event={true}` is set, CKEditor 5 automatically sends `ckeditor5:change` events

- The event payload contains `%{"data" => %{"main" => content}}` where `content` is the HTML

- This handler updates the form's changeset with the new content

- This enables real-time validation and preview of content as the user types


 

Why it's needed:-

- Keeps the LiveView state synchronized with the editor content

- Allows validation and other server-side processing as content changes

- The `"main"` key refers to the main editor instance (multi-root editors can have multiple)


 

Event Flow:-

1. User types in CKEditor 5

2. JavaScript hook detects change

3. Hook sends `ckeditor5:change` event to LiveView via WebSocket

4. LiveView receives event and calls `handle_event/3`

5. Changeset is updated and form is re-rendered


 

Step 8: Configure CKEditor 5 Presets

File:-config/config.exs


 

Added Config - 

config :ckeditor5_phoenix,
  presets: %{
    default: %{
      config: %{
        toolbar: [/* toolbar items */],
        plugins: [/* plugin names */],
        codeBlock: %{
          languages: [/* language options */]
        }
      }
    }
  }
 

Explanation:-

- Presets are pre-configured editor setups

- The default preset is used when no preset is specified

- Toolbar defines which buttons appear in the editor toolbar

- Plugins define which CKEditor 5 features are enabled

- codeBlock configures code snippet support with available languages


 

Why it's needed:-

- Allows customization of editor features without modifying JavaScript

- Centralizes configuration in Elixir config files

- Makes it easy to have different editor configurations for different use cases


 

How it works:-

1. Configuration is read at application startup

2. When `<.ckeditor>` renders, it includes the preset config as a JSON attribute

3. JavaScript hook reads the preset and initializes CKEditor 5 with those settings

 

Step 9: Build Assets


 

Command Used - 

mix assets.build

What it does:-

- Bundles JavaScript files (including CKEditor 5 hooks)

- Compiles CSS files (including CKEditor 5 styles)

- Outputs to `priv/static/assets/` for serving to browsers


 Why it's needed:-

- Phoenix serves static assets from `priv/static/`

- The build process combines all JavaScript and CSS into optimized bundles

- In development, this happens automatically, but it's needed for production


 

Complete Integration Flow

When a page with CKEditor loads:-


 

1.Server Side (Elixir)

   - LiveView renders `<.ckeditor>` component

   - Component generates HTML with `phx-hook="CKEditor5"` and config attributes

   - HTML is sent to browser via WebSocket


 

2.Client Side (JavaScript):-

   - Phoenix LiveView receives the HTML

   - Detects `phx-hook="CKEditor5"` attribute

   - Calls the `mounted()` function from the hook

   - Hook reads configuration from data attributes

   - Initializes CKEditor 5 instance on the target element

   - Sets up event listeners for content changes


 

3.Content Synchronization:

   - User types in editor

   - CKEditor 5 fires change events

   - Hook captures changes and updates hidden input

   - If `change_event={true}`, sends `ckeditor5:change` to LiveView

   - LiveView updates state and re-renders if needed


 

4.Form Submission:-

   - User submits form

   - Hidden input contains the HTML content

   - Form data is sent to LiveView's `handle_event("save", ...)`

   - Content is saved to database
 

## Key Files Modified

 

1.mix.exs- Added dependency

2.assets/js/app.js - Registered JavaScript hooks

3.assets/css/app.css - Imported CKEditor 5 styles

4.lib/blog_web.ex - Imported CKEditor5 module

5.lib/blog_web/live/article_live/new.ex - Used `<.ckeditor>` component

6.lib/blog_web/live/article_live/edit.ex - Used `<.ckeditor>` component

7.config/config.exs - Configured editor presets


 

Benefits of This Integration
 

1.**Seamless Integration:** Works naturally with Phoenix LiveView patterns

2.**Two-way Binding:** Automatic synchronization between editor and server

3.**Type Safety:** Elixir components provide compile-time checks

4.**Configuration:** Editor features configurable in Elixir config files

5.**Maintainability:** Clear separation between JavaScript and Elixir code

6.**Performance:** Only loads and initializes when needed


 

Additional Resources


 

- [CKEditor 5 Phoenix Documentation](https://hexdocs.pm/ckeditor5_phoenix)

- [CKEditor 5 Documentation](https://ckeditor.com/docs/ckeditor5/)

- [Phoenix LiveView Documentation](https://hexdocs.pm/phoenix_live_view)



 

Comments

Please log in to add a comment.

No comments yet. Be the first to comment!