The .food format
.food is the fun, hand-written authoring format for Foodlang — a tiny
recipe you write like a little script.
Recipes humans can write. Machines can understand.
Two formats, one recipe
.food = fun human authoring format
.food.yaml = official ORF-compatible structured output
Write this:
recipe "Iced Vanilla Latte" {
ingredients {
espresso 2 shots
milk 10 oz
vanilla_syrup 20 ml
ice 1 serving
}
steps {
add vanilla_syrup
add espresso
stir 5 sec
add ice
pour milk
}
}
Compile to this:
recipe_name: Iced Vanilla Latte
ingredients:
- espresso:
amounts:
- amount: 2
unit: shots
steps:
- step: Add vanilla_syrup.
Foodlang KDL is easy to write. Foodlang YAML is easy to integrate.
Nodes
A document has exactly one top-level recipe or drink node, with an
ingredients block and a steps block.
recipe "Name" {
ingredients {
NAME AMOUNT UNIT // e.g. espresso 2 shots
}
steps {
ACTION ... // e.g. stir 5 sec
}
}
Inside ingredients |
NAME AMOUNT UNIT — e.g. milk 10 oz |
|---|---|
Inside steps |
ACTION ... — e.g. pour milk, stir 5 sec |
yield AMOUNT UNIT |
optional, top-level |
description "..." |
optional |
tag NAME |
optional, repeatable |
yield is optional — most drinks don't need it.
Phases (instead of steps)
For process-y recipes (espresso, brewing) you can use a phases block instead
of steps. A phase is ACTION SUBJECT [to OUTPUT] { ITEMS }, where items are
KDL child nodes: key value unit parameters (e.g. pressure 2 bar), bare
references (vanilla_syrup), or verb measure sub-actions (stir 5 s),
separated by newlines or ;. Like the rest of .food, phases are valid
strict KDL — units are separate tokens, not glued:
drink "Iced Vanilla Latte" {
ingredients {
coffee 18 g
vanilla_syrup 20 ml
milk 10 oz
ice 140 g
}
phases {
preinfuse coffee { pressure 2 bar; flow 2 mlps; time 6 s }
brew coffee to shot { pressure 9 bar; stop 40 g; max 28 s }
build cup { vanilla_syrup; shot; stir 5 s; ice; milk }
}
}
Phases map to an X-Phases block in .food.yaml and feed the
execution plan (so machine targets like decent and s88 see the pressures
and yields). A recipe uses either steps or phases.
Steps become sentences
The action verb leads; the rest is turned into readable text.
| Step | Sentence |
|---|---|
add vanilla_syrup |
Add vanilla_syrup. |
stir 5 sec |
Stir for 5 seconds. |
grill 3 min each_side |
Grill for 3 minutes each_side. |
whisk matcha water |
Whisk matcha water. |
A leading <number> <time-unit> (sec, min, hr, …) becomes
for <number> <unit(s)>.
Valid vs. invalid units
Because Foodlang uses standard KDL, amount and unit must be separate arguments — never glue them together.
✅ Use:
ingredient milk 10 oz
step stir 5 sec
ingredient espresso 2 shots
🚫 Do not use:
ingredient milk 10oz
step stir 5s
espresso 2shots
Keep it clean
The whole point is that .food reads like a tiny recipe script. Avoid
property soup:
// avoid
ingredient "milk" amount=10 unit="oz"
step "pour" item="milk" to="cup"
// prefer
ingredient milk 10 oz
step pour milk
Validation
foodlang validate recipe.food # friendly, forgiving
foodlang validate recipe.food --strict # unknown nodes become errors
Rules: exactly one recipe/drink node with a string name; at least one
ingredient and one step; ingredients need name + amount + unit; yields need
amount + unit; steps need at least one argument. Unknown nodes warn by default,
and error under --strict.
Compile
foodlang compile recipe.food --target orf # ORF-style YAML
foodlang compile recipe.food --target json # normalized AST
foodlang compile recipe.food --target cooklang
foodlang compile recipe.food --target schema-org
Try it live in the Playground — switch the example to a
.food file and compile.