Build one capability. Connect it through a reviewed boundary.
Editra plugins should be small, explicit, and reversible. Choose a loading model, register only the commands you need, clean up every retained resource, test both host types, and publish with validated metadata.
- 01Define
- 02Build
- 03Secure
- 04Test
- 05Submit
1. Choose a loading model
Both delivery modes use the same public editor API. Select the model that matches your application's deployment needs.
Single bundle
Load dist/editra.js for the complete built-in experience. Omitting plugins preserves the full editor.
Modular core
Load editra-core.js and declare only required plugins. Plugin JavaScript remains lazy until its command is first used.
<link rel="stylesheet" href="/editra/dist/editra-core.css">
<div id="editor"></div>
<script src="/editra/dist/editra-core.js"></script>
<script>
(async function () {
"use strict";
const editor = await Editra.init({
selector: "#editor",
theme: "Word",
plugins: ["formatting", "table", "image"]
});
globalThis.editraEditor = editor;
})();
</script>Watch modular loadingView single-bundle example
2. Build your first reviewed plugin
Built-in plugins register a function in window.EditraPlugins. Guard installation with a WeakMap, register commands through the core API, and make cleanup mandatory.
(function (global) {
"use strict";
const installations = new WeakMap();
function install(core) {
if (installations.has(core)) return installations.get(core);
const removeCommand = core.registerCommand(
"exampleCommand",
() => true,
{ plugin: "example", source: "plugin" }
);
const state = { removeCommand };
core.registerCleanup(() => {
removeCommand();
installations.delete(core);
});
installations.set(core, state);
return state;
}
function ExamplePlugin(core) {
install(core);
return true;
}
ExamplePlugin.install = install;
ExamplePlugin.hydrate = install;
ExamplePlugin.plugin = Object.freeze({
name: "example",
label: "Example",
command: "exampleCommand"
});
(global.EditraPlugins ??= Object.create(null)).example = ExamplePlugin;
})(window);3. Follow the lifecycle contract
Install
Register commands and listeners once for each editor.
Hydrate
Reconnect behavior after persisted HTML enters the surface.
Execute
Use reviewed commands instead of reaching into private internals.
Destroy
Remove listeners, observers, URLs, overlays, and retained state.
Use supported surfaces
Built-ins have internal access and therefore require maintainer review and release signing.
4. Support every editor host
Plugin behavior must be identical whether Editra starts on a <div> or a synchronized <textarea>. Never query, replace, or take ownership of the original host.
Div host
Works as an embedded rich-document surface.
Test Word + divTextarea host
Sanitized content synchronizes for form submission.
Test Word + textareaClassic theme
Verify the same command on the continuous surface.
Test Classic + textarea5. Use the sandbox for community plugins
Community code receives no core object. It runs in an iframe with sandbox="allow-scripts" and exchanges structured messages with the host.
| Capability | What the plugin receives | Boundary |
|---|---|---|
document.readText | Current plain text | No DOM access |
document.readHTML | Sanitized serialized HTML | No active script |
commands.execute | Result from an allowed command | Manifest allow-list |
ui.notify | Bounded host notification | Text-only payload |
parent.postMessage({
source: "editra-plugin",
pluginId: "my-plugin",
type: "ready"
}, "*");6. Validate, version, and submit
- Metadata validates against the registry schema.
- Version follows semantic versioning.
- Compatibility uses
>=MAJOR.MINOR.PATCH. - Commands and capabilities use the smallest necessary scope.
- Cleanup, div, textarea, Word, and Classic tests pass.
- Security-sensitive behavior is documented.
Registry metadata
{
"name": "Spell Checker",
"version": "1.0.0",
"author": "Community Dev",
"description": "Adds spell checking support",
"compatibility": ">=2.0.1"
}Submit a focused, testable plugin.
Contributions are reviewed for code quality, security boundaries, compatibility, accessibility, lifecycle cleanup, and clear documentation.