# The Cooklang spec: a markup language where a recipe is a text file

> cooklang/spec is the specification repository for Cooklang, the markup language at the centre of an open source ecosystem for recipes. It defines how a plain text file becomes a machine readable recipe with ingredients, cookware, timers and metadata, and it documents where implementations still lag the language.

**cooklang/spec** — Home for language specification and discussions.

- Repository: https://github.com/cooklang/spec
- Website: https://cooklang.org/docs/spec/
- Stars: 698 · Forks: 19
- Language: Unknown
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/cooklang-spec

## A specification, not an implementation

cooklang/spec is the home for the Cooklang language specification and its discussions, and the repository layout makes that clear. There is no source tree, no build system and no package to install. What there is, a README, a conventions document, an EBNF.md file describing the grammar, a proposals directory with templates, a tests directory and an examples directory holding four recipe files such as Coffee Souffle.cook and Olivier Salad.cook. The point of the project is to fix the language so that different tools can read the same recipe file. That is a smaller and less visible job than writing an app, and it is the part that has to exist first. The homepage points at the spec hosted on cooklang.org, and the project description is simply a home for the language specification and discussions.

## The @ sigil, braces for names and the percent sign for units

The core of the language is small enough to learn in an afternoon. An ingredient is introduced with the @ symbol, and if the name contains several words the end of the name is marked with braces:

```cooklang
Then add @salt and @ground black pepper{} to taste.
```

Quantities go inside braces after the name, which is how the parser separates a number from a name:

```cooklang
Poke holes in @potato{2}.
```

A unit is attached with a percent sign between the quantity and the unit, so a weight or a volume is written in one token:

```cooklang
Place @bacon strips{1%kg} on a baking sheet and glaze with @syrup{1/2%tbsp}.
```

That single convention is what lets a tool build a shopping list, scale a recipe or check pantry stock without parsing prose. The README also points to the Cooklang Playground as the place to try the syntax, which is the fastest way to see how a line of text is parsed.

## Steps are paragraphs, comments use double dashes

Structure comes from blank lines. Each paragraph in a recipe file is a cooking step, and steps are separated by an empty line, so a file that reads as prose is also a list of steps:

```cooklang
A step,
the same step.

A different step.
```

To force a line break inside a step, a line ends with a backslash, which is replaced with a newline when the recipe is rendered, which is how you write a step that spans several visual lines without becoming several steps. Comments run to the end of the line after a double dash, and there are block comments too, written with an opening and closing marker:

```cooklang
Slowly add @milk{4%cup} [- TODO change units to litres -], keep mixing
```

That combination, paragraph-based steps plus two comment styles, is a deliberate choice for a format that people edit by hand in a text editor, and it is also the kind of detail a parser has to get right before it can be trusted with a real kitchen.

## Metadata, cookware and timers live in the same file

A recipe is more than steps and ingredients. Context such as preparation times, authorship and dietary relevance is added as YAML front matter, delimited by three dashes at the start of the file and at the end of the block:

```yaml
---
title: Spaghetti Carbonara
tags:
  - pasta
  - quick
  - comfort food
---
```

Cookware is declared with a hash sign, and as with ingredients a single word needs no braces. A timer is written with a tilde, and the duration uses the same percent-sign convention as ingredient quantities:

```cooklang
Lay the potatoes on a #baking sheet{} and place into the #oven{}. Bake for ~{25%minutes}.
```

Timers can be named, as in ~eggs{3%minutes}, and the README notes that applications can use that name in notifications, which is the sort of detail that only matters once you are building the interface.

## Sections, notes and shorthand preparation

Three extensions handle recipes that outgrow a single block of text. Sections mark components that are prepared separately, using equals signs around the name, where both the name and the number of equals signs are optional, so = Dough and == Filling == are both valid and a section body runs until the next section:

```cooklang
= Dough

Mix @flour{200%g} and @water{100%ml} together until smooth.

== Filling ==

Combine @cheese{100%g} and @spinach{50%g}, then season to taste.
```

Notes carry background or commentary that is not part of the cooking, and start a new line with a greater-than sign. Shorthand preparation attaches a preparation to the ingredient reference itself, for peeling or chopping work that would otherwise be repeated in the prose:

```cooklang
Mix @onion{1}(peeled and finely chopped) and @garlic{2%cloves}(peeled and minced) into paste.
```

A recipe can also reference another recipe file with the ingredient syntax, using a path relative to the root of the recipes directory and no file extension, as in @./sauces/Hollandaise{150%g}.

## Optional ingredients are per occurrence, not per ingredient

The optional component syntax is the most precisely specified part of the specification, and it is worth reading closely because the edge cases are spelled out. A question mark placed directly after the @ or # sigil marks the component optional, and the question mark is not part of the name:

```cooklang
Season with @salt and @?chilli flakes{1%pinch}.

Use a #?splatter guard{} if you have one.
```

The question mark has to follow the sigil directly, so @? thyme is plain text, and a question mark anywhere else in a sentence is ordinary text as well. The rule works for single word names, multi-word names with braces, names with quantities and recipe references. The important semantic point is that optionality belongs to each occurrence rather than to the ingredient, so the same ingredient can be required in one step and optional in another, and the required and optional amounts are listed separately instead of being added together:

```cooklang
Stir @parmesan{100%g} into the sauce.

Top with extra @?parmesan{50%g} before serving.
```

That recipe needs parmesan 100 g plus an optional 50 g, and optional components scale the same way required ones do. Applications are expected to mark optional items in listings, to leave optional ingredients off a shopping list unless the user asks for them, and not to treat a missing optional ingredient as a mismatch when matching a recipe against a pantry.

## Ecosystem conventions, proposals and the lag behind the spec

Beyond the core syntax, the ecosystem agrees on file types and behaviours in a separate conventions document, which covers .cook for recipes and .menu for meal plans, shopping list configuration, pantry inventory, recipe scaling, adding pictures and canonical metadata keys. The specification is also developed in the open, through a proposals directory with templates, and a tests directory that pins down expected behaviour. Two caveats belong in any honest evaluation. The first is stated at the very top of the README: not all the latest language features are supported in the apps yet, with progress tracked on a public board for the cooklang organisation. A spec can therefore describe behaviour that the current tools do not yet implement, and the two are not in lockstep. The second is the maintenance shape. There are no GitHub releases for this repository, it is a document rather than a shipped artefact, and the last push was on 2026-09-30. The specification is MIT licensed with the LICENSE file at the root, which is the usual arrangement for a text standard that implementations are expected to follow.

## Conclusion

cooklang/spec fits anyone building a recipe tool, a shopping list app or a kitchen display who needs the language agreed on rather than invented, and it is the file to read before writing a parser. It does not fit a developer who wants a ready-made recipe engine, since this repository contains a specification and discussion documents rather than an implementation. Before writing any code against it, read the README warning that not all latest language features are supported in the apps yet, check the progress board linked there for the feature you care about, and treat the EBNF.md file and the tests directory as the place where the syntax is pinned down precisely.

## FAQ

### What is Cooklang and what does the spec repository contain?

Cooklang is a markup language for recipes, where each text file is a recipe written as plain English instructions with markup for ingredients, cookware, time and metadata. The spec repository holds the specification itself, with a README, a conventions document, an EBNF.md grammar file, proposals and example recipe files.

### How do I write an ingredient with a quantity and a unit in Cooklang?

Use the @ sigil for the ingredient, put the quantity in braces after the name, and separate quantity and unit with a percent sign, as in @bacon strips{1%kg}. Multi-word names need braces to mark where the name ends.

### How are steps and comments separated in a .cook file?

Each paragraph is a cooking step and steps are separated by an empty line. Line comments start with a double dash and run to the end of the line, and block comments use an opening and closing marker such as [- TODO change units to litres -].

### Do all Cooklang apps support the latest language features?

No. The README opens with a note that not all latest language features are supported in the apps yet, and points to a progress board for tracking which features have landed. The specification can therefore describe behaviour that current tools have not implemented.

## Sources

- [cooklang/spec on GitHub](https://github.com/cooklang/spec)
- [Issues](https://github.com/cooklang/spec/issues)
- [License: MIT](https://github.com/cooklang/spec/blob/main/LICENSE)
- [Project website](https://cooklang.org/docs/spec/)
- [README](https://github.com/cooklang/spec/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cooklang-spec
