Sandbox
@ranuts/document

Browser document editor with local OnlyOffice and WASM

This project is a complete browser editor for DOCX, XLSX, PPTX, and related formats. It uses OnlyOffice plus a WASM converter on the client side, so documents are opened, edited, and saved without a document server. It also includes embed support, offline PWA support, and WebMCP tools for browser agents that need to open, read, or save documents.

1,931 stars207 forksHTMLUpdated 10d ago
Who it's for

Builders who want browser-based office editing without sending files to a backend.

What it delivers

You can edit and save office documents locally in the browser, even offline, without uploading files.

What it does

Client-side office editing

Opens and edits DOCX, XLSX, PPTX, CSV, and several older office formats in the browser tab.

Local-only file handling

Conversions, edits, and exports happen on the visitor’s device, with no server upload and no account needed.

Offline PWA support

After the first visit, the editor can run without a network connection.

Embed API

The editor can run in an iframe and be controlled through postMessage from a host app.

WebMCP tools

Exposes browser-side tools such as open, save, read text, and set read-only for in-browser agents.

How to get it

  1. 1Self-host with Docker
    docker run -d --name document -p 8080:80 ghcr.io/ranuts/document:latest
  2. 2Run from source
    git clone https://github.com/ranuts/document.git
    cd document
    pnpm install
    pnpm run dev

README

Online Document Editor

CI Status License Version Live site

English | 简体中文 | 日本語 | 한국어 | Deutsch | Español | Português | فارسی

Open and edit Word, Excel and PowerPoint files in a browser tab. There is no server: the OnlyOffice engine and its WASM converter run on the visitor's own device, so documents are never uploaded, and no account is involved.

Live site: edit.chaxus.com


✨ Features

  • 🔒 Nothing is uploaded — every conversion, edit and export happens in the tab
  • 📝 Real editing, not preview — DOCX, XLSX, PPTX and CSV, plus ODF, RTF, TXT and the legacy binary formats; PDFs open and can be annotated
  • 💾 Saves into your own file — pick it once, every save after writes back to it (Chromium; elsewhere it downloads as before)
  • 🕓 Nothing is lost if you close the tab — edits autosave into your own browser, kept for 7 days, deletable any time (details)
  • 📴 Works offline — installable as a PWA; after the first visit no network is needed
  • 🌍 Multi-language — 7 languages end to end (English, 中文, 日本語, Deutsch, Español, 한국어, Português): the pages, the app UI and the editor all follow the one you pick; the editor itself ships 45
  • 🧩 Embeddable — full postMessage API for iframe integration
  • 🤖 Agent-ready — exposes WebMCP tools so a browser AI agent can open, convert and read documents
  • 🚀 Deploy anywhere — a static build; a directory of files behind any web server

🚀 Quick start

Use it: edit.chaxus.com — nothing to install.

Self-host with Docker:

docker run -d --name document -p 8080:80 ghcr.io/ranuts/document:latest

Run from source:

git clone https://github.com/ranuts/document.git
cd document
pnpm install
pnpm run dev

📄 Formats

KindEditAlso opens
Documents.docx.doc .odt .rtf .txt
Spreadsheets.xlsx .csv.xls .ods
Presentations.pptx.ppt .odp
PDFannotate, fill, export.pdf

Any of them can be exported to PDF. CSV keeps its encoding on the way back out (UTF-8, GB18030 and Latin-1 are sniffed on open).


🔗 Routes and URL parameters

RouteWhat it is
/Landing page. No editor bundle is loaded until you open something.
/editorThe editor.
/historyDocuments this browser is holding (see below).
/help, /changelogGenerated from the markdown under content/.

Parameters on /editor:

ParameterDescription
src=<url>Open a document from a URL (the URL must allow CORS)
file=<url>Same, legacy spelling; wins if both are present
new=docxStart a blank document (docx, xlsx, pptx)
saved=<id>Reopen one of this browser's saved documents — the editor puts its own id here, so a reload returns to the same document
readonly=1Open for viewing: editing and export are disabled
embed=1Embed mode; the host page drives the editor over postMessage
locale=zh-CNInterface language

🔐 Your data stays on your device

Documents are never sent anywhere. Where the browser allows it, saving writes straight back into the file you picked, so the document lives in your own file system and not in a downloads folder. Two things are kept in the browser itself, and both are yours to remove:

  • Copies of what you edited. While you work, the editor saves the document into this browser (IndexedDB) so a refresh, a closed tab or a crash does not cost you the work. Reopening the editor offers it back. These copies exist so you can pick up where you left off — they are not a backup, so keep exporting anything you want to keep.
  • Seven days, then gone. Each document is deleted automatically seven days after you last edited or opened it, whether or not you come back.

/history lists what is stored, with a delete on every row, a delete-all, and a switch to turn autosave off entirely. Deleting there takes effect immediately. On a shared machine, that is the page to visit.


🧩 Embedding via iframe

Embed the editor and drive it over postMessage. The usual split is: your system handles auth and storage, the iframe handles editing.

<iframe
  id="documentEditor"
  src="https://your-deployment/editor?embed=1"
  style="width: 100%; height: 720px; border: 0"
></iframe>
// Open a document
iframe.contentWindow.postMessage(
  { id: '1', type: 'document:open-url', payload: { url: 'https://example.com/doc.xlsx' } },
  'https://your-deployment',
);

// Listen for the result
window.addEventListener('message', (e) => {
  if (e.data?.type === 'document:opened') console.log('Ready to edit');
  if (e.data?.type === 'document:saved') uploadFile(e.data.payload.file);
});

Embedded editors keep no local history — the document belongs to the host page.

Full API reference — every message type, the origin allowlist, read-only mode and the save flow.

Also available as a component: this project powers the document preview in @ranui/preview (docs).


🤖 Browser AI agents (WebMCP)

Where the browser supports it, the page registers tools an in-browser agent can call directly instead of driving the UI: open_document_url, open_document_buffer, create_document, save_document, get_document_text, set_readonly, get_document_state. Documents still never leave the device — the browser fetches and converts them itself. Where the API is absent, this is a no-op.


🚀 Deployment

A static build — no runtime, no database.

pnpm build   # outputs to dist/

Static hosting (Cloudflare Pages, Nginx, Vercel, Netlify…)

Upload dist/. public/_headers carries the caching contract the site expects (hashed assets immutable, service worker never cached); hosts that ignore it still work, they just revalidate more.

For Nginx, serve index.html as the fallback for unknown routes:

location / {
  root /var/www/document;
  try_files $uri $uri/ /index.html;
}

GitHub Pages

.github/workflows/pages-build-site.yml builds and deploys on push to main. Enable Pages in the repository settings with GitHub Actions as the source.

Docker

# Basic
docker run -d --name document -p 8080:80 ghcr.io/ranuts/document:latest

# With HTTPS and basic auth
docker run -d --name document -p 443:443 \
  -v /path/to/certs:/ssl \
  -e SERVER_BASIC_AUTH='user:$2y$...' \
  -e SERVER_HTTP2_TLS=true \
  -e SERVER_HTTP2_TLS_CERT=/ssl/cert.pem \
  -e SERVER_HTTP2_TLS_KEY=/ssl/key.pem \
  ghcr.io/ranuts/document:latest

SERVER_BASIC_AUTH takes a BCrypt hash; double the $ characters for shell escaping. Caching for the image is configured in sws.toml.


🔤 Fonts

The vendored OnlyOffice build ships its font library in public/fonts/, indexed by public/sdkjs/common/AllFonts.js. Fonts are fetched on demand — a document only pulls the ones it actually uses.

Font management guide — the indexed catalog's wire format, the registries, and adding fonts with bin/font-catalog.mjs.


🛠 Development

pnpm install --frozen-lockfile
pnpm run dev            # dev server
pnpm run build          # production build (bin/build.sh)
pnpm run lint           # oxlint + tsc + docker config
pnpm run test           # unit tests (Vitest)
pnpm run test:e2e       # end-to-end tests (Playwright, real editor + real WASM)

The end-to-end suite drives the real editor and the real converter rather than mocks, including document round trips, the embed protocol and the recovery flow. docs/explorations/ records why each non-obvious piece is the way it is — worth a look before changing the editor integration.


📚 Built on

🤝 Contributing

Issues and pull requests are welcome. main is protected: work on a branch and open a PR, which runs lint, unit tests and three end-to-end suites (dev server, Cloudflare Pages semantics, and the production Docker image).

📄 License

AGPL-3.0.

This is a derivative work of ONLYOFFICE (sdkjs and web-apps, (c) Ascensio System SIA), distributed under the AGPL with additional terms under its Section 7: the original product logo must be retained, and no rights under trademark law are granted. The editor therefore keeps the ONLYOFFICE logo in its header and its About pane. See NOTICE for the full text, the vendor version and every change made to it.

ONLYOFFICE is a trademark of Ascensio System SIA. This project is not an official ONLYOFFICE product and is not affiliated with or endorsed by Ascensio System SIA.

Files in the repo

Repository payload48 top-level entries
  • .github
  • .scratch
  • bin
  • content
  • docs
  • lib
  • packages
  • public
  • redirect
  • styles
  • test
  • types
  • .dockerignore
  • .gitignore
  • .oxlintrc.json
  • .prettierignore
  • .prettierrc.json
  • CHANGELOG.md
  • CLAUDE.md
  • docker-compose.yaml
  • Dockerfile
  • editor.html
  • history.html
  • index.html
  • index.ts
  • LICENSE
  • NOTICE
  • package.json
  • playwright.browsers.config.ts
  • playwright.config.ts
  • playwright.docker.config.ts
  • playwright.pages.config.ts
  • playwright.prod.config.ts
  • pnpm-lock.yaml
  • pnpm-workspace.yaml
  • readme.de.md
  • readme.es.md
  • readme.fa.md
  • readme.ja.md
  • readme.ko.md
  • readme.md
  • readme.pt.md
  • readme.zh.md
  • sws.toml
  • tsconfig.json
  • vite-env.d.ts
  • vite.config.ts
  • vitest.config.ts

Discussion (0)

Ask about usage, or say what you built with it

Sign in to join the discussion.

No comments yet. Be the first to say what this is good for.

More other

HKUDS/
Vibe-Trading

"Vibe-Trading: Your Personal Trading Agent"

33k

Your Personal AI Assistant; easy to install, deploy on your own machine or on the cloud; supports multiple chat apps with easily extensible capabilities.

35k

Open-source Claude Design alternative. One-click import your Claude Code / Codex API key. Prompt → prototype / slides / PDF. Multi-model (Claude, GPT, Gemini, Kimi, GLM, Ollama). BYOK, local-first, MIT.

7.9k

🤯 LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.

82k