In CKEditor 5, call editor.enableReadOnlyMode(featureId) to add a named lock and editor.disableReadOnlyMode(featureId) to remove that lock. The editor becomes editable when no locks remain. Read editor.isReadOnly to inspect the combined state; do not assign it directly. The official read-only guide documents this locking model.
Give independent features independent lock identifiers
For an already-created editor, this original integration fragment uses separate identifiers for two independent owners:
const APPROVAL_LOCK = 'report-awaiting-approval';
const REFRESH_LOCK = 'report-record-refresh';
function setApprovalPending(pending) {
if (pending) editor.enableReadOnlyMode(APPROVAL_LOCK);
else editor.disableReadOnlyMode(APPROVAL_LOCK);
}
function setRecordRefreshing(refreshing) {
if (refreshing) editor.enableReadOnlyMode(REFRESH_LOCK);
else editor.disableReadOnlyMode(REFRESH_LOCK);
}
editor is the application's existing CKEditor instance. This fragment does not install an editor, load a report, or decide who has approval rights. Each feature owns one identifier and releases only that identifier.
Use this expected sequence when testing the integration:
| Operation | Read-only? |
|---|---|
| Start with no active locks | false |
setApprovalPending(true) |
true |
setRecordRefreshing(true) |
true |
setRecordRefreshing(false) |
true, because approval is still pending |
setApprovalPending(false) |
false, assuming no other feature owns a lock |
The table specifies the test expectation, not observations from a running CKEditor session. Other active features may legitimately keep the final state read-only.
If one feature can start several concurrent refresh requests, a single shared identifier does not express how many are unfinished. Either serialize those operations or have the feature track its own pending operations and release its lock only after the last one settles. Repeatedly enabling one identifier is not an application-level request counter.
Show why the report cannot be edited
Read-only content remains available for copying and screen readers. It serves a different purpose from an inert region, which suppresses the entire subtree's interaction and accessibility exposure. Keep the report readable during the approval stage and place the reason beside it.
For a status element outside the editing area:
const modeStatus = document.querySelector('#report-mode');
function renderModeStatus() {
modeStatus.textContent = editor.isReadOnly
? 'Report is currently read-only.'
: 'Report can be edited.';
}
editor.on('change:isReadOnly', renderModeStatus);
renderModeStatus();
Supply #report-mode in the application's surrounding interface, for example as a paragraph with role="status". The combined Boolean does not identify the lock owner. For a more specific message such as "Awaiting approval," use the application's authoritative approval state; do not infer that reason merely because isReadOnly is true.
Some toolbar actions can remain usable because they do not modify document data. CKEditor explains this through the affectsData property. A usable nonediting action is not evidence that a document lock failed. Hiding the whole toolbar is an optional interface decision, separate from enforcing read-only mode.
Keep write authorization on the server
The editor lock prevents ordinary authoring through the interface. It does not authorize a save request or protect the report against a requester who bypasses that interface. The server must check permission and the current report state before accepting a write.
Also account for a save that started before approval became pending. Adding a read-only lock does not cancel an already-sent request or prove that unsaved changes were stored. The CKEditor autosave guide explains the separate promise and persistence boundary. Define how approval, outstanding saves, and record revisions are ordered in your application.
When only one paragraph should be protected while notes remain editable, whole-editor read-only mode is too broad. The noneditable-region guide illustrates that different document policy; it is not a replacement API for CKEditor.
Test the two-owner case before shipping
In a development copy with a fictional report, add both locks and attempt a normal text edit. Release the refresh lock and try again. The report should remain read-only until the approval feature also releases its lock.
Then test a refresh failure. Releasing the refresh feature's temporary lock must not release an unrelated approval lock. Check the visible mode message after each transition and verify that copying the report still works. Finally, send an unauthorized write through your test service and confirm the server rejects it independently of the editor interface.
Finish by reopening the intended report and checking its approval state, saved revision, and editing availability together. An unlocked editor indicates available authoring controls; the report's actual permission and storage rules still belong to the application.