How to organize and compose modular recipes
As your collection of .gram recipes grows, decomposing complex dishes into reusable components — pastries, sauces, stocks, infusions — saves time and ensures consistency across your culinary repertoire. Extracting these core components into their own dedicated files eliminates repetitive copy-pasting: whenever you refine your dough (a dialed-in resting time, a tweaked flour ratio), every recipe in your collection immediately benefits from the improvement.
This guide walks you through practical patterns for structuring, organizing, and executing modular Gram projects with @use.
Step-by-step: extracting and importing your first module
Section titled “Step-by-step: extracting and importing your first module”If you followed Your First Recipe, the Lemon Meringue Tart was written from start to finish in a single .gram file. Here is how to extract the sweet pastry dough into a reusable component and import it with @use.
-
Extract the base into its own file
Start from the dough section in your recipe:
Move this preparation into its own file at
bases/sweet-pastry-dough.gram:- Binding name is chosen on import: For a single-purpose module, the
->&pastry dough{}declaration on the section header is optional. The importing file chooses its local variable name withas &dough(@use "..." as &dough). For complex modules that produce multiple distinct components, declaring->&on each section remains the standard way to export each part by name (see What a module exports). - Automatic yield calculation: When an importing recipe asks for a specific amount (such as
&dough{250g}), Gram sums this file’s own ingredient masses to work out its yield, then scales every quantity automatically.
- Binding name is chosen on import: For a single-purpose module, the
-
Import the base into your host recipe
Back in your tart recipe, replace the dough section with an
@usedirective placed right after the frontmatter, before the first step:Downstream tools — the Gantt timeline, shopping list, and nutritional analysis — work seamlessly. The dough’s
~_{1h}passive rest interleaves with making the lemon curd because@useinlines the steps into the global schedule. -
Reuse the base across multiple recipes
A second recipe (for example, a batch of jam tartlets) can import the exact same base file:
Whenever you refine
bases/sweet-pastry-dough.gram(adjusting resting time or tuning flour ratios), both recipes instantly benefit from the update during compilation.
Organizing folders and configuring path aliases
Section titled “Organizing folders and configuring path aliases”You are free to organize your project files however you like. A standard modular structure separates reusable sub-recipes into dedicated directories:
Directory.gram/
- config.yaml
- ingredients.yaml
Directorycomponents/
Directorybases/
- shortcrust.gram
- sourdough-starter.gram
Directorysauces/
- bechamel.gram
- veloute.gram
Directorydesserts/
- lemon-tart.gram
- potato-gratin.gram
Gram provides three intuitive ways to reference component files across your project:
Use @/ to reference files relative to the project root (where .gram/ lives), regardless of how deeply nested the importing recipe is:
Declare custom path shortcuts in .gram/config.yaml:
Then import cleanly using @alias/:
Reference files relative to the current recipe’s directory:
Managing multi-component preparations (destructuring)
Section titled “Managing multi-component preparations (destructuring)”Some sub-recipes produce multiple distinct components intended to be used together.
For example, a classic tart base module can provide both a sweet pastry dough and an almond frangipane cream:
In your main recipe, destructured imports let you bind both components cleanly in a single line:
Chaining imports and diamond dependencies
Section titled “Chaining imports and diamond dependencies”Chained imports (Transitivity)
Section titled “Chained imports (Transitivity)”Recipes can import sub-recipes that themselves import other bases:
graph LR A["dinner.gram"] --> B["veloute.gram"] B --> C["chicken-stock.gram"]
- Encapsulation:
dinner.gramonly interacts with&veloute. Internal variables withinchicken-stock.gramremain completely private. - Unified shopping list & timeline: Raw ingredients (chicken, celery, carrots) and simmering steps from
chicken-stock.grambubble up into the main dinner shopping list and Gantt schedule.
Diamond dependencies
Section titled “Diamond dependencies”When a menu imports multiple dishes that share a common foundation (e.g. an entrée and a dessert that both import shortcrust.gram):
graph TD Menu["tasting-menu.gram"] --> Entree["quiche.gram"] Menu --> Dessert["lemon-tart.gram"] Entree --> Base["shortcrust.gram"] Dessert --> Base
- The shared base file is parsed only once.
- The compiler creates independent instances scaled to each dish’s exact requirements.
- Base ingredients (flour, butter) are consolidated automatically on your shopping list.
Circular imports
Section titled “Circular imports”If Recipe A imports B which imports A, Gram detects the loop and reports a MODULE_CYCLE error diagnostic showing the full dependency chain (A -> B -> A). Only that import branch is cut off — the rest of the recipe still compiles — but gram check (and any --strict run) fails on it, since MODULE_CYCLE is error-severity.
Scheduling multi-day preparations (~{-2d})
Section titled “Scheduling multi-day preparations (~{-2d})”When a preparation must begin days before cooking (such as a sourdough starter, an aged marinade, or a slow infusion), anchor it directly on the @use line:
- The sourdough starter’s completion is anchored to 2 days before the main dough is mixed.
- ALAP (As Late As Possible) scheduling automatically aligns any earlier feeding or rest steps accordingly.
Working with ready-made stock (--stock)
Section titled “Working with ready-made stock (--stock)”When you already have a component prepared (bought ready-made from a store or prepped the day before), you don’t need to make it from scratch.
Inform the CLI using the --stock option:
- Zero timeline overhead: All preparation and cooking steps for stocked items are omitted from the interactive cooking guide and Gantt schedule.
- Simplified shopping list: Aggregates as a single purchasable unit (e.g.,
1 shortcrust pastry) rather than listing raw ingredients like flour and butter. - Accurate nutritional totals: Calories and macronutrients remain fully accurate, derived directly from the base module’s nutritional profile.
Encapsulation & math scoping
Section titled “Encapsulation & math scoping”A module’s relative quantities and ingredient dependencies (@sugar{125% @&lemon juice} in the curd) resolve only against the sections within that specific module. A host recipe that uses the same ingredient names elsewhere never collides with a base’s internal math, and vice versa.
This strict section-scoping guarantees that splitting a recipe into files never introduces unexpected side effects. See Encapsulation for complete details.
Try it in the playground
Section titled “Try it in the playground”The web-based Playground supports editing multi-file recipes: open the Lemon Tart (multi-file, @use) example from the recipe dropdown to explore a host recipe and its imported base side by side in separate tabs.