Start free

Recipe schema for web extraction

A recipe page packs a lot of structured information behind inconsistent HTML: the ingredient list might live in a bulleted block, the steps in numbered list items, the timings inside small print under the title, and the rating in a star widget that loads after the page does. With Extracto you skip all of that. You send one recipe URL plus the Recipe schema below, the page is rendered in a real headless browser so JavaScript-injected ratings and lazy-loaded nutrition blocks are present, and you get back typed JSON that matches your schema exactly. You describe the fields you want in plain language instead of writing CSS selectors or XPath that break the moment a food blog reskins its theme. When a recipe genuinely omits a field (many home-cook posts have no calorie count and no aggregate rating), Extracto returns null for that field rather than inventing a plausible-looking number, so you can trust that a populated value came from the page. This entry gives you a ready-to-run schema for the common recipe shape, the field-by-field reasoning, and a realistic sample output you can diff against.


Fields in a Recipe schema

The fields teams most often extract for a recipe, what type each one is, and whether it is usually present.

Field Type Required Notes
name string yes The recipe title as shown on the page, for example 'Perfect Guacamole'. Typed as a string so it slots directly into a database column or a card header with no trimming of leftover markup or breadcrumb text.
ingredients string[] yes Each ingredient line as a separate string, including its quantity, like '2 ripe avocados' or '1/2 teaspoon kosher salt'. An array keeps every line individually addressable so you can count items, scale quantities, or render a checklist without re-splitting one blob of text.
instructions string[] yes The ordered preparation steps, one string per step, in the sequence shown on the page. Typing it as an array preserves step order and lets you number or paginate steps in a UI instead of parsing a single run-on paragraph.
prepTime string optional Hands-on preparation time as written, for example '10 minutes'. Returned as a string so you keep the original unit; if the page never states a prep time you get null rather than a guessed duration copied from a similar recipe.
cookTime string optional Active cooking or baking time, for example '0 minutes' for a no-cook dip or '25 minutes' for a bake. Kept separate from prepTime because many readers care about active stove time distinct from chopping and assembly.
totalTime string optional End-to-end time from start to finish, for example '10 minutes'. Useful when a recipe lists a total that does not equal prep plus cook (resting or chilling time), and null when the page only shows the component times.
servings string optional The yield as the page expresses it, such as '4 servings' or 'Makes about 2 cups'. A string captures both numeric counts and free-form yields without forcing a lossy conversion, and is null when no yield is stated.
rating number optional The aggregate star rating as a number, for example 4.8. Typed numerically so you can sort, average, or threshold across many recipes; null when the page has no rating widget rather than a placeholder zero that would skew aggregates.
calories number optional Calories per serving as a number, for example 262, pulled from the nutrition block. Returning a number lets you filter or chart directly, and null on recipes that publish no nutrition data keeps your dataset honest about what is actually measured.
author string optional The recipe author or attributed contributor, for example 'Hank Shaw'. Typed as a string for clean attribution and credit display, and null when the post does not name an author.

The schema

Copy this and send it with any URL.

{
  "name": "string",
  "ingredients": "string[]",
  "instructions": "string[]",
  "prepTime": "string",
  "cookTime": "string",
  "totalTime": "string",
  "servings": "string",
  "rating": "number",
  "calories": "number",
  "author": "string"
}

Example output

Validated JSON back, matching the schema.

{
  "name": "Perfect Guacamole",
  "ingredients": [
    "2 ripe avocados",
    "1/2 teaspoon kosher salt",
    "1 tablespoon fresh lime juice or lemon juice",
    "2 tablespoons minced red onion or thinly sliced green onion",
    "1 to 2 serrano chiles, stems and seeds removed, minced",
    "2 tablespoons cilantro, finely chopped",
    "1 pinch freshly ground black pepper",
    "1/2 ripe tomato, seeds and pulp removed, chopped"
  ],
  "instructions": [
    "Cut the avocados in half, remove the pit, and scoop the flesh into a bowl.",
    "Mash the avocado with a fork, leaving it a little chunky.",
    "Add the salt, lime juice, onion, chiles, cilantro, and black pepper and mix.",
    "Fold in the chopped tomato just before serving and taste for seasoning."
  ],
  "prepTime": "10 minutes",
  "cookTime": "0 minutes",
  "totalTime": "10 minutes",
  "servings": "4 servings",
  "rating": 4.8,
  "calories": 262,
  "author": "Hank Shaw"
}

Try this recipe schema against a real page in the live demo, or read the docs to use it via the API.

Recipe schema: FAQ

How do I extract recipes from sites with anti-bot protection?
Every Extracto request runs through a managed anti-bot bypass layer that handles protected sites such as those behind Cloudflare, DataDome, or PerimeterX, with proxies managed for you and no setup on your side. The page is loaded in a real headless browser so JavaScript-rendered ingredients, lazy-loaded nutrition tables, and star ratings that hydrate after load are all present when extraction runs. The one thing this does not cover is login-gated content that requires session cookies, such as a paid meal-plan service behind a member wall; that is available on the Enterprise plan. For public recipe pages, including ones that are protected or heavily JavaScript-driven, the standard request handles it.
What happens when a recipe page has no rating or calorie count?
Extracto returns null for that field instead of guessing. Many home-cook blog posts publish ingredients and steps but no aggregate rating and no nutrition panel, so rating and calories will simply come back as null on those pages. This is deliberate: a populated number always reflects something the model actually found on the page, never a plausible value borrowed from a similar recipe. That property makes the output safe to load straight into a database or to aggregate across thousands of recipes, because a missing value reads as missing rather than as a misleading zero or an averaged stand-in.
Do I need to write CSS selectors for each recipe site?
No. You describe the fields you want in the schema (name, ingredients, instructions, and so on) and Extracto figures out where they live on the page. Recipe blogs use wildly different themes and class names, so selector-based scraping breaks constantly when a site reskins. Because you are describing the data shape rather than the markup, the same Recipe schema works across Simply Recipes, a personal food blog, and a magazine site without per-site tuning. You get back JSON validated against your schema, with each field typed the way you declared it, so an array stays an array and a number stays a number.
Can Extracto crawl an entire recipe site or category page?
Extracto extracts one URL per call; it is not a crawler that follows links across a site. If you want every recipe in a category, you collect the individual recipe URLs first (from a sitemap, a category listing, or your own list) and then call Extracto once per recipe URL with the Recipe schema. This keeps each extraction focused and predictable, and it is the responsible pattern for staying within a site's Terms of Service and robots.txt while keeping your request volume modest rather than hammering a site with a broad automated crawl.
Why are prepTime, cookTime, and totalTime returned as strings instead of numbers?
Recipe pages express timing inconsistently: '10 minutes', '1 hour 15 minutes', 'overnight', or 'about 25 min'. Returning these as strings preserves exactly what the page states without a lossy or incorrect conversion to a single numeric unit. If you need machine-comparable durations, you can normalize the strings in your own code where you control the rounding and edge cases. Keeping the three time fields separate also reflects how recipes actually present them, since total time often includes resting or chilling that is not the sum of prep and cook, and any field the page omits comes back as null.