Embedding Guide
Recommended: loader-based install
The loader is a single script tag that handles everything: it calls the init endpoint, downloads the correct assets for your plan, and initializes the editor.
<script
src="https://cdn.readyeditor.io/releases/<version>/js/readyeditor.loader.min.js"
data-api-key="rk_..."
data-selector=".my-editor"
data-load-css="1"
data-plugins="auto"
data-require-sri="1"
data-auto-init="1"
></script>
Key points:
data-init-endpointdefaults to/api/v1/editor/initresolved against the loader script origin. This enables cross-origin embedding: your host page can be on a different domain than the ReadyEditor CDN.- When
data-require-sri="1", the loader enforces SRI integrity on every loaded asset using hashes from the release manifest. - Set
data-require-sri="0"when testing locally without a CDN (SRI requires assets to be served with matching headers).
Your editor element
The loader calls ReadyEditor.init() using data-selector. Provide at least one matching element:
<div class="my-editor">
<p>Start typing…</p>
</div>
Cross-origin embedding
ReadyEditor is designed for cross-origin use. Your host page (e.g. https://app.yourcompany.com) and the ReadyEditor CDN/loader can be on different origins.
How it works:
- Your page loads the loader script from the CDN.
- The loader resolves
data-init-endpoint="/api/v1/editor/init"against the loader's origin (the CDN), not your page's origin. - The init endpoint returns CORS headers allowing your page's origin (provided your domain is in the project's allowed domains list).
If you see domain_not_allowed errors, check that:
- Your page's full domain (e.g.
app.yourcompany.com) is added in the dashboard. data-init-endpointis a root-relative path (starts with/), not an absolute URL pointing to your page origin.
Toolbar presets
Control which plugins are shown using the loader:
| Attribute | Values | Description |
|---|---|---|
data-toolbar-preset |
full (default), basic, minimal |
Quick preset |
data-plugins |
auto, none, comma list |
Override which plugins to load |
data-disable-plugins |
comma list | Remove specific plugins from the final set |
data-toolbar-groups |
comma list | Allowlist toolbar groups |
data-toolbar-hide-groups |
comma list | Blocklist toolbar groups |
data-toolbar-overflow-groups |
comma list | Groups always pinned into the "More" dropdown |
data-toolbar-overflow |
on (default), off |
Set to off to disable the overflow menu entirely (toolbar wraps instead) |
Toolbar group names: Formatting, Insert, Tools, View, Other
Example — load all entitled plugins but hide the Bootstrap and emoji ones:
<script
src="https://cdn.readyeditor.io/releases/<version>/js/readyeditor.loader.min.js"
data-api-key="rk_..."
data-selector=".my-editor"
data-load-css="1"
data-plugins="auto"
data-disable-plugins="bootstrap,tools_emoji"
data-require-sri="1"
></script>
Saving content
ReadyEditor edits HTML in-place inside the contenteditable element.
Read HTML:
var html = ReadyEditor.getInstance('.my-editor').getContent();
Set HTML:
ReadyEditor.getInstance('.my-editor').setContent('<p>New content</p>');
Textarea bridge — for standard HTML form submissions, load the bridge script separately (it is not auto-loaded by the loader) and call attachTextareas() after the loader resolves:
<!-- loader with data-auto-init="0" -->
<script
src="https://cdn.readyeditor.io/releases/<version>/js/readyeditor.loader.min.js"
data-api-key="rk_..."
data-load-css="1"
data-plugins="auto"
data-auto-init="0"
></script>
<!-- bridge — must be loaded before calling attachTextareas() -->
<script src="https://cdn.readyeditor.io/releases/<version>/js/readyeditor.textarea.bridge.min.js"></script>
<script>
window.ReadyEditorLoader.init({ apiKey: 'rk_...', loadCss: true, plugins: 'auto' })
.then(function() {
ReadyEditor.attachTextareas({
selector: 'textarea.js-editor',
editorOptions: { apiKey: 'rk_...' }
});
});
</script>
The bridge is included in every release as js/readyeditor.textarea.bridge.min.js. It hides the <textarea>, inserts an editor <div> in its place, and syncs content back automatically on form submit. See setup-usage.md for the full walkthrough.
Uploading images
ReadyEditor does not upload files by itself. The media plugin exposes an integration hook:
window.ReadyEditorUploadImage = async function(file, context) {
// Upload to your backend and return the final public URL
var formData = new FormData();
formData.append('image', file);
var response = await fetch('/api/upload-image', {
method: 'POST',
body: formData,
});
var result = await response.json();
// Return a URL string, or an object with { url, alt }
return result.url;
};
Once the function is defined, the "Upload image" toolbar button in the media plugin will use it.
Sanitization
Client-side sanitization is defense-in-depth only. The editor's setContent() includes a lightweight HTML allowlist sanitizer, but you must sanitize content on your server before persisting or rendering it back to end-users.
You can control client-side sanitization via ReadyEditor.init():
// Disable built-in sanitizer (use only if you handle it yourself)
ReadyEditor.init({
selector: '.my-editor',
apiKey: 'YOUR_API_KEY',
sanitize: 'none',
});
// Provide a custom sanitizer
ReadyEditor.init({
selector: '.my-editor',
apiKey: 'YOUR_API_KEY',
sanitizeHtml: function(html, context) {
return DOMPurify.sanitize(html);
},
});
This only affects setContent() — it does not replace server-side sanitization.