Specification
Soustack Recipe Schema v0.2 — A portable, scalable, interoperable recipe format with first-class image support for both recipes and individual steps.
A Soustack document is a JSON object conforming to the JSON Schema specification. The schema defines a structured format for representing recipes with support for scaling, timing, equipment, storage, and substitutions.
Version 0.2.0 introduces normalized image fields so recipe and instruction
imagery can be shared consistently across tooling. Older documents remain valid, but
implementations should upgrade to take advantage of the image metadata outlined below.
Required Fields
Every recipe must include:
name— The title of the recipe (string)ingredients— Array of ingredients (see below)instructions— Array of instructions (see below)
Core Fields
Recipe Fields
Core document fields exposed at the recipe root:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-friendly recipe title. |
description | string | No | Short summary of the dish. |
category | string | No | Classification such as “Dessert” or “Snack”. |
tags | string[] | No | Keywords for search and filtering. |
image | string | string[] | No | One or more URLs that represent the recipe visually. |
Basic Information
id— Unique identifier, slug or UUID (string, optional)name— Recipe title (string, required)version— Semantic versioning, e.g., "1.0.0" (string, optional)description— Recipe description (string, optional)category— Recipe category, e.g., "Main Course", "Dessert" (string, optional)tags— Array of tag strings (array, optional)image— Image URL or array of URLs (string | string[], optional)dateAdded— ISO 8601 date-time (string, optional)
Source Attribution
"source": {
"author": "Jane Doe",
"url": "https://example.com/recipe",
"name": "Cookbook Name",
"adapted": true
} Yield
What the recipe produces. Required fields: amount and unit.
"yield": {
"amount": 4,
"unit": "servings",
"servings": 4,
"description": "Serves 4-6 people"
} Time
Timing information. Supports two formats:
Format 1: Numeric values (in minutes)
"time": {
"prep": 15,
"active": 30,
"passive": 60,
"total": 105
} Format 2: String values
"time": {
"prepTime": "15 minutes",
"cookTime": "45 minutes"
} Ingredients
Array that can contain strings, ingredient objects, or ingredient subsections.
Simple string:
"ingredients": [
"2 cups flour",
"1 cup sugar"
] Ingredient object:
"ingredients": [
{
"id": "flour-1",
"item": "all-purpose flour",
"quantity": {
"amount": 2,
"unit": "cups"
},
"name": "Flour",
"aisle": "Baking",
"prep": "sifted",
"prepAction": "sift",
"prepTime": 5,
"destination": "mixing bowl",
"scaling": {
"type": "linear",
"factor": 1.0
},
"critical": false,
"optional": false,
"notes": "Can substitute bread flour"
}
] Ingredient subsection:
"ingredients": [
{
"subsection": "For the dough",
"items": [
{
"item": "flour",
"quantity": { "amount": 2, "unit": "cups" }
}
]
}
] Ingredient Scaling
Scaling types:
linear— Scales proportionallydiscrete— Scales in discrete stepsproportional— Scales relative to another ingredientfixed— Does not scalebakers_percentage— Baker's percentage (requiresreferenceId)
"scaling": {
"type": "bakers_percentage",
"referenceId": "flour-1",
"factor": 100,
"roundTo": 0.25,
"min": 0.5,
"max": 10
} Instructions
Array that can contain strings, instruction objects, or instruction subsections.
Instruction Fields
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Human-readable step instructions. |
image | string | No | URL for a step-specific reference image. |
dependsOn | string[] | No | Upstream step IDs required before this step begins. |
inputs | string[] | No | Ingredient IDs or equipment IDs consumed in this step. |
Simple string:
"instructions": [
"Preheat oven to 350°F.",
"Mix dry ingredients."
] Instruction object:
"instructions": [
{
"id": "step-1",
"text": "Mix dry ingredients in a large bowl.",
"image": "https://example.com/steps/mix-dry.jpg",
"destination": "mixing bowl",
"dependsOn": [],
"inputs": ["flour-1", "sugar-1"],
"timing": {
"duration": 5,
"type": "active",
"scaling": "linear"
}
}
] Instruction subsection:
"instructions": [
{
"subsection": "Prepare the dough",
"items": [
"Mix flour and water",
{
"text": "Knead for 10 minutes",
"timing": {
"duration": 10,
"type": "active",
"scaling": "fixed"
}
}
]
}
] Instruction Timing
duration— Time in minutes (number, required)type— "active" or "passive" (string, required)scaling— "linear", "fixed", or "sqrt" (string, optional)
Image Support
Soustack recipes support images at both the recipe level and the instruction level.
Recipe Image
| Field | Type | Required | Description |
|---|---|---|---|
image | string | string[] | No | Hero image (or set of derivatives) that represents the finished recipe. |
Single image:
{
"image": "https://example.com/recipe.jpg"
} Multiple images:
{
"image": [
"https://example.com/1x1.jpg",
"https://example.com/16x9.jpg"
]
} Instruction Image
| Field | Type | Required | Description |
|---|---|---|---|
image | string | No | Reference image that illustrates the instruction. |
{
"instructions": [
{
"text": "Brown the beef until caramelized",
"image": "https://example.com/browning.jpg"
}
]
} Normalization Rules
- Accept
string,string[], ornull/undefined; coerce tostring[]internally. - Trim whitespace and discard empty strings before persisting.
- Resolve relative URLs to absolute URLs prior to export.
- Deduplicate URLs while preserving the first occurrence as the canonical hero image.
Schema.org Conversion
When importing from Schema.org, normalize ImageObject structures to URL strings.
| Schema.org | Soustack |
|---|---|
"url" | "url" |
["url1", "url2"] | ["url1", "url2"] |
{ "@type": "ImageObject", "url": "..." } | "url" |
[{ "@type": "ImageObject", "url": "..." }] | ["url", ...] |
Soustack retains the order emitted by Schema.org so responsive clients can pick the
most appropriate rendition. Missing url fields should be dropped rather
than converted into empty strings.
Image Patterns
Recipes use images in four distinct ways:
| Pattern | Recipe Image | Step Images | Description |
|---|---|---|---|
| Hero | Yes | No | Single finished dish photo. |
| Step-by-step | No | Yes | Tutorial style with process photos. |
| Hybrid | Yes | Yes | Hero image plus inline step images. |
| None | No | No | Text-only recipe. |
Implementations should detect the pattern and render appropriately (see guide for detection logic).
Image Code Examples
Hero pattern (single image)
{
"soustack": "0.2",
"name": "Chocolate Cake",
"image": "https://example.com/cake.jpg",
"ingredients": ["Flour", "Sugar", "Cocoa powder"],
"instructions": [
"Preheat oven to 350°F.",
"Bake until a skewer comes out clean."
]
} Multiple recipe images
{
"soustack": "0.2",
"name": "Summer Salad",
"image": [
"https://example.com/salad-1x1.jpg",
"https://example.com/salad-16x9.jpg"
],
"ingredients": ["Greens", "Tomatoes", "Vinaigrette"],
"instructions": [
"Dress the greens right before serving."
]
} Step-by-step pattern
{
"soustack": "0.2",
"name": "Fresh Pasta",
"ingredients": ["Flour", "Eggs"],
"instructions": [
{
"text": "Create a well in the flour",
"image": "https://example.com/pasta-1.jpg"
},
{
"text": "Add eggs to the well",
"image": "https://example.com/pasta-2.jpg"
}
]
} Hybrid pattern
{
"soustack": "0.2",
"name": "Sheet-Pan Fajitas",
"image": "https://example.com/fajitas-hero.jpg",
"ingredients": ["Peppers", "Onions", "Tortillas"],
"instructions": [
{
"text": "Season the vegetables",
"image": "https://example.com/fajitas-1.jpg"
},
{
"text": "Broil until charred",
"image": "https://example.com/fajitas-2.jpg"
}
]
} Equipment
Array of equipment objects:
"equipment": [
{
"id": "oven-1",
"name": "Oven",
"required": true,
"label": "Preheated to 350°F",
"capacity": {
"amount": 1,
"unit": "sheet pan"
},
"scalingLimit": 2,
"alternatives": ["Toaster oven", "Air fryer"]
}
] Storage
Storage information for the finished recipe:
"storage": {
"roomTemp": {
"duration": "P2D",
"method": "Airtight container",
"notes": "Best consumed within 2 days"
},
"refrigerated": {
"duration": "P1W",
"method": "Covered container"
},
"frozen": {
"duration": "P3M",
"method": "Freezer bag",
"thawing": "Thaw overnight in refrigerator"
},
"reheating": "Reheat in 350°F oven for 10 minutes",
"makeAhead": [
{
"component": "Dough",
"storage": "refrigerated",
"duration": "P2D",
"method": "Plastic wrap",
"notes": "Bring to room temperature before using"
}
]
} Duration uses ISO 8601 duration format (e.g., "P2D" = 2 days, "P1W" = 1 week, "P3M" = 3 months).
Substitutions
Array of substitution objects:
"substitutions": [
{
"ingredient": "butter",
"critical": false,
"notes": "Butter provides flavor and texture",
"alternatives": [
{
"name": "Margarine",
"ratio": "1:1",
"notes": "May affect flavor",
"impact": "Slight flavor difference",
"dietary": ["vegan"]
},
{
"name": "Coconut oil",
"ratio": "1:1",
"notes": "Use refined for neutral flavor",
"dietary": ["vegan", "paleo"]
}
]
}
] Schema Reference
The complete JSON Schema is available at http://soustack.org/schema/v0.2.
Documents should validate against this schema to ensure compatibility.
Encoding
Soustack documents must be valid JSON encoded as UTF-8. The recommended
file extension is .soustack.json or .soustack.