Status: Draft Version: 1.0.0 Last Updated: 2025-10-05 Authors: Cooklang Federation Working Group
- Introduction
- Terminology
- Feed Format
- Cooklang Namespace Extension
- Feed Discovery
- Recipe Content Format
- Validation Rules
- Implementation Guidelines
- Security Considerations
- Examples
The Cooklang Federation Specification defines a standardized format for publishing and discovering Cooklang recipes across the web using syndication feeds. This specification enables:
- Decentralized publishing of recipe collections
- Automated discovery and indexing of recipes
- Interoperability between different Cooklang tools and services
- Preservation of recipe authorship and provenance
- Simplicity: Build on existing, well-understood feed formats (Atom/RSS)
- Compatibility: Work with standard feed readers and aggregators
- Extensibility: Allow for future enhancements without breaking changes
- Decentralization: No central authority required for publishing
This specification covers:
- Feed format and structure
- Cooklang-specific metadata extensions
- Feed discovery mechanisms
- Content encoding requirements
This specification does not cover:
- Recipe parsing and syntax (see Cooklang Specification)
- Search and indexing implementation details
- User interface requirements
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
- Feed: An Atom or RSS document containing a collection of recipe entries
- Entry: A single recipe within a feed
- Publisher: An individual or organization publishing a Cooklang feed
- Indexer: A service that crawls and indexes Cooklang feeds
- Consumer: Any application that reads and processes Cooklang feeds
Cooklang feeds MUST use one of the following base formats:
- Atom 1.0 (RFC 4287) - RECOMMENDED
- RSS 2.0 - SUPPORTED
Atom is RECOMMENDED due to:
- Better standardization (IETF RFC)
- Stricter specification
- Superior internationalization support
- More precise timestamp handling (ISO 8601)
Feeds MUST be served with the appropriate MIME type:
- Atom feeds:
application/atom+xml - RSS feeds:
application/rss+xmlorapplication/xml
Feeds MUST use UTF-8 encoding and declare it in the XML declaration:
<?xml version="1.0" encoding="utf-8"?>Each entry MUST include a link to the raw .cook file using standard Atom/RSS link elements.
Atom:
Use <link> with rel="enclosure" and type="text/plain":
<entry>
<title>Chocolate Chip Cookies</title>
<link href="https://example.com/recipes/cookies" rel="alternate"/>
<link href="https://example.com/recipes/cookies.cook" rel="enclosure" type="text/plain"/>
<!-- ... -->
</entry>RSS:
Use <enclosure> element:
<item>
<title>Chocolate Chip Cookies</title>
<link>https://example.com/recipes/cookies</link>
<enclosure url="https://example.com/recipes/cookies.cook" type="text/plain"/>
<!-- ... -->
</item>The recipe file link:
- MUST point directly to the raw
.cookfile (not an HTML page) - MUST be a stable, permanent URL
- SHOULD support HTTP conditional requests (ETag, Last-Modified)
- MUST be served with
Content-Type: text/plain; charset=utf-8 - MUST use
http://orhttps://scheme
Entries MAY include multiple links:
rel="alternate"- Link to HTML page displaying the reciperel="enclosure"- Link to raw.cookfile (REQUIRED)rel="related"- Link to related resources (images, videos, etc.)
The Cooklang namespace URI is:
https://cooklang.org/feeds/1.0
Feeds MUST declare this namespace in the root element:
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:cooklang="https://cooklang.org/feeds/1.0">All Cooklang-specific elements are OPTIONAL but RECOMMENDED for better discoverability and filtering.
Container element for Cooklang-specific metadata. This element SHOULD be included in each entry.
Parent: <entry> (Atom) or <item> (RSS)
Children: Any Cooklang extension elements
Occurrence: 0 or 1
Number of servings the recipe produces.
Parent: <cooklang:recipe>
Content: Positive integer
Occurrence: 0 or 1
Example:
<cooklang:servings>4</cooklang:servings>Time information for the recipe.
Parent: <cooklang:recipe>
Content: Empty (uses attributes)
Occurrence: 0 or 1
Attributes:
total- Total time in specified units (OPTIONAL)active- Active cooking time in specified units (OPTIONAL)units- Time unit: "minutes", "hours" (REQUIRED if total or active present)
Example:
<cooklang:time total="45" active="20" units="minutes"/>Container for recipe tags/categories.
Parent: <cooklang:recipe>
Children: One or more <cooklang:tag> elements
Occurrence: 0 or 1
Individual tag/category label.
Parent: <cooklang:tags>
Content: Text string
Occurrence: 0 or more
Example:
<cooklang:tags>
<cooklang:tag>dessert</cooklang:tag>
<cooklang:tag>cookies</cooklang:tag>
<cooklang:tag>baking</cooklang:tag>
</cooklang:tags>Subjective difficulty rating.
Parent: <cooklang:recipe>
Content: One of: "easy", "medium", "hard"
Occurrence: 0 or 1
Example:
<cooklang:difficulty>easy</cooklang:difficulty>URL to a recipe image.
Parent: <cooklang:recipe>
Content: Valid HTTP(S) URL
Occurrence: 0 or more
Example:
<cooklang:image>https://example.com/images/cookies.jpg</cooklang:image>Nutritional information per serving.
Parent: <cooklang:recipe>
Content: Empty (uses attributes)
Occurrence: 0 or 1
Attributes:
calories- Calories per serving (number)protein- Protein in grams (number)carbs- Carbohydrates in grams (number)fat- Fat in grams (number)fiber- Fiber in grams (number)
All attributes are OPTIONAL.
Example:
<cooklang:nutrition calories="250" protein="4" carbs="32" fat="12" fiber="2"/>Indexers SHOULD check the following locations when discovering feeds:
/.well-known/cooklang-feed(RECOMMENDED)/recipes/feed.xml/feed.xml/atom.xml/rss.xml
Websites SHOULD include feed discovery links in HTML <head>:
<link rel="alternate"
type="application/atom+xml"
title="Cooklang Recipes"
href="/recipes/feed.xml"/>The rel attribute MUST be alternate.
The type attribute MUST match the feed format.
For GitHub repositories:
- Check for
feed.xml,atom.xml, orrecipes/feed.xmlin repository root - If no feed exists, indexers MAY auto-generate a feed from
.cookfiles in the repository - Use GitHub's raw content URLs for recipe sources
Cooklang feeds use a two-tier content model:
- Feed entries contain metadata and summaries only (lightweight)
- Recipe files (
.cook) contain the full recipe content (fetched on demand)
This approach:
- Keeps feeds small and fast to parse
- Allows efficient updates (only changed entries need re-fetching)
- Scales to hundreds or thousands of recipes per collection
- Reduces bandwidth for both publishers and indexers
Each entry MUST include a <summary> (Atom) or <description> (RSS).
Summaries SHOULD be concise (1-3 sentences) and MAY include:
- Brief description of the dish
- Key ingredients or flavors
- Cooking method or cuisine type
- Occasion or serving suggestions
Atom:
<summary>Classic chocolate chip cookies with a crispy edge and chewy center. Perfect for dessert or snacking.</summary>RSS:
<description>Classic chocolate chip cookies with a crispy edge and chewy center.</description>Recipe files served via enclosure links:
- MUST be valid Cooklang format per the Cooklang Specification
- MUST be UTF-8 encoded plain text
- SHOULD not exceed 100KB in size
- SHOULD include metadata in YAML front matter
Example .cook file:
>> servings: 24
>> time: 45 minutes
>> tags: dessert, cookies, baking
Preheat #oven to 180°C.
Cream @butter{200%g} and @sugar{150%g} together for ~{5%minutes} until fluffy.
Add @eggs{2} and @vanilla extract{1%tsp}, mix well.
...
For large recipe collections (50+ recipes), publishers SHOULD:
- Limit feed to 25-50 entries per page
- Use Atom pagination (RFC 5005):
<feed xmlns="http://www.w3.org/2005/Atom">
<!-- feed metadata -->
<link rel="first" href="https://example.com/recipes/feed.xml?page=1"/>
<link rel="next" href="https://example.com/recipes/feed.xml?page=2"/>
<link rel="previous" href="https://example.com/recipes/feed.xml?page=1"/>
<link rel="last" href="https://example.com/recipes/feed.xml?page=5"/>
<!-- entries -->
</feed>- Order entries by most recently updated first
- Provide an index page listing all available feed pages
- MUST include feed
<title> - MUST include at least one
<link>element (Atom) or<link>(RSS) - MUST include
<updated>(Atom) or<lastBuildDate>(RSS) - SHOULD include
<author>information - MUST include unique
<id>(Atom)
- MUST include entry
<title> - MUST include
<link>withrel="enclosure"to.cookfile (Atom) or<enclosure>(RSS) - MUST include unique
<id>(Atom) or<guid>(RSS) - MUST include
<updated>(Atom) or<pubDate>(RSS) - MUST include
<summary>(Atom) or<description>(RSS) with summary text - SHOULD include
<cooklang:recipe>with metadata elements
- If
<cooklang:time>is present,unitsattribute is REQUIRED <cooklang:difficulty>MUST be one of: "easy", "medium", "hard"<cooklang:servings>MUST be a positive integer- URLs in
<cooklang:image>MUST be valid HTTP(S) URLs
- Recipe files linked via enclosures MUST be valid Cooklang syntax
- Recipe files MUST be UTF-8 encoded plain text
- Recipe files SHOULD not exceed 100KB
- Summaries SHOULD be 1-3 sentences (approximately 100-500 characters)
- Enclosure URLs MUST use
http://orhttps://schemes - Enclosure type MUST be
text/plain
Publishers implementing Cooklang feeds SHOULD:
- Update feed
<updated>timestamp when recipes are added, modified, or removed - Update entry
<updated>timestamp when individual recipes are modified - Provide permanent, stable URLs for recipe IDs and
.cookfiles - Support conditional HTTP requests (ETag, Last-Modified) for both feeds and recipe files
- Set appropriate cache headers:
- Feeds:
Cache-Control: max-age=3600(1 hour) - Recipe files:
Cache-Control: max-age=86400(24 hours)
- Feeds:
- Limit feed to 25-50 entries per page (use pagination for larger collections)
- Serve
.cookfiles withContent-Type: text/plain; charset=utf-8 - Use enclosure URLs that point directly to raw recipe files (not HTML pages)
- Include descriptive, searchable summaries in each entry
- Populate as much Cooklang metadata as possible (servings, time, tags, difficulty)
Indexers consuming Cooklang feeds SHOULD:
- Respect
robots.txtand crawl-delay directives - Use conditional HTTP requests (ETag, Last-Modified) to avoid redundant downloads
- Implement exponential backoff for failed requests
- Parse both Atom and RSS formats
- Extract recipe file URLs from enclosure links (
rel="enclosure"or<enclosure>) - Fetch recipe content from enclosure URLs:
- Compare entry
<updated>timestamp with previously indexed version - Only fetch
.cookfile if entry has been updated or is new - Cache recipe content locally to avoid redundant fetches
- Use conditional requests (If-Modified-Since, If-None-Match) when re-fetching
- Compare entry
- Implement a queue system to avoid fetching all recipes simultaneously
- Validate recipe content (Cooklang syntax) before indexing
- Store original enclosure URLs and feed URLs for attribution
- Re-crawl feeds periodically (daily or weekly recommended)
- Track failed recipe fetches separately from failed feed fetches
- Respect feed pagination links (rel="next", rel="previous")
Indexers MUST:
- Limit requests to 1 per second per domain
- Implement a polite user-agent string identifying the crawler
- Honor HTTP 429 (Too Many Requests) responses
Example user-agent:
Cooklang-Indexer/1.0 (+https://federation.example.com/about)
Consumers MUST sanitize recipe content before displaying to prevent:
- Cross-Site Scripting (XSS) attacks
- Code injection
- Malformed markup exploitation
Consumers MUST validate and restrict URLs to safe schemes:
- ALLOWED:
http://,https:// - FORBIDDEN:
javascript:,data:,file://, etc.
Consumers SHOULD enforce limits on:
- Maximum feed size (2MB recommended, 5MB maximum)
- Maximum entry count per feed page (50 recommended, 100 maximum)
- Maximum recipe file size (100KB recommended, 1MB maximum)
- Maximum summary length (500 characters recommended)
- Request timeout (30 seconds for feeds, 10 seconds for recipe files)
- Maximum concurrent recipe fetches (10-20 recommended to avoid overwhelming servers)
Publishers SHOULD serve feeds over HTTPS to ensure:
- Content integrity
- Privacy protection
- Authentication of feed source
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:cooklang="https://cooklang.org/feeds/1.0">
<title>Jane's Recipe Collection</title>
<link href="https://example.com/recipes/feed.xml" rel="self"/>
<link href="https://example.com/recipes/"/>
<updated>2025-10-05T12:00:00Z</updated>
<author>
<name>Jane Doe</name>
<uri>https://example.com</uri>
<email>jane@example.com</email>
</author>
<id>https://example.com/recipes/feed.xml</id>
<subtitle>Delicious homemade recipes in Cooklang format</subtitle>
<entry>
<title>Perfect Chocolate Chip Cookies</title>
<link href="https://example.com/recipes/chocolate-chip-cookies" rel="alternate"/>
<link href="https://example.com/recipes/cookies.cook" rel="enclosure" type="text/plain"/>
<id>https://example.com/recipes/chocolate-chip-cookies</id>
<updated>2025-10-01T10:30:00Z</updated>
<published>2025-09-15T08:00:00Z</published>
<summary>Classic chocolate chip cookies with a crispy edge and chewy center. These buttery cookies are loaded with chocolate chips and bake up perfectly every time.</summary>
<cooklang:recipe>
<cooklang:servings>24</cooklang:servings>
<cooklang:time total="45" active="20" units="minutes"/>
<cooklang:tags>
<cooklang:tag>dessert</cooklang:tag>
<cooklang:tag>cookies</cooklang:tag>
<cooklang:tag>baking</cooklang:tag>
</cooklang:tags>
<cooklang:difficulty>easy</cooklang:difficulty>
<cooklang:image>https://example.com/images/cookies.jpg</cooklang:image>
<cooklang:nutrition calories="150" protein="2" carbs="20" fat="7" fiber="1"/>
</cooklang:recipe>
</entry>
<entry>
<title>Vegetarian Pad Thai</title>
<link href="https://example.com/recipes/pad-thai" rel="alternate"/>
<link href="https://example.com/recipes/pad-thai.cook" rel="enclosure" type="text/plain"/>
<id>https://example.com/recipes/pad-thai</id>
<updated>2025-09-28T14:20:00Z</updated>
<published>2025-09-28T14:20:00Z</published>
<summary>Quick and easy vegetarian Pad Thai with rice noodles, tofu, and a tangy tamarind sauce.</summary>
<cooklang:recipe>
<cooklang:servings>2</cooklang:servings>
<cooklang:time total="30" active="25" units="minutes"/>
<cooklang:tags>
<cooklang:tag>thai</cooklang:tag>
<cooklang:tag>noodles</cooklang:tag>
<cooklang:tag>vegetarian</cooklang:tag>
</cooklang:tags>
<cooklang:difficulty>medium</cooklang:difficulty>
</cooklang:recipe>
</entry>
</feed><?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:cooklang="https://cooklang.org/feeds/1.0">
<channel>
<title>Jane's Recipe Collection</title>
<link>https://example.com/recipes/</link>
<description>Delicious homemade recipes in Cooklang format</description>
<language>en-us</language>
<lastBuildDate>Sat, 05 Oct 2025 12:00:00 GMT</lastBuildDate>
<item>
<title>Perfect Chocolate Chip Cookies</title>
<link>https://example.com/recipes/chocolate-chip-cookies</link>
<enclosure url="https://example.com/recipes/cookies.cook" type="text/plain"/>
<guid isPermaLink="true">https://example.com/recipes/chocolate-chip-cookies</guid>
<pubDate>Sun, 15 Sep 2025 08:00:00 GMT</pubDate>
<description>Classic chocolate chip cookies with a crispy edge and chewy center. These buttery cookies are loaded with chocolate chips and bake up perfectly every time.</description>
<cooklang:recipe>
<cooklang:servings>24</cooklang:servings>
<cooklang:time total="45" active="20" units="minutes"/>
<cooklang:tags>
<cooklang:tag>dessert</cooklang:tag>
<cooklang:tag>cookies</cooklang:tag>
<cooklang:tag>baking</cooklang:tag>
</cooklang:tags>
<cooklang:difficulty>easy</cooklang:difficulty>
<cooklang:image>https://example.com/images/cookies.jpg</cooklang:image>
</cooklang:recipe>
</item>
</channel>
</rss><?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>My Recipes</title>
<link href="https://example.com/feed.xml" rel="self"/>
<updated>2025-10-05T12:00:00Z</updated>
<id>https://example.com/feed.xml</id>
<entry>
<title>Simple Pasta</title>
<link href="https://example.com/pasta" rel="alternate"/>
<link href="https://example.com/pasta.cook" rel="enclosure" type="text/plain"/>
<id>https://example.com/pasta</id>
<updated>2025-10-05T12:00:00Z</updated>
<summary>Quick and easy pasta recipe for a weeknight dinner.</summary>
</entry>
</feed>The file served at https://example.com/recipes/cookies.cook:
>> servings: 24
>> time: 45 minutes
>> tags: dessert, cookies, baking
Preheat #oven to 180°C.
Cream @butter{200%g} and @sugar{150%g} together for ~{5%minutes} until fluffy.
Add @eggs{2} and @vanilla extract{1%tsp}, mix well.
In a separate bowl, combine @flour{300%g}, @baking soda{1%tsp}, and @salt{1/2%tsp}.
Gradually fold dry ingredients into wet mixture.
Stir in @chocolate chips{200%g}.
Drop spoonfuls onto greased #baking sheet.
Bake for ~{12-15%minutes} until golden brown.
Cool on #wire rack before serving.
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Large Recipe Collection</title>
<link href="https://example.com/feed.xml?page=2" rel="self"/>
<link href="https://example.com/recipes/"/>
<updated>2025-10-05T12:00:00Z</updated>
<id>https://example.com/feed.xml</id>
<!-- Pagination links -->
<link rel="first" href="https://example.com/feed.xml?page=1"/>
<link rel="previous" href="https://example.com/feed.xml?page=1"/>
<link rel="next" href="https://example.com/feed.xml?page=3"/>
<link rel="last" href="https://example.com/feed.xml?page=5"/>
<!-- 25-50 entries on this page -->
<entry>
<title>Recipe Title</title>
<link href="https://example.com/recipe" rel="alternate"/>
<link href="https://example.com/recipe.cook" rel="enclosure" type="text/plain"/>
<id>https://example.com/recipe</id>
<updated>2025-10-05T10:00:00Z</updated>
<summary>Brief recipe description.</summary>
</entry>
<!-- ... more entries ... -->
</feed>- 1.0.0 (2025-10-05): Initial specification
- Cooklang Specification
- RFC 4287: The Atom Syndication Format
- RSS 2.0 Specification
- RFC 2119: Key words for use in RFCs
- RFC 3339: Date and Time on the Internet
This specification is released under CC0 1.0 Universal (Public Domain).
End of Specification