Skip to content
 
 

Latest commit

 

History

412 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ShareTab logo

ShareTab

A self-hosted, open-source alternative to Splitwise with AI-powered receipt scanning.

Quick StartFeaturesScreenshotsConfigurationDevelopment

License: MIT GitHub stars Last commit


ShareTab is a free, self-hosted alternative to Splitwise for tracking shared expenses with roommates, friends, and travel groups. Snap a photo of a receipt, let AI extract the line items, and assign them to group members. Taxes and tips split proportionally. Deploy on your own server with Docker Compose.

Screenshots

Dashboard -- see all your balances at a glance

Dashboard showing balance summary, who owes you, and group cards

AI receipt scanning -- snap a photo, assign items to people

Receipt scan: upload photo, AI extracts items, assign to group members

Split modes -- equal, exact, percentage, or shares

Switching between equal, exact, percentage, and shares split modes

Add expense -- quick and simple

Adding an expense with equal split

Multi-currency -- spend in any currency, settle in the group's currency

Adding an expense in EUR for a USD group, with automatic exchange-rate conversion

Create a group -- emoji, currency, and description

Creating a new group with emoji picker and currency selector

Settle up -- click a debt to record payment

Settlement dialog with From, To, and Amount

Venmo payments -- one tap to pay your share

Guest split with one-tap Venmo deep-link pay buttons for each person

Invite members -- share a link

Generating and copying an invite link

Guest bill splitting -- no account needed

Guest split: upload receipt, add people, assign items, share results

Group settings

Group settings page with member management

Dark mode -- toggle with one click

Dark mode toggle

9 languages -- switch the entire UI in one click

Switching the interface language between English, Spanish, and Japanese with locale-aware formatting

Admin dashboard -- manage users, AI providers, and system settings

Admin dashboard with system health, OAuth management, audit log, and tools

Features

  • Group expense tracking with multiple split modes (equal, percentage, shares, exact, item-level)
  • AI receipt scanning -- photograph a receipt, AI extracts line items, assign items to group members with proportional tax/tip; zoomable/pannable receipt viewer; rescan with correction prompts
  • Guest bill splitting -- no account needed, shareable summary links
  • Pluggable AI providers -- OpenAI (GPT-4o), OpenAI-Codex (ChatGPT OAuth), Claude (API key), Meridian (Claude Max subscription), local Ollama
  • Group archiving -- archive inactive groups to declutter your dashboard; toggle archived view on groups page
  • Friends -- track IOUs with people outside any group: add by email or as a placeholder, log non-group expenses, see one balance per person rolled up across every shared group, and settle up outside a group
  • Cross-group dashboard -- see all your balances at a glance, with per-person debt breakdown converted to your own currency
  • Debt simplification -- minimize the number of payments needed
  • Settle up -- record payments between any two group members with explicit From/To fields
  • Placeholder members -- add people without accounts; rename or remove them from group settings
  • Dark mode -- system-aware with manual toggle
  • Invite links -- share a link to add friends to your groups
  • Magic link auth -- passwordless email sign-in
  • OIDC / SSO -- sign in through any OIDC-compliant identity provider (Pocket ID, Authentik, Keycloak, Authelia, Zitadel, ...), with optional OIDC-only mode, auto-redirect, and RP-initiated logout
  • PWA -- installable on mobile with app-like experience
  • Admin dashboard -- user management, group overview, storage stats, AI usage, audit log, registration control, announcements, server logs, user impersonation, data export, expired guest split cleanup
  • Self-hosted -- Docker Compose deployment, designed for Unraid

Quick Start

ShareTab ships as an all-in-one Docker container with PostgreSQL bundled inside. No external database needed.

cd docker
cp ../.env.example .env

Edit .env with your settings -- at minimum, generate real values for NEXTAUTH_SECRET and AUTH_SECRET:

# Generate a secret
openssl rand -base64 32

Then start the container:

docker compose up -d

The app will be available at http://localhost:3000.

Unraid

If you want to run ShareTab on Unraid, this repo includes a ready-made template at unraid/sharetab.xml.

To use it:

# On your Unraid server
mkdir -p /boot/config/plugins/dockerMan/templates-user
cp /path/to/sharetab/unraid/sharetab.xml /boot/config/plugins/dockerMan/templates-user/sharetab.xml

Then in the Unraid web UI:

  1. Open Docker.
  2. Click Add Container.
  3. Select the ShareTab template from the template dropdown.
  4. Fill in the required variables like AUTH_SECRET, NEXTAUTH_SECRET, and any optional AI settings.
  5. Click Apply to create and start the container.

You can also skip the manual copy and paste the raw template URL into Unraid's template install flow:

https://raw.githubusercontent.com/sw-carlos-cristobal/sharetab/main/unraid/sharetab.xml

Backups:

docker compose exec sharetab su-exec postgres pg_dump -U sharetab sharetab > backup.sql

Upgrading

When upgrading to a new ShareTab version, pull the latest image and recreate the container:

cd docker
docker compose pull
docker compose up -d

The entrypoint automatically runs any SQL migration files in prisma/migrations/ before applying the Prisma schema. Most upgrades are fully automatic.

Manual migration (v0.7.x → v0.8.0)

Version 0.8.0 added an updatedAt column and converted the status column from text to an enum on the GuestSplit table. This migration now runs automatically on container startup. If you need to run it manually:

docker compose exec sharetab su-exec postgres psql -U sharetab -d sharetab \
  -f /app/prisma/migrations/guest_split_status_enum.sql

This is idempotent — safe to run more than once.

Configuration

All configuration is done through environment variables. Copy .env.example to .env and adjust as needed.

Required

Variable Description
NEXTAUTH_SECRET Session encryption key. Generate with openssl rand -base64 32.
AUTH_SECRET Auth.js secret. Generate the same way.

AI Receipt Scanning

Variable Description
AI_PROVIDER_PRIORITY Comma-separated provider priority list (for example openai-codex,meridian,openai). ShareTab checks providers in order, uses the first available one, and falls through to the next provider if extraction fails.
OPENAI_API_KEY Required when openai is included in AI_PROVIDER_PRIORITY.
OPENAI_MODEL OpenAI model for receipt scanning. Defaults to gpt-4o.
OPENAI_CODEX_MODEL Model for ChatGPT OAuth / Codex backend receipt scanning. Defaults to gpt-5.4.
ANTHROPIC_API_KEY Required when claude is included in AI_PROVIDER_PRIORITY.
ANTHROPIC_MODEL Claude model for receipt scanning. Defaults to claude-sonnet-4-6 (claude provider) or claude-opus-4-6 (meridian provider).
ANTHROPIC_HEALTH_MODEL Model for health-check probes (auth verification). Defaults to claude-haiku-4-5-20251001.
MERIDIAN_PORT Port for the embedded Meridian proxy. Defaults to 3457.
OLLAMA_BASE_URL Ollama server URL. Defaults to http://localhost:11434.
OLLAMA_MODEL Ollama model name. Defaults to llava.

The openai-codex provider uses ChatGPT OAuth via the Codex backend instead of an API key. Auth data lives in /app/chatgpt, so if that path is on a persistent volume the login survives restarts and image updates.

After the container is running, open the ShareTab admin dashboard and complete the ChatGPT OAuth flow there:

  1. Sign in as the admin user and open /admin.
  2. In the ChatGPT OAuth section, start the login flow.
  3. Authorize with ChatGPT in your browser.
  4. When the flow redirects to http://localhost:1455/auth/callback, copy the full URL from the browser address bar and paste it back into ShareTab.

If you use your own Docker or Unraid template, mount a persistent path to /app/chatgpt when openai-codex is in AI_PROVIDER_PRIORITY.

The meridian provider uses a Claude Max/Pro subscription via an embedded proxy -- no API key needed. Claude login data lives in /app/claude, so if that path is on a persistent volume the login survives restarts and image updates.

After the container is running, open the ShareTab admin dashboard and complete the Meridian login flow there:

  1. Sign in as the admin user and open /admin.
  2. In the Meridian auth section, start the login flow.
  3. Authorize with Claude in your browser.
  4. Copy the full callback URL from the browser address bar and paste it back into ShareTab.

The bundled Docker Compose setup persists /app/claude automatically. If you use your own Docker or Unraid template, mount a persistent path to /app/claude.

⚠️ OCR provider (removed): The ocr provider (Tesseract.js) was originally included as a free fallback for users without AI API access, but after extensive testing across hundreds of real-world receipts, the accuracy was too unreliable for production use. Common failures included extracting modifiers as line items, failing to exclude delivery fees, and poor handling of non-standard receipt layouts. The OCR provider has been removed from the codebase. Existing configs that include ocr in AI_PROVIDER_PRIORITY will silently ignore it. If you need reliable receipt scanning, configure one of the AI providers above (openai-codex or meridian are recommended). Community contributions to reintroduce OCR with improved accuracy are welcome.

AI Provider Performance

Benchmarked on a set of receipt photos (grocery, coffee shop, restaurant). Results represent typical single-receipt extraction.

Provider Speed Item Accuracy Cost Notes
OpenAI Codex (ChatGPT OAuth) ~6 s 5/5 items Free (uses ChatGPT subscription) Recommended. Best balance of speed and accuracy.
Meridian (Claude OAuth) ~16 s 5/5 items Free (uses Claude Max subscription) Same accuracy, but 2–3x slower.
OpenAI (API key) ~4 s 5/5 items Pay-per-token Fastest, but requires an API key and costs money.
Ollama (local LLM) Varies Varies Free, fully local Depends on model and hardware. Requires a running Ollama server.

Recommendation: Use openai-codex as your primary provider. It delivers the same accuracy as API-key providers at no additional cost (it piggybacks on your existing ChatGPT Plus/Pro subscription). Set your priority to:

AI_PROVIDER_PRIORITY="openai-codex"

If you also have a Claude Max subscription, you can add meridian as a fallback:

AI_PROVIDER_PRIORITY="openai-codex,meridian"

OAuth (optional)

Variable Description
GOOGLE_CLIENT_ID Google OAuth client ID for "Sign in with Google".
GOOGLE_CLIENT_SECRET Corresponding client secret.

OIDC / SSO (optional)

One generic OIDC provider, configured via issuer discovery rather than a per-vendor preset. Works with any OIDC-compliant identity provider -- Pocket ID, Authentik, Keycloak, Authelia, Zitadel, and others. Setting OIDC_ISSUER, OIDC_CLIENT_ID, and OIDC_CLIENT_SECRET enables a "Sign in with SSO" button on /login and /register; the rest are optional tuning knobs.

Variable Default Description
OIDC_ISSUER Issuer URL, e.g. https://auth.example.com. ShareTab reads ${OIDC_ISSUER}/.well-known/openid-configuration for the rest of the endpoints.
OIDC_CLIENT_ID Client ID registered with the IdP.
OIDC_CLIENT_SECRET Client secret. Leave the client's "public"/PKCE-only toggle off -- ShareTab is a confidential (server-side) client and needs a real secret.
OIDC_NAME Display name for the sign-in button, e.g. Pocket ID. Unset renders a generic "Sign in with SSO" label.
OIDC_ALLOW_LINKING true Link an OIDC sign-in onto an existing password/Google account with the same email, but only when the IdP marks that email email_verified: true.
OIDC_TRUST_EMAIL false Treat a missing email_verified claim as verified, for IdPs that omit the claim rather than sending false. Governs both linking and provisioning -- an unverified address is never turned into a new account either. Leave off unless you trust your IdP.
OIDC_AUTO_PROVISION false Provision new users on first OIDC sign-in even when registration is invite-only or closed, bypassing the normal registration mode. It does not bypass the email_verified requirement.
OIDC_ONLY false Hide the password/magic-link form on /login and /register. /login?password=1 is a permanent break-glass back to the password form.
OIDC_AUTO_REDIRECT false Skip /login entirely and redirect straight to the IdP. Also changes the post-logout redirect URI to ${NEXTAUTH_URL}/login?password=1, which has to be registered too. Only enable once OIDC sign-in is confirmed working for every account on the instance.
OIDC_RP_LOGOUT true Also end the IdP's own session on sign-out (RP-initiated logout) instead of just ShareTab's local session. Requires the IdP to advertise end_session_endpoint and to have the post-logout redirect URI below registered.

Two URLs need registering with the IdP, not one:

Register as Value
Callback / redirect URI ${NEXTAUTH_URL}/api/auth/callback/oidc
Post-logout redirect URI ${NEXTAUTH_URL}/login

For https://sharetab.example.com those are https://sharetab.example.com/api/auth/callback/oidc and https://sharetab.example.com/login.

The post-logout URI is only used when OIDC_RP_LOGOUT is on, which it is by default. Skip registering it and the first sign-out lands on the IdP’s "invalid post_logout_redirect_uri" error page -- after ShareTab’s own session has already been cleared. If you also enable OIDC_AUTO_REDIRECT, register ${NEXTAUTH_URL}/login?password=1 instead: auto-redirect changes the post-logout destination so signing out cannot bounce straight back into a still-open IdP session. Setting OIDC_RP_LOGOUT=false drops the requirement entirely, at the cost of leaving the IdP session open on sign-out.

Some IdPs (Pocket ID among them) match both URLs as a literal path rather than a wildcard, so register the exact paths.

Roll out in two steps: ship OIDC_ISSUER/OIDC_CLIENT_ID/OIDC_CLIENT_SECRET first, alongside the existing password form, and confirm SSO sign-in works for every account. Only then set OIDC_ONLY=true and OIDC_AUTO_REDIRECT=true -- /login?password=1 stays available as a fallback either way.

Magic Link Auth (optional)

Variable Description
EMAIL_SERVER_HOST SMTP host (e.g. smtp.gmail.com). Used for magic link sign-in and OAuth auth expiry alerts (Meridian / ChatGPT OAuth).
EMAIL_SERVER_PORT SMTP port. Use 465 for implicit TLS, 587 for STARTTLS.
EMAIL_SERVER_USER SMTP username / email address.
EMAIL_SERVER_PASSWORD SMTP password or app password.
EMAIL_FROM From address for sent emails.

Admin

Variable Description
ADMIN_EMAIL Email of the admin user. Grants access to /admin dashboard for managing users, groups, storage, and system settings, and receives OAuth auth expiry alerts when email is configured.

Other

Variable Default Description
NEXTAUTH_URL http://localhost:3000 Public URL of your instance.
AUTH_TRUST_HOST false Set to true when running on a local network or behind a reverse proxy.
DB_USER sharetab PostgreSQL username (Docker bundled DB).
DB_PASSWORD sharetab PostgreSQL password (Docker bundled DB).
DB_NAME sharetab PostgreSQL database name (Docker bundled DB).
UPLOAD_DIR ./uploads Directory for receipt image uploads.
MAX_UPLOAD_SIZE_MB 10 Maximum upload file size.
AUTH_RATE_LIMIT_MAX 5 Max login attempts per IP per hour.
REGISTER_RATE_LIMIT_MAX 10 Max registration attempts per IP per hour.
GUEST_RATE_LIMIT_MAX 10 Max guest split creations per IP per hour.
FRIEND_INVITE_RATE_LIMIT_MAX 20 Max friend invites sent per user per hour.
LOG_LEVEL info Logging verbosity: debug, info, warn, or error.

Tech Stack

Layer Technology
Framework Next.js 16 (App Router) + TypeScript
API tRPC v11 (end-to-end type-safe)
Database Prisma 7 + PostgreSQL 16
Auth NextAuth v5 (credentials + OAuth + magic link)
UI TailwindCSS 4 + shadcn/ui + next-themes
AI Pluggable providers: OpenAI, OpenAI-Codex, Claude, Meridian, Ollama
Testing Vitest (unit) + Playwright (e2e)

Development

Automation Commands

These commands are intended to be explicit enough for a human or an LLM to use without inferring repo-specific workflow details.

# Bump version files on main and update CHANGELOG.md
npm run version:bump -- patch

# Create a release branch + PR from main
npm run release:create -- patch

# Create a PR from the current branch
npm run pr:create -- --title "feat: example change"

# Push the current HEAD to origin/main
npm run push:main

# Publish a merged release by pushing the version tag
npm run release:publish -- v1.2.3

Intent mapping:

  • "bump the version" -> npm run version:bump -- <patch|minor|major>
  • "create a release" -> npm run release:create -- <patch|minor|major>
  • "create a PR" -> npm run pr:create -- [--base main] [--title \"...\"]
  • "push to main" -> npm run push:main
  • "publish the release" -> npm run release:publish -- [vX.Y.Z]

Release flow:

  1. Run npm run release:create -- patch from main.
  2. Merge the generated release/vX.Y.Z PR.
  3. Run npm run release:publish -- vX.Y.Z from main.

release:publish only creates and pushes the git tag. The actual GitHub release page and semver Docker images are still published by publish-release.yml.

# Install dependencies
npm install

# Generate Prisma client
npx prisma generate

# Copy and configure environment
cp .env.example .env  # Then edit .env as needed

# Option A: All-in-one (embedded PostgreSQL + schema push + seed + dev server)
npm run dev:full

# Option B: Manual setup (bring your own PostgreSQL)
# Set DATABASE_URL in .env pointing to your PostgreSQL instance
npx prisma db push
npm run db:seed    # optional -- creates demo data
npm run dev

Demo accounts after seeding: alice@example.com, bob@example.com, charlie@example.com (password: password123).

Nix / NixOS

A flake.nix provides a pinned toolchain (Node 22, PostgreSQL 16, Prisma schema engine, Playwright browsers) matching what CI uses. Nix is entirely optional -- it changes nothing for contributors who do not use it.

# Enter the dev shell
nix develop

# Install dependencies (the flake pins the toolchain, not node_modules)
npm install

# Copy and configure environment
cp .env.example .env  # Then edit .env as needed

# All-in-one: PostgreSQL + schema push + seed + dev server
nix run .#dev

# Or, from inside `nix develop`, the same thing:
sharetab-dev

Other outputs:

nix run .#build      # npm run build
nix run .#test       # npm test (unit tests)
nix fmt              # format .nix files (nixpkgs-fmt)
nix flake check      # evaluate all outputs

For e2e, wire up the browsers once, then run the suite against a server started by nix run .#dev in another terminal. Quote the whole command for bash -c -- passing Playwright's flags directly to nix develop -c lets nix consume --project and --grep before Playwright sees them:

nix develop -c link-playwright-browsers
nix develop -c bash -c 'BASE_URL=http://localhost:3000 npx playwright test'

With direnv, direnv allow activates the shell automatically on cd.

Caveats

  • npm run dev:full does not work on NixOS. It drives the embedded-postgres npm package, whose prebuilt PostgreSQL binaries are dynamically linked against an FHS layout and will not execute. Use nix run .#dev instead -- it starts postgresql_16 from nixpkgs against the same ./test-pg-data directory, on the same port (51214), so DATABASE_URL from .env.example keeps working.
  • npx prisma db push needs the flake's schema engine. The @prisma/engines npm package downloads a prebuilt binary that cannot run on NixOS -- without an override Prisma tries to fetch a linux-nixos engine and fails with a 404. Every flake output therefore exports PRISMA_SCHEMA_ENGINE_BINARY from nixpkgs' prisma-engines_7. nixpkgs and package.json version this independently (7.9.1 vs 7.6.0 at time of writing); that combination is tested and works, but a wider gap could eventually be rejected. The app runtime is unaffected either way: prisma/schema.prisma uses the Rust-free prisma-client generator with @prisma/adapter-pg, so no query engine binary is involved.
  • Playwright browsers need one extra step. nixpkgs and npm roll Playwright on different schedules, so the browser revisions rarely match. Run link-playwright-browsers once after npm install -- it builds a symlink farm in .playwright-browsers/ mapping the revisions this checkout expects onto the ones nixpkgs built. If a future version gap is too wide to alias, fall back to programs.nix-ld plus npx playwright install.
  • Prisma may warn about OpenSSL. prisma:warn Prisma failed to detect the libssl/openssl version is cosmetic -- it comes from Prisma probing for engine binaries this setup does not use.

Running Tests

# Unit tests (Vitest)
npm test

# E2E tests (requires dev server running)
BASE_URL=http://localhost:3000 npx playwright test

# E2E with visible browser
BASE_URL=http://localhost:3000 npx playwright test --headed

# Include AI-dependent tests (requires configured AI provider)
BASE_URL=http://localhost:3000 RUN_AI_TESTS=1 npx playwright test

Set AUTH_RATE_LIMIT_MAX=9999 and GUEST_RATE_LIMIT_MAX=9999 in .env to avoid rate limiting during repeated test runs.

Contributing

Contributions are welcome! See CONTRIBUTING.md for development setup, PR guidelines, and code style.

If you find a bug or have a feature request, please open an issue.

Security

To report a vulnerability, see SECURITY.md.

License

MIT

About

Self-hosted, open-source Splitwise alternative with AI receipt scanning. One Docker container, no external database.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages