How to build a Splitwise alternative

How to build a Splitwise alternative tracks a group ledger in cents, shows the short who-pays-whom list, and records a payment without moving money.

  • guide
  • vibe-coding
  • splitwise

How to build a Splitwise alternative is one group, expenses stored in cents, and a short list of who pays whom. Splitwise is that product. The catalog line is “Who owes whom, minus the group-chat forensics.”

The card lists $3.33–$4.99 a month. That is $40–$60 a year for the account. The card does not name the plans. The verdict is “Settle it yourself”. The first estimate is 8 hours: groups, unequal splits, balances, simplified debts.

The Splitwise teardown is the ledger, the help-page example, and why the algorithm is unpublished. This guide is the screens and the prompts for that 8-hour row. The project name on the card is Evensteven.

A household budget is the YNAB guide. YNAB is $9.08–$14.99 a month, about 12 hours. A receipt you submit to a manager is Expensify, $5–$9 a person a month, in the Expensify post. An invoice to a client is FreshBooks, $23–$60 a month, in the FreshBooks post. Those move different money. This build records a debt and stops.

How to build a Splitwise alternative as one group

The Splitwise page splits the work into three rows.

The first row is 8 hours. Groups, unequal splits, balances, and simplified debts. You add people, you record who paid, and the page says who should pay whom.

The second row is 2 days, 16 hours. Receipt uploads, recurring expenses, comments, and email reminders. The third row is 1 month and up, 160 hours. Mobile apps, payment integrations, currencies, and receipt scanning.

The non-goal names payment providers, bank sync, currency conversion, receipt scanning, and mobile apps. If you tell an agent to “clone Splitwise”, it aims at that month. Paste the prompts below and keep Lisbon in one currency.

The group is a trip called Lisbon. Members are Ada, Bea, and Cam. Amounts are integer cents. $20.00 is 2000. A $100.00 grocery split three ways is 10000 cents, and the shares have to add to 10000, not to 99.99.

Where the ledger and the list split

Every product prompt ships in two flavors: Node.js plus Astro, and TanStack Start. The card lists Astro, HTMX, and D1. This site is an Astro app on Cloudflare, so the pages are Astro and the ledger is D1. Use TanStack Start if React is your weekday language. The stack post is which tab you copy. On that tab the ledger is a SQL database, and the cent check still runs in the server function before the insert.

The browser adds an expense or records a payment. Astro on the request checks that the owed shares sum to the total, then writes the row. D1 stores the group, the members, the expenses, the shares, and the settlements. A balance is a sum you run when the page loads. Nothing in this v1 calls a bank, and nothing waits on a cron. Email reminders are the 2-day row. The integration line on the card is transactional email, and you leave it unused until someone asks for a nudge.

BROWSER Expense or a recorded payment Lisbon · Ada, Bea, Cam ASTRO ON THE REQUEST Shares must sum to the cost One currency, integer cents D1 LEDGER Expenses, shares, settlements Nets are summed on each read OUTSIDE V1 A charge that leaves a bank A second currency Receipt photos and scanning Comments, repeats, email Native apps
The request checks the cents. D1 keeps every row. The page adds them up when you open the group.

What one expense carries

The card lists Group, Member, Expense, ExpenseShare, and Settlement. All five are in this v1. A stored “balance” column is not. The guardrail says to derive balances from the ledger.

A group is Lisbon, one currency, USD, and an invite token. A member is Ada, Bea, or Cam. An expense is a description, a date, and a total in cents. An expense share is what that member paid and what they owe, both in cents. A settlement is a from, a to, and an amount. It is a new row. It does not edit the dinner.

const dinner = {
  totalCents: 2000,
  paid: { bea: 2000 },
  owed: { ada: 2000, bea: 0, cam: 0 },
}

The owed numbers add to 2000, so save works. Change Ada to 1999 and save refuses. The same rule covers a percentage, an equal split, and a share count. Those inputs are how you fill the form. The row that is stored is cents.

A $100.00 bill split three ways is the card’s scenario. 10000 cents. Sort the members by id. Each starts at floor(10000 / 3), which is 3333. One cent is left over, and it goes to the earliest id, so the shares are 3334, 3333, and 3333. They sum to 10000. The same rule covers every mode. 2000 split three ways leaves two cents, so the first two ids get 667 and the last gets 666. Floats that print as 33.33 are a failed save, because 3333 three times is 9999.

Nets are paid minus owed, then plus settlements in and minus settlements out. After the two Lisbon expenses in the teardown, Ada is −2000, Bea is 0, Cam is +2000. Cam paid the apartment, and only Bea owed it. Bea paid dinner, and only Ada owed it. Bea paid 2000 and owes 2000, so Bea sits at zero.

The simplified list pairs the most negative net with the most positive until both sides are zero. Ada pays Cam 2000. That is the help-page shape the teardown quotes, with these names. Splitwise has not published the pairing. The card asks for the fewest transfers, and this pairing does that for Lisbon. Raw debts stay available under a toggle: Ada pays Bea, Bea pays Cam. Turning simplify off after a settlement is how those raw lines stop matching money people already handed over. Leave the toggle on once anyone records a payment. The teardown has the help-page warning.

DINNER 2000 cents Bea paid 2000 Ada owes 2000 APARTMENT 2000 cents Cam paid 2000 Bea owes 2000 NETS FROM THE SUM Ada −2000 · Bea 0 · Cam +2000 Ada pays Cam 2000 SETTLEMENT ROW Ada to Cam · 2000 · expenses stay LATER A stored balance column A bank charge
Two expenses explain the nets. The settlement joins the same sum. Dinner is still in the ledger after the nets hit zero.

The screens you ship

Four places. The group, the expense form, the settle action, and the activity feed. Receipts and comments wait.

Group

Lisbon shows the three nets, the simplified line, and the raw debts behind the toggle. The invite link is on this screen. A person who opens it joins this group and no other.

WIREFRAME · GROUP Lisbon · USD · Ada, Bea, Cam Ada · −2000 Bea · 0 Cam · +2000 Simplified Ada pays Cam 2000 Raw debts Ada pays Bea · Bea pays Cam Invite link on this screen
Nets are the sum. The short list is a second view of the same cents.

Expense

The form asks who paid, the total in cents, and the split. Equal, percentage, share count, or exact amounts. The live line shows the remainder before save. 1999 owed against a 2000 dinner stays on the form.

WIREFRAME · EXPENSE Dinner · 2000 cents Bea paid 2000 Ada owes 2000 Bea owes 0 Cam owes 0 Remainder 0 · save allowed SAVE
Percent and share count are ways to fill the cents. The stored row is the cents.

Settle

Settle takes the simplified line. Ada pays Cam 2000. The button writes a settlement. Dinner and the apartment stay on the list. The next read shows every net at zero.

WIREFRAME · SETTLE Ada pays Cam 2000 cents · from the short list RECORD PAYMENT Dinner stays on the expense list
A settlement is another ledger row. The button does not call a payment provider.

The path from two expenses to a zero balance

You save the apartment, then dinner. Each save checks the cents. The group recomputes nets and shows Ada pays Cam. You record that payment. The expenses remain. Every net reads zero.

If a share is short by a cent, save stops and the ledger is unchanged.

TWO EXPENSES SAVED DO THE SHARES SUM? Owed cents equal the total NO Form keeps the row Ledger unchanged YES Write shares Recompute nets ADA PAYS CAM 2000 Raw lines stay on the toggle RECORD THE PAYMENT New settlement row EVERY NET READS 0 Dinner still on the list STORED BALANCE COLUMN Drifts after a later edit
Save is refused when the cents miss. A recorded payment is a row, and the expenses stay.

Prompt 1. The group and the invite

Copy the Astro prompt from Build your own Splitwise. Paste this above it.

Ship the 8-hour row only. Project name: Evensteven.
Node.js and Astro. Ledger in D1.

One group, Lisbon, currency USD.
Members Ada, Bea, and Cam, added by an unguessable invite link.
A link joins this group and no other group.

No payment provider, no bank sync, no second currency,
no receipt upload, no mobile app.

When the plan lists routes and tables, stop.
Do not store a balance column. Balances are a sum.

TanStack Start keeps the same group, the invite rule, and a SQL ledger. The cent checks below stay in the server function.

What to check after the group

Create Lisbon. Copy the invite link into a private window. Cam can join. The link does not list your other groups, because this v1 has one. Refresh. Ada, Bea, and Cam are still members. The schema has groups, members, expenses, shares, and settlements. It has no balances table.

Prompt 2. An expense that must add up

Add the expense form for Evensteven.

Fields: description, date, total in integer cents,
who paid how many cents, and what each member owes.

Modes that fill the owed cents: equal, percentage,
share count, and exact amounts. Store only the cents.

Refuse save unless owed shares sum to the total
and paid shares sum to the total.
No floats. $20.00 is 2000.

A $100.00 split three ways is 10000 cents.
Sort members by id. Each gets floor(total / n).
Hand one leftover cent to each of the first
(total % n) members. For 10000 and three people
that is 3334, 3333, 3333.

Seed dinner: Bea paid 2000, Ada owes 2000,
Bea and Cam owe 0.
Seed apartment: Cam paid 2000, Bea owes 2000,
Ada and Cam owe 0.

Done when a 1999 share on dinner is rejected
and the 2000 share saves.

What to check after the two expenses

Dinner and the apartment are both on the list. An exact share of 1999 on a 2000 total stays on the form, and D1 has no new row. A percentage of 50, 50, 0 on that dinner becomes 1000, 1000, 0. An equal split of 10000 cents across Ada, Bea, and Cam, in that id order, is 3334, 3333, 3333. An equal split of 2000 cents is 667, 667, 666. The form prints those cents before save.

Prompt 3. Nets and the short list

Derive balances when the group page loads.
Do not write them back to a column.

Net for a member is cents they paid, minus cents they owe,
plus settlements received, minus settlements they paid.

Simplified debts: while one net is negative and one is
positive, the most negative pays the most positive
the smaller of the two absolute amounts.
Show that list above the raw pairwise debts.
Raw debts stay on a toggle that defaults to simplified on.

For the two seeded expenses the page shows:
Ada −2000, Bea 0, Cam +2000.
Simplified: Ada pays Cam 2000.
Raw: Ada pays Bea 2000, Bea pays Cam 2000.

This pairing is ours. Do not call an external API for it.
Done when a reload shows the same three nets.

What to check after the list

Reload Lisbon twice. The nets match. Bea is 0 because she paid the dinner she does not owe and owes the apartment she did not pay. The simplified line is one payment. The toggle still shows the two raw lines. Edit nothing, and a third reload matches the second. If a balances row changed between reloads, the page is saving the sum. Delete that write.

Prompt 4. Record the payment, keep the history

Add settle and an activity feed.

Settle writes a settlement: from, to, amount in cents,
created time. It does not update the expense.
It does not call a payment API.

After Ada pays Cam 2000, every net reads 0.
Dinner and the apartment remain.

Activity is append-only: expense added, expense edited,
expense deleted, settlement recorded.
A delete removes the shares from the sum and leaves
the deletion visible. No orphaned shares.

Authorize every write by membership in this group.
Done when the settlement is recorded, the nets are 0,
and dinner is still on the expense list.

What to check after settle

Record Ada to Cam, 2000. Reload. Three zeros. Dinner is still there. The activity feed has two expense lines and one settlement, in order. Delete the apartment. Bea’s net and Cam’s net change, the deletion is still in the feed, and no share row points at a missing expense. A second browser that is not in Lisbon gets refused on the same post.

Failures the agent ships anyway

Dollars as floats

The form stores 20.00 and 19.99 and calls them equal after rounding on read. The guardrail is integer cents, checked before insert. 1999 against 2000 is a refused save.

A balance column

The group looks right on day one. You edit dinner from 2000 to 2500 and the column still says −2000. Nets have to be a query. The activity feed can show the edit. The column cannot be the source.

Simplify deletes the raw debts

The short list replaces the pairwise rows in the database. Then a settlement along Ada to Cam has nothing to compare with Ada to Bea. Keep both views. The teardown explains why the help page says to leave simplify on after people have paid.

The settlement edits the expense

Recording the payment lowers Ada’s owed share on dinner. Delete that settlement later and dinner is already wrong. The settlement is its own row. Removing it recomputes the nets from whatever expenses remain.

A Pay button

The agent adds Stripe because “settle up” sounds like checkout. The non-goal says you never move real money. The button label is record payment. The amount is a note that Ada paid Cam.

A second currency on Lisbon

Cam adds a taxi in euros beside a dinner in dollars. The card puts currencies on the month row, and simplify must not add those units. One group, one currency, for this v1. The same two people can have another group later. They do not need a converted total inside Lisbon.

Deploy the ledger

Evensteven wants Astro, HTMX, and D1. Put it on a subdomain you already own, splits.flaviocopes.com. The hosting post is the adapter, the D1 binding, and the custom domain.

Enter the two Lisbon expenses, check that Ada pays Cam, record that payment, and reload from a phone. The nets read zero. Dinner is still listed. Export D1 before you cancel the $3.33 plan. The card also stands in for Tricount and Settle Up. It does not give them prices.

Comments, receipt files, and a reminder email are the 16-hour row. Add them after the cents already match.

What to skip on purpose

Leave out banks, scanning, a second currency, and the apps. Leave out a friend-wide shuffle across groups. The teardown treats that 2012 behavior as a different product from the group list you just built.

8-HOUR V1 One group, one currency Shares that sum to the cost A settlement row, no charge SKIP THIS HALF Banks and receipt scanning A second currency Native apps
Eight hours is a ledger and one short list. The bank and the scanner stay on the later rows.

A budget that gives every dollar a job is the YNAB guide, not a second screen on Lisbon. An invoice with a paid mark is the FreshBooks post. Build those when the money is yours alone, or a client’s, instead of a trip’s.

Start now

Go to Build your own Splitwise. Copy the Astro prompt, or TanStack Start if that is what you write.

Tell the agent the first estimate. Groups, unequal splits, balances, simplified debts. Eight hours. Amounts in cents. Shares that sum to the total.

Enter the two Lisbon expenses. Ada pays Cam. Record it. Reload, and read the zeros with dinner still on the list.

Related articles

Read all articles →