Download
=OPEN(your_workbook.xlsm, Nextcloud)
Leaving Microsoft?

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

EditorsSumSheet for .xlsx / .xlsm / .xlsb, SumDoc for .docx — the same engines as the desktop apps, running on your server, one process per open document.
Inside NextcloudThrough 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.
MeasuredA 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 countedPeople, 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_urlThe Nextcloud server fetches our discovery from here. Must be reachable from inside the Nextcloud container.
public_wopi_urlThe browser. Nextcloud also builds its content-security policy from it — frame-src and form-action.
wopi_callback_urlThe 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 discoveryNot 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.com behind your TLS reverse proxy. This is what the compose file above expects.
  • One origin for everything — one nginx in front: / to Nextcloud, /f1, /a4 and /hosting to 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-srcThe 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 addressCached discovery. Overwrite the file (never delete it) and restart Nextcloud — the memory cache holds it for an hour.
The editor asks you to sign inThe cross-site cookie. Move to https, or serve Nextcloud and the editors from one origin.
The editor opens and saving never arriveswopi_callback_url points somewhere the editor container cannot reach, or that host is not in the editor's allowed list.
Editor log: CheckFileInfo … → 400Nextcloud 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-hostsNEXTCLOUD_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 fileNextcloud 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 falseNextcloud 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 .xlsx or .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.

Then

SumOffice assistant

Answers are assembled from the documentation and may be inaccurate — check the sources.