# Recipe schema: the fields that earn the rich result

> The Recipe JSON-LD Google needs for a recipe card in search, with durations, ingredients, HowToStep instructions, nutrition, ratings and video, the rules that get sites flagged, and the plugin notes.

Updated 2026-09-25 · Structured data · HTML version: https://getreport.app/guides/recipe-schema

A recipe with the right structured data appears in search as a card: photo, star rating, cooking time, calories, and on mobile in the recipe carousel. A recipe without it is a blue link between the cards. The markup is not hard, but it has more fields than most types, two of them (durations and instructions) have formats people get wrong, and the rating rules are enforced. This guide gives the full JSON-LD, explains each field, and covers the plugin situation on WordPress, where most recipes live.

## Quick answer

- Required for the rich result: `name` and `image`. Everything else is recommended, and the card is thin without it.
- Add `author`, `datePublished`, `description`, `prepTime`, `cookTime`, `totalTime` (ISO 8601 durations: `PT1H30M`), `recipeYield`, `recipeCategory`, `recipeCuisine`, `keywords`, `nutrition.calories`.
- `recipeIngredient` is an array of strings, one per ingredient. `recipeInstructions` is an array of `HowToStep` objects, one per step.
- Ratings go in `aggregateRating` on the `Recipe`, from real ratings visible on the page.
- One `Recipe` per page. On a roundup page, an `ItemList` of links to the recipe pages.
- Run the [schema validator](https://getreport.app/tools/schema-validator) after any change; then Google's Rich Results Test for the preview.

## Why recipe markup matters

Recipe search is almost entirely rich results. A query like "banana bread" returns a carousel of cards on mobile and card-style results on desktop, each with an image, a rating, a time and calories. A plain listing next to them gets a fraction of the clicks. The card is built only from `Recipe` structured data; Google does not infer ingredients from the page.

The same markup feeds Google's recipe filters ("under 30 minutes", "vegetarian") and the image search recipe badge. So the fields that look optional decide which filters your page qualifies for.

## How getReport checks it

> **Free tool:** [Schema markup validator](https://getreport.app/tools/schema-validator): Find and validate JSON-LD and microdata on any page: parse errors, missing required properties, rich-result eligibility and duplicate entities, with the fix for each.

The validator parses every JSON-LD block on the page, lists the root types, checks each known type against its required-property table, and flags duplicates. `Recipe` is in the table, with the same two required properties Google lists: `name` and `image`.

> **Check: JSON-LD syntax.** A JSON-LD block with a syntax error is ignored completely, so Google reads no structured data from it. One stray comma or missing quote costs the whole block.
>
> 1. Open the technical detail to see the parser message and the block number.
> 2. Paste the block into a JSON validator, fix the character it points at, and run this check again.

Recipe blocks are long and often assembled by a plugin from user-entered text, so this is where they break: a quotation mark inside an ingredient ("1 cup "00" flour"), a stray comma after the last step. The finding names the block number and the parser message; the whole block is invisible to Google until it parses.

> **Check: Structured data types.** These are the entities the page declares. Google can only show rich results for the types it documents, so the list tells you what you can earn from this page.
>
> 1. Compare the list with the rich-result types Google supports (Product, Article, Event, BreadcrumbList, LocalBusiness…) and add the one that fits this page.

On a recipe post you should see `Recipe` plus the article and site entities your SEO plugin adds (`BlogPosting`, `WebSite`, `Organization`, `BreadcrumbList`). If the list shows `Recipe ×2`, or `HowTo` appears alongside it, read the platform notes below; when the two recipes share a name and URL the duplicate-entities finding names them too. The [rich results](https://getreport.app/learn/rich-results) page lists every type the validator has a table for.

> **Check: Required schema properties.** Google shows a rich result (price, stars, breadcrumb, event date) only when the entity carries the properties it requires. Without them the block is valid but earns nothing in search.
>
> 1. Open the technical detail for the full list, one line per entity type and property.
> 2. Add each property to the JSON-LD block. In WooCommerce, Yoast or Rank Math this usually means filling in the price, image or author field on the item itself.

For `Recipe` the check is `name` and `image`. Everything else in this guide is beyond what the validator tests: it confirms the block is present, parses and has the two required fields, and does not check duration formats, ingredient arrays or rating counts. Those rules are yours to keep, and the Rich Results Test enforces some of them.

![The structured data panel of a report on a recipe page: the types detected, the JSON-LD parse result, and the required-properties finding with any missing property listed one per line](https://getreport.app/guides/img/recipe-schema/panel.webp "Types, parse status and required properties in one panel; the format rules below are what the panel does not check.")

## Google's requirements

Google's recipe documentation lists two required and many recommended properties. The recommended ones are what fill the card:

| Property | Type | What it shows |
| --- | --- | --- |
| `name` (required) | Text | The recipe title |
| `image` (required) | URL or array | The card photo; several aspect ratios |
| `aggregateRating` | AggregateRating | The stars and count |
| `author` | Person or Organization | The byline |
| `prepTime`, `cookTime`, `totalTime` | Duration (ISO 8601) | "1 hr 30 min" on the card; the time filter |
| `datePublished` | Date | Freshness |
| `description` | Text | The snippet |
| `keywords` | Text | Comma-separated, for search matching |
| `nutrition.calories` | NutritionInformation | "320 calories" on the card |
| `recipeCategory`, `recipeCuisine` | Text | "Dessert", "Italian"; the filters |
| `recipeIngredient` | Text array | Ingredient filters ("without eggs") |
| `recipeInstructions` | HowToStep array | The steps |
| `recipeYield` | Text | "4 servings" |
| `video` | VideoObject | The video thumbnail and a possible video result |

Two surfaces are worth knowing. The recipe card and the mobile carousel are live and built from the recipe page's own `Recipe`. A host page (a roundup like "12 soups for autumn") can carry an `ItemList` of `ListItem`s whose `url` points at each recipe page, and Google may show a carousel for it. The guided recipes that once fed Google Assistant are no longer part of Google's recipe documentation; ignore old advice about them.

## Step by step

### 1. The complete block

On the recipe page, once, in a `<script type="application/ld+json">`:

```html
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Recipe",
  "name": "Štrukli with fresh cheese",
  "image": [
    "https://example.com/img/strukli-1x1.jpg",
    "https://example.com/img/strukli-4x3.jpg",
    "https://example.com/img/strukli-16x9.jpg"
  ],
  "author": { "@type": "Person", "name": "Ana Kovač" },
  "datePublished": "2026-09-12",
  "description": "Baked štrukli with a soft cheese filling, the way they are made in Zagorje.",
  "prepTime": "PT40M",
  "cookTime": "PT35M",
  "totalTime": "PT1H15M",
  "keywords": "štrukli, cheese pastry, croatian, baked",
  "recipeYield": "6 servings",
  "recipeCategory": "Main course",
  "recipeCuisine": "Croatian",
  "nutrition": {
    "@type": "NutritionInformation",
    "calories": "410 calories"
  },
  "recipeIngredient": [
    "500 g plain flour",
    "1 egg",
    "250 ml lukewarm water",
    "600 g fresh cow's cheese",
    "200 ml sour cream",
    "50 g butter",
    "1 tsp salt"
  ],
  "recipeInstructions": [
    {
      "@type": "HowToStep",
      "name": "Make the dough",
      "text": "Mix the flour, egg, water and salt into a smooth dough. Rest it under a bowl for 30 minutes.",
      "url": "https://example.com/recipes/strukli/#step-1",
      "image": "https://example.com/img/strukli-step-1.jpg"
    },
    {
      "@type": "HowToStep",
      "name": "Stretch and fill",
      "text": "Stretch the dough thin over a floured cloth, spread the cheese mixed with sour cream and egg, and roll it up using the cloth.",
      "url": "https://example.com/recipes/strukli/#step-2"
    },
    {
      "@type": "HowToStep",
      "name": "Bake",
      "text": "Cut into pieces, lay in a buttered dish, pour over cream and bake at 200 °C for 35 minutes until golden.",
      "url": "https://example.com/recipes/strukli/#step-3"
    }
  ],
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.7",
    "ratingCount": "38"
  },
  "video": {
    "@type": "VideoObject",
    "name": "How to stretch štrukli dough",
    "description": "Stretching the dough thin over a cloth without tearing it.",
    "thumbnailUrl": "https://example.com/img/strukli-video.jpg",
    "contentUrl": "https://example.com/video/strukli.mp4",
    "embedUrl": "https://example.com/video/strukli/embed",
    "uploadDate": "2026-09-12T09:00:00+02:00",
    "duration": "PT2M40S"
  }
}
</script>
```

### 2. Durations, the field most often wrong

`prepTime`, `cookTime` and `totalTime` are ISO 8601 durations, not text: `PT15M` is fifteen minutes, `PT1H` an hour, `PT1H30M` ninety minutes, `P1D` a day for something that rests overnight. "30 minutes" or "1.5 hours" is rejected. `totalTime` should equal prep plus cook; if you give only one of prep and cook, give `totalTime` too, because the card and the time filter read it.

### 3. Ingredients and instructions as arrays

`recipeIngredient` is one string per ingredient, with quantity and unit in the string: `"250 ml lukewarm water"`. A single string with line breaks, or an HTML list pasted in, is one ingredient as far as Google is concerned.

`recipeInstructions` is an array of `HowToStep` objects with `text`; `name`, `url` (an anchor on the page) and `image` per step are optional but feed the step-by-step display. Long recipes can group steps in `HowToSection` objects with `itemListElement` arrays of steps. A single block of text is accepted but shows as one paragraph. Do not mark the recipe as a separate `HowTo` as well; `HowTo` no longer earns a rich result and a second entity for the same steps is a duplicate.

### 4. Ratings: real, visible, on the Recipe

`aggregateRating` needs `ratingValue` and either `ratingCount` or `reviewCount`. The rules Google enforces: the ratings must come from visitors, the same rating must be visible on the page (a star widget or "4.7 from 38 ratings"), and there must be at least one. A recipe with `"ratingCount": "0"` or a rating the page does not show fails a review snippet check and, repeated across a site, can cost the site its review snippets. Put the rating on the `Recipe` itself; do not add a separate `Review` block per rating or a `Review` of your own recipe.

### 5. Images

`image` accepts one URL or an array. Google's guidance for recipes asks for images that are crawlable, that belong to the recipe (the finished dish, not a stock photo of ingredients), and offered in the 1:1, 4:3 and 16:9 aspect ratios so each surface can pick its crop. 1200 px wide covers every surface, including Discover. The image must not be blocked in robots.txt, which is where CDN image paths sometimes are.

### 6. Video

A `VideoObject` on the recipe shows a thumbnail on the card and can earn a video result. Required by Google for the video object: `name`, `thumbnailUrl`, `uploadDate`, and `contentUrl` or `embedUrl` (the validator's own required list is `name`, `thumbnailUrl`, `uploadDate`). `duration` in ISO 8601 and `description` are recommended. Key moments are optional: `hasPart` with `Clip` objects carrying `startOffset`, `endOffset` and `url` with a timestamp.

### 7. The long story before the recipe

The narrative before the recipe card does not affect the structured data; Google reads the JSON-LD regardless of where the card sits. It affects visitors, who have to scroll, and time on page, which matters less than people think. A "Jump to recipe" link at the top, pointing at the card's `id`, is the standard answer and the recipe plugins add it. Keep the card itself complete and visible: the markup must match what the page shows, so the ingredients and steps in the JSON-LD have to be on the page as text.

### 8. Validate

Run the schema validator on the recipe URL. Read the parse finding first, then types detected (one `Recipe`, one article entity), then required properties. Then paste the URL into Google's Rich Results Test (linked from the [Recipe structured data documentation](https://developers.google.com/search/docs/appearance/structured-data/recipe)), which checks the formats above and shows a preview of the card. The [JSON-LD basics](https://getreport.app/guides/json-ld-basics) guide covers how the two tools differ and how to read a parser error.

## Platform notes

**WordPress.** The three common recipe plugins, WP Recipe Maker, Tasty Recipes and Create by Mediavine, all print a `Recipe` JSON-LD block from the recipe card's fields and add a star rating widget whose votes feed `aggregateRating`. The block is only as complete as the card: leave the prep time or the servings empty and the property is missing. Run the validator on one recipe, open the types-detected finding, and check that there is exactly one `Recipe`. Two means the theme, a second plugin or an SEO plugin's "Recipe" schema type is also printing one; turn the extra one off. Yoast and Rank Math both hand recipe markup to the recipe plugin when one is active, but a manually chosen schema type on the post overrides that.

**Squarespace and Wix.** Neither has recipe fields or recipe structured data; the block has to be added by hand in a code block per page, or the site does without the card. Keep the durations and arrays exactly as in the example.

**Shopify.** Recipe pages on a food shop are usually blog posts; recipe apps exist and print the block, or add it in a theme section with metafields. Do not combine `Recipe` and `Product` for the same item on one page unless the page genuinely sells the dish.

## Verify

- The validator's parse finding passes, `Recipe` appears once under types detected, and the required-properties finding passes for `Recipe`.
- The Rich Results Test shows "Recipe" as eligible with no errors; work through its warnings for missing recommended fields.
- The rating, times and ingredients in the JSON-LD match what the page shows.
- After a few weeks, the recipe appears as a card with stars and a time in a search for its name.

## Common mistakes

- **Durations as text.** `"cookTime": "30 minutes"` is invalid. Use `PT30M`.
- **Ingredients as one string.** A single `recipeIngredient` with commas or line breaks is one ingredient. One array element per ingredient.
- **Instructions as one string.** Accepted, but no steps on the card. One `HowToStep` per step.
- **Ratings with zero reviews.** `ratingCount: 0` or a rating the page does not display violates the review snippet guidelines. Show the widget or remove `aggregateRating`.
- **A second Recipe block from the theme.** Two `Recipe` entities for one dish; types detected shows `Recipe ×2` and, when they share a name and URL, the duplicate finding names both. Keep the plugin's, remove the theme's.
- **Marking every post as a recipe.** A plugin setting that adds a `Recipe` block to posts without a recipe card earns a warning from Google and nothing else. Only recipe pages get the block.
