Your Excel keeps working inside Nextcloud.
Three steps, about twenty minutes if you already run Nextcloud in Docker. A workbook with macros opens from Files in the browser, is edited, saved back — and opens in Excel afterwards with its VBA project byte for byte; a Word document does the same and opens in Word with every picture in place. Everything below was measured on a real corpus; the last section says what is in scope today.
What you get
| Editors | SumSheet for .xlsx / .xlsm / .xlsb, SumDoc for .docx — the same engines as the desktop apps, running on your server, one process per open document. |
|---|---|
| Inside Nextcloud | Through the standard Nextcloud Office app (richdocuments): SumOffice presents itself the way Collabora does, so nothing in Nextcloud is patched. Click a file → editor. Save → a new file version in Nextcloud. |
| Measured | A macro workbook from the public Enron corpus (2 sheets, 861 cells, 197 formulas, VBA): 4.3 s from the click in Files to a ready sheet on a laptop, recalculation on open 126 ms, save back < 1 s. The saved file: 14 parts of 14, vbaProject.bin identical, 197 formulas of 197, defined names kept, the values your VBA functions had computed kept — and Excel opens it without a word. A DOCX with five pictures (jpeg, png, wmf, emf, pict): edited in SumDoc, saved back, 16 parts of 16, media identical, Word opens it clean. |
| Not counted | People, documents, clicks. Evaluation is free with no time limit; production inside an organization is the per-application license. Your customized Nextcloud, SSO, storage rules — a change under your process, price and date before we start. |
Step 1 — start the editors
Clone the public stack and fill in .env — three lines. PUBLIC_URL is the address you give the editors, behind your TLS reverse proxy. NEXTCLOUD_URL is the address the editors call Nextcloud back at; with one public Nextcloud address that is simply the one your people open. NEXTCLOUD_HOST is the host name of that same address, with no scheme and no port (with a port, opening a file answers 403). Generate the local proof key once before the first start: both editors must sign with the same key.
mkdir sumoffice-nextcloud && cd sumoffice-nextcloud
git clone https://github.com/SumOfficeApp/sumoffice-docker && cd sumoffice-docker/nextcloud
cp env.example .env # PUBLIC_URL=https://office.example.com NEXTCLOUD_URL=https://cloud.example.com NEXTCLOUD_HOST=cloud.example.com
sh sdelat-proof-klyuch.sh # one local signing key for SumSheet and SumDoc
docker compose up -d
. ./.env && curl -s $PUBLIC_URL/hosting/discovery | head -c 300 # the WOPI discovery Nextcloud will read
The . ./.env matters: without it $PUBLIC_URL is empty in your shell and curl prints nothing, with no error. Apple Silicon or another arm64 machine: the images are linux/amd64 only, and docker compose up -d stops with no matching manifest for linux/arm64/v8. Put this next to the compose file as docker-compose.override.yml and run it again — Docker then runs the two editors under x86 emulation, slower but complete (do not set DOCKER_DEFAULT_PLATFORM instead: it also forces the small helper images to amd64 and breaks them if you already have them for arm64):
services:
sumsheet: { platform: linux/amd64 }
sumdoc: { platform: linux/amd64 }
Why one address with /f1 and /a4: Nextcloud's content-security policy allows exactly one origin for the editor frame. The small discovery.py merges the two editors' discovery into one — temporary glue until the images publish it themselves. Images: hissih/sumsheet-webhost, hissih/sumdoc-webhost.
Step 2 — point Nextcloud at them
On the Nextcloud host (or docker exec -u www-data nextcloud php occ …):
php occ app:install richdocuments
php occ config:app:set richdocuments wopi_url --value https://office.example.com
php occ config:app:set richdocuments public_wopi_url --value https://office.example.com
php occ richdocuments:activate-config
The last command must print no error: it fetched our discovery and capabilities. If the editors reach Nextcloud by another address than your people do — an internal name, or host.docker.internal when everything runs on one machine — tell Nextcloud that address and let it accept that host name; otherwise it answers the editors 400 and the browser only says the document failed to load:
php occ config:app:set richdocuments wopi_callback_url --value http://cloud.internal:8080 # after activate-config, never before
php occ config:system:set trusted_domains 2 --value cloud.internal # a free index; list them with config:system:get trusted_domains
That address goes into NEXTCLOUD_URL and NEXTCLOUD_HOST in .env, not the one people open. Two things we measured on a live stand and wish someone had told us: richdocuments:activate-config clears wopi_callback_url, so set that one after it and not before; and when you change the address later, Nextcloud keeps the old discovery in two places at once — overwrite the file data/appdata_*/richdocuments/remoteData/discovery (deleting it makes Nextcloud answer 500) and restart the container or PHP, because the same value also sits in the local memory cache for an hour.
The address rule — read this before you debug anything
Almost every «document failed to load» is one address in the wrong place. There are three settings and they serve three different sides, and a fourth address that is not a setting at all:
wopi_url | The Nextcloud server fetches our discovery from here. Must be reachable from inside the Nextcloud container. |
|---|---|
public_wopi_url | The browser. Nextcloud also builds its content-security policy from it — frame-src and form-action. |
wopi_callback_url | The editors call Nextcloud back here to read and write the file. Must be reachable from inside the editor containers; it must also be in the editor's own list of allowed WOPI hosts. |
urlsrc in discovery | Not a Nextcloud setting: the address of the editor frame comes from the discovery XML we serve. It has to be the same address as public_wopi_url, or the browser blocks the frame and the policy names the mismatch. |
One more, and it is the one that looks like a bug in the editor: the editor page lives on a different origin from Nextcloud, so its session cookie is a cross-site cookie. Browsers accept those only as SameSite=None; Secure — that is, over https. On plain http the editor accepts the WOPI token (the editor log says [wopi] вход: user → файл … — the logs are in Russian, search for [wopi]) and then shows you a sign-in page, because the cookie never arrived. A trial with both on localhost (different ports) is not affected: to the browser that is one site, and plain http works. Two ways out, both fine:
- https on the editors — put
PUBLIC_URL=https://office.example.combehind your TLS reverse proxy. This is what the compose file above expects. - One origin for everything — one nginx in front:
/to Nextcloud,/f1,/a4and/hostingto the editors. Then the cookie is first-party and the policy is satisfied by'self'.
If the document does not load
Open the browser console; the first line is usually the whole answer.
Framing '…' violates … frame-src | The urlsrc in discovery is not the address in public_wopi_url. Same cause for Sending form data to '…' violates … form-action. |
|---|---|
| The console still shows the old address | Cached discovery. Overwrite the file (never delete it) and restart Nextcloud — the memory cache holds it for an hour. |
| The editor asks you to sign in | The cross-site cookie. Move to https, or serve Nextcloud and the editors from one origin. |
| The editor opens and saving never arrives | wopi_callback_url points somewhere the editor container cannot reach, or that host is not in the editor's allowed list. |
Editor log: CheckFileInfo … → 400 | Nextcloud does not trust the host name the editors call it by. Add it to trusted_domains (Step 2). |
Spinner forever on a .docx; SumDoc log: [wopi] отказ: хранилище … не в списке --wopi-hosts | NEXTCLOUD_URL in .env is not the address the editors call back at. Put that address there and run docker compose up -d again. |
Document loading failed due to timeout on the very first file | Nextcloud Office waits 15 seconds by default; the first document after the stack starts can take longer, especially under x86 emulation on arm64. Open it again, or give it more time: php occ config:app:set richdocuments timeout --value 60. |
activate-config: Call to a member function xpath() on false | Nextcloud refuses to fetch from a private address (such as host.docker.internal), while curl from the same container works. php occ config:system:set allow_local_remote_servers --value=true --type=boolean. Measured on Nextcloud 31 with Nextcloud Office 8.8; Nextcloud 32 with Nextcloud Office 9.2 fetched without it. |
No Nextcloud yet? A trial on one machine
A throwaway Nextcloud next to the stack, no TLS and no domain. Everything stays on localhost for the browser, and the containers reach each other through host.docker.internal (Docker Desktop; on Linux add --add-host host.docker.internal:host-gateway):
docker run -d --name nextcloud -p 127.0.0.1:8080:80 -e SQLITE_DATABASE=nc \
-e NEXTCLOUD_ADMIN_USER=admin -e NEXTCLOUD_ADMIN_PASSWORD=choose-one -e NEXTCLOUD_TRUSTED_DOMAINS=localhost \
nextcloud:32-apache
# .env: PUBLIC_URL=http://localhost:8093 NEXTCLOUD_URL=http://host.docker.internal:8080 NEXTCLOUD_HOST=host.docker.internal
alias occ='docker exec -u www-data nextcloud php occ'
occ app:install richdocuments
occ config:app:set richdocuments wopi_url --value http://host.docker.internal:8093 # Nextcloud → editors
occ config:app:set richdocuments public_wopi_url --value http://localhost:8093 # browser → editors
occ richdocuments:activate-config
occ config:app:set richdocuments wopi_callback_url --value http://host.docker.internal:8080 # editors → Nextcloud
occ config:system:set trusted_domains 2 --value host.docker.internal
Here wopi_url and public_wopi_url differ, and that is right: inside the Nextcloud container localhost:8093 is the container itself. Measured 1 October 2026 on Nextcloud 32.0.15 with Nextcloud Office 9.2.3, on an Apple Silicon Mac: a workbook and a document opened, edited, saved back, and the change was in the downloaded file.
Re-measured 20 October 2026 on a native amd64 server with this recipe and hissih/sumsheet-webhost:2026.10.19-amd64: Nextcloud 35.0.2 with Nextcloud Office 12.0.1, editors and Nextcloud behind one https address. A macro workbook opened from Files in about six seconds; a macro ran, its changes were committed, Ctrl+S saved a new version, and the VBA project came back byte for byte.
Prefer a settings page to occ? The SumOffice app (0.1.6, Nextcloud 29–35) points Nextcloud Office at your editors with one field and checks what the server offers.
Step 3 — open a workbook
- In Files, click any
.xlsxor.xlsm. The editor opens in the Nextcloud window; the first document takes a few seconds while its process starts (under x86 emulation on arm64, up to about twenty), the next ones are faster. - Edit, press Ctrl+S (or just close): the file goes back through WOPI, Nextcloud keeps the previous version in its history.
- Download the file and open it in Excel: the VBA project and the formulas are the ones you put there.
Scope today
- Spreadsheets and documents: .xlsx / .xlsm / .xlsb and .docx are edited and saved back natively; .ods / .odt open by import and save as .xlsx / .docx.
- Macros and Power Query travel with the file untouched; the queries refresh in the desktop SumSheet, inside Nextcloud they are preserved.
- Presentations: not in this server yet. SumSlide, the presentations editor, ships as desktop apps for macOS, Windows and Linux.
Before you move a department, run its workbooks through Check my Excel file: it names every macro, every function and every data source the engine knows or does not know, in seconds, without running anything.
- Order a change under your process — your SSO, your storage rules, your customized Nextcloud: from 150 €, price and date before we start.
- Tell us what kind of organization embeds it — the production license is per embedding application, no meters on your users.