Warning
EXPERIMENTAL and VIBE-CODED. This is an AI-assisted port of SplitPro to Cloudflare Workers. It is a hobby project. It has no automated tests, it has not been security-reviewed, and parts of it are untested (bank sync, Splitwise import, recurring expenses under load). It handles money records. Use at your own risk, and do not trust it with anything important.
This is a fork of oss-apps/split-pro, the open source Splitwise alternative. All credit for the app, UI, and features goes to the original authors. For the original project, its documentation, and the supported Docker/Postgres setup, go to https://github.com/oss-apps/split-pro (website: https://splitpro.app).
This fork is not affiliated with or endorsed by the upstream project. License: MIT (see LICENSE).
Everything runs on Cloudflare Workers. There is no Node server, no Docker, and no Postgres.
| Area | Upstream | This fork |
|---|---|---|
| Runtime | Next.js on Node | Next.js on Workers via vinext + @cloudflare/vite-plugin |
| Database | Postgres + Prisma | Cloudflare D1 + Drizzle (money writes use atomic db.batch) |
| Auth | next-auth | better-auth, Google sign-in only |
| Uploads | local disk | R2 |
| nodemailer / SMTP | Cloudflare Email Sending binding | |
| Cron / recurring expenses | pg_cron |
A Workflow with a * * * * * schedule, driven by the job table |
| Push | web-push |
web-push on Workers (VAPID keys as secrets) |
| Observability | none | Workers Logs, Traces, and Issues, unsampled; tRPC, Worker, and browser errors are logged |
The Dockerfile, docker/, start.sh, flake.nix, and most of docs/ are leftovers from upstream.
They describe the Postgres/Prisma setup, which this fork removed. They no longer work here.
You need a Cloudflare account on a plan that supports Workers, D1, R2, Workflows, and Email Sending, a domain on Cloudflare, and a Google OAuth client.
- Install:
pnpm install - Create resources:
npx wrangler d1 create splitpro npx wrangler r2 bucket create splitpro-uploads
- Copy
wrangler.example.jsonctowrangler.jsonc. Set your domain, the D1database_id, and the sender address (FROM_EMAIL). Your domain must have Email Sending enabled. - Create a Google OAuth client (Web application). Add the redirect URI
https://YOUR_DOMAIN/api/auth/callback/google. - Apply the migrations:
npx wrangler d1 migrations apply splitpro --remote - Set the secrets (
npx wrangler secret put NAME):GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,NEXTAUTH_SECRET(a random string; the name is kept from upstream), and for push notificationsWEB_PUSH_PUBLIC_KEY,WEB_PUSH_PRIVATE_KEY,WEB_PUSH_EMAIL. Generate VAPID keys withnpx web-push generate-vapid-keys. - Build and deploy:
SKIP_ENV_VALIDATION=1 pnpm build && npx wrangler deploy
Local development: put the same values in .dev.vars (git-ignored, with NEXTAUTH_URL=http://localhost:8787),
run npx wrangler d1 migrations apply splitpro --local, then npx wrangler dev --port 8787.
wrangler.example.jsonc includes a previews block with non-production settings and D1/R2 bindings.
Run npx wrangler preview --name <preview-name> from the branch you want to preview; Wrangler
defaults the name to the current Git branch. Set NEXTAUTH_URL in that branch's previews.vars to
its actual Preview URL. Preview URLs are public unless protected by Cloudflare Access.
The example D1 ID and Access audience are placeholders. Create a non-production D1 database and R2
bucket, then set their IDs/names in the previews block. D1/R2 resources are shared whenever
previews use the same ID/name. For per-branch isolation, create separate resources for each branch,
update that branch's previews bindings and D1 migration target, and apply migrations to that D1
before deploying its Preview. Do not point Preview bindings at production resources.
When protecting a Preview with Cloudflare Access, set PREVIEW_ACCESS_AUTH=true, set
PREVIEW_ACCESS_AUD to the Access application's audience tag, and ensure the Access policy covers
the Preview hostname. The Worker verifies the Access identity and maps it to an app session; this
setting belongs only in previews.vars, not production vars.
- Test coverage is small and focused on the riskiest paths (see below). There are no unit or component tests.
- Plaid / GoCardless bank sync and the Splitwise import are untested on Workers.
- Only Google sign-in is wired up. There is no email or password login.
drizzle-kitandwrangler d1 migrationsown the schema.migrations/0001_balance_view_and_triggers.sqlis hand-written (theBalanceViewview and a trigger). Back it up before regenerating migrations.
- Time Travel: D1 keeps point-in-time history for 30 days on the Workers Paid plan.
Restore with
npx wrangler d1 time-travel restore splitpro --timestamp=<unix>. - Daily backup to R2: the
splitpro-backupWorkflow (03:00 UTC) exports D1 through the REST API tod1/splitpro-<date>-<time>.sql, then writes a JSON manifest beside it. It also paginates theUPLOADSbucket and copies new or changed objects touploads/<original-key>in the privatesplitpro-backupsbucket, preserving HTTP and custom metadata. Existing upload copies are retained even if their source object is later deleted, so R2 upload backups are not pruned automatically. SQL dumps and manifests older thanBACKUP_RETENTION_DAYS(default 90) are pruned. The D1 export endpoint may block database requests while an export is running. It needs theD1_EXPORT_TOKENsecret (an API token with D1 Write; the endpoint rejects D1 Read), and theCF_ACCOUNT_IDandD1_DATABASE_IDvars. - Restore: load a SQL dump into a database with
npx wrangler d1 execute <db> --remote --file <dump>.sql, then copy objects under the backup bucket'suploads/prefix back toUPLOADS, removing that prefix from each key. The application code, migrations, and non-secret Worker configuration are in Git. Recreate Worker secrets from a secure password manager; they cannot be recovered from a database or R2 dump. - Both buckets are in the same Cloudflare account. This protects against application-level deletion
from
UPLOADS, but not account compromise or account-wide deletion; keep an additional copy in a separate account or provider for that threat model. - Trigger one by hand:
npx wrangler workflows trigger splitpro-backup.
pnpm test:integration # Vitest in workerd with a real local D1 and the production migrations
pnpm build && pnpm test:e2e # Playwright against the built Worker (wrangler dev), 4 parallel workerssrc/tests/integration: authorization, balances and simplification, expense create/edit/delete atomicity, settlements, recurring expenses (including idempotent retries), and regression tests for D1 query bugs.tests/e2e: sign-in redirect, signed-in session, groups, expenses, settle up, a stranger being blocked, and recurring expenses, all against the production bundle. Google sign-in cannot be automated, so the tests seed users and signedbetter-authsession cookies in a throwaway D1.- CI (
.github/workflows/ci.yml) runs typecheck, integration, and e2e as three parallel jobs with the pnpm store, Playwright browsers, and the build cached. The target is under 2 minutes.
Bug reports for this fork go to this repository's issues. Please do not report them upstream.