Living Document Notice
Published 2026-09-17. The evolving architecture, revisions, and connected notes for this dispatch live in the Stax Digital Garden.
From Scraper to Galley Markdown
Summary
Extracting structured recipe data from the web is only half of the conversion task. Once raw JSON-LD or microdata records are parsed and normalized, they must be formatted into clean, durable plain-text files that integrate with offline kitchen tools and text editors. The target document format must support human readability, checklist interaction, and machine-readable frontmatter.
FreeMyRecipes formats extracted culinary data into standardized Markdown files adhering to the Galley data exchange specification. This dispatch details the mapping pipeline that transforms nested Schema.org properties into flat YAML metadata, ingredient checklists, and step-by-step instructions.
The Schema Mapping Architecture
The transformation process converts nested JSON-LD objects into linear Markdown files containing structured frontmatter blocks.
Schema.org JSON-LD Payload
│
▼
┌──────────────────────────────────────────────┐
│ Translation Engine │
│ - Map schema types to scalar strings │
│ - Unroll ISO durations to human minutes │
│ - Format ingredients as markdown tasks │
│ - Number instructions sequentially │
└──────────────────────────────────────────────┘
│
▼
Portable Markdown Document (.md)
By standardizing on plain Markdown with YAML headers, the resulting recipes can be viewed in Obsidian, parsed with static site generators, or rendered on e-ink displays without custom client software.
Property Mapping Specification
The table below defines the mapping contract between Schema.org fields and the generated Markdown frontmatter attributes.
| Schema.org / JSON-LD Source Key | Target Frontmatter Attribute | Internal Representation | Fallback Behavior |
|---|---|---|---|
name | title | String | Extracted from HTML <title> tag |
description | description | String | First paragraph of body content |
recipeYield | yield | String | Defaults to "Not specified" |
prepTime | prep_time_minutes | Integer | Calculated from ISO 8601 string |
cookTime | cook_time_minutes | Integer | Calculated from ISO 8601 string |
totalTime | total_time_minutes | Integer | Sum of prep and cook times |
recipeCategory | category | String | Omitted if absent |
recipeCuisine | cuisine | String | Omitted if absent |
recipeIngredient | Document Checklist | Markdown List Items (- [ ]) | Raw text lines |
recipeInstructions | Document Body | Ordered Numbered List (1. ) | Paragraph blocks |
Template Generation Code
The Markdown generator uses a template pipeline that processes the normalized dictionary and outputs a complete document.
from datetime import date
from typing import Any, Dict
def render_recipe_markdown(data: Dict[str, Any]) -> str:
frontmatter = [
"---",
f"title: \"{data.get('name', 'Untitled Recipe')}\"",
f"date: {date.today().isoformat()}",
f"yield: \"{data.get('recipeYield', 'Unspecified')}\"",
f"prep_time: {data.get('prepTime', 'PT0M')}",
f"cook_time: {data.get('cookTime', 'PT0M')}",
f"source: \"{data.get('source_url', '')}\"",
"draft: false",
"tags:",
" - recipe",
f" - cuisine/{data.get('recipeCuisine', 'general').lower()}",
"---",
"",
f"# {data.get('name', 'Untitled Recipe')}",
"",
data.get("description", "").strip(),
"",
"## Ingredients",
""
]
for ing in data.get("recipeIngredient", []):
frontmatter.append(f"- [ ] {ing}")
frontmatter.append("")
frontmatter.append("## Instructions")
frontmatter.append("")
for i, step in enumerate(data.get("recipeInstructions", []), start=1):
step_text = step.get("text") if isinstance(step, dict) else str(step)
frontmatter.append(f"{i}. {step_text}")
frontmatter.append("")
return "\n".join(frontmatter)CLI Invocation Example
FreeMyRecipes allows piping extraction output directly into document writers:
# Extract recipe schema and stream markdown output
curl -s "https://example.com/herb-roasted-chicken" \
| freemyrecipes parse --input-format=html \
| freemyrecipes format --target=galley-markdown \
> ~/recipes/herb-roasted-chicken.md---
title: "Herb-Roasted Whole Chicken"
date: 2026-09-15
yield: "4 servings"
prep_time: PT20M
cook_time: PT75M
source: "https://example.com/herb-roasted-chicken"
draft: false
tags:
- recipe
- cuisine/french
---
# Herb-Roasted Whole Chicken
Crispy-skin roast chicken seasoned with coarse salt and fresh rosemary.
## Ingredients
- [ ] 1 whole chicken (4 lbs)
- [ ] 2 tbsp unsalted butter, softened
- [ ] 1 tbsp fresh rosemary, chopped
- [ ] 1 tsp kosher salt
## Instructions
1. Preheat oven to 425 degrees Fahrenheit.
2. Pat chicken dry with paper towels and season with salt.
3. Rub butter and chopped herbs under the breast skin.
4. Roast in a cast-iron skillet for 75 minutes until internal temp reaches 165F.- Directus Target: freemyrecipes
- Garden Source Reference: galley-pkm-bridge, bosun-ast-specs, MOC - Data Liberation Workbenches, MOC - Culinary & Domain Workspaces