Сreating ONLYOFFICE plugins: tips, tricks, and hidden pitfalls
Building plugins for ONLYOFFICE editors can be straightforward, but real-world development often reveals unexpected challenges, from performance bottlenecks and networking quirks to subtle differences between web and desktop environments. This article covers practical tips, proven techniques, and common pitfalls you’ll likely encounter when creating reliable plugins that work smoothly across editors and deployment scenarios.

Bridge and workflow
Your plugin UI runs in its own window and talks to the editor through a message bridge. This bridge works best with small amounts of data and quick, focused edits. Keeping your UI work (like fetching data or filtering lists) separate from document changes (like inserting text) makes everything faster, easier to debug, and more predictable when multiple users edit at once.
Keep document edits short and focused
Why: Editor commands run in one go; grouping related changes avoids flickering and keeps the document state consistent.
Example (Document Editor, insert a citation):
// UI side: prepare compact data
const citation = `${title} - ${source}${date ? ', ' + date : ''}`;
Asc.scope.citation = citation;
// Editor side: run atomically
window.Asc.plugin.callCommand(function () {
var doc = Api.GetDocument();
var p = Api.CreateParagraph();
p.AddText(Asc.scope.citation);
doc.InsertContent([p]);
}, true);
Tip: Test editor API calls in the playground to verify syntax and behavior before integrating into the plugin.
Don’t fetch data inside editor commands
Why: Waiting for network requests inside a command freezes the editor.
Example (anti-pattern vs. correct):
// Anti-pattern: async in callCommand (don't do this)
window.Asc.plugin.callCommand(async function () {
const res = await fetch(url);
const text = await res.text();
Api.GetDocument().InsertContent([Api.CreateParagraph().AddText(text)]);
}, true);
// Correct: fetch in the plugin window, then pass small data
const res = await fetch(url);
const text = await res.text().then(t => t.slice(0, 200)); // trim
Asc.scope.snippet = text;
window.Asc.plugin.callCommand(function () {
var p = Api.CreateParagraph();
p.AddText(Asc.scope.snippet);
Api.GetDocument().InsertContent([p]);
}, true);
Send only small amounts of data across the bridge
Why: The bridge is optimized for JSON-like payloads; large strings or nested objects increase latency.
Keep UI work and document work separate
Why: Fetching, parsing, and filtering belong in your plugin window; only the final insert happens in the editor command.
Security and networking
Plugins often pull in content from external sources like RSS feeds or APIs. Without proper checks, you can accidentally let harmful code into your plugin UI, run into cross-origin errors that block requests, or silently fail when mixing HTTP and HTTPS. A simple security and networking plan prevents these headaches.
Key practices, with examples
Clean up untrusted HTML before showing it
Why: Feed summaries or snippets can contain HTML that breaks your UI or introduces security risks.
Example:
// Quick plain-text extraction
function toPlainText(html) {
const tmp = document.createElement("div");
tmp.innerHTML = html;
return tmp. textContent || "";
}
const safeText = toPlainText(untrustedHtml);
// If rendering controlled HTML, use a sanitizer (e. g., DOMPurify)
// const safeHtml = DOMPurify.sanitize(untrustedHtml);
// container.innerHTML = safeHtml;
Prefer inserting plain text into documents
Rationale: Plain text avoids unexpected formatting carryover and keeps bridge payloads small; reserve rich formatting for deliberate, editor-side API usage.
Use HTTPS and avoid mixed content
Rationale: Browsers block HTTP resources in secure contexts; silent failures are common when endpoints are not HTTPS.
Use a simple proxy for cross-origin requests
Rationale: Many endpoints do not allow direct cross-origin access; a tiny proxy adds CORS headers and normalizes output.
Example (Node/Express sketch):
// Minimal CORS/normalize proxy (server-side)
import express from "express";
import fetch from "node-fetch";
const app = express();
app.get("/feed", async (req, res) => {
try {
const url = new URL(req.query.src);
const r = await fetch(url.toString(), { timeout: 8000 });
const text = await r.text();
// Convert to normalized JSON shape server-side as needed
res.set("Access-Control-Allow-Origin", "*");
res.json({ items: normalizeToCommonShape(text) });
} catch (e) {
res.set("Access-Control-Allow-Origin", "*");
res.status(502).json({ error: "Upstream fetch failed" });
}
});
app.listen(3000);
Performance, packaging, and pre‑shipping
Plugins run while users are actively editing documents, so responsiveness matters. Good performance habits, clean packaging, and thorough testing before release keep your plugin fast, predictable, and easy to update.
Performance depends on consistent habits: debounce searches and filters, virtualize long lists, precompute the exact strings that will be inserted to reduce per-action cost. On close, clear timers, abort in‑flight requests, and remove listeners to prevent slow leaks that accumulate across sessions.
const controller = new AbortController();
const timer = setInterval(refresh, 300000);
window.Asc.plugin.button = function () {
controller.abort();
clearInterval(timer);
window.Asc.plugin.executeCommand("close", "");
};
Key practices
- Bundle your JavaScript and CSS locally instead of relying on external CDNs.
- Define your own light and dark color schemes instead of assuming the editor will provide them.
- Keep your plugin identifier (GUID) stable across versions so updates work smoothly.
- Use clear version numbers (1.0.0, 1.1.0, 2.0.0) that signal what changed.
- Include all required icons and metadata for the Plugin Manager and Marketplace.
- Test with production-like URLs to catch broken image or script paths.
Before shipping, run a compact but strict pass across supported editors (Document, Spreadsheet, Presentation), environments (web and desktop), themes (light and dark), collaboration (simultaneous inserts), network conditions (offline, slow, proxy down), dataset sizes (tiny and very large), and a stricter CSP with no inline scripts or styles, so behavior stays consistent when real users and real networks put the plugin to the test.
Conclusion
Solid plugins come from straightforward habits: prepare data first, make document changes in one focused step, sanitize external content, route network calls through a small proxy, and package everything locally so it works the same everywhere. Test across editors, environments, themes, and network conditions before you ship, so your plugin behaves predictably when real users and real networks put it to work.
Create your free ONLYOFFICE account
View, edit and collaborate on docs, sheets, slides, forms, and PDF files online.


