Open-source project
spliit-app/spliit avatar
spliit-app/spliit

Spliit: the manifest says spliit2 0.1.0, the releases are 1.29, and two compose files disagree

Free and Open Source Alternative to Splitwise. Share expenses with your friends and family.

2,979 stars514 forksTypeScriptMIT

At a glance

What is it?
Spliit is a self-hostable Splitwise alternative whose documentation is unusually operational: three compose files, a Playwright suite that drives a browser against the container image rather than a dev server, and a runtime configuration section that explains why the build deletes its own mock environment file. The inconsistencies are equally concrete, from a package manifest named spliit2 at version 0.1.0 beside a 1.29.0 release, to two different Postgres pins, to default database credentials printed in both example configs.
Who is it for?
Spliit suits a group that wants to run its own expense sharing rather than hand its data to a hosted service, and the operational documentation is better than most self-hosted projects manage. Three things to check before you deploy it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 10, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The manifest is named spliit2 at version 0.1.0, private and unpublished

The repository, the running service and the package are three different names. The repository is spliit-app/spliit and the hosted instance is spliit.app, while package.json is named spliit2, marked private and sitting at version 0.1.0. The published releases are 1.29.0, published the same day this repository was last pushed, with 1.28.0 and 1.27.0 both on 2026-09-26 four hours apart. So the version in the tree is a placeholder that never tracks a release, which is consistent with the package being marked private and never intended for a registry. The same name leaks into the prose: the container instructions say npm run start-container starts the postgres and the spliit2 containers. Nothing here breaks a deployment, but anyone scripting a version check against the manifest gets a number that means nothing.

The compose sample ships default credentials and a fixed user id

The deployment sample is short enough to audit line by line, and it starts with a known password. The database service sets POSTGRES_USER to spliit, and both connection strings for the app are identical:

yaml
POSTGRES_PRISMA_URL: postgresql://spliit:spliit@database:5432/spliit
POSTGRES_URL_NON_POOLING: postgresql://spliit:spliit@database:5432/spliit

So the password is the user name. Both services also declare user 1000:1000 with a comment saying to change it to your own user id or remove it if you want root. The same pattern appears in the development environment file, where .env.example sets both database variables to postgresql://postgres:1234@localhost. Two different default passwords across the two example files, one of them committed to the repository. The sample also mounts a single cache directory, ./app/cache onto /usr/app/.next/cache, and publishes host port 8080 onto container port 3000.

Two compose files pin Postgres differently

There are three compose files in the repository and they do not agree about the database. compose.yaml is the development and self-hosting path: the app is built from the checkout with build dot, the image is tagged spliit:latest, host port 3000 maps straight to container port 3000, environment comes from container.env, and the database is postgres:latest with a five second healthcheck interval running pg_isready against the postgres user and a bind mount at ./postgres-data. The deployment sample in the documentation is a different file: it pulls the published image ghcr.io/spliit-app/spliit:latest and pins postgres:17.3. A third file, compose.e2e.yaml, belongs to the test harness. So the database version you get depends on which of two documented routes you take, one of which tracks whatever the latest Postgres tag currently is.

The end-to-end suite tests the container, not the dev server

The Playwright suite in e2e/ is written against the deployable artifact. The page states it drives a real browser against the app running in Docker so it exercises the same image users deploy, and that it needs Docker and a free port 3000 and nothing else, because the stack builds itself from the checkout and throws its database away afterwards. One command does the whole cycle:

sh
npm run e2e

It builds the image, starts the app and PostgreSQL from compose.e2e.yaml, waits for the readiness endpoint, runs the suite and tears everything down, and it never touches the development stack or the ./postgres-data directory. For writing tests there are four more: e2e:up to leave the stack running, e2e:test with a flag that opens Playwright's UI mode for picking tests and stepping through a trace, e2e:report to open the HTML report of the last run, and e2e:down to stop and delete the test database. The UI mode does not start the stack itself. If port 3000 is busy, for instance with npm run dev, the page says to set E2E_HOST_PORT on every command of the session. The same suite runs in GitHub Actions from a workflow named E2E, triggerable by hand and automatic on release tags.

Three headline features are opt-in and two of them call an API

The feature list is a tracker, with every entry linked to its issue number, and the newest three are not free. Attaching images to expenses requires ENABLE_EXPENSE_DOCUMENTS plus four S3 settings, and an endpoint value that the example marks as needed only for non-AWS providers. Creating an expense by scanning a receipt requires ENABLE_RECEIPT_EXTRACT and an OpenAI key. Auto-suggesting the category from the title requires ENABLE_CATEGORY_EXTRACT and an OpenAI key. Both models default to gpt-5-nano, and OPENAI_BASE_URL exists for OpenAI-compatible endpoints, defaulting to OpenAI itself. Analytics are disabled unless a provider is chosen, with console, plausible and umami listed as comma-separated options. So a deployment that enables the two most visible features is making metered calls to a third party unless the base URL is pointed elsewhere.

The image is built with a mock environment file and then has it removed

The Dockerfile explains itself in comments, and the interesting part is a deliberate removal. Before compiling, it copies scripts/build.env to .env and runs the build. Because Next.js copies .env into the standalone output, that file would otherwise ship inside the image carrying the mocked build values: a database URL pointing at the db service, placeholder S3 credentials and placeholder OpenAI credentials. The comment is explicit about the failure mode it avoids, that real configuration from the container environment would win, but a variable the operator forgot to set would silently resolve to a build placeholder instead of failing. The fix is one line, rm -f on the standalone environment file. The same reasoning explains the retry loop around npm ci, which the comment attributes to a registry connection reset that has nothing to do with the code.

Prisma runs migrations on install, with scripts disabled inside the image

There is a lifecycle hook that matters more than it looks. postinstall is prisma migrate deploy followed by prisma generate, so a plain npm install applies pending migrations to whatever database the environment points at, and the local setup page says so as a convenience rather than as a warning. Inside the image that hook does not run, because the install uses the ignore-scripts flag, and a later stage installs the Prisma CLI on its own; the comment explains that the standalone output traces its own dependencies so the runtime no longer installs a production node_modules, that the CLI is still needed at container start to run migrate deploy, and that the CLI is not part of the application's module graph so nothing traces it. The practical shape is that migrations run when the container starts, not when the image is built.

Two test runners, oxlint instead of eslint, and a perf harness

The scripts reveal a wider toolchain than the prose does. Unit and integration tests run under jest, with jest.config.ts and a separate jest.environment.ts at the root, while end to end tests run under Playwright with playwright.config.ts, so two runners coexist with two config files each. Linting is oxlint rather than eslint, configured through .oxlintrc.json, and there is no eslint configuration in the tree. Formatting is prettier over a fixed path list of src, e2e, perf and playwright.config.ts, which means the prisma directory and the scripts directory are not covered by either the format or the format check. There is also a performance harness with five scripts backed by its own compose file, compose.perf.yaml, including an up, a seed, a bench and a down pair, and a data generator run through tsx for currency data. The base image is node 26 on alpine while the engines field asks for Node 24 or newer.

Editorial conclusion

Spliit suits a group that wants to run its own expense sharing rather than hand its data to a hosted service, and the operational documentation is better than most self-hosted projects manage. Three things to check before you deploy it. The credentials in the example files, because .env.example ships a postgres user with the password 1234 and the compose sample ships a spliit user with the password spliit, so a deployment that copies either file unchanged starts with a known password. The feature flags, because three of the headline capabilities are opt-in and two of them call an OpenAI endpoint, meaning receipt scanning and category suggestions cost money per use and image attachments need an S3 bucket. And the version you are running, since the manifest reports 0.1.0 under the name spliit2 while the published line is 1.29.0, so the image tag and the package name in the tree tell you different things about what you deployed.

Frequently asked questions

What is Spliiit?

Spliit is a free and open source alternative to Splitwise for sharing expenses with friends and family, built on Next.js with TailwindCSS, shadcn/UI, Prisma and Postgres, and available either as the hosted instance at spliit.app or as your own deployment, including a Docker image published under the spliit-app organisation.

How do I run spliit locally?

Clone or fork the repository, start a PostgreSQL server with ./scripts/start-local-db.sh if you do not have one, copy .env.example to .env, run npm install, which also applies database migrations and updates Prisma Client, and then npm run dev. The example environment file ships a database URL using the user postgres with the password 1234.

What do the spliit health endpoints check?

GET /api/health/readiness, or its alias /api/health, reports whether the app can serve requests including database connectivity, and GET /api/health/liveness reports whether it is running without being ready. The end-to-end harness waits on the readiness path before starting the suite.

Which spliit features need an OpenAI key or S3 storage?

Scanning a receipt to create an expense needs ENABLE_EXPENSE_DOCUMENTS-style S3 settings plus ENABLE_RECEIPT_EXTRACT and an OpenAI key, auto-suggesting the category needs ENABLE_CATEGORY_EXTRACT and an OpenAI key, and attaching images needs ENABLE_EXPENSE_DOCUMENTS with S3 credentials. Both OpenAI models default to gpt-5-nano, and OPENAI_BASE_URL can point at a compatible endpoint.

Is spliit safe and legal to run yourself?

The project is MIT licensed and describes itself as free and open source with no ads, with hosting, database and API costs paid by donations through Open Collective or a GitHub sponsorship. The page does not address the deployment question directly. What it does show is that the example files carry default credentials, with a postgres password of 1234 in .env.example and spliit:spliit in the compose sample.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. spliit-app/spliit on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/spliit-app-spliit.svg)](https://hysenlabs.com/projects/spliit-app-spliit)