GitHub

Every unit that moved, and what moved it.

Stock changes are recorded as movements with the quantity before, the change and the quantity after — and the three have to reconcile.

PostgreSQLRedisS3StripeResend
admin.yourstore.com/inventory
LIN-OAT-M18 in stock
Sale · order #4192
20 → 18
Return accepted · #412
18 → 20
Transfer to Camden
26 → 20
Stock take
24 → 26

A stock number tells you nothing.

It says what the system believes right now. It cannot tell you what it believed yesterday, which sale took the last unit, or why the shelf and the screen disagree.

Stock is a history you can read back.

Every change is a row carrying the quantity before, the delta and the quantity after. A correction is a new row, never an edit — so the ledger reconstructs any point in time.

Movements this week

Live
297rows

Sales, returns, adjustments, transfers

29Sat
MonTueWedThuFriSat

The shape of the codebase.

What keeps a store you run yourself honest: the tests, the schema, and the events.

Stock across locations

An append-only ledger per location, so orders are fulfilled from wherever holds the item.

Conversations

Every thread opens with the order already attached, so nobody pastes links between tabs.

0

tests, run on every push

0

database models, one schema

0+

domain events, all bindable

Before plus change equals after.

The database rejects a movement where the arithmetic does not hold, and rejects a zero-delta movement outright. There is no path to an unbalanced ledger.

previous + delta = newdelta ≠ 0quantity >= 0

Warehouses and shops hold stock separately.

Fulfilment picks from the location that holds the item, and a transfer between them is two movements instead of an edit to a total.

By location3 active
Main warehouse
1,204
Retail — Camden
318
Retail — Bristol
96

The badge is never out of date.

Low stock and sold out are read from the same movements the ledger records, so what a customer sees and what the shelf holds are the same fact.

New season
Sunset Linen Shirt4.8

4 colours · in stock

$128
Everyday Merino Crew4.6

6 colourways · machine washable

$96

The oversell that cannot happen.

The check and the decrement are one transaction, so there is no window between them for a second buyer to slip through.

Two buyers, one last unit

  1. 1Both carts reach checkoutOrderIntent ×2
  2. 2The first write takes the row lockSELECT … FOR UPDATE
  3. 3Its movement commits18 → 17
  4. 4The second is refused before paymentout of stock
Stock movement
Append-only

Sale · #4192

20+−2=18
committed

Bad write

20+−2=17
rejected
Rule
b+d=a
Movements
297
Drift
0

What if the shelf and the database disagree?

Then you hear about it. Inventory is compared against the movements behind it and a gap raises a drift row rather than being quietly rounded away.

0%
Inventory vs ledger
0 open
Order money
0 open
Customer spend
0 open

Build any page from blocks.

Storefront blocks

10 types
banner
products
collections
bannersliderproductsfeedcategoriescollectionsbadgesnavcontentfilter

Categories and collections resolve live. Publish one and it appears on the homepage.

Commerce core

70+ tables
Order

what they bought and paid

StockMovement

every stock change, logged

RefundRequest

approved or rejected, once

Coupon

codes, rules, gift cards

One media table, one translation table, one activity log. Adding an entity means adding an owner type, not another table.

Your infrastructure

Self-hosted
Database

PostgreSQL

Yours
Cache

Redis

Yours
Media

S3-compatible

Yours
Payments

Stripe account

Yours
VercelDockerBare metal
Stripe
PostgreSQL
Prisma
Redis
S3 storage
Resend
Shopify import
Klaviyo
PostHog
Meta & GA4
3Models

Inventory, StockLocation, StockMovement

≠0Delta

A zero-change movement is rejected

≥0Quantity

Stock can never go negative

1Alarm

InventoryLedgerDrift

Every one of these is a table in your own database. Query it or export it whenever you want.

1.2k
318
96

Fulfil from the right location

Warehouses and shops hold stock separately, so you can fulfil from either.

Corrections are new rows

Payment rows are never edited, so an order keeps its full payment history.

Options and variants

Define options once and the variants are generated, so size and colour stay in sync.

Know what you have, and how it got there.

The ledger is there from the first order. There is nothing to switch on.

Append-onlyMIT licensedDirect SQL access