Open Food Log

Development draft, v0. The format may change; every change is in the changelog.

Open Food Log v0

Status: draft, 2026-11-09. Anything may change until v1. The key words MUST, SHOULD and MAY are used as in RFC 2119. Its home is openfoodlog.org.

The Open Food Log (OFL) describes what a person ate so any app or assistant can record it, read it and carry it elsewhere, with every number's standing intact. It is the sister of the Open Workout Schema and the Open Exercise Schema: one person's training and eating, in formats that share their rules.

1. Why this exists

Food logs don't travel. Every tracker keeps its own database and its own export, and none is documented. Standards that exist sit elsewhere: FHIR NutritionIntake serves clinical records, Health Connect and HealthKit store numbers without saying where they came from, and food databases (USDA FoodData Central, Open Food Facts) describe foods, not what someone ate. OFL fixes three things:

  1. A number always has its unit and its standing. Every nutrient key names its unit. A value that was guessed (a size the person didn't say, a photo, a model's estimate) says so. A nutrient that isn't known is left out, never written as zero.
  2. The person's words survive. Each item keeps what the person said beside the food it was matched to, and how sure that match is.
  3. Amounts become grams the same way everywhere. Portions carry their weight, volumes need a density, and the conversions are fixed and tested.

2. Documents

Document Schema What it is
Food food.json One food: per-100 g nutrients, portions, identifiers, provenance
Meal meal.json One meal or snack: items eaten, with amounts and nutrients
Log log.json A whole log as one file: meals, the person's own foods, deleted meals
Nutrients nutrients.json The nutrient keys and their mappings to other systems
Amount vectors amount-vectors.json Worked conversions every implementation must agree on

Documents are UTF-8 JSON, valid against JSON Schema draft 2020-12. Consumers MUST ignore members starting x- that they don't understand; producers MAY add x-<name> members to a food, a meal or an item, and x-<name> nutrient keys. Examples: a food, a meal and a log.

3. Identity

id = namespace ":" slug

A food is a distinct record when its nutrients differ: raw and cooked, dry and prepared, whole and skimmed are different foods.

4. Nutrients

5. Foods

6. Amounts and grams

An item's amount is the measure the person used: a value and a unit of g, kg, oz, lb, ml, l, fl_oz, or portion with the portion's id. grams is that amount as a weight:

Unit Grams
g, kg, oz, lb value × 1, 1000, 28.349523125, 453.59237
ml, l, fl_oz (US) value × 1, 1000, 29.5735295625 × density_g_per_ml
portion value × that portion's grams

Then each nutrient as eaten is nutrients_per_100g[key] × grams / 100.

7. Meals

8. The log

log.json is a whole log as one file, what an export holds and an import reads: exported_at, meals (newest first), foods (at least the usr: foods the meals name) and deleted_meals. A server MAY add more of the person's record (a profile, the OWS workouts) beside these keys; readers ignore keys they don't know.

9. Assistants

An MCP tool profile, like Open Workout's, comes with the first implementation: a neutral core of log_meal, get_meals, update_meal, delete_meal, search_foods and undo. The rules it will hold an assistant to are already part of this specification:

10. Other systems

OFL FHIR R5 NutritionIntake Health Connect NutritionRecord HealthKit
meal one NutritionIntake one record per item one sample per nutrient per item
item one consumedItem one record —
eaten_at occurrenceDateTime startTime / endTime sample start and end
meal_type — mealType (breakfast 1, lunch 2, dinner 3, snack 4) —
item text / food.name consumedItem.nutritionProduct.concept.text name —
grams consumedItem.amount in g — —
nutrients ingredientLabel (nutrient, amount), summed over the meal's items the properties in nutrients.json the identifiers in nutrients.json
notes note — —
estimates, match, source — — —

The last row is the point of OFL: none of the platforms can say which numbers were guessed, which matches are unconfirmed, or which app wrote them. Exporting to them loses that; OFL keeps it. FHIR R5's ingredientLabel belongs to the whole intake, so per-item nutrients survive only as one NutritionIntake per item. R6 restructures the resource (consumedItem and ingredientLabel become nutritionItem); the mapping will follow when it's published. Checked against R5 (5.0.0), the Health Connect and HealthKit references on 2026-11-09.

11. Not in v0