Google Maps Menu Scraper
Pricing
from $3.00 / 1,000 results
Google Maps Menu Scraper
Extract restaurant menu items, popular dishes, photos, categories, and external menu links from any Google Maps place page.
Pricing
from $3.00 / 1,000 results
Rating
0.0
(0)
Developer
Crawler Bros
Maintained by CommunityActor stats
0
Bookmarked
31
Total users
7
Monthly active users
a day ago
Last modified
Categories
Share
Extract restaurant menu items, popular-dish indicators, dish photos, menu categories, and external menu links from any Google Maps place page.
What this actor does
Give this actor a Google Maps URL for a restaurant or food business and it returns:
- Menu items with names and photos (one record per item)
isPopularindicator —truewhen Google highlights a dish as popular- Menu categories — the section names (e.g.
Appetizers,Ramen,Desserts) that Google exposes for that restaurant - External menu URL when the restaurant links to its own menu site, PDF, or ordering portal
- Place ID / feature ID for cross-referencing with the Google Maps ecosystem — a canonical
ChIJ…token is emitted asplaceId, and a legacy0x…:0x…token asfeatureId - Typed
menuSummaryrecord whenever Google serves no inline menu cards for the loaded place — a common, expected outcome because the inline dish-card surface is intermittent — preserving the observed place identity, external menu URL, categories, andmenuFound: falsewithout inventing an item
Availability: Google serves the inline dish-card surface intermittently. On many runs a loaded restaurant page returns no inline cards at all, and the actor emits a truthful
menuSummaryrow withmenuFound: falseinstead. That is an expected outcome, not a malfunction — the actor never fabricates items to fill the gap, and the same place URL can return cards on one run and none on the next.The two "no items" outcomes are kept apart. Google also intermittently serves a degraded place page — a "limited view" shell in which the place loads with its real name but the tab strip drops the Menu tab entirely. That render has no menu surface at all, so the actor does not treat it as an empty menu. It retries the page in a fresh anonymous context a bounded number of times (the degraded state is per-context, so only a fresh context clears it), and it accepts an attempt only once the menu content has actually rendered. If the surface still never renders, the row carries
menuSurfaceRendered: falseand the status message says the surface was never observed, somenuFound: falseis never read as "this place has no menu".
Use cases
- Power menu intelligence for delivery, restaurant tech, or food blog platforms
- Discover signature / popular dishes per restaurant
- Aggregate menu category coverage by cuisine, location, or chain
- Enrich CRM / lead-gen datasets with menu signals
- Build product catalogues that link back to the operator's own menu page
Input
| Field | Type | Required | Description |
|---|---|---|---|
placeUrl | String | One of placeUrl/placeId | Google Maps URL of a single restaurant |
placeId | String | No | Canonical Google Place ID beginning with ChIJ; use instead of placeUrl |
maxItems | Integer | No | Maximum menu items per run (1-100,000). Default 100; records are emitted progressively and Google may expose fewer |
language | Enum | No | UI language (en, es, fr, de, it, pt, ja, ko, zh, ar, ru). Default en |
proxyConfiguration | Object | No | US RESIDENTIAL recovery |
Examples
Example: restaurant menu by URL
{"placeUrl": "https://www.google.com/maps/place/McDonald's/@40.7577297,-73.9853627,17z/","maxItems": 50,"language": "en"}
Example: restaurant menu by canonical Place ID
{"placeId": "ChIJabtoT7hlwokRLECK0SW3_8I","maxItems": 1000,"language": "en"}
Example: Japanese menu extraction
{"placeUrl": "https://www.google.com/maps/place/Sushi+no+Midori/@35.6704396,139.7596577,17z/","maxItems": 100000,"language": "ja"}
Tip: Copy the URL straight from the address bar after opening a restaurant on Google Maps. Country-specific domains, short links, wrapping quotes, and tracking parameters are normalized.
Output per record
The actor emits one of two record shapes. Which shape you get for a given run is decided by Google, not by the input: the inline dish-card surface is served intermittently, so both are normal observed output.
Place-level summary row (menuSummary)
When the place page loads but Google serves no inline dish cards, the actor emits one place-level row. This is the shape recent runs returned for McDonald's, The Halal Guys, Sushi no Midori, and The Cheesecake Factory — menuFound: false, itemCount: 0, and no fabricated item. Field values below are illustrative; each row contains only what Google exposed for that place:
{"recordType": "menuSummary","businessName": "The Halal Guys","featureId": "0x89c258ff097dbc2d:0x86df3a18a06daf7c","placeUrl": "https://www.google.com/maps/place/...","menuUrl": "https://example.com/menu","menuCategories": ["Platters", "Sandwiches", "Sides"],"menuFound": false,"menuSurfaceRendered": true,"itemCount": 0,"sourceUrl": "https://www.google.com/maps/place/...","scrapedAt": "2026-06-16T12:00:00Z"}
This row is a place-level observation, not a menu item: it preserves any observed external menuUrl, categories, place identity, and menuFound: false; item-only fields (itemName, isPopular, itemImage, rank) remain absent. menuFound: false means "Google served no inline dish cards on this run" — it does not mean the restaurant has no menu. menuSurfaceRendered states whether the menu surface was actually seen: on an item row it is always true (the card came from the rendered surface), and on a summary row true means the Menu panel rendered and held no inline cards (an observed-empty menu), while false means the surface was never observed — Google's degraded limited view, or a place page that exposes no Menu tab — in which case the status message says so explicitly and no empty-menu claim is made. Because the flag now rides on item rows too, it is present on every row of a run, including a populated one. If the place itself cannot be reached after the bounded navigation/fallback attempts, no summary row is emitted and the run status explains the unavailable source (an upstream load failure, not a place that publishes no menu).
Inline item row (menuItem)
When Google does serve the dish cards, the actor emits one record per item. The values below illustrate the emitted field set — they are not a claim about current availability: an earlier capture of the same McDonald's URL returned 20 such rows, while a later capture of the same input returned none.
{"recordType": "menuItem","businessName": "The Halal Guys","featureId": "0x89c258ff097dbc2d:0x86df3a18a06daf7c","placeUrl": "https://www.google.com/maps/place/...","menuUrl": "https://example.com/menu","menuCategories": ["Platters", "Sandwiches", "Sides", "Beverages", "Desserts"],"menuFound": true,"menuSurfaceRendered": true,"itemName": "Chicken and Beef Gyro Platter","isPopular": true,"itemImage": "https://lh3.googleusercontent.com/...=s0","rank": 1,"sourceUrl": "https://www.google.com/maps/place/...","scrapedAt": "2026-06-16T12:00:00Z"}
When Google exposes a canonical ChIJ… Place ID it is emitted as placeId; the legacy 0x…:0x… form is always emitted as featureId and is never placed under placeId. Only the observed form is emitted — if just the legacy token is seen, placeId is omitted.
Empty / null fields are stripped from output to keep records compact. A record contains only menu and place fields that Google exposed for that item.
Field Reference
| Field | Type | Notes |
|---|---|---|
recordType | String | menuItem for an inline menu item, menuSummary for the place-level row emitted when Google exposes no inline items |
businessName | String | Restaurant name from the place page heading |
placeId | String | Canonical Google Place ID beginning with ChIJ, when observed |
featureId | String | Legacy 0x…:0x… feature token, when observed. Never emitted under placeId |
placeUrl | String | The original Google Maps URL |
menuUrl | String | External menu link (restaurant site, PDF, ordering portal) when present |
menuCategories | Array<String> | Menu section names exposed by Google for this restaurant |
menuFound | Boolean | true when at least one inline menu item was extracted; false on a truthful menuSummary row |
menuSurfaceRendered | Boolean | Stated on every row. On a menuItem row it is always true — an emitted dish card is itself proof the Menu surface rendered — so a populated run is verifiable without a summary row. On a menuSummary row it is true when the Menu panel rendered but held no inline cards (an observed-empty menu), or false when the surface was never observed (Google's degraded limited view, or no Menu tab). Absent only when it was never observed |
itemCount | Integer | Number of inline menu items, present only on a menuSummary row (always 0) |
itemName | String | Menu item name |
isPopular | Boolean | true when Google flags the item as Popular |
itemImage | String | Max-resolution photo URL of the dish (when available) |
rank | Integer | Position of the item within the menu (1-based) |
sourceUrl | String | Source Google Maps URL for the row |
scrapedAt | String | ISO 8601 UTC timestamp |
FAQ
Does every restaurant on Google Maps have a menu?
No — inline items only appear for restaurants where Google has indexed menu cards and serves that surface on the run. Google serves the inline dish-card surface intermittently, so a place that returned cards yesterday can return none today (and vice versa). When inline items aren't served, the actor emits a truthful place-level menuSummary row instead; any observed external menu link is available in that row. menuFound: false is an expected outcome, not evidence the actor is broken. When the page came back in Google's degraded "limited view" shell — no Menu surface at all — the actor retries in a fresh context and, if the surface still never renders, reports menuSurfaceRendered: false with a status message that says the surface was never observed rather than claiming an empty menu.
Why no prices on inline items?
Google Maps does not currently render per-item prices on the place page Menu tab — it shows the dish name, image, and a "Popular" badge but the price (when published) lives behind the external menu link. The actor surfaces every signal Google exposes; if you need item-level prices, follow the menuUrl to the restaurant's own menu site.
Do I need a proxy or cookies?
No login or cookies are required. The actor attempts direct public access, retries the menu surface in fresh anonymous contexts, and then uses its hardcoded Apify US RESIDENTIAL recovery path when no menu signal is observed. {"useApifyProxy": false} is available for direct-only diagnostics.
Which languages are supported?
Eleven languages are exposed via the language input: English, Spanish, French, German, Italian, Portuguese, Japanese, Korean, Chinese, Arabic, Russian. This controls the Google Maps UI language only — menu item text is returned exactly as published. Google's menu content is crowd-sourced/aggregated, so a single place's menu can legitimately carry an item whose name is in another language (an English-language US McDonald's page was observed serving French Fries alongside a Turkish Patates Buyuk Boy and a Spanish Papas Fritas, all within the same Menu region of that place). Such entries are returned verbatim rather than filtered: they are Google's own data for that place, not a mix-up.
How many items per run?
The default cap is 100 and you can request up to 100,000 items per run with maxItems. Records are emitted progressively and the actor stops when the public menu cards are exhausted. The ceiling is a limit, not a promise: because Google serves the inline dish-card surface intermittently, a place may expose dozens of cards, a handful, or none at all on a given run. Observed captures of the same popular-chain URL have returned anywhere from 0 to about 20 cards, so treat maxItems as an upper bound rather than an expected count.
Can I batch multiple restaurants?
This actor accepts one placeUrl per run. To process many restaurants, schedule the actor with different inputs or chain it inside a workflow that iterates over URLs.
Why are some fields missing from my records?
Empty / null fields are stripped to keep your dataset compact. If itemImage or menuUrl is absent it simply means Google did not publish that detail for that restaurant. Each emitted item includes its record type, place URL, and scrape timestamp.
Notes & Limitations
- Google's inline Menu data is shallow. Google Maps shows dish name + image + a "Popular" badge on the place page, but does not render per-item prices or descriptions inline. Where the operator publishes the full menu (prices, sizes, modifiers, etc.) it lives behind the external
menuUrlwe surface. - Coverage varies, and the inline surface is intermittent. Google serves the inline dish-card surface for some places and some runs, not for others, and the same place URL can return cards on one run and none on the next.
menuFound: falsewith a truthfulmenuSummaryrow is therefore a normal, expected result and not proof the actor is broken or that the place publishes no menu. When cards are served you typically get a handful to a few dozen dish rows (observed captures of the same chain URL returned 0 to about 20); the actor never pads the dataset up to the requestedmaxItemsand never emits a fake dish row. - Place resolution is search-first. The actor resolves the input URL via Google Maps search; very ambiguous names without coordinates may resolve to a different branch — include
@lat,lngin the URL when targeting a specific location.
You are responsible for using the exported menu data lawfully and in line with Google's terms.
Complete Google Maps Scraper Suite
This actor is part of a comprehensive Google Maps data extraction toolkit by crawlerbros. All actors run cookielessly on the free Apify plan with bounded direct, retry, and residential recovery paths, and return clean, structured data.
| Actor | What it does |
|---|---|
| 🏢 Google Maps Business Scraper | Extract business data — name, address, phone, website, rating, reviews, hours, amenities |
| ⭐ Google Maps Reviews Scraper | Scrape reviews with reviewer Local Guide level, photos, mentioned items, owner replies |
| 📸 Google Maps Photos Scraper | Extract all photos from any place — max-resolution URLs, contributor info, categories |
| 🕐 Google Maps Business Hours Scraper | Full 7-day hours, timezone, current local time, next open/close, holiday hours |
| 📊 Google Maps Popular Times Scraper | Busy hours histogram for all 7 days + current busyness + typical visit time |
| 📧 Google Maps Email Extractor | Find business emails + social media links by crawling websites |
| 🗺️ Google Maps Area Scanner | Geographic grid scanning — bypass the 120-place limit with bounding box / circle / polygon |
| 💼 Google Maps Leads Scraper | B2B lead generation with email + phone enrichment, US states + global countries |
| 🤖 Google Maps MCP Server | Unified MCP server combining search + reviews for AI assistants |
| 🧭 Google Maps Directions Scraper | A→B routing — distance, duration, traffic, route alternatives for driving/walking/transit |
| 📍 Google Maps Geocoding Scraper | Bidirectional geocoding — address ↔ coordinates, with address components |
| 🔗 Google Maps Similar Places Scraper | "People also search for" / related place discovery |
| 📌 Google Maps Nearby Scraper | Find places near a coordinate point — lightweight POI search by category |
| 📋 Google Maps Place List Scraper | Extract Google's curated "Top X in Y" lists |
| 🌍 Google Maps Timezone Scraper | IANA timezone + current local time from coordinates |