Chow, a plain text and FOSS food diary

Hi folks,

Does tracking what you eat count as "accounting"? I'll let you decide...

Today I'm showing off Chow, a plain-text FOSS food diary. You can self-host it with the Dockerfile or just use the one I host on chow.fit.

Why does the world need another food diary app? Well, I can't speak for the whole world, but as for me:

I used MyFitnessPal back in 2017-2019. It was mostly tolerable with an ad blocker. A month ago due to numbers I didn't like, I wanted to start tracking calories again. I quickly noticed that in the MFP android app, the barcode scanner is now a paid feature. It felt like paying to use my own phone camera. Their web app is slow and stuck in 2013, so I made my own web app which better aligns with my values and goals. I've lost 5% of my body weight!

I like to call an app like this "un-SaaS" because it subverts a lot of the usual SaaS playbook.

  • Distraction-free. No AI, no ads, no tracking, no engagement-hancing e.g. no activity/news feed. It logs food and gets out of the way.

  • All features except sync work without a subscription.

  • An optional subscription enables sync. A year covers my hosting costs and buys me a T-shirt.

  • Sync is local-first. You can still log food e.g. in an airplane. The app syncs up when you go back online.

  • If you stop the subscription, the app keeps working without sync.

  • Data is plain text files on your device

  • It uses public food data (FDC and OFF), not proprietary

  • It can import from MyFitnessPal using a bookmarklet to get your auth token which stays local.

  • It has a desktop app, Android/iOS app, and a CLI. None currently shipped, but you can build from source.

Folks who track what they eat: Let me know what else you'd like to see!

HN post

This is interesting. I went to the link and couldn't really find much information before I was pushed into a demo. What does the actual plain text format look like?

Sorry about the omission. The file format would have been a good thing to highlight when posting to the PTA forum!

In the browser, you can see the files in Settings > Files

In the CLI:

โžœ  tree
.
โ”œโ”€โ”€ burns
โ”‚   โ””โ”€โ”€ 2026.chow
โ”œโ”€โ”€ entries
โ”‚   โ””โ”€โ”€ 2026.chow
โ”œโ”€โ”€ foods.chow
โ”œโ”€โ”€ main.chow
โ”œโ”€โ”€ metrics
โ”‚   โ”œโ”€โ”€ 2016.chow
โ”‚   โ”œโ”€โ”€ 2017.chow
โ”‚   โ”œโ”€โ”€ 2018.chow
โ”‚   โ”œโ”€โ”€ 2019.chow
โ”‚   โ”œโ”€โ”€ 2020.chow
โ”‚   โ”œโ”€โ”€ 2021.chow
โ”‚   โ”œโ”€โ”€ 2022.chow
โ”‚   โ”œโ”€โ”€ 2023.chow
โ”‚   โ”œโ”€โ”€ 2024.chow
โ”‚   โ”œโ”€โ”€ 2025.chow
โ”‚   โ””โ”€โ”€ 2026.chow
โ”œโ”€โ”€ recipes
โ”‚   โ””โ”€โ”€ 2026.chow
โ””โ”€โ”€ targets
    โ””โ”€โ”€ 2026.chow

There are food definitions, food entries (actual consumption of food), recipes (groups of foods so you don't log the same 7 salad ingredients over and over), targets (e.g. nutritional goals e.g. 1800 kcal, macronutrients), metrics (e.g. weight measurements), and burns (e.g. exercise)

Excerpt from today from entries/2026.chow:

2026-10-01 08:14  mfp/62817990135085  1 x 1 teaspoon  ; id:715VDA6BSF
2026-10-01 11:51  homechef/EOdxZJOG  1 x 1 serving  ; id:BGAAR1JRGB
2026-10-01 11:52  mfp/28183369641261-132362180312125  1 x 1 cup skim milk  ; id:KDKCJ8DKB0
2026-10-01 16:00  off/04971502  355 x 1 g  ; id:6Z12Q6ENBH
2026-10-01 19:11  homechef/GqyeKDqB  1 x 1 serving  ; id:V6C78E38TX
2026-10-01 19:12  fdc/169225  160 x 1 g  ; id:9MAR1R6PYA

Excerpt from foods.chow:

food off/0850397004071 "That's it. 1 Apple + 1 Mango Fruit Bar"  ; brand:"That's it"
    35 g = 100 kcal 27 carb 23 sugar 3 fiber 25 sodium 165 potassium
        11 calcium 1 iron 15 vit_c
    100 g = 285.71 kcal 77.14 carb 65.71 sugar 8.57 fiber 71.43 sodium
        471.43 potassium 31.43 calcium 2.86 iron 42.86 vit_c
food mfp/101455336819965 "Greek Plain Nonfat Yogurt"  ; basis:rdi1993 brand:Kroger verified:yes
    0.67 cup = 100 kcal 7 carb 17 protein 7 sugar 7 netcarb 60 sodium
        10 cholesterol 220 potassium 19% calcium
food homechef/GqyeKDqB "Deconstructed Pork Bรกnh Mรฌ Bowl"  ; url:https://www.homechef.com/meals/deconstructed-pork-banh-mi-bowl-standard
    1 serving = 570 kcal 54 carb 17 fat 44 protein 19 sugar 6 fiber 5 satfat
        11 addedsugar 0 transfat 1420 sodium 110 cholesterol 590 potassium
        100 calcium 2.1 iron 0 vit_d
    614 g = 570 kcal 54 carb 17 fat 44 protein 19 sugar 6 fiber 5 satfat
        11 addedsugar 0 transfat 1420 sodium 110 cholesterol 590 potassium
        100 calcium 2.1 iron 0 vit_d
food fdc/169225 "Cucumber, peeled, raw"
    100 g = 10 kcal 2.16 carb 0.16 fat 0.59 protein

Full grammar:

diary      = { line } ;
line         = blank | comment | include | food_block | entry | target
             | burn | reading | container | recipe_block ;

container    = "container" ws container_id ws name ws number ws weight_unit
               [ trail ] newline ;
recipe_block = date ws "recipe" ws localid ws name [ trail ] newline
               { recipe_line } ;               (* at least one ingredient *)
recipe_line  = ws ( "container" ws container_id
                  | "weight" ws number ws weight_unit
                  | "servings" ws number
                  | food_id ws number ws "x" ws serving ) [ trail ] newline ;
container_id = idchar { idchar } ;
weight_unit  = unit ;                          (* one that folds to g, kg, mg, oz or lb *)

comment      = ws? ";" text newline ;
include      = "include" ws path [ trail ] newline ;

food_block   = food_header serving_line { serving_line } ;   (* at least one serving *)
food_header  = "food" ws food_id ws name [ ws ";" ws tags ] newline ;
serving_line = ws serving ws "=" [ ws nutrients ] [ trail ] newline
               { continuation } ;
continuation = ws nutrients [ trail ] newline ;   (* no "=" *)

entry        = date ws time ws food_id ws number ws "x" ws serving
               [ trail ] newline ;

target       = date ws "target" ws nutrients [ trail ] newline
               { continuation } ;

burn         = date ws "burn" ws time ws number ws "kcal" ws name
               [ trail ] newline ;
reading      = date ws "metric" ws time ws metric_id ws number ws metric_unit
               [ trail ] newline ;
metric_id    = idchar { idchar } ;
metric_unit  = char { char } ;                  (* no whitespace *)

food_id      = [ backend "/" ] localid ;        (* bare id means local *)
backend      = idchar { idchar } ;              (* fdc, off, mfp, homechef, local, or any other *)
localid      = idchar { idchar } ;
idchar       = letter | digit | "_" | "-" ;

name         = '"' { char - '"' } '"' ;
tags         = tag { ws tag } ;
tag          = key ":" ( bareword | '"' { char - '"' } '"' ) ;

serving      = number ws unit ;                 (* "170 g", "1 medium onion" *)
unit         = word { ws word } ;

nutrients    = nutrient { ws nutrient } ;
nutrient     = ( number | number "%" ) ws nutrient_id ;   (* "150 kcal", "15% iron" *)
nutrient_id  = idchar { idchar } ;

date         = digit digit digit digit "-" digit digit "-" digit digit ;
time         = digit digit ":" digit digit ;    (* 24-hour, HH:MM, local *)
number       = digit { digit } [ "." digit { digit } ] ;   (* never negative *)
trail        = ws? ";" text ;

I'll prioritize publishing documentation and the CLI.

Thanks for sharing. I am far from an expert in all things plain-text, but doesn't look like the format is intended to support direct editing by the user with a text-editor. Even from a read point of view the references IDs to the food item wouldn't that helpful to a someone looking over their log. I am guessing that human read-write is not a design goal for you, but it is the kind of thing I would look for in a plain-text logging application.

I appreciate your feedback.

It's true, I haven't made a single food entry by hand. I don't plan to, and that shows in this design. In contrast to a financial transaction, food data is much higher-dimensional (nutrients, servings, etc). Entering manually is tedious, so I prioritized integration with food data sources. That requires using the source's ID.

But I'm open to what the community wants. For manual entry, I could see about making the ID optional. Can you tell me what you'd want to see in the file format? Would you combine food definition and food consumption?

I really haven't thought much about what I would want in a food-logging format. I have tried food logging in the past and it was always too much work for me. Creating a human friendly read/write format could really be a non-goal for this kind of application. If it is a goal, then I could imagine using wiki-style links for references and timestamps (which a decent editor can generate) for IDs.

Thanks again for the feedback.

I've made the file format easier to type by hand, added docs for it, and published the CLI.

food greek-yogurt "Greek yogurt, nonfat"
    170 g = 150 kcal 11 carb 0 fat 26 protein

food banana "Banana"
    1 medium = 105 kcal 27 carb 0.4 fat 1.3 protein

2026-10-01
    07:45  greek-yogurt  170 g
    07:45  banana  1 medium  ; with breakfast

The simpler format expects "handles" rather than generated IDs. You'll still see IDs when using food data sources.

That looks nice. And a bit like https://hledger.org/dev/hledger.html#commodity-aliases. (For people wanting to try something similar in their PTA app.)