There is a signature to this problem, and once you have seen it you cannot unsee it.
Open Search Console, go to the performance report, and sort your queries by clicks. If the top twenty are your business name, your name plus a city, and a few near misses on your own brand, and there is nothing resembling a product, a strain, or a brand you stock, then your menu is not in the index. You do not have a ranking problem. You have a visibility problem, and no amount of content will fix it.
That query report is the fastest diagnosis in cannabis SEO. Two minutes, and it tells you whether the next six months of budget should go on writing or on plumbing.
Why this happens, and why it is not the platform’s fault
Almost every dispensary runs its menu through a third party. That is a sensible decision. Building compliant inventory software with live stock, state traceability reporting, and age verification is not a reasonable use of a retailer’s money.
The problem is not Dutchie, or Jane, or Meadow, or Tymber. It is which integration somebody chose, usually years ago, usually during a rush to open.
The default embed on most platforms drops your menu into the page inside an iframe. An iframe is a window onto a different website, and everything inside it belongs to the domain that served it. Your page contains the window. It does not contain the products. Which fix is available to you depends on the platform and on what your developer can change, and the options, in the order worth trying, are set out in the technical module.
Here is the part most operators miss: this is not a bug anyone is going to fix. It is how iframes have always worked and how they are supposed to work. The content is being credited correctly. It is simply being credited to somebody else.
The half-truth that keeps this problem alive
Somebody will tell you Google renders JavaScript now, so a JavaScript menu is fine.
That is true, and it is not the whole story, and the gap between those two things has cost dispensaries a great deal of money.
Google indexes in two passes. The first reads your HTML immediately. The second renders JavaScript and comes back later, from a queue, on a schedule nobody outside Google can see. For a high authority news site that second pass might arrive in minutes. For a regional dispensary with a modest crawl budget it can be days, and it is not guaranteed to complete.
So a JavaScript menu is genuinely better than an iframe, because at least the content could eventually belong to you. But “could eventually” is a poor foundation for the page carrying your entire catalogue, particularly when stock rotates weekly and the second pass may arrive after the product has sold out.
An iframe menu is never yours. A JavaScript menu is yours on a delay. A server rendered menu is yours immediately. That is the real hierarchy, and most conversations collapse it into “JavaScript is fine now”, which is exactly how the problem survives.
What it looks like on a real account
On a fifty store chain, the menu had been embedded the same way on every location page for three years. Search Console showed the pages indexed, and the marketing team reasonably took that as confirmation everything was working.
Not one product name and not one brand they carried appeared in the rendered HTML.
The fix was scoped inside a week. The three years of product and brand searches that went elsewhere were not recoverable.
The detail worth sitting with is that nobody had been careless. The pages were indexed, the site was fast, the design was good, and the agency reports were full of green.
Indexed means Google has your URL. It says nothing about whether Google has your content, and no standard report distinguishes between the two. That single ambiguity is responsible for more wasted cannabis marketing spend than any other technical issue I encounter.
The commercial problem underneath the technical one
There is a second cost here that rarely gets named.
When your products live on your menu provider’s domain, the search value they generate accrues to your menu provider. You pay a monthly fee for the software, then pay again in visibility that lands on somebody else’s asset.
Worth raising when your contract renews, and worth asking before you sign a new one. The question to put to any menu vendor is short: does your integration place product content in my page’s HTML, on my domain, and can you show me a customer whose product pages rank?
Most sales conversations never reach that question, which is why most integrations default to the version that is easiest to install rather than the version that is best for you.
How to check it yourself
Do not take a tool’s word for it, including mine.
Open Search Console. Run URL Inspection on a menu page. Choose Live Test, open the rendered HTML, and search it for a product you stock and a brand you carry. If neither appears, you have your answer. If they appear only inside an <iframe> tag pointing at another domain, you have the same answer.
There is a cruder ten second version, and it is the one I use most. View source rather than Inspect Element. Inspect shows the live DOM after every script has run, which is why it always looks fine, and why it has misled so many teams. View source shows the HTML as delivered. If your products appear in Inspect but not in view source, that comparison is the entire problem in one screen.
Check it in about twenty seconds
The menu visibility checker detects which platform you run, works out whether the embed is an iframe or JavaScript, and counts how many product terms, brand names, and prices survive into the HTML Google reads.
Open the menu visibility checker
How to fix it
Three options, cheapest first. Be honest about which one your team will actually ship, because a plan nobody executes is worth less than a smaller plan somebody does.
1. Build real content around the embed. Category descriptions, brand explainers, and buying guidance written into the page HTML. This does not make your menu visible and it is not a fix. What it does is stop the page being thin, which removes the immediate penalty risk while you work on the real solution.
2. Ask your provider for a server rendered menu. Several platforms now offer an SEO friendly hosted option with its own sitemap and crawlable product URLs. Enabling it is often a support ticket rather than a project, and a surprising number of operators have simply never asked.
3. Render the menu through their API. Pull product data server side and render it into your own pages. This is the version that wins product and brand searches, because the content is yours, it is always in the HTML, and it sits on your domain.
Why this hits chains harder than single stores
One thin page is a missed opportunity. Forty of them, one per store, built from the same template, is the pattern that precedes a scaled content manual action.
That is how a menu integration decision made in year one quietly becomes a penalty in year three. And a manual action does not remove the page that triggered it. It removes everything matching the pattern, which means every location you operate, on the same day, with no warning and no staggered recovery.
Do not commission a content programme before fixing delivery.
Writing more pages onto a foundation Google can only half read is how agencies burn a quarter of budget with nothing to show for it. If the fix turns out to be a rebuild rather than a setting, what that work involves is set out here. It is the most common sequencing mistake in this industry, largely because content is easy to sell and menu rendering is not.
One note on subdomains
Some operators are told to put the menu on a subdomain, like menu.yourdispensary.com. That does work, but it needs deliberate setup, because a subdomain builds its own authority rather than feeding yours.
If you go that route: include it in your sitemap, point real internal links at it, and check that canonical tags on both sides are not contradicting each other. Done carelessly it either outranks your own brand pages or it never ranks at all.
If you take one thing away
Fix how the content is delivered before you invest in writing more of it.
