Build a YNAB alternative

Build a YNAB alternative as accounts, categories, and typed-in transactions. $9.08–$14.99 a month is rent you can cancel after a weekend of real spending.

  • getting-started
  • vibe-coding
  • scope

You can build a YNAB alternative as accounts, categories, and typed-in transactions. YNAB is that product. The catalog line is “Give every dollar a job without giving a company your bank history.”

You pay $9.08–$14.99 a month for it. That is $109–$180 a year. The basic replacement is 12 hours: accounts, categories, and manual transactions.

I would ship this if I still typed spending into a rented envelope budget. It is marked Weekend. The verdict on the card is “Your budget, your rules”.

Last time I wrote about vibe coding your own Hotjar. That was a teardown. This one is a regular backlog topic. The useful v1 is a private plan you type into, not a bank login.

What you need to build a YNAB alternative

The YNAB page splits the work into three rows.

The first row is the one you want this weekend. Cash, checking, savings, and a credit card, each with a reconciled balance. Monthly category groups with assigned, activity, and available amounts. Transactions you type: payee, category, memo, cleared or not.

The second row is 4 days. Funding targets, CSV import, reconciliation, reports. Those can wait until you have assigned a real paycheck by hand.

The third row is a month and up. Bank sync, mobile apps, shared budgets. The table labels that row as the part you should skip.

If you tell an agent to “clone YNAB”, it aims at the third row. Copy the prompt, then pin it to the first one.

Here is the shape of the data. Nothing else has to exist at 12 hours.

const budget = {
  id: 'bud-flavio-2026',
  ownerId: 'U-flavio',
  currency: 'USD',
}

const checking = {
  id: 'acc-checking',
  type: 'checking',
  name: 'Arancio checking',
}

const groceries = {
  id: 'cat-groceries',
  group: 'Monthly',
  name: 'Groceries',
}

const paycheck = {
  id: 'txn-oct-salary',
  accountId: 'acc-checking',
  payee: 'Bootcamp.dev',
  amountCents: 100000,
  date: '2026-10-26',
  type: 'inflow',
}

const rentJob = {
  id: 'asg-rent-oct',
  categoryId: 'cat-rent',
  month: '2026-10',
  amountCents: 70000,
}

A paycheck lands as an inflow. You assign it. Groceries later spend from what you assigned. Ready-to-assign is whatever is left.

PAYCHECK INFLOW $1,000 into checking Stored as 100000 cents READY TO ASSIGN Inflows minus assignments Derived, never a cached total GIVE EACH DOLLAR A JOB $700 rent, $300 groceries Ready to assign hits zero Type the grocery spend Available falls. Assigned stays.
The 12-hour v1 is an inflow, an assignment, and a typed spend. Bank sync stays off.

How does YNAB work

I am describing the catalog’s model here, not reverse-engineering YNAB. An envelope budget is a pile of money you have already received, split into jobs before you spend it.

YNAB’s public method is zero-based. You give every dollar a job. The site’s v1 is that method on a laptop you own.

The screens on the page are budget setup, a monthly plan, an account register, and a reflect view. For 12 hours you can live with the monthly plan and the register. Setup is a name, a currency, and opening balances. Reports wait for the 4-day row.

The data model is Budget, Account, CategoryGroup, Category, Transaction, Target. This weekend you need Budget, Account, Category, and Transaction. Target is a funding goal. It belongs on the 4-day row.

The capability line is Astro, HTMX, and D1. This site is an Astro app on Cloudflare, so that is the path I default to. Use TanStack Start if React is your weekday language. Scope matters more than the framework.

One owner owns one budget. Isolate every query by ownerId. A guessed budget id from another account returns nothing: no category names, no amounts, no 404 that confirms the slug.

Who a YNAB alternative is for

The audience line on the page is specific. One person or household who wants a private, hands-on spending plan.

The goal is to allocate available money, record spending, and know what is safe to spend before the next payday. This weekend, that means one currency, one budget, and numbers you typed.

If you need bank connections, a shared household with invites, or a phone app that syncs while you shop, keep paying. The site puts that rebuild at a month on purpose.

The same v1 also replaces Monarch Money, Copilot Money, and Lunch Money if what you wanted was envelopes you enter by hand. One focused build covers whichever of those you currently pay for.

A receipt you photograph for reimbursement is Expensify, $5–$9 a person a month, about 12 hours. Who owes whom after a trip is Splitwise, $3.33–$4.99 a month, about 8 hours. Build this if the question is “what is left in groceries”, not “who paid the Airbnb”.

Every dollar is an integer

The first mini-prompt on the page is “Every dollar has a job, and it’s an integer”.

Store every amount as an integer in the currency’s minor unit. Cents, never a float. 0.1 + 0.2 is a debugging afternoon you can skip by refusing decimals on the money path.

Nothing stores a running total. Account balances, category activity, category available, and ready-to-assign all come from two record types: transactions and assignments. An edit writes a new input. It does not patch a cached sum.

function readyToAssign(transactions, assignments) {
  const inflows = transactions
    .filter((row) => row.type === 'inflow')
    .reduce((sum, row) => sum + row.amountCents, 0)

  const assigned = assignments.reduce((sum, row) => sum + row.amountCents, 0)

  return inflows - assigned
}

readyToAssign(
  [{ type: 'inflow', amountCents: 100000 }],
  [
    { categoryId: 'cat-rent', amountCents: 70000 },
    { categoryId: 'cat-groceries', amountCents: 30000 },
  ]
)
//0

The scenario on the page is that assignment. $1,000 in. $700 to rent. $300 to groceries. Ready-to-assign is exactly zero after a refresh and after you click into November and back.

An inflow becomes ready to assign only in its effective month. Moving money between categories changes those two categories. The budget total stays put.

A full recompute from the raw rows must match the screen. Month-bucketed queries can make that fast, and if they disagree you trust the recompute.

The guardrail on the page also says never treat those numbers as financial advice. The app shows a ledger. It does not recommend what to buy.

How a YNAB alternative records spending

A spend is a transaction on an account, pointed at a category. Available falls by the amount. Assigned stays where you put it. Activity is the sum of those spends for the month.

function categoryAvailable(assignments, spends) {
  const assigned = assignments.reduce((sum, row) => sum + row.amountCents, 0)
  const spent = spends.reduce((sum, row) => sum + row.amountCents, 0)
  return assigned - spent
}

categoryAvailable(
  [{ month: '2026-10', amountCents: 30000 }],
  [{ payee: 'Esselunga', amountCents: 1850 }]
)
//28150

$18.50 at Esselunga is 1850. Groceries still show $281.50 available. You can see that without a bank feed.

Transfers are two linked account rows with no spending category. Paying a credit card from checking is a transfer. Deleting one side has to delete or flag the other, or checking and the card stop matching.

Cleared is a flag you toggle when the bank statement agrees. Working balance is every transaction. Cleared balance is the ones you marked. Reconciliation, the ritual that locks those marks, sits on the 4-day row.

What not to build in a YNAB clone

Leave out bank connections, moving money, investment advice, tax filings, and a second currency in the same budget.

The non-goal on the page already names those. You are replacing the envelope plan you currently rent.

Bank sync is the trap. Plaid-style connections, OAuth to every Italian bank, and a feed that posts three days late with a mangled payee. That is a compliance product. The catalog puts it on the skip row so you have a sentence to paste when the agent starts a “connect your bank” button.

If you want a file later, the 4-day row already has CSV import. You download a statement, map the columns, and confirm the rows. That is still your data, on purpose.

12-HOUR V1 Accounts and category groups Typed inflows and spends Ready-to-assign from the ledger SKIP THIS HALF Bank sync and Plaid logins Native mobile apps Shared household budgets
Ship the 12-hour ledger. Leave bank sync, apps, and shared budgets on the skip row.

YNAB alternative or an expense tracker

Reimbursements belong in Expensify, $5–$9 a person a month. Photo, merchant, amount, a report your manager approves. I would not put those receipts inside Pocketplan and call that done.

Housemates splitting a trip belong in Splitwise, $3.33–$4.99 a month. Groups, unequal splits, a settle-up row. A household that already shares one checking account does not need that list.

Hours you bill a client are Harvest, $13–$17.50 a person a month, about 4 hours for a timer and a weekly sheet. That money is not yours to assign until the invoice clears. Keep the timer there.

I would not start here as a first vibe-coded app. The first-project post still points at a tonight card. This is the weekend after you have shipped one small tool and you still type groceries into someone else’s envelope.

The 4-day row, after one real paycheck

Use the plan for a month as the real budget, not as a demo. Then look at the second estimate.

Funding targets with progress, and a one-click suggestion for underfunded categories. CSV import with a column mapping, fuzzy duplicates, and an atomic commit. Reconciliation that locks cleared rows and writes a visible adjustment when the bank disagrees. Spending and net-worth reports that drill back to the rows.

The credit-card mini-prompt lives here. A $40 grocery charge on the card drops groceries by $40 and raises the card-payment category by $40. The cash to pay the bill is now reserved. Paying the card is a transfer. A refund has to reverse both movements.

The catalog says 4 days for that row. Stop there. Skip the month-long bank platform.

The scope post is the same three-row table if you want the habit, not just this card. The prompt post is the extra line that says do not connect a bank.

Host it, type a real paycheck, cancel

The product pages put hosting this way: a focused replacement costs your build time plus close-to-zero hosting.

This one wants Astro, HTMX, and D1, because that is the stack on the card. There is a table for transactions and assignments, so take a backup before you delete the paid account.

Put the plan on a hostname you already own, budget.flaviocopes.com. Type last month’s opening balances. Enter one paycheck. Assign it to zero. Type three spends you already made. Confirm ready-to-assign and the grocery available number still match a from-scratch recompute.

If you miss CSV import or a target, add them from the 4-day row.

Then cancel YNAB, or Monarch Money, or Copilot Money, or Lunch Money, whichever one you pay for.

Start now

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

Tell the agent the first estimate only: accounts, categories, manual transactions. Twelve hours.

Assign one real paycheck until ready-to-assign is zero.