Johannes Schwartz38 downloadsA cookbook with guest preferences, adaptive portions, and package-aware scaling.
This is my cooking plugin to personalize my cookbooks.
This is not a easy cooking plugin be aware:)
First off it can do the most things other cookbook plugins can do but you can't give your recipe stars (just delete them if you don't like them).
Under personalize I mean you can manage your guests and your shopping behavior.
To set up a shopping list just hit the button next to the recipe.
It is all JSON, sorry no messy thousand recipe files for you, but wait...you still can:(
Backup is a setting that creates backups of your json files but only when you create or edit the files in the plugin itself.
If this is still too easy for you:
Why do I write like this do you ask?
[Cooking Navigator Demo]
Cooking Navigator is an Obsidian cookbook plugin for managing recipes, guests, ingredient restrictions, portions, shopping lists, supermarkets, and cooking timers.
It is designed for households where recipes need to adapt to the people eating them and to the stores where ingredients are bought.
Preparation recipes allow recipes to produce ingredients that can be used by other recipes.
Examples:
When an ingredient has a linked preparation recipe, the shopping list can either include the finished product or expand it into the correctly scaled ingredients needed to prepare it yourself.
The plugin can create rotating JSON backups for files it writes itself.
cookbook-library.json backup from the Cookbook error screen.All Cookbook data is stored as JSON files inside your Obsidian vault.
cookbook-library.jsonThe library file contains shared data:
recipes-*.jsonRecipes are loaded from every JSON file whose filename starts with recipes-.
You can keep all recipes in one file, for example:
Cookbook/recipes-main.json
Or split them across multiple files:
Cookbook/recipes-drinks.json
Cookbook/recipes-desserts.json
Cookbook/recipes-main-dishes.json
cookbook-library.json and recipes-*.json files.Install dependencies:
npm install
Start development mode:
npm run dev
Create a production build:
npm run build
Run the TypeScript check:
npm run check
Run ESLint:
npm run lint
The following schemas document every currently supported field in
cookbook-library.json and recipes-*.json.
They use JSON Schema Draft 2020-12 and can be used by editors or CI tools that support JSON Schema validation.
[!NOTE] JSON Schema validates structure, data types, and simple value constraints. It cannot fully validate cross-file rules of Cooking Navigator. In particular, make sure manually that:
- IDs are unique within their respective collections;
- recipe ingredient IDs refer to existing library ingredients;
- category, restriction-scope, allergen, location, and market IDs refer to existing library entries;
- every
utensilRequirements[].categoryIdrefers to an existingutensilCategoriesentry;produces.ingredientIdmatches the ingredient used by recipes that link to this recipe throughpreparationRecipeId;- preparation recipes do not form cycles.
cookbook-library.json{
"title": "Cooking Navigator library",
"type": "object",
"additionalProperties": false,
"required": [
"version",
"categories",
"restrictionScopes",
"ingredients",
"people"
],
"properties": {
"version": {
"const": 1
},
"categories": {
"type": "array",
"items": {
"$ref": "#/$defs/category"
}
},
"utensilCategories": {
"type": "array",
"items": {
"$ref": "#/$defs/utensilCategory"
}
},
"restrictionScopes": {
"type": "array",
"items": {
"$ref": "#/$defs/restrictionScope"
}
},
"allergens": {
"type": "array",
"items": {
"$ref": "#/$defs/allergen"
}
},
"shoppingLocations": {
"type": "array",
"items": {
"$ref": "#/$defs/shoppingLocation"
}
},
"markets": {
"type": "array",
"items": {
"$ref": "#/$defs/market"
}
},
"ingredients": {
"type": "array",
"items": {
"$ref": "#/$defs/ingredient"
}
},
"people": {
"type": "array",
"items": {
"$ref": "#/$defs/person"
}
}
},
"$defs": {
"id": {
"type": "string",
"minLength": 1
},
"color": {
"type": "string",
"minLength": 1
},
"positiveNumber": {
"type": "number",
"exclusiveMinimum": 0
},
"category": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"name": {
"type": "string",
"minLength": 1
},
"color": {
"$ref": "#/$defs/color"
}
}
},
"utensilCategory": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name",
"shape"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"name": {
"type": "string",
"minLength": 1
},
"shape": {
"enum": [
"cylinder",
"rectangular"
]
}
}
},
"restrictionScope": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"name": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string"
}
}
},
"allergen": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"name": {
"type": "string",
"minLength": 1
}
}
},
"shoppingLocation": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"name": {
"type": "string",
"minLength": 1
}
}
},
"market": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"name": {
"type": "string",
"minLength": 1
},
"locationId": {
"$ref": "#/$defs/id"
},
"color": {
"$ref": "#/$defs/color"
}
}
},
"ingredient": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name",
"defaultUnit",
"resourcePriority"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"name": {
"type": "string",
"minLength": 1
},
"defaultUnit": {
"type": "string",
"minLength": 1
},
"defaultUnitPlural": {
"type": "string",
"minLength": 1
},
"resourcePriority": {
"enum": [
"none",
"primary",
"secondary"
]
},
"allergenIds": {
"type": "array",
"items": {
"$ref": "#/$defs/id"
}
},
"storeIds": {
"type": "array",
"items": {
"$ref": "#/$defs/id"
}
},
"packageSizes": {
"type": "array",
"items": {
"$ref": "#/$defs/positiveNumber"
}
},
"storePackageSizes": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/$defs/positiveNumber"
}
}
},
"packageRoundUpThreshold": {
"type": "number",
"minimum": 0,
"maximum": 1
}
}
},
"avoidance": {
"type": "object",
"additionalProperties": false,
"required": [
"scopeId",
"ingredientIds"
],
"properties": {
"scopeId": {
"$ref": "#/$defs/id"
},
"ingredientIds": {
"type": "array",
"items": {
"$ref": "#/$defs/id"
}
}
}
},
"person": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name",
"color",
"eaterWeight",
"avoidances",
"favoriteRecipeIds"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"name": {
"type": "string",
"minLength": 1
},
"avatarUrl": {
"type": "string",
"minLength": 1
},
"color": {
"$ref": "#/$defs/color"
},
"eaterWeight": {
"$ref": "#/$defs/positiveNumber"
},
"avoidances": {
"type": "array",
"items": {
"$ref": "#/$defs/avoidance"
}
},
"intoleranceAllergenIds": {
"type": "array",
"items": {
"$ref": "#/$defs/id"
}
},
"favoriteRecipeIds": {
"type": "array",
"items": {
"$ref": "#/$defs/id"
}
}
}
}
}
}
recipes-*.json{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Cooking Navigator recipe document",
"type": "object",
"additionalProperties": false,
"required": [
"version",
"recipes"
],
"properties": {
"version": {
"const": 1
},
"recipes": {
"type": "array",
"items": {
"$ref": "#/$defs/recipe"
}
}
},
"$defs": {
"id": {
"type": "string",
"minLength": 1
},
"positiveNumber": {
"type": "number",
"exclusiveMinimum": 0
},
"ingredient": {
"type": "object",
"additionalProperties": false,
"required": [
"ingredientId",
"amount"
],
"properties": {
"ingredientId": {
"$ref": "#/$defs/id"
},
"sectionHeading": {
"type": "string",
"minLength": 1,
"description": "Optional heading displayed before this ingredient, for example “For the dough”."
},
"preparationRecipeId": {
"$ref": "#/$defs/id"
},
"alternativeIngredientId": {
"$ref": "#/$defs/id"
},
"amount": {
"$ref": "#/$defs/positiveNumber"
},
"unit": {
"type": "string",
"minLength": 1
},
"unitPlural": {
"type": "string",
"minLength": 1
},
"Suffix": {
"type": "string",
"minLength": 1,
"description": "Recipe-specific text appended to the ingredient name."
},
"note": {
"type": "string",
"minLength": 1
}
}
},
"tip": {
"type": "object",
"additionalProperties": false,
"required": [
"heading",
"text"
],
"properties": {
"heading": {
"type": "string",
"minLength": 1,
"description": "Prominent tip heading, for example “SECRET TIP”."
},
"text": {
"type": "string",
"minLength": 1,
"description": "Tip text. Newlines are preserved in cooking mode."
}
}
},
"step": {
"type": "object",
"additionalProperties": false,
"required": [
"ingredients",
"instructions"
],
"properties": {
"title": {
"type": "string",
"minLength": 1
},
"ingredients": {
"type": "array",
"items": {
"$ref": "#/$defs/ingredient"
}
},
"instructions": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"timerMinutes": {
"$ref": "#/$defs/positiveNumber"
},
"tips": {
"type": "array",
"items": {
"$ref": "#/$defs/tip"
}
}
}
},
"produces": {
"type": "object",
"additionalProperties": false,
"required": [
"ingredientId",
"amount"
],
"properties": {
"ingredientId": {
"$ref": "#/$defs/id"
},
"amount": {
"$ref": "#/$defs/positiveNumber"
},
"unit": {
"type": "string",
"minLength": 1
}
}
},
"utensilRequirement": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": ["categoryId", "diameterCm", "heightCm"],
"properties": {
"categoryId": { "$ref": "#/$defs/id" },
"diameterCm": { "$ref": "#/$defs/positiveNumber" },
"heightCm": { "$ref": "#/$defs/positiveNumber" },
"maxHeightCm": { "$ref": "#/$defs/positiveNumber" }
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["categoryId", "widthCm", "lengthCm", "heightCm"],
"properties": {
"categoryId": { "$ref": "#/$defs/id" },
"widthCm": { "$ref": "#/$defs/positiveNumber" },
"lengthCm": { "$ref": "#/$defs/positiveNumber" },
"heightCm": { "$ref": "#/$defs/positiveNumber" },
"maxHeightCm": { "$ref": "#/$defs/positiveNumber" }
}
}
]
},
"pairing": {
"type": "object",
"additionalProperties": false,
"properties": {
"text": {
"type": "string",
"minLength": 1,
"description": "Free text shown in the “Pairs well with” section."
},
"recipeId": {
"$ref": "#/$defs/id",
"description": "Optional ID of a linked recipe."
}
},
"anyOf": [
{
"required": [
"text"
]
},
{
"required": [
"recipeId"
]
}
]
},
"recipe": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"title",
"categoryIds",
"baseEaters",
"restrictionScopeIds",
"ingredients",
"steps"
],
"properties": {
"id": {
"$ref": "#/$defs/id"
},
"title": {
"type": "string",
"minLength": 1
},
"categoryIds": {
"type": "array",
"items": {
"$ref": "#/$defs/id"
}
},
"categorySortIndexes": {
"type": "object",
"description": "Optional category-specific browser priority. 1 is shown before 2, 2 before 3, and 3 before recipes without an index.",
"additionalProperties": {
"enum": [
1,
2,
3
]
}
},
"baseEaters": {
"$ref": "#/$defs/positiveNumber"
},
"pairsWellWith": {
"type": "array",
"items": {
"$ref": "#/$defs/pairing"
}
},
"tips": {
"type": "array",
"description": "Optional tips shown below the ingredient overview.",
"items": {
"$ref": "#/$defs/tip"
}
},
"produces": {
"$ref": "#/$defs/produces"
},
"restrictionScopeIds": {
"type": "array",
"items": {
"$ref": "#/$defs/id"
}
},
"imageUrl": {
"type": "string",
"minLength": 1
},
"imagePlacement": {
"enum": [
"cover",
"after-ingredients"
]
},
"prepMinutes": {
"$ref": "#/$defs/positiveNumber"
},
"cookMinutes": {
"$ref": "#/$defs/positiveNumber"
},
"bakeMinutes": {
"$ref": "#/$defs/positiveNumber"
},
"inactiveMinutes": {
"$ref": "#/$defs/positiveNumber"
},
"utensilRequirements": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/utensilRequirement"
}
},
"ingredients": {
"type": "array",
"items": {
"$ref": "#/$defs/ingredient"
}
},
"steps": {
"type": "array",
"items": {
"$ref": "#/$defs/step"
}
}
}
}
}
}
Suffix intentionally starts with an uppercase S because this is the
existing JSON field name used by Cooking Navigator. It is kept for backwards
compatibility.
sectionHeading can be set on an ingredient to visually split an ingredient
overview into sections such as “For the dough” and “For the filling”.
Both recipes and individual cooking steps support a tips array. Every tip
contains a prominent heading and free-form text; several tips can be added
at each location.
categorySortIndexes optionally prioritizes recipes inside individual
categories. A lower index is shown first (1, then 2, then 3); recipes
without an index retain the regular loading order. The keys are category IDs.
{
"id": "pizzateig",
"title": "Pizzateig",
"categoryIds": ["pizza", "brot"],
"categorySortIndexes": {
"pizza": 1
}
}
---