Edit DOCX/XLSX/PPTX in your browser — client-side, no server, works offline (OnlyOffice + WebAssembly)
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
Use it: edit.chaxus.com — nothing to install.
Self-host with Docker:
docker run -d --name document -p 8080:80 ghcr.io/ranuts/document:latestRun from source:
git clone https://github.com/ranuts/document.git
cd document
pnpm install
pnpm run dev| Kind | Edit | Also opens |
|---|---|---|
| Documents | .docx | .doc .odt .rtf .txt |
| Spreadsheets | .xlsx .csv | .xls .ods |
| Presentations | .pptx | .ppt .odp |
| annotate, 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).
| Route | What it is |
|---|---|
/ | Landing page. No editor bundle is loaded until you open something. |
/editor | The editor. |
/history | Documents this browser is holding (see below). |
/help, /changelog | Generated from the markdown under content/. |
Parameters on /editor:
| Parameter | Description |
|---|---|
src=<url> | Open a document from a URL (the URL must allow CORS) |
file=<url> | Same, legacy spelling; wins if both are present |
new=docx | Start 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=1 | Open for viewing: editing and export are disabled |
embed=1 | Embed mode; the host page drives the editor over postMessage |
locale=zh-CN | Interface language |
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:
/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.
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).
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.
A static build — no runtime, no database.
pnpm build # outputs to dist/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/workflows/pages-build-site.yml builds and deploys on push to main.
Enable Pages in the repository settings with GitHub Actions as the source.
# 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:latestSERVER_BASIC_AUTH takes a BCrypt hash; double the $ characters for shell
escaping. Caching for the image is configured in sws.toml.
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.
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.
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).
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.
ranuts/document
June 8, 2025
August 24, 2026
HTML