Structured Data for a Restaurant Menu

Restaurant, Menu, MenuSection and MenuItem: what the vocabulary actually offers, how to generate it so it cannot drift, and what Google does and does not promise.

Infographic with Tansy, the MenuWebStudio kitten mascot: Structured data for a restaurant menu. Menu, MenuSection, MenuItem; Generate it from the page; Eligibility, not a guarantee; Read the review rules first.

Structured data for restaurant menus is surrounded by more overpromising than any other part of restaurant web work. The honest position is narrow and worth stating plainly: the vocabulary exists, it maps cleanly onto a menu, marking it up makes you eligible for features, and Google says explicitly that eligibility is not a guarantee. Everything past that is speculation being sold as a deliverable.

What follows is what the vocabulary actually offers, how to generate it so it cannot go stale, and where the line is between doing this properly and being sold something.

What the vocabulary actually is

Schema.org is a shared vocabulary for describing things on the web. For restaurants it offers a small set of types that fit together, and the fit is unusually good because the vocabulary was designed by people who had looked at real menus.

Restaurant, and hasMenu

Schema.org’s Restaurant type is a subtype of FoodEstablishment, which itself sits under LocalBusiness. It inherits everything a local business has: name, address, telephone, opening hours, geo coordinates.

The property that matters here is hasMenu, which schema.org describes as either the URL of a menu, or the menu itself, as a structured representation or as text.

That sentence contains the whole argument. The vocabulary treats a structured menu as a first-class option, not as an exotic extra. A URL tells a machine where to look. A structured menu tells it what is there.

The Menu type represents a menu, and carries a hasMenuSection property for its sections and a hasMenuItem property for items that sit directly on the menu rather than in a section.

MenuSection represents a section, and it carries both hasMenuSection and hasMenuItem in turn. Sections nest. A menu can have a Dinner section containing a Starters section containing items, or a wine list can have Red containing Burgundy containing bottles, without inventing anything.

This nesting is the property that makes the vocabulary genuinely usable rather than merely present. A real menu is a tree, and this is a tree.

MenuItem is the leaf. It carries a name, a description, an offers property for price information, a nutrition property, a menuAddOn property for things that can be added, and a suitableForDiet property that takes a RestrictedDiet value.

Two of those deserve care. Nutrition and dietary properties describe claims about food, and whether to publish them is a decision for the kitchen and its own advisers. We build the field; we do not fill it, and we would treat any web studio that offered to as having wandered outside its competence.

What Google promises, and what it does not

This is where restaurant SEO proposals most often part company with the documentation.

The eligibility sentence

Google’s local business structured data documentation explains how to mark up a business so Google can understand details including the menu for food establishments. And Google’s documentation is consistent on the mechanism: adding structured data makes a page eligible for a feature, and eligibility is not a guarantee that the feature appears.

That is not a disclaimer tucked into a footer. It is how the whole system is described. A proposal that lists a specific rich result as a deliverable is promising something the vendor does not control.

Why it is still worth doing

Three reasons survive the caveat.

Without markup there is no eligibility, so the floor is definitely lower. Structured data is not read only by one search engine; it is a shared vocabulary that other systems, aggregators and increasingly language model based tools can consume. And generating it costs essentially nothing recurring once it is derived from content the page already holds.

The honest framing is that it is cheap insurance with an uncertain upside, not a growth lever.

The rule that governs everything

Google’s general structured data guidelines set out technical, quality and content requirements, and the central one is that structured data must be representative of the main content of the page and must not mislead users.

Markup describing dishes the page does not show, prices the page does not state, or hours the page contradicts is a violation of that requirement. This is the rule that makes generated markup not merely convenient but close to necessary.

Generating it so it cannot drift

Drift is the failure mode. It is quiet, it is guaranteed with hand-maintained markup, and it is entirely avoidable.

How drift happens

A restaurant launches with careful markup: every course, every dish, every price. Three months later the kitchen changes the menu. Whoever updates the page updates the visible text. Nobody updates the block of JSON in the template, because it is in a different file and it is not visible.

By the second changeover the markup describes a menu that no longer exists. By the fourth it is describing a season the kitchen has forgotten. It is now both useless and, under Google’s guidelines, a misrepresentation of the page.

One source, two renderings

The fix is structural rather than procedural. The menu is stored once, as structured content: sections containing items, each item with named fields. The page renders that content as typeset HTML. The markup serialises the same content as JSON-LD.

Nobody maintains the markup. Nobody can forget to update it. If the page says the dish is there, the markup says the dish is there, because they are two renderings of one object.

This is the same argument that applies to the printable version of a wine list, and it generalises: any time a piece of information exists in two hand-maintained places, it will eventually disagree with itself.

What to include, and what to leave out

A generated menu markup does not have to carry every field the vocabulary offers. The useful default is to emit what the page actually shows and nothing else.

If the page shows a dish name and a description, emit name and description. If it shows a price, emit an offers block with the price and currency. If it does not show a price, do not invent one for the markup, because the markup would then be describing content the page does not contain.

The restraint is the point. A minimal markup that exactly matches the page is worth more than an elaborate one that half matches it.

Ratings and reviews: read the policy first

This is the part of restaurant structured data where the risk is real rather than theoretical.

Why it is different

Most structured data is descriptive: this is a dish, this is its name. Review and rating markup is evaluative, it produces a visible star rating in results when it is shown at all, and it is therefore heavily policed.

Google’s structured data policies cover quality and content requirements that apply here, and there are additional documented requirements specific to review snippets, including on reviews a business collects about itself.

What we do on our own sites, and why

The client quotes on this site come from real clients. They carry no business names, no star counts and no invented numbers.

Because they are genuine, we mark each one up as a plain Review: who said it and what they said. We emit no rating and no AggregateRating, and we would not, because no ratings were ever collected and the markup would assert a score nobody gave. That is not a legal opinion; it is a decision about what we are willing to state in a machine-readable format about ourselves.

A restaurant displaying genuine reviews on its own site should read the requirements in full before marking them up, and if any part of the situation is ambiguous, take advice from someone qualified rather than from a web studio.

Testing, and what a test actually tells you

The tools

Google provides a Rich Results Test for checking whether a page is eligible for rich results, and the Schema Markup Validator at validator.schema.org checks markup against the vocabulary generally rather than against any one search engine’s requirements.

Both are worth running. They answer different questions: one asks whether Google can use this for a specific feature, the other asks whether the markup is valid schema.org at all.

What a green result means

It means the markup parsed and, in the Rich Results Test’s case, that the page is eligible. It does not mean a rich result will appear, it does not mean the markup is accurate, and it certainly does not mean the markup matches the page.

A page can pass every validator while describing a menu from last spring. No tool checks that, which is why the generation approach matters more than the testing approach.

The test that actually catches drift

The one worth building into a routine is trivial: open the page, open the markup, and check that a dish you can see on the page appears in the markup with the same name and the same price.

On a generated system this check passes by construction and takes ten seconds. On a hand-maintained system it is the check that will eventually fail, which is exactly why it is worth doing.

A worked shape, in plain English

It helps to see what the tree looks like before writing any code, because the shape is the part people get wrong.

The outer object

At the top sits the restaurant itself, with the fields any local business has: the name exactly as the business uses it, the postal address broken into its parts rather than as one string, the telephone number, the URL of the site, and opening hours expressed as specifications rather than as a sentence.

Opening hours are worth a note. A sentence like “closed Mondays, dinner from six” is readable and unparseable. The vocabulary wants a set of specifications, each naming days and times, which a generator can produce from structured opening-hours data and a page can render as the sentence the guest reads. Once again, one source, two renderings.

The menus underneath

A restaurant frequently has more than one menu: dinner, lunch, a tasting menu, a wine list, a bar list. Each is its own Menu object, each with its own name, and the restaurant references all of them.

This resolves a question that otherwise causes confusion, which is what to do when the site has several menu pages. The answer is that each page carries the menu it shows, and each menu is named, rather than one page trying to describe everything the kitchen serves.

Sections and the order they are in

Inside a menu, the sections are the courses, in the order the menu presents them. A tasting menu is a sequence, and the sequence is part of what it is, so the order of sections in the markup should match the order on the page rather than being alphabetised by whatever generated it.

Most generators preserve source order by default. It is worth checking once, because a tasting menu whose markup lists dessert first is describing a different restaurant.

Items, minimally

Each item carries the name as written and the description as written. Not a cleaned-up version, not a shortened version, not a version with the adjectives removed to save space. The kitchen chose those words and the markup should carry them, both because the guidelines require the markup to represent the page and because those words are the ones a search for a dish would match.

Price, where the page shows one, goes in an offers block with an explicit currency. A number with no currency is ambiguous to anything reading it and costs nothing to state properly.

Two failure modes worth naming

Marking up a PDF

A restaurant whose menu is a PDF sometimes adds menu markup describing the PDF’s contents to the page that links to it. The visible page contains a link and a sentence; the markup contains forty dishes.

This is a direct conflict with the requirement that markup represent the main content of the page, and it also solves nothing for the guest, who still has a PDF. If the dishes are worth describing to a machine, they are worth putting on the page.

Markup as a substitute for content

The subtler version is a page with thin visible content and elaborate markup, on the theory that the machine-readable version is what matters.

It is not, and it has not been for a long time. Structured data helps a machine understand content that exists. It does not stand in for content that does not. A menu page with real, well-set, complete text and minimal markup will outperform a thin page with exhaustive markup, and it will also be the page a guest actually wants.

Beyond the menu: the rest of a restaurant’s markup

Menu markup sits inside a larger set, and the rest of it is simpler and equally worth generating.

The business itself, as a Restaurant with its address, telephone, opening hours and the URL of the site. Breadcrumbs on deeper pages, so the hierarchy is explicit. Articles as BlogPosting with a real author, where the site publishes them. Frequently asked questions as FAQPage, but only where the questions and answers are actually visible on the page, because emitting question markup for content a visitor cannot see is precisely the misrepresentation the general guidelines prohibit.

Each of these follows the same rule as the menu: generate it from what the page renders, emit only what is actually there, and keep the number of hand-maintained blocks of JSON on the site at zero.

The honest summary

Schema.org gives a restaurant a genuinely good vocabulary for its menu, one that nests the way a real menu nests and that treats a structured menu as a first-class thing rather than as a link to a file.

Marking it up makes a page eligible for features and does not guarantee them. Doing it by hand guarantees drift. Doing it by generation costs nothing after the first build and cannot drift. Rating markup carries real risk and deserves to be read about directly rather than delegated.

That is the whole of it. It is a modest, cheap, worthwhile piece of technical work that should be described modestly, and a restaurant owner being sold it as a route to a specific position in search results is being sold something the seller cannot deliver.

Sources

  1. Restaurant, Schema.org
  2. Menu, Schema.org
  3. MenuItem, Schema.org
  4. Structured data general guidelines, Google Search Central
  5. Local business structured data, Google Search Central

Frequently asked questions

Does adding menu structured data get a restaurant a rich result in Google?

Not necessarily. Google's documentation states that adding structured data makes content eligible for a feature and that eligibility does not guarantee the feature appears. The markup is still worth adding, because without it there is no eligibility at all, but it should never be sold as a guaranteed placement.

What is the difference between hasMenu and a link to a menu page?

Schema.org's Restaurant type describes hasMenu as accepting the actual menu as a structured representation, as text, or a URL of the menu. A URL tells a machine where the menu is; a structured representation tells it what the menu contains, which is a meaningfully different amount of information.

Should structured data be hand-written for each menu update?

No. Hand-maintained markup drifts from the page within a season or two, and Google's general structured data guidelines require that markup represent the content actually visible on the page. Generating the markup from the same content the page renders removes the possibility of drift entirely.

Can a restaurant mark up its own customer ratings?

Google's structured data policies place specific requirements on review and rating markup, including on self-serving reviews, and getting this wrong risks a manual action. A restaurant should read the policies directly and, if in any doubt, not emit rating markup rather than guess.

Does MenuItem support dietary information?

Schema.org's MenuItem type includes a suitableForDiet property that takes a RestrictedDiet value. Whether to publish anything of that kind is a decision for the kitchen and its own advisers, not for a website studio, and the property should only be used where the kitchen is confident the statement is accurate.

Where should JSON-LD go in the page?

In a script tag of type application/ld+json, which is the format Google recommends for structured data. Placing it in the head or the body both work; what matters far more is that its contents are generated from the same source as the visible page.

Want a site like the one described here? Book a demo with MenuWebStudio.