Yes, you can add JSON-LD with Google Tag Manager, and Google will read it. Google lists Tag Manager as a supported way to generate structured data, and Googlebot picks the markup up when it renders the page. The catch: crawlers that don't run JavaScript never see GTM-injected JSON-LD, and that includes most AI crawlers, OpenAI's and Anthropic's among them. Even for Google, the markup arrives later and is harder to debug than JSON-LD sitting in your HTML. So GTM is fine as a stopgap. For anything that matters, put it in the page source.
Below I'll go through when GTM is good enough, when it isn't, and how to set it up properly if it's your only option. If structured data is new to you, my JSON-LD structured data guide is a better starting point.
Does Google read JSON-LD added through Google Tag Manager?
Yes, but only after it renders the page. Google's own guide to generating structured data with JavaScript names Tag Manager as one of the two common methods. You create a Custom HTML tag, paste a JSON-LD block into it, publish the container and test. The same guide says that "Google Search can understand and process structured data that's available in the DOM when it renders the page."
Notice the word renders. A GTM tag doesn't touch the HTML your server sends. It adds a script element after the browser (or Googlebot's renderer) has loaded and run the GTM container. Anything that only reads the raw HTML gets your page with no schema on it.
How does GTM-injected schema reach Google?
It gets there at the end of a chain. The HTML is crawled, the page waits in Google's render queue, the GTM container loads, and then the Custom HTML tag fires. Here's that path, and the point where it stops for crawlers that don't run JavaScript:
Google's JavaScript SEO basics describes three phases: crawling, rendering and indexing. Once a page is crawled it sits in a separate render queue, and Google admits it isn't obvious whether a page is waiting to be crawled or waiting to be rendered. JSON-LD in the HTML is there on the first pass. JSON-LD from GTM only exists after rendering, and only if the container loads, the trigger fires and nothing blocks the script.
Most of the time all of that works. But every step is one more place it can quietly break.
Which crawlers miss JSON-LD added with GTM?
OpenAI's crawlers, Anthropic's ClaudeBot and any tool that only reads raw HTML will miss it. Googlebot, Gemini and Applebot render JavaScript, so they can see it. This is the big thing that's changed since people started putting schema in GTM: Google isn't the only reader any more. Vercel looked at how AI crawlers behave in The rise of the AI crawler and found that "none of the major AI crawlers currently render JavaScript." Some of them download JavaScript files, but they don't run them. The research notes two exceptions: Gemini uses Googlebot's infrastructure, and Applebot renders pages with a browser-based crawler.
| Crawler | Runs JavaScript? | Sees GTM-injected JSON-LD? |
|---|---|---|
| Googlebot (Search) | Yes | Yes, after rendering |
| Gemini (via Google's infrastructure) | Yes | Yes |
| Applebot | Yes | Yes |
| OpenAI (GPTBot) | No | No |
| Anthropic (ClaudeBot) | No | No |
| PerplexityBot | No | No |
| Tools that read raw HTML only | No | No |
So if you want ChatGPT, Claude or Perplexity to understand your business, products or articles, schema that only appears after GTM runs does nothing for them. Whether AI tools can use your site at all comes down to two things. First, your robots.txt rules (my free AI Crawler Checker reads them for you). Second, whether your content is actually in the HTML.
What else goes wrong with JSON-LD in GTM?
The rendering gap is the headline problem, but there are five others I look for on any site running schema through GTM: data drift, product markup, consent banners, Content Security Policy blocks and debugging.
The data drifts away from the page. Google's guide warns that copying information into GTM "increases the risk of having a mismatch" between the page and the markup, and suggests GTM variables that read values from the page instead. That helps. But those variables scrape the DOM using CSS selectors, so if someone renames a class in a redesign, the price or headline variable starts returning blank or wrong values. The site looks fine. Nobody notices.
Product markup is the riskiest case. Google's merchant listing documentation recommends putting Product structured data in the initial HTML. It warns that dynamically generated markup "can make Shopping crawls less frequent and less reliable," which hurts most when prices and stock change often. If you run a WooCommerce or Shopify store, keep product schema out of GTM.
Consent banners can block it. Plenty of sites only load GTM, or only fire some tags, after a visitor accepts cookies. Googlebot doesn't click Accept. Put your schema tag behind a consent trigger and it never fires for crawlers. Structured data collects no personal data, so there's no reason for it to wait.
A Content Security Policy can block it. A GTM Custom HTML tag injects an inline script. A strict Content-Security-Policy that doesn't allow inline scripts or GTM's nonce will block it, and the only sign is an error in the browser console. I maintain the HTTP Security Header plugin for WordPress, and this is exactly the sort of clash a tight policy causes. Fix it by allowing the specific script or nonce you need. Don't open the whole policy up.
Debugging gets harder. A developer checking View Source or the theme templates won't find the markup. Then an SEO plugin adds its own Organization or Article schema, GTM adds another, and the page ends up with two conflicting entities. Often whoever set up the tag has moved on and nobody remembers it exists.
When is GTM fine for structured data?
When the setup is temporary, or when only Google needs to read the markup. Real examples:
- You can't edit the templates. You're on a locked-down hosted platform, or a template change takes months to get through release, and GTM is the only door open.
- You're testing. You want to see if Google picks up a markup type before asking a developer to build it properly.
- Only Google needs it. Simple site-wide markup like Organization or WebSite, where Google is the main audience and the values hardly ever change.
- You're mid-rebuild. The new site will output schema from its templates, but you want coverage until it launches.
In every one of these, treat the GTM version as temporary and write down where it lives.
When should JSON-LD go in the page source instead?
Whenever the markup describes things that change, make money or need to be quoted by AI assistants: products, prices, articles, events. Here's the comparison:
| Factor | JSON-LD via GTM | JSON-LD in page source |
|---|---|---|
| Read by Googlebot | Yes, after rendering | Yes, on first crawl |
| Read by AI crawlers that don't run JS | No | Yes |
| Product prices and stock | Google advises against it | Recommended by Google |
| Stays in sync with content | Only if variables keep working | Yes, from the same data |
| Affected by consent banners or CSP | Yes | No |
| Visible in View Source | No | Yes |
| Needs a developer | No | Usually, once |
On WordPress, an SEO plugin or the theme templates can output most schema types straight into the HTML. Custom post types with ACF fields can feed things like event dates or author details into the markup. On Next.js, render the JSON-LD as a <script type="application/ld+json"> in a server component, so it's in the first HTML response rather than added in the browser. That's how it should be set up on any Next.js or React website. Either way, the markup comes from the same data as the visible page, so it can't drift.
I use GTM all the time, just for what it's built for, which is tracking. On the Aura Clips rebuild, Tag Manager is there to run GA4. That's its job.
How do you add JSON-LD with Google Tag Manager properly?
Put valid markup in a Custom HTML tag, fire it on a reliable trigger that doesn't wait for consent, test the live URL, and write down where it lives. If GTM really is your only option for now, here's how I'd do it. My free Schema Markup Generator gives you a valid block to start from:
- 1
Write valid JSON-LD
Generate the markup for your schema type and check it against schema.org.
- 2
Create a Custom HTML tag
Paste the script block into a new Custom HTML tag. Use GTM variables for values that change per page, instead of hard-coding them.
- 3
Pick a reliable trigger
Fire it on Page View or DOM Ready, limited to the pages it describes. Don't put it behind a consent trigger.
- 4
Check it in Preview mode
Use Tag Assistant to confirm the tag fires and the variables return real values, not undefined.
- 5
Test the live URL
Run the Rich Results Test with the URL input, then check the rendered HTML in Search Console's URL Inspection tool.
- 6
Document it
Note in your site docs that schema lives in GTM, so the next developer or SEO plugin doesn't duplicate it.
When you test, use the URL input in the Rich Results Test rather than the code input. Google recommends this because the code input has JavaScript limitations such as CORS restrictions (source).
How do you check what non-Google crawlers see?
Open the page, choose View Source (not Inspect) and search for application/ld+json. View Source shows the raw HTML. Inspect shows the DOM after scripts have run, which hides the exact problem you're looking for. Or from a terminal:
curl -s https://example.com/your-page | grep -c 'application/ld+json'
If that returns 0 but the Rich Results Test finds your schema, the markup only exists after JavaScript runs. Google sees it. The AI crawlers in Vercel's study don't.
Inspect is not View Source
Browser dev tools show JSON-LD that GTM injected, so the schema looks present. Check View Source or curl to see what a crawler that doesn't run JavaScript gets.
What should you do next?
Take your three most important pages and run them through the Rich Results Test and the curl check above. If the schema only shows up after rendering, and those pages sell products or you want AI assistants quoting them, move the markup into your templates. On WordPress or Next.js that's usually a small one-off job, and you can delete the GTM tag once the templates are outputting it.
If you'd rather I audit your structured data and move it into the page source, that's part of my technical SEO and Core Web Vitals service. Send me the URL through the contact page and I'll tell you what each crawler can see right now.



