It seems that semantic, or schema, markup is everywhere now. From humble beginnings as microdata tags wrapped around important elements on a page, it’s evolved into an altogether different beast. We can state the actions a person can take on a page, or complex relationships between physical stores and the products they offer. Combined with new developments in AMP, schema markup powers the scrollable carousels in SERPs for hotels and recipes.

The move to JSON has removed the need for fiddly template changes. Still, we often end up with complex JSON objects on page with many elements needing to be dynamically updated. Because of this, getting new markup added to a site and tested still takes a while.

Inspired by this post last year from Chris Goddard on Moz, we decided to give injecting markup through GTM a go. In our opinion, using GTM variables for the dynamic elements is a little long-winded, but the process we took is similar. Almost every website these days uses the jQuery library in some form, and we can use its powerful element selection functions to get the information we need.

The Test

First, we built out the full markup for one example page, and ran it through the testing tool to check. In our case, we wanted to markup the webpage and the breadcrumb structure, so ended up with the following:

<script type="application/ld+json">
{
"@context": "http://schema.org",
    "@type": "WebPage",
    "url": "https://www.example.com/category/page",
    "name": "The Page Title",
    "description": "A short description of the page",
    "breadcrumb": {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "item": {
            "@id": "https://www.example.com/",
            "name": "Home"
          }
        },
        {
          "@type": "ListItem",
          "position": 2,
          "item": {
            "@id": "https://www.example.com/category",
            "name": "Category Name"
          }
        },
        {
          "@type": "ListItem",
          "position": 3,
          "item": {
            "@id": "https://www.example.com/category/page",
            "name": "The Page Title"
          }
        }
      ]
    }
}
</script>

The next step was to identify the parts in this markup that would need to change between pages. In this case, it was only a few elements: the page name, the URL, the description, and the 2nd and 3rd breadcrumb items. For each of these, we found the element on the page that contained the information and replaced the value in our JSON with the relevant jQuery selector.

One thing to be aware of is that our template uses relative URLs, but the specification asks for absolute URLs including protocol. To get around this, we added the protocol and hostname to the relative URL returned by jQuery.

<script type="application/ld+json">
{
"@context": "http://schema.org",
    "@type": "WebPage",
    "url": "https://" + document.location.href,
    "name": $("h1").text(),
    "description": $('meta[name=description]').attr("content"),
    "breadcrumb": {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "item": {
            "@id": "https://www.example.com/",
            "name": "Home"
          }
        },
        {
          "@type": "ListItem",
          "position": 2,
          "item": {
            "@id": "https://" + document.location.hostname + $('#country-tour-list > ul > li:nth-child(3) > a').attr('href'),
            "name": $('#country-tour-list > ul > li:nth-child(3) > a').text()
          }
        },
        {
          "@type": "ListItem",
          "position": 3,
          "item": {
            "@id": "https://" + document.location.href,
            "name": $("h1").text()
          }
        }
      ]
    }
}
</script>

All that’s left is to wrap the markup in a little JavaScript that creates a new script element, fills it with the markup, and adds it to the header. We can then put this code in a custom HTML tag in GTM, set it to fire on the pages we want, and put it live.

<script>
var data = {
"@context": "http://schema.org",
    "@type": "WebPage",
    "url": "https://" + document.location.href,
    "name": $("h1").text(),
    "description": $('meta[name=description]').attr("content"),
    "breadcrumb": {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "item": {
            "@id": "https://www.example.com/",
            "name": "Home"
          }
        },
        {
          "@type": "ListItem",
          "position": 2,
          "item": {
            "@id": "https://" + document.location.hostname + $('#country-tour-list > ul > li:nth-child(3) > a').attr('href'),
            "name": $('#country-tour-list > ul > li:nth-child(3) > a').text()
          }
        },
        {
          "@type": "ListItem",
          "position": 3,
          "item": {
            "@id": "https://" + document.location.href,
            "name": $("h1").text()
          }
        }
      ]
    }
}
var script = document.createElement('script');
script.type = "application/ld+json";
script.innerHTML = JSON.stringify(data);
document.getElementsByTagName('head')[0].appendChild(script);
</script>

The Results

After deploying, we ran a live page through the testing tool to make sure it was being picked up. Then, we sat back and waited for Search Console to update. Thankfully, it didn’t take long for things to trickle through, and we began to see the first pages after just a few days. Trickle is likely the correct word, however – it took longer for all pages to show up compared to what we would expect for hard-coded markup.

GTM Deployed JSON Structured Data Pickup

We wouldn’t recommend deploying markup in this way as a permanent solution. The way we read data off the page is completely dependent on the template and page structure. If these change at all, then the markup will stop working. It is, however, an excellent temporary fix during template changes or prior to initial schema markup deployment.