Wiring a Custom CKEditor 5 Rich Text Field into Oracle APEX

Architecture flow: Syncing CKEditor 5 value to Oracle APEX CLOB column via page submit hook.

A walkthrough of the table, the page item, the static files, and the one gotcha that will empty out your description column if you miss it.

APEX ships with a Rich Text Editor item type out of the box, and for most forms, that's genuinely all you need. But sometimes you already have a custom CKEditor 5 build wrapped up as a reusable module—maybe with your own toolbar config, plugin set, or enterprise licensing setup—and you want to drop that into a page instead of the native item. This post walks through exactly how to do that: the database structure, page item configuration, static files, and the critical JavaScript wiring.

We'll use a simple demo table called demo_articles with a title and a rich text description to build our page against.

1. The Table and Sequence

The description column must be a CLOB. Rich text content accumulates HTML markup rapidly once you include headings, tables, blockquotes, and lists. A standard VARCHAR2 column will run out of room far faster than anticipated.

CREATE TABLE demo_articles (
    id            NUMBER          NOT NULL,
    title         VARCHAR2(200)   NOT NULL,
    description   CLOB,
    created_by    VARCHAR2(100),
    created_date  DATE            DEFAULT SYSDATE,
    updated_by    VARCHAR2(100),
    updated_date  DATE,
    CONSTRAINT demo_articles_pk PRIMARY KEY (id)
);

CREATE SEQUENCE demo_articles_seq
    START WITH 1
    INCREMENT BY 1
    NOCACHE
    NOCYCLE;

2. Keep the Page Item as a Native Textarea

This is where most implementations break down initially. Do not set the APEX page item type to Rich Text Editor. CKEditor 5 attaches itself to a plain HTML DOM element and manages its own rendering engine. The underlying APEX item must remain a Native Textarea. Selecting Rich Text Editor in APEX results in two competing editor engines mounting on top of the same field.

Setting Value
Type Native Textarea
Name P11_DESCRIPTION
Source Column description

3. Upload the static files and reference them on the page

Your CKEditor module (bundled JS and CSS) needs to live in Shared Components > Static Application Files for the specific app you're building. It's easy to upload it to the wrong app if you're copying a setup between workspaces, and the failure mode is silent: no toolbar, no error message, just a plain textarea sitting there.

You can grab the CKEditor JS and CSS files directly from GitHub: apex-ckeditor-integration

JavaScript File URLs:
#APP_FILES#app-ckeditor.js

CSS File URLs:
#APP_FILES#app-ckeditor.css

Below is the custom CKEditor 5 toolbar controls and options mounted onto our APEX field:





Custom CKEditor 5 toolbar controls integrated into the form item

In the page's Execute when Page Loads JS section, instantiate the custom editor on top of the native item ID:

var height = $('#main').innerHeight();

window.$cntnRTE = AppCKEditor.asLegacyRte('P11_DESCRIPTION');

AppCKEditor.init('P11_DESCRIPTION', { height: (height - 155) + 'px' })
    .catch(function (e) {
        apex.message.alert('Content editor failed to load: ' + e.message);
    });

init() attaches the toolbar directly over the textarea target. The wrapper method provides getter/setter abstractions for interaction elsewhere in your application.

4. The Gotcha: The Hidden Textarea Value Problem

CKEditor 5 maintains state in an internal virtual Data Model. It does not automatically stream content back into the original HTML <textarea> element on every keypress.

If your PL/SQL submit process reads :P11_DESCRIPTION upon form submission, APEX will receive whatever initial value was present during page render (typically NULL), overwriting existing column data with empty values.

To resolve this, sync the editor data back to the APEX page item immediately prior to submit by listening to the apexbeforepagesubmit event:

$(document).on('apexbeforepagesubmit', function() {
    apex.item('P11_DESCRIPTION').setValue($cntnRTE.getHtml());
});

5. The Save Process & Verification

Once synchronized prior to submit, standard PL/SQL page processing handles database operations normally using standard bind variable syntax:

DECLARE
    v_id demo_articles.id%TYPE := :P11_ID;
BEGIN
    IF v_id IS NULL THEN
        -- INSERT NEW RECORD
        SELECT demo_articles_seq.NEXTVAL INTO v_id FROM dual;

        INSERT INTO demo_articles (
            id, title, description, created_by, created_date
        ) VALUES (
            v_id, :P11_TITLE, :P11_DESCRIPTION, :APP_USER, SYSDATE
        );

        :P11_ID := v_id;
    ELSE
        -- UPDATE EXISTING RECORD
        UPDATE demo_articles
        SET title         = :P11_TITLE,
            description   = :P11_DESCRIPTION,
            updated_by    = :APP_USER,
            updated_date  = SYSDATE
        WHERE id = v_id;
    END IF;
END;

Below is the operational editing view inside Oracle APEX showing full formatting (headings, lists, blockquotes, tables, and links) rendered inside the custom editor instance:


Demo Article Details page with active CKEditor 5 instance

After saving, we can verify the rich content round-trips correctly and renders styled HTML output across report regions:


Interactive Report displaying synchronized HTML data stored in the CLOB column

Native RTE vs. Custom CKEditor Wrapper

Feature Native APEX RTE Custom CKEditor Wrapper
Setup Effort Zero configuration required Requires static files, onload JS, submit sync listeners
Customization Restricted to native APEX properties Full control over plugins, toolbars, alignment, and features
State Synchronization Automatic Requires manual synchronization via apexbeforepagesubmit

Implementation Checklist

  • Ensure database target column is created as a CLOB.
  • Configure APEX Page Item explicitly as Native Textarea.
  • Load custom JS/CSS through Static Application Files.
  • Bind the CKEditor instance to the APEX Item ID on page load.
  • Hook state sync logic to the apexbeforepagesubmit event.

Comments