Here is a rich result that never showed. A 60-seat Italian place ships a homepage with a stars block, an hours block, a menu block — all present, all in the <head>, all stamped by the theme. The owner pastes the URL into the validator and one line comes back red: "priceRnge": "$". A four-letter typo. Google reads the whole block as malformed and renders a plain blue link — no stars, no price chip, no Reserve button — for the eight months nobody thinks to re-check. The schema was there the entire time. It just didn’t work, and “present” and “working” are the two words this whole guide lives between.

That is the failure this teardown is built to catch. The honest version of restaurant schema markup is six schema.org types — Restaurant, Menu, OpeningHoursSpecification, AggregateRating, Reservation, and FAQ — and the job is less “add schema” than “tear down the broken block your CMS already stamped and paste the one Google can actually read.” Most independent sites have two of the six working; the other four are missing, or present-but-malformed in exactly the way the typo above is — valid-looking, silently disqualified.

Each broken or absent type is a different surface in Google’s results you’re not eligible for — the local-pack card, AI Overview citations, the Reserve button, the rich snippet, the holiday-hours badge. So the gap between “some schema markup” and “all six, working” isn’t linear. Below: the broken block torn down beside the fixed one for each of the six types, what Google reads from each, and the validator pass that tells you which version you’re actually running.

  1. 1

    Pass one — Google reads your prose

    The crawler reads the visible page: menu text, the hours line, the About paragraph. It can guess you’re a restaurant — but it has to infer cuisine, price band, and hours from sentences written for humans, and it infers wrong often.

  2. 2

    Pass two — Google reads your JSON-LD

    The same facts arrive labeled: @type: Restaurant, servesCuisine: Italian, priceRange: $, opens: 17:00. No inference. This is the pass the rest of this guide is about — the six types are what fill it.

  3. 3

    Result — what Google is willing to render

    Labeled facts are what Google will show as stars, an open-now chip, a price chip, and a Reserve button — because it now trusts them. Prose alone earns a plain blue link; the schema is what unlocks the rich result.

Schema isn’t a ranking trick — it’s the second read. Pass one makes Google guess your facts from prose; pass two hands them over labeled, which is the only version Google trusts enough to render as a rich result.

1. Restaurant — the foundational type

This is the one you cannot skip. Restaurant schema tells Google what business type you are, your address, your phone, your price range, and a handful of other facts that determine whether you appear in queries like "Italian restaurants near me" at all.

Minimum viable JSON-LD:

{
  "@context": "https://schema.org",
  "@type": "Restaurant",
  "name": "Your Restaurant",
  "image": "https://example.com/photos/storefront.jpg",
  "telephone": "+1-555-555-0100",
  "priceRange": "$",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "123 Main St",
    "addressLocality": "Your City",
    "addressRegion": "ST",
    "postalCode": "00000",
    "addressCountry": "US"
  },
  "servesCuisine": ["Italian", "Bistro"],
  "acceptsReservations": "True"
}
The minimum viable Restaurant block

The servesCuisine array is doing more work than people realize — it's how Google knows to show you for cuisine-specific queries. Don't put more than two cuisines unless you actually serve them; ambiguity demotes you in queries you should win.

2. Menu — the type most restaurants fake

This is the most common teardown of the six. Almost every restaurant site has either no Menu schema at all, or the platform stamped the entire menu as one text string — the whole carte jammed into a single field. It validates. It is also useless: Google can’t parse individual dishes out of a blob, so you don’t rank for "tacos al pastor near me" at the dish level, and the AI Overviews have nothing to lift.

Here is the broken block your builder probably stamped, torn down beside the structured one that actually ranks. Read what Google gets from each:

Menu schema, torn down: the blob your builder stamps vs the block Google can read

Broken — the whole menu as one string (validates, ranks nothing)
{
  "@type": "Menu",
  "text": "Tacos al Pastor $14, Carnitas
    $13, Pollo $12, Veggie $11,
    Chips & Salsa $6 ..."
}

What Google reads: one text field. No dish is a thing. Nothing ranks at the dish level; AI Overviews get a wall of text they won’t quote.

Fixed — structured, paste-ready (same dishes, ranks each one)
{
  "@context": "https://schema.org",
  "@type": "Menu",
  "name": "Dinner",
  "hasMenuSection": [{
    "@type": "MenuSection",
    "name": "Tacos",
    "hasMenuItem": [{
      "@type": "MenuItem",
      "name": "Tacos al Pastor",
      "description": "Marinated pork, pineapple, white onion, cilantro on house corn tortillas.",
      "offers": { "@type": "Offer", "price": "14.00", "priceCurrency": "USD" }
    }]
  }]
}

What Google reads: a named dish, a real description, a price. The dish can rank on its own — and the description is the field AI Overviews quote verbatim.

Both blocks validate. Only the right one is legible to Google dish-by-dish — the teardown isn’t about errors, it’s about the structure a blob silently throws away.

The description matters more than the name — that's the field Google's AI Overviews lift verbatim when someone asks "what are tacos al pastor like at Bistro Petit." An empty description, or a dish swallowed into the blob above, earns no AI citation.

The teardown, at the level the guest sees it: the same result, broken vs fixed schema

Without schema — a bare blue link

Your Restaurant — Italian Bistro in Your City

yourrestaurant.example › menu

Italian bistro in Your City serving hand-rolled pasta, seasonal entrees, and a short wine list. Reservations welcome Friday and Saturday.

With the six schema types — rich result

Your Restaurant — Italian Bistro in Your City

yourrestaurant.example › menu

★★★★★ 4.7 (412) · $ · Open · Closes 10 PM · Map pin

Italian bistro in Your City serving hand-rolled pasta, seasonal entrees, and a short wine list. Reservations welcome Friday and Saturday.

[ Reserve a table ] · [ Directions ] · [ Call ]

Same page, same meta description. The five extra surface elements — the star rating, the price chip, the open-now indicator, the map pin, and the Reserve button — each map to one of the six schema types. Each missing type silences one of those elements.

3. OpeningHoursSpecification — the one that costs you walk-ins

If your hours are wrong on Google, your hours are wrong everywhere. OpeningHoursSpecification is the schema that powers the "Open now" / "Closes at 10 PM" badge on your search panel — and the one that gets stale when you change Sunday hours and forget to update the site.

"openingHoursSpecification": [
  {
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Tuesday", "Wednesday", "Thursday"],
    "opens": "17:00",
    "closes": "22:00"
  },
  {
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Friday", "Saturday"],
    "opens": "17:00",
    "closes": "23:00"
  }
]
openingHoursSpecification — one entry per day-of-week group

Holiday overrides go in a separate specialOpeningHoursSpecification block — both Google and Apple Maps read it, and a missing holiday closure is the most common reason an angry-on-Christmas-Eve review lands. The failure mode worth naming: CMS or template updates can quietly strip JSON-LD overnight. If you don't have a monthly schema check on your calendar, the first time you'll know your hours block went missing is when the one-star reviews start arriving.

Run Schema Checker on your homepage to see whether your current hours schema is valid; Open Hours generates the JSON-LD if you need a fresh start.

Four field-level mistakes disqualify a block the way the hours example does — quietly, with no error a human notices. Each has a torn-down twin: the field as builders leave it, and the field as Google needs it.

Broken field "opens": "5:00 PM" — the 12-hour clock. Google reads opens as 24-hour only, so the open-now badge never fires.
Fixed field "opens": "17:00" — 24-hour, zero-padded. Now the panel can compute “Open · closes 10 PM” in the guest’s own timezone.
Broken field "priceRange": "moderate" — a word where Google expects a symbol pattern. The price chip stays blank.
Fixed field "priceRange": "$$" — the dollar-sign band Google renders directly as the price chip on the result.
Broken field "telephone": "(301) 555-0123" with a different number on the contact page. The mismatch demotes the listing.
Fixed field "telephone": "+1-301-555-0123" — E.164, identical everywhere. One number, one entity, no contradiction to resolve.
Broken field "servesCuisine": ["Italian","Pizza","Bar","Cafe","Brunch"] — five cuisines dilutes the one you should win.
Fixed field "servesCuisine": ["Italian"] — one or two you actually are. Ambiguity demotes you in the query you should own.

4. AggregateRating — the one that boosts CTR by double digits

If you have reviews on your own site, AggregateRating shows up as the gold-star pattern in your search snippet. The CTR lift is real — typically 8-15% for restaurant queries.

"aggregateRating": {
  "@type": "AggregateRating",
  "ratingValue": "4.7",
  "reviewCount": "412"
}
AggregateRating — the stars-and-review-count snippet

The constraint: the reviews have to be on your site. You can't aggregate Google's reviews into your own AggregateRating — that violates Google's structured-data policies and can earn a manual action that takes weeks to lift.

The teardown that looks like a feature

This is the broken block that hurts most, because it doesn’t look broken — it looks like initiative. Someone copies the Google star average into AggregateRating to “match the listing,” the validator goes green, the stars appear for a week. Then the manual action lands and pulls every rich result on the domain, not just the stars. A type that’s absent costs you one surface. A type that’s wrong this way can cost you all of them. Tear it down to your own on-site reviews, or don’t ship the block at all.

5. Reservation — the one that unlocks Find a Table

If you're on OpenTable, Resy, Tock, or SevenRooms, your reservation system already publishes the right schema. But if your booking is a custom form on your own site, you need to publish Reservation schema yourself — otherwise Google can't show the "Reserve a table" button in your panel.

The integration with Find a Table requires this schema to be valid AND your booking endpoint to be publicly reachable. Check both with Schema Checker.

FAQ schema is the cheapest AI-citation surface a restaurant has. The fact that almost nobody ships it is the opportunity.

6. FAQ — the AI-citation magnet

FAQ schema isn't restaurant-specific, but it's the highest-leverage AI-citation surface for restaurant pages. ChatGPT, Perplexity, and Google's AI Overviews read FAQ blocks first when answering operational questions.

{
  "@type": "FAQPage",
  "mainEntity": [{
    "@type": "Question",
    "name": "Do you take reservations for parties of 8 or more?",
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "Yes — for parties of 8 to 14 we ask for a deposit of $25 per guest, refunded with the bill on the night."
    }
  }]
}
FAQPage — Question and Answer pairs for AI-citation surfaces

Six well-written FAQ entries — actually useful operational questions, not marketing copy — typically lift AI-search-driven traffic 20-40% within 60 days.

Schema present + valid on a typical restaurant site (by @type)

Restaurant · identity card

~80% · usually present

OpeningHoursSpecification

~60% · rest are stale

Reservation · via OpenTable etc.

~40% · booking-tool publishes it

AggregateRating · gold stars

~30% · often absent

Menu · parses dish-by-dish

~25% · rest are string blobs

FAQ · AI-citation magnet

~15% · rarest of the six

Sorted by coverage. The bottom three — AggregateRating, Menu, FAQ — are absent on the majority of restaurant sites. They’re also the three that unlock the highest-value surfaces (stars in the snippet, dish-level ranking, AI Overview citations). The gap is the opportunity.

How to check what you have today

To check your restaurant schema, view-source the live page and search for application/ld+json to confirm a block exists, then paste the page URL into validator.schema.org to confirm it passes with no red errors. “Present” is not the same as “working” — the more common failure is schema that is technically on the page but silently disqualified, so walk the four questions below in order; the first “no” is your problem.

  1. Q1Is a JSON-LD <script> tag in the rendered <head> at all?

    View-source the live page and search for application/ld+json. A CMS theme swap or a plugin update can strip the tag overnight without touching the editor view.

    No tag Stop here. Nothing downstream matters — re-ship the block and put a monthly check on the calendar. This is the most common silent failure.

    Tag present Go to Q2.

  2. Q2Does validator.schema.org pass with no red errors?

    One unknown property (a typo like priceRnge) or one missing required field disqualifies the whole block. The rating value can be sitting right there and the stars still never render.

    Red errors Fix the red items — they’re almost always a missing comma, a stray quote, or a misspelled property. Yellow warnings are usually fine to ship.

    Clean Go to Q3.

  3. Q3Is AggregateRating sourced from your own reviews?

    The rating has to come from reviews published on your own site. Copying your Google star average into AggregateRating is the trap that looks valid and isn’t.

    Aggregated from Google Remove it. Aggregating Google’s reviews violates policy and can earn a manual action that pulls the stars and takes weeks to lift.

    Own-site reviews Go to Q4.

  4. Q4Do your name, address, and phone match everywhere?

    Compare the NAP on your site against Google Business Profile and Yelp, character for character. Schema can’t resolve a contradiction it doesn’t know about.

    Conflicts Reconcile them first. Valid schema still loses the local pack to the more-coherent competitor when the NAP disagrees with itself.

    Consistent Your schema is doing its job. Re-run this walk monthly — the failures here are silent until a one-star review tells you.

“Present” isn’t “working.” Each question gates a different way valid-looking schema gets silently disqualified — stripped tag, validator error, policy violation, NAP conflict. The first “no” is the fix.

Run your homepage through Schema Checker. It returns a present/valid/missing verdict for each of these six types and points at the broken pieces. Save the result to your Restaurant Audit and re-run monthly — schema rots when CMS updates touch templates.

The compounding argument: each missing schema type is a different Google surface you're not eligible for. The six types together unlock the local-pack card, AI Overview citations, the Reserve button, the rich snippet, and the holiday-hours badge. Add them in order, and the cumulative effect on impressions usually lands somewhere between 20% and 60% within a quarter.

The move is: ship Restaurant + OpeningHours + Menu this week, FAQ next, and let AggregateRating + Reservation come from your booking platform. Six types in roughly two weeks of deliberate work. The surfaces queue up behind them.

Schema isn't optional. Each missing type is a Google surface you've voluntarily disqualified yourself from.

The complete, paste-ready Restaurant JSON-LD

The six types above describe what Google can use. This section is the how — a single production-grade JSON-LD payload that ships Restaurant + Menu + OpeningHoursSpecification + FAQ together, in the validated shape that drops into the <head> of a restaurant page. Adapt the values to your own restaurant, validate, deploy.

How to use it

Three steps:

  1. Copy the block below into a single <script type="application/ld+json"> tag inside your <head>. One tag, not six.
  2. Change the seven fields marked // EDIT: the restaurant name, address parts (street, city, region, zip), phone, opening hours, and the menu items.
  3. Validate at validator.schema.org by pasting the URL of your page. Fix anything red. Most errors are missing commas or stray quotes.

The complete example

Below is a complete worked schema for an illustrative 60-seat DMV Italian restaurant. The structure nests six types: Restaurant (root), Menu, OpeningHoursSpecification, AggregateRating, Reservation, and FAQ. Each one unlocks a different Google surface (per the six-types article). Save it as a starting point; tighten as needed.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Restaurant",
  "@id": "https://romacucina.example/#restaurant",
  "name": "Roma Cucina",            // EDIT
  "image": "https://romacucina.example/og.jpg",
  "telephone": "+1-301-555-0123",   // EDIT
  "priceRange": "$",
  "servesCuisine": ["Italian", "Pasta"],
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "8125 Georgia Ave",   // EDIT
    "addressLocality": "Silver Spring",     // EDIT
    "addressRegion": "MD",                  // EDIT
    "postalCode": "20910",                  // EDIT
    "addressCountry": "US"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": 38.9907, "longitude": -77.0261
  },
  "openingHoursSpecification": [
    { "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Tuesday","Wednesday","Thursday"],
      "opens": "17:00", "closes": "22:00" },
    { "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Friday","Saturday"],
      "opens": "17:00", "closes": "23:00" },
    { "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Sunday"],
      "opens": "17:00", "closes": "21:00" }
  ],
  "acceptsReservations": true,
  "potentialAction": {
    "@type": "ReserveAction",
    "target": "https://romacucina.example/reservations"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.6",   // EDIT (pull from your Google review average)
    "reviewCount": "412"    // EDIT
  },
  "hasMenu": {
    "@type": "Menu",
    "name": "Dinner Menu",
    "hasMenuSection": [{
      "@type": "MenuSection",
      "name": "Pasta",
      "hasMenuItem": [
        { "@type": "MenuItem",
          "name": "Pici al Ragù",         // EDIT (your dish)
          "description": "Hand-rolled pici with traditional ragù from Marche",
          "offers": { "@type": "Offer", "price": "24.00", "priceCurrency": "USD" }
        }
      ]
    }]
  },
  "subjectOf": [{
    "@type": "FAQPage",
    "mainEntity": [{
      "@type": "Question",
      "name": "Do you take reservations?",
      "acceptedAnswer": { "@type": "Answer",
        "text": "Reservations are recommended on Fridays and Saturdays. Walk-ins welcome Tuesday through Thursday." }
    }]
  }]
}
</script>

That’s 60 lines of JSON-LD covering the six types Google reads first on a restaurant page. The seven // EDIT fields are everything you have to change to make it yours.

The same Google result, before and after the paste-ready schema

Before — bare blue link, no rich result

Roma Cucina — Italian Restaurant in Silver Spring

romacucina.example › menu

Italian restaurant in Silver Spring serving hand-rolled pasta and seasonal entrees. Reservations welcome.

After — rich result with six new surface elements

Roma Cucina — Italian Restaurant in Silver Spring

romacucina.example › menu

★★★★★ 4.6 (412) · $ · Open · Closes 10 PM · Map pin

Italian restaurant in Silver Spring serving hand-rolled pasta and seasonal entrees. Reservations welcome.

[ Reserve a table ] · [ Directions ] · [ Call ]

Same URL, same meta description, same 60 lines of JSON-LD between them. Six new surface elements: stars, review count, price range, open-now, map pin, Reserve button.

Before you publish

Three checks. Each one catches a class of error that’s otherwise invisible until you’re wondering why the local-pack still hasn’t shown you.

  1. Run it through validator.schema.org. Paste your page URL. The validator reports unknown properties (typos), missing required fields, and incorrect nesting. Fix the red items. Yellow is usually fine to ship.
  2. Confirm the rendered page matches. Open your page in a browser, view-source, search for application/ld+json. Confirm the script tag is there and the JSON is what you expect. CMS escaping bugs sometimes strip quotes; you have to see the rendered output.
  3. Test with Google’s Rich Results test. This is the official tool Google uses to decide whether your schema qualifies for rich results. It tells you which surfaces your schema is eligible for. Run it after the validator passes.
  1. 1

    Paste into the <head> of every public page

    One <script type="application/ld+json"> tag per page. Homepage, menu page, reservations page, contact page. Not just the homepage.

  2. 2

    Run Google’s Rich Results Test on three URLs

    Homepage, menu, reservations. Fix every red error before you walk away from the keyboard. Yellow warnings are usually fine to ship.

  3. 3

    Wait 7–14 days for the re-crawl

    Googlebot has to see the schema in production before any feature appears. The local-pack and stars land first; FAQ and Reserve take longer. Skip this and you’ll think it failed.

  4. 4

    Verify in Search Console › Enhancements

    Each schema type gets its own report — Local business, FAQ, Sitelinks. Valid pages count up; errors get flagged with the URL and the field.

The whole post-paste workflow in four steps. Skip step three and you’ll think the schema didn’t work; skip step four and you’ll miss the silent drift when a template update strips a field.

What happens after you ship

Don’t expect immediate effect. Google’s crawler may take 24–72 hours to revisit your page and pick up the new schema. Some surfaces (the local-pack, AggregateRating stars) update within a week. Others (Reservation deep links via Find a Table, FAQ rich snippets) can take 30+ days. The longest-tail ones are the AI Overview citations, which can take 90 days to start landing.

Once it’s shipped, leave the schema alone. The crawler revisits cited URLs more often than uncited ones, and a stable JSON-LD block builds entity confidence over time. Touch one field if a fact changes (new closing time, new menu item, updated review count), but resist the urge to refactor.

Rich-result appearance odds 90 days after pasting (by @type)

AggregateRating · gold stars

~90% · within 30 days

OpeningHoursSpecification · open-now chip

~85% · within 30 days

Restaurant + PostalAddress · local-pack card

~70% · within 60 days

Menu · dish-level + AI citation

~55% · within 60 days

FAQPage · FAQ rich snippet

~40% · within 60 days

ReserveAction · Reserve-a-table button

~35% · within 90 days

Stars and hours land fastest; Reserve takes longest and depends on whether your booking endpoint is publicly reachable. None are guaranteed — Google decides which to show on which query.

What this won’t do. Three honest limitations, in operator language: 1) Schema doesn’t guarantee any feature — it makes you eligible; Google still chooses which surfaces to show on which query. 2) Google picks the rich result it wants — you can ship perfect FAQ schema and still get a plain blue link if the query is ambiguous or the FAQ is thin. 3) Schema won’t fix a broken NAP — if your name, address, or phone is inconsistent across Google Business Profile, Yelp, and your site, the schema can’t paper over the conflict, and the local-pack will keep ranking the more-coherent competitor.

Schema is plumbing. It doesn’t lift rankings on its own — it makes you eligible for surfaces you weren’t eligible for before. Combined with the citation-shaped writing from the AI Overviews piece, it’s the highest-leverage single edit a restaurant site can ship in 2026.


Tell us

Be the first field note on this piece.

Tried this in your own restaurant? 100–400 words, your name on it. Don reads every one. Your note shows up here once approved.

Sources & further reading

Google Search Central — structured data documentation

Google Search Central — Google's public documentation lists Restaurant, Menu, OpeningHoursSpecification, AggregateRating, Reservation, and FAQPage as supported types for restaurant pages, each with its own eligibility surface in Search results. The mapping between schema type and surface in this post follows that documentation.

schema.org — Restaurant + Menu type definitions

schema.org — The JSON-LD examples in this post are minimum-viable subsets of the canonical schema.org definitions. Required vs. recommended properties (the "doing more work than people realize" line about servesCuisine) come directly from schema.org's spec for the Restaurant type.

Baymard Institute — rich snippet CTR research

Baymard Institute — Baymard's ongoing research on SERP behavior consistently finds CTR lifts in the 8-15% range when star ratings appear in a snippet vs. when they don't. The figure in the AggregateRating section reflects that range — exact lift varies by query and competitor density.

Google AI Overviews — citation behavior

Google AI Overviews — Google's public guidance on AI Overviews indicates that structured data, including FAQPage, is among the signals used to identify quotable answers. The "FAQ as AI-citation magnet" framing reflects observed citation patterns in 2025-2026 AI search results, not a controlled study.