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:
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:
After saving, we can verify the rich content round-trips correctly and renders styled HTML output across report regions:
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
apexbeforepagesubmitevent.
Comments
Post a Comment