An Obsidian plugin that turns recipe notes into a consolidated, category-grouped grocery list you can check off while shopping — and renders those recipe notes as interactive cards while you cook.
Grouped grocery list with recipe links, checkboxes, and category sections:

Recipe view with hero image, scaled ingredients, instructions, and portion multiplier:

1 cup milk + 2 cups milk becomes 3 cup milk.type: recipe open in a dedicated view with a hero image, nutrition card, an instructions panel, meat-temperature warnings, and a portion multiplier that scales both the displayed quantities and the grocery list.diet, prepTime, and cookTime to the frontmatter and the recipe view shows them as quick-glance badges below the title.allergens frontmatter overlaps yours show a red banner in the recipe view and a warning icon next to the recipe in the grocery list.favorite: true frontmatter is set automatically. Used by the meal recommender.lastMade and increment cookedCount whenever you add a recipe to the grocery list — powering the cooking-stats leaderboard.↑ GI badge in the recipe view. Ships with a curated regex dictionary you can edit freely.groceryList).Ingredients).---
groceryList: true
---
# Pasta with Tomato Sauce
## Ingredients
- 1 lb spaghetti
- 2 cups crushed tomatoes
- 3 cloves garlic
- 2 tbsp olive oil
- salt to taste
The plugin looks for the configured Ingredients heading (any level) inside each selected recipe and parses every list item beneath it as an ingredient. If no matching heading is found, every list item in the file is parsed.
Each ingredient line is interpreted as [quantity] [unit] [name]. Supported quantities include whole numbers, decimals, simple fractions (1/2), mixed fractions (1 1/2), and unicode fractions (½). Common units (cup, tbsp, tsp, oz, lb, g, kg, can, clove, bunch, ...) are recognised and normalised so plurals consolidate. Trailing parentheticals like (diced) are stripped before consolidation.
Lines without a recognisable quantity (salt to taste) still appear on the list - they just won't aggregate quantities.
Append #IgnoreIngredient to any ingredient line you don't want on the shopping list (pantry staples you always have on hand, garnishes, water, etc.):
- 2 tbsp olive oil #IgnoreIngredient
- salt to taste #IgnoreIngredient
- 1 lb spaghetti
The tag is matched case-insensitively; #ignoreingredient, #ignore-ingredient, and #ignore_ingredient all work too. Ignored lines are dropped before consolidation, so they won't show up under any category.
Pantry ships a custom view for recipe notes. To opt a note in, add type: recipe to its frontmatter (the value is configurable in Settings → Recipe view). When auto-open is on (the default), opening such a note switches the leaf into the recipe view automatically; you can always switch back with the Edit as Markdown button in the view's action bar, the Recipe mode entry in the pane's three-dot menu, or the Open as Markdown command.
The view shows a meta banner with the multiplier, servings, and nutrition; a hero image card; a tabular ingredients list with safe-cooking-temperature badges next to detected meats; and a numbered instructions card.
Frontmatter properties the view reads and writes:
| Property | Type | Purpose |
|---|---|---|
type |
string | When equal to recipe (case-insensitive), opens the file in the recipe view. |
image |
string | Hero image for the recipe. Accepts a wikilink ([[my-photo.jpg]]), a vault-relative path, or an external URL. The key is matched case-insensitively (Image, IMAGE). |
multiplier |
number | Portion multiplier (default 1, minimum 0.5). Scales the displayed ingredient quantities, the grocery list contribution, and the displayed servings count. Does not change per-serving nutrition. |
servings |
number | Optional. Used to compute per-serving nutrition. |
calories, protein, fat, carbs |
number | Totals for the recipe as written (multiplier 1). Per-serving values are computed by dividing by servings. |
diet |
array of strings | Diet tags shown as badges (e.g. ["vegan", "gluten-free"]). Free-form. |
allergens |
array of strings | Allergens this recipe contains (e.g. ["nuts", "dairy"]). Compared case-insensitively against your allergen list in settings. |
prepTime, cookTime, totalTime |
number | Minutes. totalTime is computed from prepTime + cookTime when not provided. |
favorite |
boolean | Set with one click via the star button in the recipe view. |
lastMade |
date | Auto-stamped to today's date when you add the recipe to the grocery list (configurable in settings). |
cookedCount |
number | Auto-incremented when lastMade advances to a new day. Powers the cooking stats leaderboard. |
Example:
---
type: recipe
image: "[[pasta-photo.jpg]]"
groceryList: true
multiplier: 1.5
servings: 4
calories: 720
protein: 28
fat: 18
carbs: 96
---
## Ingredients
- 1 lb spaghetti
- 2 cups crushed tomatoes
- 3 cloves garlic
## Instructions
1. Boil water and salt heavily.
2. Cook spaghetti to al dente.
3. Toss with sauce and serve.
With multiplier: 1.5, the recipe view shows 1.5 lb spaghetti and the grocery list adds 1.5 lb spaghetti (consolidating with any other recipe that also calls for spaghetti). Servings displays Serves 6 (4 × 1.5). Per-serving nutrition stays constant at the values derived from nutrition / servings. The grocery list also displays the multiplier badge next to recipe links so you can see at a glance which recipes are scaled up or down.
| Command | Description |
|---|---|
| Open grocery list | Reveal the grocery list in the right sidebar. |
| Refresh grocery list from recipes | Re-scan selected recipes. Runs automatically on file changes. |
| Add one-off grocery item | Open the modal to add a one-off. |
| Reset grocery list checks | Uncheck everything without removing items. |
| Clear grocery list | Remove all recipe selections, one-offs, and checked state. |
| Toggle this recipe in the grocery list | Toggle the selection property on the active note. |
| Open as recipe | Switch the active markdown note into the recipe view. |
| Open as Markdown | Switch the active recipe view back to standard Markdown editing. |
| Suggest a meal | Open a modal with N recipes you haven't cooked recently. Filter by favorites, hide allergens. |
| Show cooking stats | Open the leaderboard ranking every recipe by cookedCount. |
| Export grocery list | Open a modal to copy the current list or append it to a note (plain, checklist, or grouped). |
All commands appear in the command palette under the Pantry: prefix.
type: recipe in the configured folders) that you haven't cooked within the Suggestion day window (default: 14 days). The window and number of suggestions live in Settings → Recipe library.cookedCount (descending), then by lastMade. Click any row to jump straight to the recipe.Add a comma-separated list of allergens you care about in Settings → Recipe library → My allergens (e.g. nuts, dairy). Pantry compares them, case-insensitively, against each recipe's allergens frontmatter:
Enable Settings → Diabetic mode → Enable diabetic mode to surface high-glycemic-index ingredients in the recipe view. When the cleaned ingredient name matches a regex in the dictionary, an ↑ GI badge appears next to it (alongside the meat-temperature badge for meats). Hovering shows a tooltip explaining the warning.
When diabetic mode is on, the High GI dictionary editor appears below the toggle. It's a plain text area with one regex per line:
# High glycemic index (GI ≥ 70) ingredients.
# One regex per line, case-insensitive. Lines starting with # are comments.
\bwhite\s+rice\b
\bbaguette\b
\bmashed\s+potato(es)?\b
\bcorn\s+syrup\b
\bwatermelon\b
Patterns are case-insensitive and matched against the cleaned ingredient name (no quantity, no markdown, no trailing tags). Invalid patterns are skipped silently and surfaced in a small red panel below the editor so they're easy to fix. The Reset GI dictionary button restores the shipped list.
GI values vary by source, preparation, and even cultivar - this list is informational only and is not medical advice. Tune the dictionary to match your own dietary plan, and treat it as a hint rather than a verdict.
When diabetic mode is off, no GI badges appear and the dictionary editor stays hidden.
There are three ways to assign categories, controlled by Settings → Category source:
- 1 lb Ground Beef #Meat), the trailing tag becomes the category. Items with no tag fall into "Other."When multiple recipes contribute different tags for the same merged item, the most common tag wins (ties broken by first-seen order).
You can override or extend the dictionary at any time in Settings → Category overrides with one entry per line:
oat milk: Dairy & Eggs
seltzer: Beverages
chocolate chip: Pantry
Matches are case-insensitive substrings of the ingredient name; the longest match wins. Overrides take precedence over both tags and the dictionary.
The order in which categories appear in the view is configurable via Category order. Unknown categories are appended alphabetically.
The plugin runs entirely offline and does not access your system clipboard. Export opens a modal with the generated text so you can copy it yourself or append it to a note.
Recipe discovery walks only the folders you configure in Recipe folders (or the vault root when that list is empty) — it does not call vault.getMarkdownFiles() or enumerate unrelated files. The plugin writes to its own data file and only modifies recipe frontmatter when you explicitly clear the list, use the toggle command, or change the multiplier in the recipe view.
npm install
npm run dev # watch + rebuild main.js
npm run build # type-check and produce a production main.js
npm run lint # run eslint
To test locally, this folder must live at <Vault>/.obsidian/plugins/pantry/ (matching the id in manifest.json). After building, reload Obsidian and enable Pantry under Settings → Community plugins.
Push a tag whose name exactly matches the version in manifest.json (no leading v, e.g. 1.0.3). The release workflow builds the plugin, attests main.js and styles.css, and publishes a GitHub release with auto-generated notes and the three assets (main.js, manifest.json, styles.css).