What is Gram?
Gram is a data-driven recipe markup language built for developers.
Most recipe formats out there are just static blocks of text. Gram takes a different approach: it treats recipes as code. It takes your human-readable cooking instructions and compiles them into structured, predictable JSON data.
The Philosophy
Cooking is a bit like an algorithm: you take raw ingredients as input and output a finished dish. But to me, it's above all a passion. That's where Gram comes from: I love cooking, and I wanted a tool that combines the rigor of code with creativity in the kitchen.
I've always loved the idea of writing recipes in plain text, heavily inspired by pioneering projects like the excellent Cooklang. Cooklang is fantastic for quick, straightforward home recipes. But when things get a bit more complex—like professional pastry formulas with multiple sub-components, baker's math, or tricky scheduling—relying entirely on natural language can get messy.
Gram also draws structural inspiration from the classic Cooking for Engineers tabular format. Because Gram compiles recipes into a predictable JSON tree, the door is wide open for developers to build advanced visual renderers in the future, like Gantt schedules or tabular flowcharts.
I created Gram as an alternative for these more demanding scenarios. It introduces relational logic and data integrity into your recipe writing:
- Relational Variables: If you make a dough in step 1, you can reference it as an ingredient in step 5, cleanly preventing duplication.
- Physical Accuracy: Gram understands the difference between the "zest of 1 lemon" and "juice of 2 lemons", ensuring your shopping list accurately aggregates to "Buy 2 lemons" instead of 3.
- Strict Data Contracts: By using explicit tags, Gram ensures that temperatures, times, ingredients, and cookware are never confused by the parser.
The Ecosystem Approach
Gram isn't just a syntax; it's a complete ecosystem for building culinary applications. If you're building a recipe app, a meal planner, or a smart kitchen dashboard, you shouldn't have to parse raw text manually. The Gram Engine processes your text into a rich Abstract Syntax Tree (AST), enriches it with physical properties (like mass standardization and nutrition), and outputs a reliable JSON structure you can actually work with.
Designed for Developers
The Gram ecosystem gives you everything you need to treat recipes like software:
- Parser & Compiler (
@gram/kitchen): Simulates the execution timeline, aggregates base shopping lists, and scales dynamic quantities. - Semantic Analyzer (
@gram/analyzer): Handles mass standardization, yield calculation, nutritional estimation, and advanced shopping list aggregation based on your local database. - Language Server (LSP): Brings proper editor support to recipes (autocompletion, diagnostics, hover tooltips).
- CLI (
@gram/cli): Compile, scale, diff, and manage your ingredient database straight from your terminal. - Renderer (
@gram/renderer): Instantly generate Semantic HTML or Markdown from your compiled JSON.
How it looks
Gram remains highly readable, but quietly introduces some powerful data structures. Here is a look at a classic French pastry recipe written in Gram:
---
title: 'Cannelés'
portions: 10
---
## Batter ~{-1d} ->&batter
[Mix] In a #mixing bowl{}, combine the @flour{300 g}, @sugar{500 g}, and @salt{3 g}. ->&dry ingredients{}
[Heat] In a #large saucepan{}, bring the @milk{1 l}, @butter{100 g}, and @vanilla bean{1}(split and scraped) to °{85°C}. ->&hot milk{}
[Mix] Pour the &hot milk{} over the &dry ingredients{} all at once. Whisk vigorously.
[Incorporate] Add the @egg yolks{6}<@eggs{6} and the @rum{100 ml}.
[Rest] Cover with #plastic wrap{} touching the surface and chill in the refrigerator for at least ~&refrigerator{24 h}.{
"title": "Cannelés",
"meta": { "portions": "10" },
"metrics": {
"totalTime": 1465,
"totalMass": 2132,
"nutrition": {
"total": {
"calories": 4927,
"protein": 80.2,
"carbs": 781.6
// ...
}
}
},
"shopping_list": [
{
"id": "flour",
"qty": 300,
"unit": "g",
"normalizedMass": 300,
"conversionMethod": "physical"
},
// ...
{
"type": "composite",
"id": "eggs",
"qty": 6,
"usage": [ { "id": "egg-yolks", "qty": 6, "normalizedMass": 102 } ]
}
],
"sections": [
{
"title": "Batter",
"retro_planning": "-1d",
"intermediate_preparation": "batter",
"steps": [
{
"type": "step",
"action": "Mix",
"content": [
"In a ",
{ "id": "mixing-bowl", "_usageId": "36", "fixed": false },
", combine the ",
{ "id": "flour", "qty": 300, "unit": "g", "normalizedMass": 300 },
// ... (rest of the parsed AST)
],
"timings": { "start": 0, "end": 2, "activeDuration": 2 }
}
// ... (other steps)
]
}
// ... (other sections)
]
}Notice how the [Incorporate] step uses <@eggs{6}? This composite ingredient syntax tells Gram that you are only using the yolks, but the shopping list should correctly aggregate the whole eggs required. The ~{-1d} section tag creates a retro-planning schedule relative to the final baking time, and ->&hot milk{} sets up an intermediate variable you can pour later.
Ready to dive in? Check out the Getting Started guide.