Menu Interchange Format 2.0
A vendor-neutral JSON format for exchanging restaurant and cafe menus between systems — points of sale, ordering apps, delivery platforms, and websites. The normative definition is menu.schema.json (JSON Schema draft 2020-12); this page explains the structure and states the conformance rules that JSON Schema alone cannot express.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119. A producer is any system that authors or emits documents; a consumer is any system that parses them.
Machine-readable & LLM-friendly
Point a tool or an LLM straight at the raw files — the schema and example are served live from the source of truth, so they never drift.
1. Document structure
One document describes one venue and any number of menus (Breakfast, Lunch, Drinks…). Menus contain sections, which hold items and may nest sub-sections to any depth.
document
├── schemaVersion "2.0.0"
├── venue name, logo, timezone, contact info, openingHours
├── defaults currency + language inherited by everything below
├── modifierGroups[] reusable option sets, referenced by id
└── menus[]
├── available 86' the whole menu on/off (also on sections, items, variants, options)
├── availability day/time schedule, optional date range
└── sections[] nestable
└── items[]
├── price | variants[] one base price, or sizes/forms
├── modifierGroupIds[] references to shared groups
├── modifierGroups[] or inline, item-specific groups
├── dietaryLabels[] / allergens[] / spiceLevel
├── nutrition, media, tags, sku
└── available / availabilityCore types
| Type | Purpose | Key rules |
|---|---|---|
LocalizedText | Any display string | Plain string (in defaults.language) or a map of BCP-47 tags to strings. |
Price | Money | Integer minor units: {"amount": 450, "currency": "USD"} is $4.50. currency falls back to defaults.currency. |
PriceDelta | Modifier adjustment | Same as Price but the amount may be negative (“no cheese −0.50”). |
Variant | Purchasable form | Mutually exclusive sizes/forms with full prices. When present, they replace the item’s base price. |
ModifierGroup | Option set | minSelections > 0 makes it required; maxSelections: 1 makes it single-choice; omitted maxSelections means unlimited. |
Availability | Schedule | Day-of-week windows in venue.timezone, plus optional startDate/endDate bounds. Omitted means “always”. |
allergens / dietaryLabels | Filterable facts | Closed enums (EU 14 + US FDA superset). Free-form labels go in tags instead. |
extensions | Vendor data | Map whose keys must start with x-. The only place arbitrary properties are allowed. |
2. Conformance: authoring (producers)
- MUSTEmit documents that validate against
menu.schema.json. The schema setsadditionalProperties: falseeverywhere, so misspelled or invented fields fail validation rather than being silently carried along. - MUSTExpress every price in integer minor units of its currency, using the ISO 4217 exponent —
450is USD 4.50,120is JPY 120. Never emit floating-point money. - MUSTEnsure every price resolves to a currency: either an inline
currencyor adefaults.currency. A document containing a price with neither is non-conformant even though it validates. - MUSTEnsure every
modifierGroupIdsentry references theidof a group in the top-levelmodifierGroupsarray. Dangling references are non-conformant. - MUSTKeep
idvalues unique within their collection and stable across exports, so consumers can diff successive documents.skuis the place for an external POS identifier;idbelongs to this document. - MUSTPut vendor-specific data only under
extensionswithx--prefixed keys. Extensions MUST NOT change the meaning of core fields — a document is still correct when all extensions are ignored. - SHOULDSet
defaults.languagewhenever plain (non-map) strings are used, andvenue.timezonewhenever any availability uses times of day. - SHOULDOmit the base
pricewhen an item hasvariants— the variants replace it, so a base price is dead data at best and contradictory at worst. - SHOULDOmit optional fields entirely rather than emitting empty arrays, empty objects, or empty strings. Absence means “not stated”, never “present but empty”.
- SHOULDUse
available: falsefor temporary unavailability (an 86’d dish) and actual removal for items no longer sold. List allergens the item contains or may contain; when in doubt, include the allergen.
3. Conformance: parsing (consumers)
- MUSTResolve currency as
price.currency→defaults.currency; and language as the requested tag →defaults.language→ any available translation. If a price resolves to no currency, treat that item as invalid rather than guessing. - MUSTTreat
variantsas replacing the basepricewhenever both are present, and treat absence of an allergen as no claim — never as a guarantee that the item is free of it.dietaryLabelsare positive claims by the producer. - MUSTInterpret schedules in
venue.timezone, withstartTimeinclusive andendTimeexclusive; anendTimeat or beforestartTimewraps past midnight. Omitted availability means always available; an item’savailabilityfurther narrows the schedule of every section and menu that contains it. - MUSTInterpret
venue.openingHoursas when the venue is open, invenue.timezone, using the sameTimeWindowmodel: a split shift is two windows on one day, and a window with nostartTime/endTimeis open all day.openingHoursis required — every venue declares at least one window — and a weekday absent from every window means closed that day. It is venue metadata for display and does not gate any node’savailability. - MUSTTreat a node as unavailable whenever it or any ancestor has
available: false. The flag exists on menus, sections, items, variants, and modifier options — anavailable: falsemenu or section hides everything beneath it. Exclude unavailable nodes from ordering flows; they MAY still be displayed as sold out. - MUSTCompute an item’s allergen set as the union of its own
allergensand theallergensof every selected modifier option (oat milk → gluten). A modifier option’s allergens apply only when that option is chosen. - MUSTWhen re-emitting a document, preserve
extensionsand translations in languages the consumer does not itself handle. Round-tripping a document through a conforming system loses nothing. - SHOULDValidate incoming documents against the schema and reject those that fail, rather than repairing them silently. When resolving modifiers, apply shared groups (in
modifierGroupIdsorder) before inline groups; a dangling reference SHOULD be skipped with a warning, not treated as fatal. - SHOULDOn duplicate
ids within a collection, either reject the document or keep the first occurrence — consistently. Do not merge duplicates. - MAYIgnore fields irrelevant to the consumer’s purpose (a print renderer may skip
skuandnutrition), provided ignored data survives round-tripping per the rule above.
4. Versioning
schemaVersion is semver. Within a major version, new releases only add optional fields or enum values — a valid 2.0 document is a valid 2.x document. A major bump may add or tighten requirements: 2.0 makes venue.timezone and venue.openingHours required, so a 1.x document is not a valid 2.0 document. Consumers MUST reject documents whose major version they do not support, and SHOULD validate against the newest schema available for the major version they support, since a validator built from an older minor may reject fields added in a newer one. Producers MUST declare the version whose features they actually use.
Unknown values of open-ended strings (tags, cuisine) carry no interoperability burden. New enum values (allergens, dietary labels) only appear with a minor version bump, so a consumer validating against the matching schema never sees a value it cannot list.
5. Validating
npm install # ajv + ajv-formats node validate.mjs # checks examples/cafe-menu.json against menu.schema.json
Note that ajv’s optional strictRequired lint flag false-positives on the schema’s “at least one of items / sections” idiom; leave it at its default (off).
6. Change log
Every published version of the format, newest first. Each version's normative schema and example are served at a stable URL.
venue.timezone and venue.openingHours become required.
- BREAKING: venue.timezone and venue.openingHours are now required — every venue must declare its IANA timezone and at least one opening window. A 1.x document that omits them is not a valid 2.0 document.
Adds optional venue opening hours.
- Added the optional venue.openingHours field — a list of time windows, interpreted in venue.timezone, declaring when the venue is open.
Availability toggles across every level.
- Extended the available on/off toggle to sections, variants, and options (menus and items already had it), so any level of the menu tree can be 86'd while staying in the document.
- Extended the availability day/time schedule to sections.
Initial format.
- First schema: one venue with any number of menus, nestable sections, items, variants, and shared or inline modifier groups.
- Prices as integer minor units, localizable text, dietary labels, allergens, spice level, nutrition, media, and tags.
- Availability model: available on/off flags and availability day/time schedules on menus and items.
Check a file against this spec — open a file and see every problem with its line number, explained in plain language.