Common Gotchas
1. Mixed content blocks assets on HTTPS pages
If your page is served over HTTPS and any ReadyEditor assets are loaded over HTTP, the browser will block them silently.
Always use HTTPS URLs for the loader script and any custom assets you include.
2. Toolbar icons require an icon font
Toolbar buttons use Remix Icon classes (ri-bold, ri-link, etc.). If icons are missing, the font is not loaded. Two ways to fix it:
- Enable
data-load-css="1"on the loader tag — the editor CSS includes the icon font. - Or add the Remix Icon stylesheet manually:
https://cdn.jsdelivr.net/npm/remixicon@3/fonts/remixicon.css
3. Text selection is lost when clicking toolbar buttons
Many editing actions depend on the user's text selection. Clicking a <button> normally moves focus to that button, collapsing the selection before the action runs.
The UI core avoids this by running toolbar actions on pointerdown/mousedown and calling preventDefault(). If you build custom toolbar controls, do the same.
4. Modal submit button must be wired to the form
The modal footer buttons are rendered outside the <form> element. The UI core binds the submit button to the form via the HTML form attribute. If you modify modal markup, verify this binding still works, or clicking "Apply" will appear to do nothing.
Also: avoid <input name="id"> inside modal forms. Some browsers expose named form controls as properties on the form object, so form.id can resolve to the input element instead of the form's string ID, breaking the binding.
5. Toolbar Paste is restricted by browsers
document.execCommand('paste') is blocked by all modern browsers. The clipboard plugin handles this:
- On HTTPS with clipboard permission: uses
navigator.clipboard.readText() - Otherwise: opens a modal for the user to paste manually
Ctrl+V / Cmd+V into the editor content area is never affected.
6. Use instance.insertHTML(), not execCommand('insertHTML') directly
execCommand('insertHTML') can fail silently in modern browsers (returns false). EditorInstance.insertHTML() handles this reliably:
- Ensures the caret is inside the editor element first
- Tries
execCommand('insertHTML') - Falls back to
Rangeinsertion
Always prefer instance.insertHTML(html) and instance.insertText(text) in plugin code.
7. Client-side sanitization is not a security boundary
setContent() runs a lightweight allowlist sanitizer. It is defense-in-depth only — not a substitute for server-side sanitization. Always sanitize user-generated HTML on your server before storing it or rendering it back to other users.
8. domain_not_allowed on cross-origin embeds
The init endpoint checks the domain derived from the request's Origin header (or the origin query parameter sent by the loader). If your embed page is on a different domain from what's registered in the project, the request will be rejected.
Make sure:
- The domain registered in your project matches the host page's domain exactly (including/excluding
www.). data-init-endpointis a root-relative path (/api/v1/editor/init), not a URL pointing to your page's origin. The loader resolves it against the loader script origin (the CDN), which is the correct behavior for cross-origin embeds.