Everyone has an opinion about grocery prices. Mine was vibes. Eggs felt expensive. Milk felt expensive. Everything felt expensive.

I didn’t need to guess, though. I have kept the household books for well over a decade, and every receipt gets scanned and filed next to the ledger. From 2019 to today that is 1,754 grocery receipts from twelve stores. Costco, Superstore, Walmart, Co-op, T&T, Sobeys, Safeway, Save-On-Foods, Bulk Barn and a few others.

The ledger only records totals: $86.24 at Costco, $8.97 at Superstore. What we bought is on the receipts. So I read them.

The receipts were pictures

Every receipt is a scan from a document scanner. A PDF with an image inside and no text layer. A computer sees a photograph of a till slip.

So I wrote larder. It’s a Go CLI that reads a directory of receipt scans and works out three things:

  • what we buy on a regular basis,
  • how often we buy it, and
  • how the price has moved over time.

Text recognition uses Apple’s Vision framework through cgo. No tesseract, no cloud OCR, no API key. Receipts never leave the machine, which matters when the documents are your family’s spending.

Every grocery receipt prints a code next to each item: a Costco item number, a Superstore UPC, a produce PLU. That code is the key that makes price tracking possible. 39036 was romaine in 2019 and it is romaine today.

39036 ROMAINE                8.99
339054 PF GOLDFISH          13.99
2014432 TPD/339054           3.00-

That third line is a Costco instant saving. Hold that thought.

Reading a receipt is harder than it looks

Text recognition on a clean document is a solved problem. Receipts are not clean documents.

  • Thermal paper fades. A receipt from 2019 reads 458 MILK 49 where it should say 4.49.
  • Scans are crooked. At three degrees of tilt, the price on the right drifts onto the line below its item.
  • Digits look like letters. 2.0C-, B.49, $1 4T.
  • Long receipts are long. Vision downsamples a tall image, and a big Costco shop turns to mush.

Each store also prints differently. Superstore puts the price on the next line when there is a multi-buy. Walmart says 2 AT $0.05. T&T prefixes sale items with (SALE) and never prints the regular price.

The trick that made this work is that every receipt checks itself. The items have to add up to the printed subtotal. When they don’t, larder tries again:

  1. Re-read the receipt at 300 DPI, then 150 DPI. Faint print often reads correctly at one resolution and not another.
  2. Straighten the page. Vision reports the slope of each line of text, so rows can be regrouped along the tilt.
  3. If exactly one price is unreadable, infer it from the subtotal.
  4. If one item reads .39 beside an identical item at 8.39, try the sibling’s price and see if the receipt balances.

Here is how that went across every store:

StoreReceiptsReconciledItems read
Costco637475 (75%)4,663
Real Canadian Superstore434364 (84%)2,467
Walmart282213 (76%)1,538
Calgary Co-op217153 (71%)440
Sobeys5343 (81%)131
T&T Supermarket4436 (82%)200
Save-On-Foods3828 (74%)100
Bulk Barn2214 (64%)88
Wholesale Club1613 (81%)64
Safeway87 (88%)24
FreshCo22 (100%)7
IGA11 (100%)2
Total1,7541,349 (77%)9,724

Seventy-seven percent of receipts add up to the cent. The rest still count. They are just missing a line or carrying a misread price somewhere, and larder throws out any price that is more than double or less than half of a product’s usual price.

The first pass took about six minutes. After that everything is cached and the whole analysis runs in a second.

What we actually buy

I defined a regular as anything bought on at least four separate days spanning at least two months. That gives 349 products. 191 of them we still buy; the other 158 have lapsed.

These are the twenty we buy most often:

ProductDays boughtEveryOn salePrice (2026)Shelf changePaid change
Milk, homogenised 4 L2279 days1 of 244$6.31+30%+30%
Bananas14414 days0 of 154$1.99+5%+5%
Milk, skim 4 L10815 days0 of 113$5.46+23%+23%
Eggs, 5 dozen9525 days1 of 97$20.26+46%+46%
Chips, No Name7625 days21 of 106$1.45+34%+34%
Cucumbers6018 days6 of 60$3.58−4%−12%
Yogurt, Foremost tub5816 days0 of 58$17.39+10%¹+10%¹
Decaf coffee, Nescafé Rich4851 days24 of 70$7.87+103%+62%
Tortillas, 36 ct4035 days21 of 56$9.99+9%+9%
Romaine hearts3819 days0 of 39$8.51+58%+58%
Whipping cream 1 L3646 days0 of 36$5.79+54%+54%
Pepperoni pizza, 18" (Costco food court)3332 days0 of 33$12.990%²0%²
Milk, 1% 4 L3222 days0 of 42$5.96+18%+18%
Cheddar slices3167 days29 of 41$9.99+9%+16%
Roma tomatoes3118 days1 of 31$4.95+7%+7%
Baguette3072 days10 of 31$5.99+33%+44%
Goldfish crackers2886 days44 of 46$13.99+40%+34%
Potatoes, yellow2542 days3 of 26$7.99+16%+6%
English muffins2462 days5 of 28$2.06+4%³+18%³
Mayonnaise2296 days27 of 29$10.99+52%+62%

¹ Since 2024. ² Since 2023. ³ Since 2022. Everything else is since 2019. “Every” is the median gap between purchases. “On sale” counts individual purchases, so it can exceed the days bought.

The thing that surprised me is how slow “regular” is. Of the 191 products we still buy:

How oftenProducts
Every two weeks or more often3
Every two weeks to a month13
Every one to three months27
Less often than every three months148

Homogenised milk, bananas and bean sprouts are the only things we buy at least every fortnight. Everything else is a long tail of products we come back to every few months. That long tail is where most of a grocery bill hides.

What it costs now

Individual products are noisy. To get one number, larder builds a price index for our own basket. Each year, every regular product’s price is compared with the year before, weighted by what we spend on it, and the changes are chained together. 2019 is 100.

YearShelf pricesOn the yearWhat we paidOn the yearProducts compared
2019100.0—100.0——
2020102.3+2.3%103.4+3.4%85
2021105.4+3.0%103.7+0.3%100
2022114.9+9.0%113.5+9.4%118
2023123.6+7.6%122.4+7.8%131
2024125.9+1.9%125.6+2.6%151
2025128.8+2.3%129.0+2.7%147
2026130.1+1.0%130.7+1.3%125

The things we buy regularly cost 30% more than they did in 2019. Most of that arrived in two years. 2022 and 2023 added 17% between them. 2026 is only through the start of October.

The biggest increases among things we still buy:

ProductFromThenNowShelf changePaid change
Decaf coffee, Nescafé Rich2019$3.87$7.87+103%+62%
Romaine hearts2019$5.39$8.51+58%+58%
Small buns (T&T)2019$1.49$2.30+54%+54%
Whipping cream 1 L2019$3.66$5.64+54%+54%
Mayonnaise2019$7.24$10.99+52%+62%
Flour 10 kg2020$6.66$9.79+47%+47%
Eggs, 5 dozen2019$13.90$20.26+46%+46%
Brioche buns2022$4.99$6.99+40%+67%
Goldfish crackers2019$9.99$13.99+40%+34%
Corn, organic2020$9.99$13.99+40%+40%

And a few that went the other way:

ProductFromThenNowShelf change
Pork blade2024$33.02$24.79−25%
Superfries seasoned, McCain2023$5.39$4.10−24%
Pizza sauce, No Name2021$1.09$1.00−8%
Dishwasher detergent, No Name2024$5.99$5.57−7%
Honey, creamed2022$19.74$18.49−6%

Bananas deserve a mention. $1.89 at Costco in 2019, $1.99 since 2021, and not a cent more since. Five percent in seven years.

Sales change the answer

Two numbers sit side by side in every table above, and they are not the same thing.

  • Shelf price is what the store charges at regular price. The Goldfish line in that Costco receipt has a shelf price of $13.99.
  • Paid price is what we actually handed over after sales and instant savings. The same Goldfish cost us $10.99.

If you only track what you paid, a good sale year looks like deflation. If you only track the shelf price, you miss that you can dodge some of the increase by shopping the flyer. So larder tracks both, over the same purchases, with the same averaging. When there’s no sale, the two numbers are identical. Any gap between them is entirely sales.

Every store says “on sale” differently. Costco prints TPD instant savings. Superstore prints the regular price beside the price you paid. Walmart tags multi-buy items with a group number and prints the discount later. Sobeys and Co-op print YOU SAVED. Each needed its own parser.

Across the whole basket, sales barely moved the needle. Shelf prices are up 30.1%; what we paid is up 30.7%. We bought on sale at roughly the same rate every year, so sales lowered the bill without changing the slope.

Product by product, it is a different story:

ProductShelf changePaid changeOn sale
Decaf coffee, Nescafé Rich+103%+62%24 of 70
Mac & cheese, white cheddar+19%−7%6 of 17
Brioche buns+40%+67%11 of 13
Onions, yellow0%+38%3 of 11
Superfries seasoned, McCain−24%0%8 of 12
Broccoli (large pack)+40%+57%15 of 20

The decaf price doubled on the shelf. We caught enough sales to hold our increase to 62%.

Brioche buns went the other way, and the reason is subtle. Costco’s instant saving on them stayed at a flat $2 while the shelf price went from $4.99 to $6.99. The sticker rose 40%. What we paid went from $2.99 to $4.99, which is 67%. A fixed-dollar discount shrinks as prices rise.

Some products we only ever buy on sale:

ProductOn saleTypical discountSaved
Cheerios29 of 2929%$69.00
Multigrain Cheerios23 of 2321%$47.00
KFI sauce19 of 1921%$38.00
Froot Loops18 of 1822%$39.00
Facial tissue, Scotties15 of 1522%$83.00
Mini-Wheats14 of 1423%$32.50
Welch’s fruit snacks, 60 ct14 of 1421%$44.00
Dare crackers13 of 1322%$28.00

Every one of those is a Costco product, and every single purchase carried an instant saving. Either those products are permanently discounted or we only buy them when they are. The receipts can’t say which.

The staples are the opposite. Bananas: 0 of 154 on sale. Skim milk: 0 of 113. Romaine: 0 of 39. Milk is almost never on sale, and we buy it anyway.

In total, 2,662 of 9,030 items were bought on sale, just under 30%. The receipts record $7,620 in savings since 2019, a little over $1,000 a year.

What I left out

The interactive version has all 349 regular products. It shows purchase timelines, every price paid, and sales marked on each one. I was careful about what went into it:

  • No health, personal-care or alcohol purchases. What a family buys for the medicine cabinet is nobody’s business. larder has a --public mode that drops those categories and anything whose name looks like it belongs in them.
  • No exact dates. Every purchase date is rounded to the start of its week. The cadence still shows; the household routine doesn’t.
  • No store item codes, card numbers, member numbers or file paths. None of them were ever in the dashboard data, and the public mode strips the codes too.
  • No Flashfood. Those “receipts” are app screenshots of clearance food, and clearance prices would distort every trend they touched.

Some honest limitations:

  • Superstore doesn’t always print the regular price beside a sale price. When it doesn’t, the “shelf” price is really a sale price, and that product’s shelf increase is understated.
  • Stores without item codes (Co-op, Sobeys, Safeway, T&T, Save-On-Foods, Bulk Barn) are matched on description. One misread letter can split a product in two.
  • Pack sizes change. When a store keeps the same description but sells a bigger box, larder keeps them apart if the prices are far enough apart. Shrinkflation that keeps the price and the code is invisible.

What I learned

  • The receipt checks itself. Reconciling against the printed subtotal turned OCR from “probably right” into “provably right” for 77% of receipts, and told me exactly which ones to distrust.
  • Codes beat names. A UPC is stable for years. A description is whatever the till operator’s software felt like printing.
  • Shelf price and paid price answer different questions. The first is about the stores. The second is about us.
  • “Regular” is slower than you think. Three products a fortnight, and a long tail of things we buy every few months.
  • Thirty percent since 2019. Now it is a number instead of a feeling.

larder is written in Go and runs on macOS. The code is on GitHub. It was built around one household’s receipts, and it shows.