- Overview: https://ethercalc.net/
- 中文版: 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.
Via npm (requires Bun ≥ 1.1 on PATH — the CLI
spawns bunx wrangler):
npm install -g ethercalc
ethercalc # starts on http://localhost:8000
Via Docker (no Bun needed on the host — the image carries it):
git clone https://github.com/audreyt/ethercalc
cd ethercalc
docker compose up -d
Both paths boot the same Miniflare-backed Worker, persist state
under ./ethercalc-data/ (or /data in the container), and need no
Cloudflare account.
git clone https://github.com/audreyt/ethercalc
cd ethercalc
docker compose up -d
This boots the Miniflare-backed Worker on http://localhost:8000 and
persists all spreadsheet state (Durable Objects, D1, KV, R2) to
./ethercalc-data/ in the repo. No Redis, no Node runtime, no
Cloudflare account.
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_CORS |
(unset) | 1 enables permissive CORS headers. |
ETHERCALC_BASEPATH |
(unset) | URL prefix, e.g. /ethercalc behind a proxy. |
ETHERCALC_EXPIRE |
(unset) | Seconds of inactivity before a room is pruned. |
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.
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 env vars:
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).
cd packages/worker
npx wrangler deploy
Store the HMAC secret as a Worker secret:
npx wrangler secret put ETHERCALC_KEY
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.sh
One 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.
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-redis
Then attach your domain in the Cloudflare dashboard under Workers & Pages → your worker → Triggers → Custom Domains.
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_TOKEN
O(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).
bun install
bun run --cwd packages/worker dev # wrangler dev --local
bun run --cwd packages/worker test # workers-pool + node tests
See 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.
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.
- socialcalcspreadsheetcontrol.js
- socialcalctableeditor.js
- formatnumber2.js
- formula1.js
- socialcalc-3.js
- socialcalcconstants.js
- socialcalcpopup.js
- l10n/fr.json
- static/jquery.js
- static/vex-theme-flat-attack.css
- static/vex.combined.min.js
- static/vex.css
- static/jszip.js
- static/shim.js
- static/xlsx.core.min.js
- static/xlsxworker.js
- assets/start.html (xlsx2socialcalc.js)
- src/*.ls (legacy LiveScript sources, preserved until Phase 12 sweep)
- packages/**/*.ts (TypeScript rewrite)
- images/sc_*.png