NOVUS / RESTAURANT

Tutorial

Watch a competitor's published menu prices

Add a permitted public menu URL, read the extraction status before the prices, and understand why the result is usually shorter than their real menu.

About 7 minutes. Last reviewed against the product on .

By the end of this you will have one competitor's published menu prices stored beside your own, and you will understand why that list is usually shorter than the menu they actually serve.

1. Put your own menu in first

Open Menu and add the items you want to compare. Without them there is nothing to compare against, and a competitor's price list on its own tells you very little: a dish you do not sell at a price you do not charge is trivia.

2. Add the competitor

Open Competitors. Give the competitor a name, paste the public URL of the page that carries their menu, and tick the permission confirmation. That tick is stored as a record: the site keeps who confirmed it and when.

Before anything is fetched, the URL has to survive several checks. It must use HTTP or HTTPS, it must carry no username or password, and it must not resolve to a local or private address. The site then reads the origin's robots.txt and refuses the path if robots.txt disallows it.

3. Read the status before the prices

Each competitor card shows an extraction status and a message. Read those first, because three of the four outcomes mean there are no prices to look at.

What each extraction status means
StatusWhat happenedWhat to do
ProcessingThe row was created and no result has been recorded yetSubmit the URL again
CompletedStructured menu items were found and storedCompare them with your menu
EmptyThe page published no MenuItem or Product structured dataNothing here can be read this way
FailedA check or the fetch stopped it, and the card states the reasonRead the message before retrying

Measured from the competitor server action at app/app/competitors/actions.ts.

An empty result is the common one, and it is a statement about their markup rather than about their prices. Only structured data embedded in the page is read. A menu that is an image, a PDF, or ordinary HTML text produces nothing at all, no matter how clearly a person can read it.

Fetch timeout
12 seconds
per request, including the robots.txt read
Redirects followed
5
each hop is re-checked against the same URL rules
Page size read
2 MB
a page declaring more is refused, an undeclared one is cut here
Items stored
500 maximum
per competitor, after duplicates are removed

Measured from the competitor extractor at src/lib/competitors/extractor.ts.

4. Compare only what is comparable

A stored price is what that page published, in the currency that page declared. It is not their cost, not their margin, and not necessarily current: a menu page can sit stale for months and nothing here can detect that. Portion size, inclusions and service model are not captured at all, so two dishes sharing a name are not evidence of the same offer.

5. Leave with one item

Choose the single dish where their published price and yours differ most, and decide one thing about it: hold, change, or find out more. A whole-menu response to a competitor's whole menu is the reliable way to act on nothing.

What is measured and what is assumed

Measured: the item names, descriptions, prices and currency codes that page published as structured data, the URL they were read from, and the moment they were read.

Assumed: that the published structured data matches what the kitchen actually charges, and that a matching dish name means a comparable dish.

Not available at all: history. Re-submitting the same URL replaces that competitor's stored items rather than adding to them, and nothing re-runs on a schedule. There is no record of what a competitor charged last month, so this surface can answer what they publish now and cannot answer whether they have raised it. Someone with viewer access cannot add a competitor or trigger an extraction.

Next: use guest reviews without inventing a score.