Bring Your Own Template: Custom Reports From Your Recipes
Most recipe apps give you exactly one way to look at a recipe: their way. One shopping-list format. One ingredient view. If you want the quantities as a table, or a printable card, or a column of allergens, you're out of luck — that's a feature request, not something you can do yourself.
Cook takes the opposite stance. Your recipes are plain-text Cooklang files, and Cooklang is structured — the editor knows which words are ingredients, what their quantities are, what's metadata. That means a recipe isn't just text to display. It's data you can run reports on.
The latest Cook Editor makes that concrete with a new command: Cooklang: Render Report…
A two-minute shopping list
Open any recipe, run Cooklang: Render Report… from the command palette (or right-click the file), and pick a template. Two come built in: Ingredients List and Shopping List.
The output opens in its own tab — and it's live. Edit the recipe, add an ingredient, change a quantity, and the report updates as you type.
That's already useful on its own. But the interesting part is what those built-in templates actually are: they're just text you could have written yourself. Here's the entire Shopping List template:
# Shopping List
{% for (aisle, items) in aisled(ingredients) | items -%}
## {{ aisle | titleize }}
{% for ingredient in items -%}
- [ ] {{ ingredient.name }}{% if ingredient.quantity %}: {{ ingredient.quantity }}{% endif %}
{% endfor %}
{% endfor %}
It loops over your ingredients, groups them by aisle, and prints a checkbox for each. Nothing is hidden in the app. If you'd rather the list wasn't grouped, or you want it as a numbered list, or you want to drop pantry staples you always have on hand — you change the template.
Bring your own template
A report template is a Jinja2 file — the same templating language used across loads of tools — with your recipe's data wired in. Save a file ending in .jinja, .j2, or .jinja2 anywhere in your recipe folder, and it shows up automatically in the Render Report picker. (The search respects your .gitignore, so scratch files stay out of the way.)
Inside a template you have:
ingredients— every ingredient, each with a.nameand.quantitymetadata— anything in the recipe's YAML frontmatter (title, servings, tags, source…)scale— the current scale factor, so quantities can adjust- filters and helpers like
sort,titleize,quantity,aisled(), andexcluding_pantry()
So a stripped-down recipe card with sorted ingredients is just:
# {{ metadata.title | default("Recipe") }}
{% for item in ingredients | sort(attribute='name') -%}
- {{ item.quantity | quantity }} {{ item.name }}
{% endfor %}
Want the same thing as a printable web page instead of Markdown? Name the file card.html.jinja and write HTML — Cook renders the output as a styled page. Cook decides how to display the result from the file's inner extension:
report.md.jinja(or no inner extension) → Markdownreport.html.jinja→ HTMLreport.csv.jinja,report.txt.jinja, … → plain text (handy for exporting a CSV you paste into a spreadsheet)
One template language, three output shapes, all from the same recipe data.
Going further: a datastore you own
Templates can do more than reformat what's already in the recipe. They can pull in facts you keep about your ingredients — through a small datastore that lives right next to your recipes.
It's just a folder of YAML files, one per ingredient. For example, db/eggs/meta.yml:
density: 1.03
storage:
shelf life: 30
fridge life: 60
Then a template can look those facts up with db():
Eggs keep {{ db('eggs.meta.storage.fridge life') }} days in the fridge.
The keys are entirely yours. Record calories and protein, and you've got a nutrition report. Record a price per ingredient, and you've got a cost breakdown. Record allergens, and you've got a label. Cook doesn't decide what's worth tracking — you do, and your data stays in plain files you can edit, back up, and check into git like everything else.
The same idea powers the built-in shopping list: drop an aisle.conf in your workspace and the aisled() helper groups items the way your supermarket is laid out; add a pantry.conf and excluding_pantry() quietly removes the salt and olive oil you never need to buy.
It works on menus too
Reports aren't limited to single recipes. Point the command at a .menu file — the plain-text plan CookBot builds for a week of dinners — and the same templates run across the whole thing. One template, one keystroke, and you've turned a week's meals into a single combined shopping list, or a cost estimate for the week, or a printable menu for the fridge door.
Why this is the point
This is what owning your data actually buys you. Because your recipes are structured plain text rather than rows in someone's database, a "report" isn't a feature the app has to ship for you — it's a template you write once and reuse forever. New idea on a Tuesday? Write the template, and you have it. No waiting, no export, no lock-in. (More on that philosophy in the plain-text canon and recipe sync without lock-in.)
Update to the latest Cook Editor, open a recipe, and run Cooklang: Render Report… — start with the built-in shopping list, then open the template and make it yours. For the full set of filters and datastore details, see the Cooklang reports docs.