New: multi-organisation is live, run every Xero org you have from one login. What changed

Xero knows the ledger.
This knows the shelf.

FIFO inventory costing Xero doesn't do. Every sale traced to the exact stock layers it consumed, voids restocked at original cost, freight capitalised into layers, and one balanced COGS journal pushed back each day.

Free · self-hosted · Postgres · NUMERIC money, never floats
By the numbers
$0licence fee. Self-hosted means your server, your rules.
1manual journal per day. Balanced. Draft. You post it.
2decimal places that are never guessed, layers carry full precision.
0writes to AR, AP or bank accounts. Ever.
The costing engine

Built around one obsession

Layer-exact FIFO

Sales consume receipts oldest-first with deterministic tie-breaks. Two oak batches at $8.50 and $9.20 stay two truths, margins per batch, not a blend.

Voids rewind cleanly

A voided invoice restocks the precise slices it consumed at their original costs. The audit trail shows every slice, always.

Bills become layers

Approved bills land as cost layers automatically. Amend a bill: untouched layers recost, sold ones raise a conflict.

BOM assemblies

Build finished goods from components, cost rolls up into one finished layer at true consumed value, divided by units built.

Landed costs to the cent

Freight and duty spread across open layers by value or quantity. Shares sum exactly; history stays frozen.

Trial-balance proof

Stock valuation reconciles against Xero's trial balance on demand. Drift gets a number, not a vibe.

The Xero loop

Two directions. Zero double-entry.

Xero → shelf

Approved bills create cost layers. Approved sales consume them. Credit notes restock. Webhooks trigger instant syncs; a poller catches anything missed; every ingest is idempotent by event reference, so replays never duplicate.

Voids reverse themselves. Amendments recost or escalate. You read the conflict log, not tea leaves.

Ingest, idempotent by ref
xero-bill:BILL-118layered
xero-inv:INV-214consumed 3 × FG
xero-cn:CN-009restocked
webhook retry #7skipped, already seen

Shelf → Xero

Nightly (or on demand), the day's movements collapse into one balanced manual journal: Dr COGS, Cr Inventory, split by category account. Pushed as a draft with an idempotency key, so you keep the approval step Xero was built for.

Locked periods? The push surfaces Xero's validation instead of forcing past it.

Daily journal, draft
Dr 500 · COGS$241.25
Cr 631 · Inventory$241.25
Xero ManualJournalDRAFT · awaiting you
Guarantees

What it will never do

Touch AR, AP or bankXero stays the ledger. One write path exists: draft manual journals.
Average your costsNo weighted-average shortcuts. If a number can't trace to a layer, it doesn't ship.
Rewrite historySold layers are frozen. Conflicts surface for a human.
Honest comparisons

Pick your poison

SpreadsheetsCin7 / DEARThis
Exact FIFO per saleby hand, badlyyesyes, layer-level
Voids restock original costyou, at 11pmyesautomatic + audited
Fits a workshop budget$0$300+/mo$0 self-hosted
Time to first numberweeks of formulasdays + onboarding callabout 60 seconds
Your server, your datayestheir cloudyes, tokens encrypted
Multi-org from one loginfile copiesyesyes, isolated & tested
"Averaging isn't costing. When oak goes from $8.50 to $9.20 and you sell half of each batch, those are two different margins, and I want both numbers, exactly."
Why the engine exists · read the whole story
Questions people actually ask

FAQ

Do I have to change how I invoice?

No. Keep invoicing in Xero exactly as-is. The app reads approved invoices and bills and does its own inventory maths locally.

I already have stock. Can it start from my history?

Yes, one-click bootstrap pulls your bill/invoice history and reconstructs layers and consumptions from scratch, then maps every Xero item to a product.

Does my data leave my machine?

The app talks to api.xero.com and nothing else. Tokens are stored AES-256-GCM encrypted in your own Postgres. There is no telemetry, no phone-home, no vendor cloud.

Why FIFO instead of weighted average?

Because margins differ batch to batch, and average hides that. When material prices move, average costing quietly reprices your history. FIFO keeps every sale tied to what its units truly cost.

What happens if the sync dies mid-month?

Xero retries webhooks for 24 hours, a poller backfills anything missed, and ingest is idempotent, replays can't duplicate. Failures land in a conflict log you can clear item by item.

Multi-currency?

Documents in foreign currencies convert at the rate Xero recorded on the document; everything posts in your org's base currency, same as Xero's own GL.

Self-host

Sixty seconds, honestly

# postgres running? then:
git clone <your-repo> && cd xero-inventory
npm ci
cp .env.example .env      # add XERO keys + ENCRYPTION_KEY
npm run seed              # demo workshop, so it's never empty
npm start                 # → http://localhost:3000/setup

Your next month-end could be ten minutes.

Create the account, connect Xero, let it pull your history once.
After that it just runs, and proves itself every time you look.