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
Pass one — Google reads your prose
-
2
Pass two — Google reads your JSON-LD
-
3
Result — what Google is willing to render
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 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
{
"@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.
{
"@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.
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
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.
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 ]
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"
}
]
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.
"opens": "5:00 PM" — the 12-hour clock. Google reads opens as 24-hour only, so the open-now badge never fires.
"opens": "17:00" — 24-hour, zero-padded. Now the panel can compute “Open · closes 10 PM” in the guest’s own timezone.
"priceRange": "moderate" — a word where Google expects a symbol pattern. The price chip stays blank.
"priceRange": "$$" — the dollar-sign band Google renders directly as the price chip on the result.
"telephone": "(301) 555-0123" with a different number on the contact page. The mismatch demotes the listing.
"telephone": "+1-301-555-0123" — E.164, identical everywhere. One number, one entity, no contradiction to resolve.
"servesCuisine": ["Italian","Pizza","Bar","Cafe","Brunch"] — five cuisines dilutes the one you should win.
"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"
}
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."
}
}]
}
Six well-written FAQ entries — actually useful operational questions, not marketing copy — typically lift AI-search-driven traffic 20-40% within 60 days.
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.
-
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.
-
Q2Does
validator.schema.orgpass 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.
-
Q3Is
AggregateRatingsourced from your own reviews?The rating has to come from reviews published on your own site. Copying your Google star average into
AggregateRatingis 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.
-
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.
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:
- Copy the block below into a single
<script type="application/ld+json">tag inside your <head>. One tag, not six. - Change the seven fields marked
// EDIT: the restaurant name, address parts (street, city, region, zip), phone, opening hours, and the menu items. - 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
Roma Cucina — Italian Restaurant in Silver Spring
romacucina.example › menu
Italian restaurant in Silver Spring serving hand-rolled pasta and seasonal entrees. Reservations welcome.
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 ]
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.
- 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.
- 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. - 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
Paste into the <head> of every public page
-
2
Run Google’s Rich Results Test on three URLs
-
3
Wait 7–14 days for the re-crawl
-
4
Verify in Search Console › Enhancements
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.
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.