[Announcement] The Watchdog will be removed in v49.0.0
Summary / TL;DR
CKEditor 5 v49 will remove the Watchdog. Nothing will restart a crashed editor. In its place there will be one function, onEditorError(), which tells you that an error escaped an editor. That is all it does.
The restart is not reliably helping: the content it puts back can be a state that never existed, and recreating the editor repeats every side effect of initialization. Most integrators already hold a better copy than the Watchdog ever produced — in their own backend, in Autosave, or in Cloud Services. So both the decision to recover and the data to recover from will move to the integrator.
This will be a breaking change, and it will be largest in the React, Vue and Angular integrations, where the Watchdog is on by default — most of their users have been running it without knowing.
What will break, in short:
- Anything that creates an
EditorWatchdogorContextWatchdog, or readsEditor.EditorWatchdogorEditor.ContextWatchdog. The@ckeditor/ckeditor5-watchdogpackage will be gone. - The React, Vue and Angular components lose their watchdog props and inputs, and
<CKEditorContext>in React changes shape. - The error callbacks keep firing, but their payload no longer announces a restart — and in Angular it changes type.
- All three integrations require CKEditor 5 v49 or higher.
ActionsRecorder, which happens to live in the Watchdog package, will move to@ckeditor/ckeditor5-core.
Context
Currently, CKEditor 5 offers a Watchdog in the @ckeditor/ckeditor5-watchdog package, re-exported from ckeditor5. Instead of creating an editor directly, you create a Watchdog and let it create the editor for you:
const watchdog = new EditorWatchdog( ClassicEditor );
watchdog.setCreator( config => ClassicEditor.create( config ) );
await watchdog.create( { attachTo: element, ...config } );From that point the Watchdog owns the editor. It listens on window for uncaught errors and unhandled promise rejections. When an error looks like it came from the editor it watches, it destroys that editor, creates a new one, and tries to put the previous content back. There is also a ContextWatchdog that does the same for a context and every editor inside it.
How you get one depends on the setup. In plain JavaScript you create it yourself, as above, so it is opt-in. In the React, Vue and Angular integrations the component creates it for you, and it is on unless you pass a prop to turn it off.
Problem
The Watchdog was added when the editor was much less stable and a restart was often enough to recover. Neither is true any more: the editor has grown, so have the applications built on it, and most errors we see today are not fixed by restarting. One built-in recovery path no longer fits everyone — integrators have different expectations of what should happen after a failure, and most need to handle it their own way rather than accept a single generic answer.
1. Restoring the data is not reliable
The Watchdog keeps a copy of the content in the background and puts it back after a restart. The copy is taken while the editor is working, so it can come from a moment when an operation was only half applied — the restored state may be one that never existed. It also covers only what the Watchdog itself knows about, so anything a feature keeps outside the model is lost.
2. A restart repeats the whole initialization
Creating the editor again runs every plugin and every hook the integrator attached. If you add a comment when the editor becomes ready, five restarts give you five comments. Any other side effect of initialization repeats in the same way.
3. It makes integrations harder, not easier
Because the editor can be destroyed and rebuilt at any moment, the instance an integration holds is not stable. Everything built on top has to deal with that. None of that machinery protects anyone from anything — it exists only because the editor can disappear and come back as a different object.
4. It is enabled for people who never asked for it
Because the integrations turn it on by default, most of their users run it without knowing, and see restarts they did not ask for and cannot explain.
5. Everyone pays for it in bundle size
ckeditor5-core imports the Watchdog package unconditionally, so its code is in every build, whether or not anyone uses it.
6. It makes support harder
A restart hides the original failure. The editor looks like it recovered, so problems are reported late, with the interesting state already gone.
Decision
We are removing the Watchdog in v49. The problems above are not bugs waiting for a fix: restoring data cannot be made correct in general, because we cannot know what state a feature keeps outside the model.
In its place we are adding a way to be told about editor errors. It reports, and that is all it does. It does not create editors, it does not destroy them, it does not restart anything, and it does not keep or return editor data.
Recovery moves to the integrator, who already holds a better copy than the Watchdog ever produced, because those copies are written at points where the content is known to be complete.
Error reporting API
One function, exported from ckeditor5:
import { onEditorError } from 'ckeditor5';
const off = onEditorError( ( { error, source } ) => {
if ( source !== myEditor ) {
return;
}
reportToMyErrorTracker( error );
} );error is the CKEditorError that escaped. source is the editor or context the error came from — the editor as a whole, not the object that threw; that one is in error.context. onEditorError() returns a function that unregisters the callback.
The same function is reachable as Editor.onEditorError() and Context.onEditorError(). That matters for code that is handed an editor class rather than importing one — the framework integrations above all, which cannot import anything from CKEditor 5 as a value, because doing so loads the npm build and breaks loading from the CDN.
There is one registration surface for the whole page, not one per editor. If you run several editors, you compare source with your own instance, as above. You can register as many callbacks as you like, and each one unregisters independently.
Underneath, it listens on window for uncaught errors and unhandled promise rejections — the same two events the Watchdog used. The listeners go up on the first registration and come down when the last one is removed, so importing the function does not touch window on its own.
The reporter covers the same errors the Watchdog reacted to, no more and no less: errors that carry enough information to be attributed to an editor, and only while that editor is ready. An editor that is still initializing or already destroyed is not reported on, because there is nothing useful you could do with one.
Nothing is swallowed. The error still reaches the console exactly as it does today.
Impact
Everything below describes the state after v49.
Architecture
The change here is not that a restart is missing. It is that the editor no longer decides what happens after a failure: it reports, and the application decides. The automatic restart was that decision being made for you, and there is nothing between the error and your own handling of it.
An editor instance also becomes stable for its whole life. Nothing replaces it behind your back, so whatever you attached to it stays attached. In the integrations this is the end of a long-standing hazard: the component could hand you a different editor object at any moment.
Every build also gets smaller.
API
@ckeditor/ckeditor5-watchdogis removed, and with itEditorWatchdog,ContextWatchdog, theWatchdogbase class and theWatchdogConfigtype. They are no longer re-exported fromckeditor5.- The
Editor.EditorWatchdogandEditor.ContextWatchdogstatic fields are gone from every editor class. onEditorError()is added, together with theEditor.onEditorError()andContext.onEditorError()statics described above.ActionsRecordermoves to@ckeditor/ckeditor5-core. It was never part of the Watchdog — it only lived in that package — so anyone importing it from@ckeditor/ckeditor5-watchdoghas to change the import. Importing fromckeditor5is unaffected.
Integrations
In all three integrations the Watchdog is created by the component and is on by default. This is where most people will notice the change, including those who never used the Watchdog directly.
All three require CKEditor 5 v49 or higher. That is where the error reporting they use appears. Both the declared peer dependency and the runtime version check were raised to match.
Errors are still reported. The onError prop in React, the error event in Vue and the error output in Angular all stay, and they still fire. What they no longer tell you is that a restart is coming, because none is.
Editors stop restarting on their own. After a crash the editor stays down until you decide what to do. Recreating it is a remount.
An error attributed to a Context reaches no component. If you share a context and want to hear about those, register Editor.onEditorError() yourself.
Each integration also loses its watchdog props and a few types are renamed. The exact list will be in the migration guide, per integration; what follows is what changes behaviour rather than just names.
React
<CKEditorContext> changes shape, and this one is compile-breaking. The contextWatchdog prop is gone, and it used to be required, so every use of the component has to drop it. The context prop is now required, because the watchdog used to be able to supply the class instead. onReady and onChangeInitializedEditors change shape for the same reason.
Example: reporting and remounting
Before, with the Watchdog on by default:
<CKEditor
editor={ ClassicEditor }
config={ config }
onError={ ( error, { phase, willEditorRestart } ) => {
reportToMyErrorTracker( error, { phase, willEditorRestart } );
} }
/>After:
<CKEditor
editor={ ClassicEditor }
config={ config }
onError={ ( error, { phase } ) => {
reportToMyErrorTracker( error, { phase } );
} }
/>The prop is the same and it still fires. willEditorRestart is gone, because nothing restarts.
If you want the editor back after a crash, remount it:
const [ instanceKey, setInstanceKey ] = useState( 0 );
<CKEditor
key={ instanceKey }
editor={ ClassicEditor }
config={ config }
onError={ ( error, { phase } ) => {
reportToMyErrorTracker( error, { phase } );
if ( phase === 'runtime' ) {
setInstanceKey( key => key + 1 );
}
} }
/>The phase check matters: without it, an editor that fails during initialization would remount forever.
That gives you a fresh editor with the data from config. If you need the content as it was just before the crash, take it from wherever you already persist it — your own backend, or Cloud Services.
Vue
Runtime errors are now reported in cases where they were not before. The runtime half of the error event only existed when a watchdog was actually attached, so an editor created with disable-watchdog never reported anything at runtime. Every editor reports now.
Angular
The error output carries a different value. A runtime crash used to arrive as the watchdog's EventInfo, or as undefined when the editor was an item of a ContextWatchdog. It is now the CKEditorError that escaped, so handlers written against the old shape need updating — and nothing warns you, because what changed is the value, not the signature.
What you have to do
If you want a crashed editor back — recreate it yourself. In a framework this is one line, because remounting the component destroys and recreates the editor. In plain JavaScript it is a few lines. The migration guide will show both.
If you use React, Vue or Angular and never touched the Watchdog — nothing to change. Errors keep arriving through the same callback. The one behavioural difference is that a crashed editor now stays down, so decide whether you want to remount it.
If you create a Watchdog yourself — create the editor directly instead, and register onEditorError() if you want to be told about errors.
If you configured or disabled the Watchdog through an integration — remove those props. Their names differ by integration, and the migration guide lists them.
If you used a ContextWatchdog to share a context — create the Context yourself. In plain JavaScript and in Angular you pass it in the editor configuration; in React you pass it to <CKEditorContext>.
If you read Editor.EditorWatchdog or Editor.ContextWatchdog — those static fields are gone.
What we are deliberately not providing
No restart — not automatic, not opt-in.
No data snapshot or recovery. For the reasons above, we cannot produce a copy we would trust, and in most setups you already have a better one.
No per-editor or configuration-based registration. One page-level function, and you compare source with your own instance.
No deprecation period. The Watchdog is removed in v49, not deprecated in v49 and removed later.
The migration guide will cover each of these with working code.
Feedback
We want to hear from you before v49 ships. The removal itself is decided, but the way we help you move off the Watchdog is not settled: if the replacement misses something your setup depends on, or if the migration above does not cover your case, please say so in this thread.
Source: ckeditor/ckeditor5