Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (ethercalc) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
EtherCalc
- Overview: https://ethercalc.net/
- User guide: docs.ethercalc.net (Starlight). Local:
bun run --cwd packages/docs dev - 中文版: http://tw.ethercalc.net/
- 简体中文: http://cn.ethercalc.net/
- REST API: API.md
EtherCalc is a web spreadsheet for real-time collaborative editing.
This branch is the TypeScript rewrite on the Cloudflare fullstack
(Hono + Workers + Durable Objects + D1 + KV + R2). It deploys to
Cloudflare via wrangler deploy, and self-hosts anywhere via
docker compose up with no Cloudflare account required. See
CLAUDE.md for the plan-of-record, full architecture,
and phase status.
Integrated with content management systems:
Browsers tested: Safari, Chrome, Firefox.
Install
Via npm (requires Bun ≥ 1.1 on PATH — the CLI
spawns bunx wrangler):
npm install -g ethercalc
ethercalc # starts on http://localhost:8000Via Docker (no Bun needed on the host — the image carries it):
git clone https://github.com/audreyt/ethercalc
cd ethercalc
docker compose up -dThe CLI path boots wrangler/Miniflare; the Docker path boots standalone
workerd. Both need no Cloudflare account, and Docker persists room state
under ./ethercalc-data/ (or /data in the container).
Self-hosting
git clone https://github.com/audreyt/ethercalc
cd ethercalc
docker compose up -dThis boots the standalone workerd Worker on http://localhost:8000 and
persists spreadsheet room state to ./ethercalc-data/ in the repo. No
Redis, no Node runtime, no Cloudflare account.
For an internet-facing self-host, put EtherCalc behind a reverse proxy that terminates TLS and applies rate limits. The app deliberately keeps anonymous read/write for anyone who knows a room URL; the edge is where you bound request volume. A runnable nginx recipe is included:
docker compose -f docker-compose.proxy.yml up -dThe proxy config at deploy/nginx/ethercalc.conf sets a 25 MiB body
limit to match the Worker write cap, limits request/connection rates per
source address, and forwards WebSocket upgrades (with long read
timeouts, so idle spreadsheets stay connected). For production HTTPS:
place your certificates under deploy/nginx/certs/, uncomment the 443
listener in that file, and uncomment the 443 ports mapping in
docker-compose.proxy.yml — or copy the same limits to your existing
nginx/caddy/traefik edge. The bundled proxy serves the app at the URL
root; don't combine it with ETHERCALC_BASEPATH (the config does no
prefix stripping).
Environment variables
Override defaults by exporting these before docker compose up:
| Variable | Default | Effect |
|---|---|---|
ETHERCALC_PORT |
8000 |
Listening port (remaps container bind). |
ETHERCALC_HOST |
0.0.0.0 |
Listening address. |
ETHERCALC_KEY |
(unset) | HMAC secret; enables read-only vs. edit auth. |
ETHERCALC_DISABLE_ROOM_INDEX |
1 |
Hide /_rooms* and /_exists/:room. Set 0 to reopen (on the Docker image the directory endpoints then return empty bodies — there is no D1 index; only /_exists becomes a live oracle). |
ETHERCALC_CORS |
(unset) | Legacy room-index gate; CORS headers are always permissive for embeds. |
ETHERCALC_BASEPATH |
(unset) | URL prefix, e.g. /ethercalc behind a proxy. |
ETHERCALC_EXPIRE |
(unset) | Seconds of inactivity before a room is pruned. |
Recommended public-instance settings:
- Set
ETHERCALC_KEYif you want edit/delete URLs to require a per-room HMAC rather than anonymous write/delete. - Leave
ETHERCALC_DISABLE_ROOM_INDEX=1unless you intentionally want a public room directory and existence oracle. - Set
ETHERCALC_EXPIREfor public scratch instances, e.g.ETHERCALC_EXPIRE=2592000for a 30-day inactivity TTL. - Keep the container on plain HTTP and terminate TLS at the reverse
proxy. If a local proxy fronts the container, publish the container
port on loopback, e.g.
127.0.0.1:8000:8000; do not changeETHERCALC_HOST, which must stay reachable inside the container.
On Apple Silicon, Docker Desktop's virtio networking has an
intermittent quirk that can make curl localhost:8000 hang even
against a healthy container. If you hit it, run the worker directly
(bun run --cwd packages/worker dev) or use a Linux host.
CLI
For non-Docker runs (local dev, systemd, etc.) use the bin/ethercalc
wrapper. It accepts the legacy flag surface and forwards to
wrangler dev + Miniflare/--var bindings:
bin/ethercalc [--key SECRET] [--cors] [--port N] [--host ADDR] \
[--expire SEC] [--basepath PREFIX] \
[--persist-to DIR]Run bin/ethercalc --help for the full flag table. --keyfile /
--certfile are accepted for backward compatibility but currently
print a warning — wrangler dev does not expose TLS. Terminate TLS at
a reverse proxy (nginx/caddy/traefik).
The ETHERCALC_* environment variables from the table above work here
too (exported before bin/ethercalc). Note that the CLI forwards them
to wrangler dev as --var arguments, which are visible in the local
process list — on shared machines, prefer a loopback bind or put
secrets in packages/worker/.dev.vars instead of the environment.
Deploy to Cloudflare
cd packages/worker
npx wrangler deployStore the HMAC secret as a Worker secret:
npx wrangler secret put ETHERCALC_KEYStaying on legacy (Redis-backed) EtherCalc
audreyt/ethercalc:latest (and every 0.20260422.* tag and later)
ships the 2026 TypeScript rewrite. It stores rooms in Durable Object SQLite
files, not Redis — pulling latest over an existing Redis-backed
install will look broken until you migrate.
To keep using Redis without migrating yet, pin the last pre-rewrite release:
docker pull audreyt/ethercalc:0.20201228.1Or use the bundled compose file (builds the same image locally if the tag is not cached yet):
git clone https://github.com/audreyt/ethercalc
cd ethercalc
# Reuse your existing Redis data directory:
ETHERCALC_LEGACY_REDIS_DATA=/var/lib/redis docker compose -f docker-compose.legacy.yml up -dRoom state lives in Redis (appendonly yes). The legacy stack listens on
port 8000 and speaks socket.io — same URLs and behaviour as pre-2026
self-hosts. When you are ready to move forward, see the migration section
below.
Migration from a legacy (Redis-backed) EtherCalc
Turnkey (recommended)
If you have a legacy Redis-backed EtherCalc and just want to upgrade:
# Preserve the Redis dump outside the repo — this is your rollback point
sudo cp /var/lib/redis/dump.rdb ~/ethercalc-dump-$(date +%F).rdb
git clone https://github.com/audreyt/ethercalc
cd ethercalc
cp ~/ethercalc-dump-$(date +%F).rdb ./legacy-dump.rdb
./bin/migrate-legacy.shOne command stands up a temporary Redis loaded with your dump,
builds and runs the new Worker, streams every room across, and
writes a dated backup to ./backups/ethercalc-<timestamp>.tar.gz
containing both the migrated state and your source dump. On success
the Worker is left running on http://localhost:8000 — open any
existing room by its URL to confirm.
Requires only docker + the docker compose plugin on the host.
On Ubuntu: sudo apt install -y docker.io docker-compose-plugin.
Tested against OrbStack and Docker Desktop on macOS/arm64; Docker
Engine on Linux.
Migrating rooms to a Cloudflare deployment
Once the turnkey path above has verified locally, the same dump can be pushed to a Cloudflare Workers deployment. From the repo root:
# Deploy the worker. Spits out https://ethercalc.<subdomain>.workers.dev
cd packages/worker
npx wrangler login # one-time browser auth
npx wrangler deploy
# Mint a migration token and store it as a Cloudflare secret
TOKEN=$(openssl rand -hex 16)
echo "$TOKEN" | npx wrangler secret put ETHERCALC_MIGRATE_TOKEN
# Stand up a temporary local Redis loaded with the legacy dump
cd ../..
docker run -d --name ec-migrate-redis -p 6379:6379 \
-v "$PWD/legacy-dump.rdb:/input/dump.rdb:ro" \
redis:7-alpine sh -c \
'cp /input/dump.rdb /data/dump.rdb && exec redis-server --save "" --appendonly no'
sleep 3 # let redis finish loading the dump
# Push every room up to the Cloudflare deployment
./bin/ethercalc migrate \
--source redis://localhost:6379 \
--target https://ethercalc.<subdomain>.workers.dev \
--token "$TOKEN"
docker rm -f ec-migrate-redisThen attach your domain in the Cloudflare dashboard under Workers & Pages → your worker → Triggers → Custom Domains.
Manual (advanced)
bin/ethercalc migrate streams a running Redis or Zedis directly
into a Worker you already have up:
bin/ethercalc migrate \
--source redis://localhost:6379 \
--target http://new-worker.example/ \
--token $ETHERCALC_MIGRATE_TOKENO(1)-per-room memory regardless of dump size — Redis owns the decoding.
The target endpoint is gated by env.ETHERCALC_MIGRATE_TOKEN (when
unset, the route returns 404). Pass --dry-run to preview without
writing. --source file:///path (or bare /path) also works for
on-disk legacy dumps (the Sandstorm grain fallback format).
Development
bun install
bun run --cwd packages/worker dev # wrangler dev --local
bun run --cwd packages/worker test # workers-pool + node testsSee CLAUDE.md for the directory map, testing strategy (100% line/branch/function/statement coverage plus Stryker mutation gates on gated packages), and the remaining phase plan.
REST API
See API.md. The public HTTP surface is preserved byte-for-byte where deterministic, minus a small allow-list of sensible fixes documented in CLAUDE.md §6.1.
Licensing
Common Public Attribution License (Socialtext Inc.)
- socialcalcspreadsheetcontrol.js
- socialcalctableeditor.js
Artistic License 2.0 (Socialtext Inc.)
- formatnumber2.js
- formula1.js
- socialcalc-3.js
- socialcalcconstants.js
- socialcalcpopup.js
Artistic License 2.0 (Framasoft)
- l10n/fr.json
MIT License (John Resig, The Dojo Foundation)
- static/jquery.js
MIT License (HubSpot, Inc.)
- static/vex-theme-flat-attack.css
- static/vex.combined.min.js
- static/vex.css
MIT License (Stuart Knightley, David Duponchel, Franz Buchinger, Ant'onio Afonso)
- static/jszip.js
Apache License 2.0 (SheetJS)
- static/shim.js
- static/xlsx.core.min.js
- static/xlsxworker.js
- assets/start.html (xlsx2socialcalc.js)
CC0 Public Domain (唐鳳 / Audrey Tang)
- src/*.ls (legacy LiveScript sources, preserved until Phase 12 sweep)
- packages/**/*.ts (TypeScript rewrite)
Mozilla Public License Version 2.0 (LibreOffice contributors)
- images/sc_*.png