Andrew Welch · Insights · #SEO #json-ld #structured-data

Published , updated · 5 min read ·


For more tools, technologies, and techniques, check out the devMode.fm podcast!

Annotated JSON-LD Structured Data Examples

Want to see a some real-world exam­ples of JSON-LD Struc­tured Data in the wild”? This arti­cle has that, plus annotation.

Struc­tured data allows you to tell search engines con­tex­tu­al infor­ma­tion about what is on your web pages, rather than leav­ing it up to them to guess. It uses the stan­dard­ized vocab­u­lary from schema​.org expressed via JSON-LD to con­vey the con­text and con­nec­tions between data.

Many arti­cles talk about struc­tured data; this arti­cle is going to focus on real-world exam­ples of using struc­tured data in the wild”. It uses the SEO­mat­ic plu­g­in for Craft CMS in the exam­ples, but this arti­cle pro­vides tool­ing-agnos­tic anno­tat­ed JSON-LD struc­tured data exam­ples that any­one can use.

I’m not going to try to sell you on the ben­e­fits of using struc­tured data in this arti­cle. I’m going to assume that you’re already on board, and want to see some real-world, anno­tat­ed examples.

For more infor­ma­tion on JSON-LD struc­tured data in gen­er­al, check out the JSON-LD, Struc­tured Data and Erot­i­ca article.

Link A General Approach to Structured Data

While there are sev­er­al for­mats for struc­tured data, Google, Apple, and oth­er major play­ers in the indus­try have made it clear that JSON-LD is the way for­ward. JSON-LD is the same JSON we’re used to from JavaScript, with the LD stand­ing for Linked Data”.

Schema​.org defines a large vocab­u­lary of struc­tured data schemas to choose from; how do we decide what to imple­ment? My approach is twofold:

After all, we’re putting struc­tured data onto our web­site so that search engines can under­stand it, but we also want to be prag­mat­ic about it, and focus our ener­gy on the schema types that we know Google will consume.

Google only concerns itself with a subset of schema.org types

Check out the schema types that Google has pub­licly sup­port­ed and also explore the Google Search Gallery for an inter­ac­tive look at how schema types can affect not just the knowl­edge graph, but also the Search Engine Results Page (SERP).

Link Node Identifiers and mainEntityOfPage

Before we get into the spe­cif­ic exam­ples, there are a cou­ple of gen­er­al con­cepts that I want to cover.

The first is that when rep­re­sent­ing JSON-LD, I make heavy use of Node Iden­ti­fiers (the "@id" prop­er­ties) to ref­er­ence spe­cif­ic schemas with­out repeat­ing data need­less­ly. We can have an Organization schema and then just ref­er­ence it from mul­ti­ple places, rather than repli­cat­ing the entire schema data each time.

Here’s a trun­cat­ed example:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@id": "our-organization",
    "@type": "Organization"
}

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "Article",
    "publisher": {
        "@id": "our-organization"
    }
}
</script>

In this case we used our-organization as a Node Iden­ti­fi­er, but often just as a con­ven­tion we can use URLs or URLs with hash­es so that it feels name spaced. It real­ly does­n’t mat­ter, as long as it is local­ly unique on your webpage.

Think of Node Iden­ti­fiers as a way to define a label or alias to anoth­er node in your local schema graph. You assign a local­ly unique "@id" to a JSON-LD schema, and then you can refer to that "@id" from oth­er JSON-LD schemas.

More on Node Iden­ti­fiers can be found in the What is the use of @id in json-ld syn­tax? and Schema​.org JSON-LD ref­er­ence articles.

The mainEn­ti­ty­Of­Page prop­er­ty also deserves spe­cial mention:

Indicates a page (or other CreativeWork) for which this thing is the main entity being described

So if a web page is an arti­cle like the one you’re read­ing right now, it’ll have Arti­cle JSON-LD with the mainEntityOfPage prop­er­ty’s val­ue being the URL to the arti­cle itself:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "Article",
    "mainEntityOfPage": "https://nystudio107.com/blog/implementing-custom-json-ld-structured-data"
}
</script>

It’s say­ing Hey, I’m the main sub­ject of the web page at this URL!”

Now let’s dive into look­ing at some spe­cif­ic examples.

Link nystudio107.com Article Page

Let’s start by look­ing at the struc­tured data for the page you’re read­ing right now. Just by choos­ing a few set­tings of how to map data, here’s what you’ll get by default from SEO­mat­ic:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "Article",
    "author": {
        "@id": "https://nystudio107.com/#identity"
    },
    "copyrightHolder": {
        "@id": "https://nystudio107.com/#identity"
    },
    "copyrightYear": "2019",
    "creator": {
        "@id": "https://nystudio107.com/#creator"
    },
    "dateModified": "2019-05-28T17:33:57-04:00",
    "datePublished": "2019-05-27T08:45:00-04:00",
    "description": "Want to see a some real-world examples of JSON-LD structured data \"in the wild\"? This article has that, plus annotation.",
    "headline": "Implementing Custom JSON-LD Structured Data",
    "image": {
        "@type": "ImageObject",
        "url": "https://nystudio107.com/img/blog/_1200x630_crop_center-center_82_none/json-ld-structured-data-in-the-wild.jpg"
    },
    "inLanguage": "en",
    "mainEntityOfPage": "https://nystudio107.com/blog/implementing-custom-json-ld-structured-data",
    "name": "Implementing Custom JSON-LD Structured Data",
    "publisher": {
        "@id": "https://nystudio107.com/#creator"
    },
    "url": "https://nystudio107.com/blog/implementing-custom-json-ld-structured-data"
}
</script>

Most things in this Arti­cle schema are pret­ty straight­for­ward; but why did we choose Arti­cle to begin with? Why not Blog­Post­ing?

The answer is sim­ple. Google lists Arti­cle as an explic­it­ly sup­port­ed type. Addi­tion­al­ly, if we have Google AMP ver­sions of our pages (and we do for this arti­cle), this Article JSON-LD is required for your arti­cle to appear in the carousel of AMP stories.

Note that we are using Node Iden­ti­fiers with "@id" in sev­er­al places to refer to oth­er JSON-LD struc­tured data nodes rather than repeat­ing the content.

By doing this, we are Linking Data nodes together… the LD part of JSON-LD.

The oth­er key thing to note is the mainEn­ti­ty­Of­Page prop­er­ty, which is iden­ti­fy­ing the fact that this Arti­cle JSON-LD struc­tured data is the pri­ma­ry sub­ject of the web page at the list­ed URL.

This helps tell Google what you con­sid­er to be the impor­tant part of or thing on the page, rather than hav­ing it try to guess it.

This Orga­ni­za­tion schema is for the enti­ty that owns the web­site (note the "@id": "https://nystudio107.com/#identity" Node Identifier):

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@id": "https://nystudio107.com/#identity",
    "@type": "Organization",
    "address": {
        "@type": "PostalAddress",
        "addressCountry": "US",
        "addressLocality": "Webster",
        "addressRegion": "NY",
        "postalCode": "14580"
    },
    "alternateName": "nys",
    "description": "We do technology-based consulting, branding, design, and development. Making the web better one site at a time, with a focus on performance, usability & SEO",
    "email": "info@nystudio107.com",
    "founder": "Andrew Welch, Polly Welch",
    "foundingDate": "2013-05-02",
    "foundingLocation": "Webster, NY",
    "image": {
        "@type": "ImageObject",
        "height": "2048",
        "url": "https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/site/nys_logo_seo.png",
        "width": "2048"
    },
    "logo": {
        "@type": "ImageObject",
        "height": "60",
        "url": "https://nystudio107.com/img/site/_600x60_fit_center-center_82_none/nys_logo_seo.png",
        "width": "600"
    },
    "name": "nystudio107",
    "sameAs": [
        "https://twitter.com/nystudio107",
        "https://www.facebook.com/newyorkstudio107",
        "https://plus.google.com/+nystudio107com",
        "https://www.youtube.com/channel/UCOZTZHQdC-unTERO7LRS6FA",
        "https://github.com/nystudio107"
    ],
    "url": "https://nystudio107.com/"
}
</script>

It exists pri­mar­i­ly so that oth­er JSON-LD struc­tured data such as the Article can link to it and say this is the author” and this is the copy­right hold­er” and so on and so forth.

You could take this much fur­ther and add a list of peo­ple who work at the orga­ni­za­tion via the employee prop­er­ty, or list peo­ple who used to work at the orga­ni­za­tion via the alumni property.

In this way, you build up the con­text and rela­tion­ship between things to help Google under­stand the con­nec­tions between them.

This Orga­ni­za­tion schema is for the enti­ty that cre­at­ed the web­site (note the "@id": "https://nystudio107.com/#creator" Node Identifier):

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@id": "https://nystudio107.com/#creator",
    "@type": "Organization",
    "address": {
        "@type": "PostalAddress",
        "addressCountry": "US",
        "addressLocality": "Webster",
        "addressRegion": "NY",
        "postalCode": "14580"
    },
    "alternateName": "nys",
    "description": "We do technology-based consulting, branding, design, and development. Making the web better one site at a time, with a focus on performance, usability & SEO",
    "email": "info@nystudio107.com",
    "founder": "Andrew Welch, Polly Welch",
    "foundingDate": "2013-05-02",
    "foundingLocation": "Webster, NY",
    "image": {
        "@type": "ImageObject",
        "height": "1042",
        "url": "https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/site/nys_logo_seo.png",
        "width": "1042"
    },
    "logo": {
        "@type": "ImageObject",
        "height": "60",
        "url": "https://nystudio107.com/img/site/_600x60_fit_center-center_82_none/nys_logo_seo.png",
        "width": "600"
    },
    "name": "nystudio107",
    "url": "https://nystudio107.com/"
}
</script>

It exists pri­mar­i­ly so that oth­er JSON-LD struc­tured data such as the Article can link to it and say this is the cre­ator” and so on and so forth.

Just as with the identity Orga­ni­za­tion, you can take this much fur­ther to add addi­tion­al infor­ma­tion about employees, alumni, etc.

Final­ly we have the auto­mat­i­cal­ly gen­er­at­ed Bread­crum­b­List schema that indi­cates the page’s posi­tion in the site hierarchy:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "BreadcrumbList",
    "description": "Breadcrumbs list",
    "itemListElement": [
        {
            "@type": "ListItem",
            "item": {
                "@id": "https://nystudio107.com/",
                "name": "Homepage"
            },
            "position": 1
        },
        {
            "@type": "ListItem",
            "item": {
                "@id": "https://nystudio107.com/blog",
                "name": "Blog Index"
            },
            "position": 2
        },
        {
            "@type": "ListItem",
            "item": {
                "@id": "hthttps://nystudio107.comt/blog/tips-for-using-seomatic-effectively",
                "name": "Advanced SEOmatic Tips"
            },
            "position": 3
        }
    ],
    "name": "Breadcrumbs"
}
</script>

This is a sup­port­ed Google type that helps it under­stand the struc­ture of your web­site, and also can show up as actu­al bread­crumb links on your Search Engine Results Page (SERP).

So this is a nice start­ing point, but we can do a bit bet­ter. Let’s use the SEO­mat­ic Twig API to add a lit­tle bit to our mainEntityOfPage JSON-LD:

{#
 # Add Article schema for the this plugin as per:
 # https://schema.org/Article
 #
 # @param  article  the Entry for the blog article
 #}

{# Merge in the additional properties to the Article mainEntityOfPage #}
{% set mainEntity = seomatic.jsonLd.get('mainEntityOfPage') %}
{% do mainEntity.setAttributes({
    'articleSection': article.blogCategory[0].title,
    'genre': 'Technology',
    'headline': article.title,
    'speakable': {
        'type': 'SpeakableSpecification',
        'cssSelector': [
            '.blog-wrapper',
        ],
    },
}) %}

This adds a few prop­er­ties, result­ing in the fol­low­ing JSON-LD struc­tured data:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "Article",
    "articleSection": "Insights",
    "author": {
        "@id": "https://nystudio107.com/#identity"
    },
    "copyrightHolder": {
        "@id": "https://nystudio107.com/#identity"
    },
    "copyrightYear": "2019",
    "creator": {
        "@id": "https://nystudio107.com/#creator"
    },
    "dateModified": "2019-05-28T17:33:57-04:00",
    "datePublished": "2019-05-27T08:45:00-04:00",
    "description": "Want to see a some real-world examples of JSON-LD structured data \"in the wild\"? This article has that, plus annotation.",
    "genre": "Technology",
    "headline": "Implementing Custom JSON-LD Structured Data",
    "image": {
        "@type": "ImageObject",
        "url": "https://nystudio107.com/img/blog/_1200x630_crop_center-center_82_none/json-ld-structured-data-in-the-wild.jpg"
    },
    "inLanguage": "en",
    "mainEntityOfPage": "https://nystudio107.com/blog/implementing-custom-json-ld-structured-data",
    "name": "Implementing Custom JSON-LD Structured Data",
    "publisher": {
        "@id": "https://nystudio107.com/#creator"
    },
    "speakable": {
        "@type": "SpeakableSpecification",
        "cssSelector": [
            ".blog-wrapper"
        ]
    },
    "url": "https://nystudio107.com/blog/implementing-custom-json-ld-structured-data"
}
</script>

Every­thing should be pret­ty straight­for­ward here in terms of the prop­er­ties we’ve added. We’re just flesh­ing things out a bit by adding some addi­tion­al information.

Prob­a­bly the coolest part is the speakable prop­er­ty, which lets you tell screen read­ers which text is par­tic­u­lar­ly speak­able” via the Speak­a­ble­Spec­i­fi­ca­tion. In our case, the .blog-wrapper CSS class selec­tor is on the <div>s around our tex­tu­al arti­cle content.

This aids acces­si­bil­i­ty in terms of screen read­ers, and also is used by Google Assis­tant and smart devices.

Addi­tion­al things that could be done:

  • Instead of just list­ing the identity Orga­ni­za­tion as the author, have infor­ma­tion on the actu­al per­son who wrote it
  • Add employee and alum­ni prop­er­ties to the identity & cre­ator Orga­ni­za­tion
  • Add Inter­ac­tion­Counter via interactionStatistic for user com­ments on arti­cles, retweets, etc.
  • Add How­To steps for parts of arti­cles that have spe­cif­ic steps to accom­plish a task

Go through the schema​.org spec­i­fi­ca­tion for the mainEntityOfPage schema type (Arti­cle in this case) and see what prop­er­ties it makes sense to add. Then val­i­date your page using the Google Struc­tured Data Test­ing Tool.

Google SERP Pre­viewnystudio107 Arti­cle Page

Google Struc­tured Data Test­ing Tool link → nys​tu​dio107​.com Arti­cle Page

Link nys​tu​dio107​.com Plugin Page

Next up let’s have a look at the JSON-LD struc­tured data for the plu­g­in pages on nys​tu​dio107​.com, specif­i­cal­ly the SEO­mat­ic plugin.

We’re going to skip the iden­ti­ty, cre­ator, and bread­crumbs men­tioned ear­li­er, since they’ll be the same here as well.

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "WebApplication",
    "author": {
        "@id": "https://nystudio107.com/#identity"
    },
    "copyrightHolder": {
        "@id": "https://nystudio107.com/#identity"
    },
    "copyrightYear": "2017",
    "creator": {
        "@id": "https://nystudio107.com/#creator"
    },
    "dateModified": "2019-05-21T11:35:49-04:00",
    "datePublished": "2017-12-31T19:48:00-05:00",
    "description": "SEOmatic facilitates modern SEO best practices & implementation for Craft CMS 3. It is a turnkey SEO system that is comprehensive, powerful, and flexible.",
    "headline": "SEOmatic Craft CMS Plugin",
    "image": {
        "@type": "ImageObject",
        "url": "https://nystudio107.com/img/plugins/seomatic/_1200x630_crop_center-center_82_none/plugin-logo.png"
    },
    "inLanguage": "en",
    "mainEntityOfPage": "https://nystudio107.com/plugins/seomatic",
    "name": "SEOmatic Craft CMS Plugin",
    "publisher": {
        "@id": "https://nystudio107.com/#creator"
    },
    "url": "https://nystudio107.com/plugins/seomatic"
}
</script>

Here we again get some pret­ty rea­son­able defaults for our WebAp­pli­ca­tion JSON-LD struc­tured data, and we make it clear that this is the mainEntityOfPage as dis­cussed previously.

But with a lit­tle cus­tom code from the SEO­mat­ic Twig API we can make it much more interesting:

{#
 # Add WebApplication schema for the this plugin as per:
 # https://schema.org/WebApplication
 #
 # @param  appInfo  the appInfo Entry for the plugin
 #}

{# Offer for the plugin #}
{% set offer = seomatic.jsonLd.create({
    'type': 'Offer',
    'id': '{seomatic.site.identity.genericUrl}#plugin-offer',
    'availability': 'http://schema.org/InStock',
    'price': appInfo.pluginPrice ??? '0.00',
    'priceCurrency': 'USD',
    'priceValidUntil': now | date_modify('+1 year') | atom,
    'url': appInfo.pluginStoreLink,
}) %}

{# Merge in the additional properties to the WebApplication mainEntityOfPage #}
{% set mainEntity = seomatic.jsonLd.get('mainEntityOfPage') %}
{% set screenshotAsset = appInfo.pluginSeoImage.one() %}
{% set logoAsset = appInfo.pluginIcon.one() %}
{% do mainEntity.setAttributes({
    'name': appInfo.title,
    'applicationCategory': 'Plugin',
    'browserRequirements': 'Requires Craft CMS 3',
    'downloadUrl': appInfo.pluginStoreLink,
    'installUrl': appInfo.pluginStoreLink,
    'image': {
        'type': 'ImageObject',
        'url': logoAsset.url,
        'width': logoAsset.getWidth(),
        'height': logoAsset.getHeight(),
    },
    'screenshot': {
        'type': 'ImageObject',
        'url': screenshotAsset.url,
        'width': screenshotAsset.getWidth(),
        'height': screenshotAsset.getHeight(),
    },
    'offers': offer,
    'operatingSystem': 'Web Browser',
}) %}

{# Product for the plugin #}
{% set productConfig = mainEntity.getAttributes() | merge({
    'key': 'Plugin-Product',
    'type': 'Product',
    'brand': {
        'id': '{seomatic.site.identity.genericUrl}#identity',
    },
    'category': 'Plugin',
    'isRelatedTo': {
        'type': 'Product',
        'name': 'Craft CMS',
        'description': 'Craft is a flexible, user-friendly CMS for creating custom digital experiences on the web and beyond.',
        'slogan': 'Craft is the CMS that makes the whole team happy.',
        'url': 'https://craftcms.com/',
    },
    'logo': {
        'type': 'ImageObject',
        'url': logoAsset.url,
        'width': logoAsset.getWidth(),
        'height': logoAsset.getHeight(),
    },
    'productID': appInfo.slug,
    'sku': appInfo.slug,
    'slogan': appInfo.pluginSlogan,
}) %}
{% set product = seomatic.jsonLd.create(productConfig) %}

This results in a much more fleshed out WebAp­pli­ca­tion JSON-LD struc­tured data, as well as cre­at­ing new Offer and Prod­uct JSON-LD struc­tured data schemas:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "WebApplication",
    "applicationCategory": "Plugin",
    "author": {
        "@id": "https://nystudio107.com/#identity"
    },
    "browserRequirements": "Requires Craft CMS 3",
    "copyrightHolder": {
        "@id": "https://nystudio107.com/#identity"
    },
    "copyrightYear": "2017",
    "creator": {
        "@id": "https://nystudio107.com/#creator"
    },
    "dateModified": "2019-05-21T11:35:49-04:00",
    "datePublished": "2017-12-31T19:48:00-05:00",
    "description": "SEOmatic facilitates modern SEO best practices & implementation for Craft CMS 3. It is a turnkey SEO system that is comprehensive, powerful, and flexible.",
    "downloadUrl": "https://plugins.craftcms.com/seomatic",
    "headline": "SEOmatic Craft CMS Plugin",
    "image": {
        "@type": "ImageObject",
        "height": "250",
        "url": "https://nystudio107.com/img/plugins/seomatic/seomatic-icon.svg",
        "width": "250"
    },
    "inLanguage": "en",
    "installUrl": "https://plugins.craftcms.com/seomatic",
    "mainEntityOfPage": "https://nystudio107.com/plugins/seomatic",
    "name": "SEOmatic",
    "offers": {
        "@id": "https://nystudio107.com/#plugin-offer"
    },
    "operatingSystem": "Web Browser",
    "publisher": {
        "@id": "https://nystudio107.com/#creator"
    },
    "screenshot": {
        "@type": "ImageObject",
        "height": "500",
        "url": "https://nystudio107.com/img/plugins/seomatic/plugin-logo.png",
        "width": "450"
    },
    "url": "https://nystudio107.com/plugins/seomatic"
}
</script>

Here we are once again fill­ing in a num­ber of prop­er­ties that make sense for a WebAp­pli­ca­tion (which is the most cor­rect fit for a plu­g­in for a web-based CMS).

We’ve added in prop­er­ties like downloadUrl, installUrl, screenshot, and more inter­est­ing­ly offers via the "@id": "https://nystudio107.com/#plugin-offer" Node Iden­ti­fi­er:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@id": "https://nystudio107.com/#plugin-offer",
    "@type": "Offer",
    "availability": "http://schema.org/InStock",
    "price": "99",
    "priceCurrency": "USD",
    "priceValidUntil": "2020-05-28T19:03:39-04:00",
    "url": "https://plugins.craftcms.com/seomatic"
}
</script>

Since some of our plu­g­ins are avail­able for sale, we can indi­cate that via the Offer schema that lists a price, priceCurrency, availability, etc.

Pro tip: if your prod­uct is free, you should indi­cate that by hav­ing all of the above prop­er­ties, but set­ting the price to 0.

We imple­ment­ed this with a Node Iden­ti­fi­er so that we can ref­er­ence it from both the WebApplication and Product JSON-LD:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "Product",
    "brand": {
        "@id": "https://nystudio107.com/#identity"
    },
    "category": "Plugin",
    "description": "SEOmatic facilitates modern SEO best practices & implementation for Craft CMS 3. It is a turnkey SEO system that is comprehensive, powerful, and flexible.",
    "image": {
        "@type": "ImageObject",
        "height": "250",
        "url": "https://nystudio107.com/img/plugins/seomatic/seomatic-icon.svg",
        "width": "250"
    },
    "isRelatedTo": {
        "@type": "Product",
        "description": "Craft is a flexible, user-friendly CMS for creating custom digital experiences on the web and beyond.",
        "name": "Craft CMS",
        "slogan": "Craft is the CMS that makes the whole team happy.",
        "url": "https://craftcms.com/"
    },
    "logo": {
        "@type": "ImageObject",
        "height": "250",
        "url": "https://nystudio107.com/img/plugins/seomatic/seomatic-icon.svg",
        "width": "250"
    },
    "mainEntityOfPage": "https://nystudio107.com/plugins/seomatic",
    "name": "SEOmatic",
    "offers": {
        "@id": "https://nystudio107.com/#plugin-offer"
    },
    "productID": "seomatic",
    "sku": "seomatic",
    "url": "https://nystudio107.com/plugins/seomatic"
}
</script>

We took all of the prop­er­ties from our WebAp­pli­ca­tion, and used them to cre­ate a new Prod­uct JSON-LD schema (SEO­mat­ic strips out any prop­er­ties that don’t exist in the new schema type).

Then we added a few things spe­cif­ic to a Product such as the isRelatedTo prop­er­ty, relat­ing it to Craft CMS (since our plu­g­ins require it).

So why cre­ate a new Product schema? Because once again, it’s a type that Google specif­i­cal­ly sup­ports.

This is also some­thing that can result in your Product appear­ing on the Search Engine Results Page (SERP) with a thumb­nail screen­shot, reviews, rat­ings, and oth­er fea­tures that encour­age engage­ment.

Addi­tion­al things that could be done:

  • The Prod­uct should have an aggregateRating
  • Adding legit­i­mate user-sub­mit­ted Reviews

Google SERP Pre­viewnystudio107 Plu­g­in Page

Google Rich Results Testnystudio107 Plu­g­in Page

Google Struc­tured Data Test­ing Tool link → nys​tu​dio107​.com Plu­g­in Page

Link devMode.fm About Page

The dev​Mode​.fm About page serves as a good exam­ple of how we can imple­ment an FAQ­Page schema​.org type with Ques­tions & Answers that can show up on the Google Search Engine Results Page (SERP). Check out the Google Devel­op­ers Search FAQ page for details.

{
  "@context": "http://schema.org",
  "@type": "FAQPage",
  "author": {
    "@id": "https://devmode.fm/#identity"
  },
  "copyrightHolder": {
    "@id": "https://devmode.fm/#identity"
  },
  "copyrightYear": "2017",
  "creator": {
    "@id": "https://devmode.fm/#creator"
  },
  "dateModified": "2019-08-19T11:19:14-04:00",
  "datePublished": "2017-12-11T13:44:33-05:00",
  "description": "devMode.fm is a bi-weekly podcast dedicated to the tools, techniques, and technologies used in modern web development. Each episode, we have a cadre of hosts discussing the latest hotness, pet peeves, and technologies we use every day. We all come from a Craft CMS background, but we'll be focusing on other cool frontend development technologies as well.",
  "headline": "About the podcast",
  "image": {
    "@type": "ImageObject",
    "url": "https://devmode.fm/assets/site/_1200x630_crop_center-center_82_none/devmode_light-itunes.jpg?mtime=1515536278"
  },
  "inLanguage": "en-us",
  "mainEntity": [
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "<p>devMode.fm is a bi-weekly podcast dedicated to the tools, techniques, and technologies used in modern web development. Each episode, we have a cadre of hosts discussing the latest hotness, pet peeves, and technologies we use every day. We all come from a Craft CMS background, but we'll be focusing on other cool frontend development technologies as well.</p>"
      },
      "name": "What is the devMode.fm podcast all about?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "<p>It sure is! The source code for this website is MIT licensed, and can be found on Github: <a href=\"https://github.com/nystudio107/devmode\" target=\"_blank\" rel=\"noreferrer noopener\">nystudio107/devmode</a>.</p>"
      },
      "name": "Is the source code to the devMode.fm website available?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "<p>The devMode.fm website is built with:</p><a href=\"https://craftcms.com/\" target=\"_blank\">Craft 3 CMS</a>, <a href=\"https://tailwindcss.com/\" target=\"_blank\">Tailwind CSS</a>, <a href=\"https://vuejs.org/\" target=\"_blank\">Vue.js</a>, <a href=\"https://webpack.js.org/\" target=\"_blank\">webpack</a>, <a href=\"https://www.nginx.com/\" target=\"_blank\">Nginx</a>, <a href=\"https://www.postgresql.org\" target=\"_blank\">PostgreSQL</a>, <a href=\"https://forge.laravel.com\" target=\"_blank\">Forge</a>, <a href=\"https://github.com/nystudio107/craft\" target=\"_blank\">nystudio107/craft</a>"
      },
      "name": "What technologies were used building the devMode.fm website?"
    }
  ],
  "mainEntityOfPage": "https://devmode.fm/about",
  "name": "About the podcast",
  "publisher": {
    "@id": "https://devmode.fm/#creator"
  },
  "url": "https://devmode.fm/about"
}

Here we have set the Main Enti­ty of Page to FAQ­Page in the SEO­mat­ic set­tings in the CP, and then we’re using a bit of Twig to read FAQs from a matrix block that has the fields question, answer, and links:

{#
 # Add FAQPage schema for the this page as per:
 # https://schema.org/FAQPage
 #
 # @param  faqs      the FAQs matrix blocks
 # @param  showInfo  the showInfo Global for the show
 #}

{% set faqsArray = [] %}
{% for faq in faqs.all() %}
    {% set faqLinks = '' %}
    {% for link in faq.links %}
        {%- set linkHtml -%}
            <a href="{{ link.linkUrl }}" target="_blank">{{ link.linkText }}</a>
        {%- endset -%}
        {% set faqLinks = faqLinks ~ linkHtml ~ (loop.last ? '' : ', ') %}
    {% endfor %}
    {% set faqsArray = faqsArray | merge([seomatic.jsonLd.create({
        'type': 'Question',
        'name': faq.question,
        'acceptedAnswer': {
            'type': 'Answer',
            'text': (faq.answer ~ faqLinks),
        },
    }, false)]) %}
{% endfor %}

{# Merge in the additional properties to the FAQpage mainEntityOfPage #}
{% set mainEntity = seomatic.jsonLd.get('mainEntityOfPage') %}
{% do mainEntity.setAttributes({
    'mainEntity': faqsArray,
}) %}

In this case we set the mainEntity prop­er­ty to an array of Ques­tion JSON-LD types, each of which has an Answer JSON-LD type embed­ded in the acceptedAnswer field.

This is some­thing that can result in your FAQPage Ques­tions & Answers appear­ing on the Search Engine Results Page (SERP).

Google SERP Pre­viewdev​Mode​.fm About Page

Google Rich Results Testdev​Mode​.fm About Page

Google Struc­tured Data Test­ing Tool link → dev​Mode​.fm About Page

Link devMode.fm Episode Page

Final­ly let’s have a look at the JSON-LD struc­tured data for the episode pages on dev​Mode​.fm, in this case the web­pack inside & out with Sean Larkin episode.

We’re going to skip the iden­ti­ty, cre­ator, and bread­crumbs men­tioned ear­li­er. Even though the val­ues will be dif­fer­ent for this site, the same prop­er­ties are filled in.

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "RadioEpisode",
    "author": {
        "@id": "https://devmode.fm/#identity"
    },
    "copyrightHolder": {
        "@id": "https://devmode.fm/#identity"
    },
    "copyrightYear": "2019",
    "creator": {
        "@id": "https://devmode.fm/#creator"
    },
    "dateModified": "2019-05-27T09:10:22-04:00",
    "datePublished": "2019-05-27T09:05:00-04:00",
    "description": "In this episode, we talk to webpack core maintainer Sean Larkin about what webpack is, who it's intended for, and where it's going in the future!\n\nWe discuss the serendipitous history of how Sean came to be a webpack core maintainer, and how his job at Microsoft came about as a result of it.\n\nJoin by guest host Jake Dohm, we then go on to discuss a whole lot of gritty technical detail of how webpack works, go through the various terminology, talk about Web Assembly, and what the future holds for webpack 5 and beyond.\n\nSean also drops some truth-bombs about CSS being flawed, and browser makers conspiring to kill off webpack. Tune in for the good stuff!",
    "headline": "webpack inside & out with Sean Larkin",
    "image": {
        "@type": "ImageObject",
        "url": "https://devmode.fm/assets/site/_1200x630_crop_center-center_82_none/devmode_light-itunes.jpg"
    },
    "inLanguage": "en-us",
    "mainEntityOfPage": "https://devmode.fm/episodes/webpack-inside-out-with-sean-larkin",
    "name": "webpack inside & out with Sean Larkin",
    "publisher": {
        "@id": "https://devmode.fm/#creator"
    },
    "url": "https://devmode.fm/episodes/webpack-inside-out-with-sean-larkin"
}
</script>

Once again we have a pret­ty good start­ing point for our RadioEpisode schema (there is no Pod­cast schema yet) that is our mainEntityOfPage. But we can add a bunch more infor­ma­tion in this case to describe the series that this pod­cast belongs to, and more.

Once again we’ll use the SEO­mat­ic Twig API to do so:

{#
 # Add audio schema for the this episode as per:
 # https://schema.org/RadioEpisode
 #
 # @param  episode   the episode Entry for the current page
 # @param  fileInfo  summary of the episode media file info as per:
 #                   https://github.com/nystudio107/craft-transcoder#getting-information-about-a-videoaudio-file
 # @param  showInfo  the showInfo Global for the show
 # @param  audioUrl  the URL to the audio for this episode, wrapped in the
 #                   PodTrac.com tracking redirect
 #}

{% from "_partials/macros.twig" import addPersonArray %}
{% from "_partials/macros.twig" import addMentionsArray %}

{# RadioSeries for the episode to belong to #}
{% set showImage = showInfo.showImage.one() %}
{% set radioSeries = seomatic.jsonLd.create({
    'type': 'RadioSeries',
    'id': '{seomatic.site.identity.genericUrl}#radio-series',
    'name': showInfo.showTitle,
    'description': showInfo.showDescription,
    'url': showInfo.showUrl,
    'mainEntityOfPage': siteUrl,
    'inLanguage': '{seomatic.meta.language}',
    'copyrightHolder': {
        'id': '{seomatic.site.identity.genericUrl}#identity',
    },
    'author': {
        'id': '{seomatic.site.identity.genericUrl}#identity',
    },
    'creator': {
        'id': '{seomatic.site.creator.genericUrl}#creator',
    },
    'image': {
        'type': 'ImageObject',
        'url': showImage.url,
        'width': showImage.width,
        'height': showImage.height,
    },
}) %}

{# Person array of the episode hosts #}
{% do addPersonArray(radioSeries, 'actor', craft.users.group("hosts").all()) %}
{# Person array of the episode directors #}
{% do addPersonArray(radioSeries, 'director', craft.users.group("owners").all()) %}

{# AudioObject for the episode #}
{% set audio = seomatic.jsonLd.create({
    'type': 'AudioObject',
    'bitrate': '64k',
    'contentSize': fileInfo.size,
    'contentUrl': audioUrl,
    'duration': fileInfo.duration,
    'embedUrl': siteUrl("/player-card/#{episode.slug}"),
    'encodingFormat': 'audio/mpeg',
    'partOfSeries': {
        'id': '{seomatic.site.identity.genericUrl}#radio-series',
    },
    'productionCompany': {
        'id': '{seomatic.site.creator.genericUrl}#creator',
    },
    'uploadDate': episode.postDate | rss,
}, false) %}

{# Merge in the additional properties to the RadioEpisode mainEntityOfPage #}
{% set mainEntity = seomatic.jsonLd.get('mainEntityOfPage') %}
{% do mainEntity.setAttributes({
    'audio': audio,
    'episodeNumber': episode.episodeNumber,
    'genre': showInfo.showGenre,
    'isAccessibleForFree': true,
    'productionCompany': {
        'id': '{seomatic.site.creator.genericUrl}#creator',
    },
}) %}

{# Person array of the episode hosts #}
{% do addPersonArray(mainEntity, 'actor', episode.episodeHosts.all()) %}
{# Person array of the episode directors #}
{% do addPersonArray(mainEntity, 'director', craft.users.group("owners").all()) %}
{# Thing array of the episode mentions #}
{% do addMentionsArray(mainEntity, 'mentions', episode.episodeReferenceLinks) %}

This is get­ting a whole lot more com­plex, and we’re even includ­ing two macros to help gen­er­ate the addi­tion­al JSON-LD struc­tured data:

{# Add a Person array from `users` to the `property` of `jsonLd` #}
{% macro addPersonArray(jsonLd, property, users) %}
    {% set usersArray = [] %}
    {% for user in users %}
        {% set usersArray = usersArray | merge([seomatic.jsonLd.create({
            'type': 'Person',
            'affiliation': user.profileCompany,
            'description': user.profileBio,
            'jobTitle': user.profileTitle,
            'familyName': user.lastName,
            'givenName': user.firstName,
            'name': user.fullName,
            'sameAs': [
                user.profileTwitterUrl,
                user.profileGithubUrl,
            ],
            'url': user.profileUrl,
        }, false)]) %}
    {% endfor %}
    {% do jsonLd.setAttributes({
        (property): usersArray
    }) %}
{% endmacro addPersonArray %}

{# Add a Thing array from `mentions` to the `property` of `jsonLd` #}
{% macro addMentionsArray(jsonLd, property, mentions) %}
    {% set mentionsArray = [] %}
    {% for mention in mentions %}
        {% set mentionsArray = mentionsArray | merge([seomatic.jsonLd.create({
            'type': 'Thing',
            'name': mention.linkName,
            'url': mention.linkUrl,
        }, false)]) %}
    {% endfor %}
    {% do jsonLd.setAttributes({
        (property): mentionsArray
    }) %}
{% endmacro addMentionsArray %}

I think the eas­i­est way to under­stand what the code above does is to look at the result­ing out­put, so let’s do just that, and look at our RadioEpisode mainEntityOfPage JSON-LD now:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "RadioEpisode",
    "actor": [
        {
            "@type": "Person",
            "affiliation": "nystudio107",
            "description": "Andrew Welch has been in the tech business since he was 15 years old.  After a stint running a software company for a couple of decades, he's now immersing himself in doing consulting to help businesses use technology effectively.",
            "familyName": "Welch",
            "givenName": "Andrew",
            "jobTitle": "Demolitions Expert",
            "name": "Andrew Welch",
            "sameAs": [
                "https://twitter.com/nystudio107",
                "https://github.com/nystudio107"
            ],
            "url": "https://nystudio107.com"
        },
        {
            "@type": "Person",
            "affiliation": "MDD",
            "description": "Jonathan is principal/CEO of MDD, a small web agency based in Atlanta, Georgia. He has been developing for the web since 2005 and Craft CMS since 2013. He used to have free time and cool hobbies until his second child arrived on December 6, 2017. He loves his work and his family.",
            "familyName": "Melville",
            "givenName": "Jonathan",
            "jobTitle": "Jonathan Melville",
            "name": "Jonathan Melville",
            "sameAs": [
                "https://twitter.com/jonmelville",
                "https://github.com/jonathanmelville"
            ],
            "url": "https://codemdd.io"
        }
    ],
    "audio": {
        "@context": "http://schema.org",
        "@type": "AudioObject",
        "bitrate": "64k",
        "contentSize": "72969149",
        "contentUrl": "https://devmode.fm/transcoder/devmode0041_64kbps_22050_1c.mp3",
        "duration": "4560.535510",
        "embedUrl": "https://devmode.fm/player-card/webpack-inside-out-with-sean-larkin",
        "encodingFormat": "audio/mpeg",
        "productionCompany": {
            "@id": "https://nystudio107.com/#creator"
        },
        "uploadDate": "Mon, 27 May 2019 09:05:00 -0400"
    },
    "author": {
        "@id": "https://devmode.fm/#identity"
    },
    "copyrightHolder": {
        "@id": "https://devmode.fm/#identity"
    },
    "copyrightYear": "2019",
    "creator": {
        "@id": "https://devmode.fm/#creator"
    },
    "dateModified": "2019-05-27T09:10:22-04:00",
    "datePublished": "2019-05-27T09:05:00-04:00",
    "description": "In this episode, we talk to webpack core maintainer Sean Larkin about what webpack is, who it's intended for, and where it's going in the future!\n\nWe discuss the serendipitous history of how Sean came to be a webpack core maintainer, and how his job at Microsoft came about as a result of it.\n\nJoin by guest host Jake Dohm, we then go on to discuss a whole lot of gritty technical detail of how webpack works, go through the various terminology, talk about Web Assembly, and what the future holds for webpack 5 and beyond.\n\nSean also drops some truth-bombs about CSS being flawed, and browser makers conspiring to kill off webpack. Tune in for the good stuff!",
    "director": [
        {
            "@type": "Person",
            "affiliation": "nystudio107",
            "description": "Andrew Welch has been in the tech business since he was 15 years old.  After a stint running a software company for a couple of decades, he's now immersing himself in doing consulting to help businesses use technology effectively.",
            "familyName": "Welch",
            "givenName": "Andrew",
            "jobTitle": "Demolitions Expert",
            "name": "Andrew Welch",
            "sameAs": [
                "https://twitter.com/nystudio107",
                "https://github.com/nystudio107"
            ],
            "url": "https://nystudio107.com"
        },
        {
            "@type": "Person",
            "affiliation": "Mildly Geeky",
            "description": "Patrick Harrington has been obsessed with the web ever since picking up a copy of HTML for Dummies at age 13. After working for larger Boston-area agencies, he started his own company in 2011 and specializes in Craft CMS.",
            "familyName": "Harrington",
            "givenName": "Patrick",
            "jobTitle": "President, Founder",
            "name": "Patrick Harrington",
            "sameAs": [
                "https://twitter.com/p_harrington83",
                "https://github.com/mildlygeeky"
            ],
            "url": "https://mildlygeeky.com"
        }
    ],
    "episodeNumber": 41,
    "genre": "Technology",
    "headline": "webpack inside & out with Sean Larkin",
    "image": {
        "@type": "ImageObject",
        "url": "https://devmode.fm/assets/site/_1200x630_crop_center-center_82_none/devmode_light-itunes.jpg"
    },
    "inLanguage": "en-us",
    "isAccessibleForFree": true,
    "mainEntityOfPage": "https://devmode.fm/episodes/webpack-inside-out-with-sean-larkin",
    "mentions": [
        {
            "@type": "Thing",
            "name": "webpack concepts",
            "url": "https://webpack.js.org/concepts/"
        },
        {
            "@type": "Thing",
            "name": "webpack in Wikipedie",
            "url": "https://en.wikipedia.org/wiki/Webpack"
        },
        {
            "@type": "Thing",
            "name": "Webpack 4 Fundamentals videos",
            "url": "https://frontendmasters.com/courses/webpack-fundamentals/"
        },
        {
            "@type": "Thing",
            "name": "Webpack — The Confusing Parts",
            "url": "https://medium.com/@rajaraodv/webpack-the-confusing-parts-58712f8fcad9"
        },
        {
            "@type": "Thing",
            "name": "An Annotated webpack 4 Config for Frontend Web Development",
            "url": "https://nystudio107.com/blog/an-annotated-webpack-4-config-for-frontend-web-development"
        },
        {
            "@type": "Thing",
            "name": "A tale of Webpack 4 and how to finally configure it in the right way.",
            "url": "https://hackernoon.com/a-tale-of-webpack-4-and-how-to-finally-configure-it-in-the-right-way-4e94c8e7e5c1"
        }
    ],
    "name": "webpack inside & out with Sean Larkin",
    "productionCompany": {
        "@id": "https://nystudio107.com/#creator"
    },
    "publisher": {
        "@id": "https://devmode.fm/#creator"
    },
    "url": "https://devmode.fm/episodes/webpack-inside-out-with-sean-larkin"
}
</script>

I’ll skip over some of the added prop­er­ties, and focus on the more inter­est­ing bits.

The addPersonArray() macro is used to add in the directors of the show, and it is also re-used to add in the actors who par­tic­i­pate in the episode as well. Adding this type of rela­tion­ship infor­ma­tion real­ly helps Google fig­ure out the con­nec­tions between peo­ple and their works on the web.

Then we’ve also added an AudioOb­ject JSON-LD object in the audio prop­er­ty that rep­re­sents the episode audio itself, and oth­er var­i­ous asso­ci­at­ed meta infor­ma­tion about the audio.

Final­ly, all of the show links” we dis­play on the web­page are added in the mentions prop­er­ty via the addMentionsArray() macro, so every­thing we men­tion on the show is linked to.

Final­ly, we add a new RadioSeries JSON-LD struc­tured data schema:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@id": "https://devmode.fm/#radio-series",
    "@type": "RadioSeries",
    "actor": [
        {
            "@type": "Person",
            "affiliation": "nystudio107",
            "description": "Andrew Welch has been in the tech business since he was 15 years old.  After a stint running a software company for a couple of decades, he's now immersing himself in doing consulting to help businesses use technology effectively.",
            "familyName": "Welch",
            "givenName": "Andrew",
            "jobTitle": "Demolitions Expert",
            "name": "Andrew Welch",
            "sameAs": [
                "https://twitter.com/nystudio107",
                "https://github.com/nystudio107"
            ],
            "url": "https://nystudio107.com"
        },
        {
            "@type": "Person",
            "affiliation": "Hypatia Industries",
            "description": "Earl Johnston is a freelance web developer operating under the Hypatia Industries banner. He specializes in high fives and bourbon.",
            "familyName": "Johnston",
            "givenName": "Earl",
            "jobTitle": "Professor Doctor",
            "name": "Earl Johnston",
            "url": "http://www.hypatia-industries.com"
        },
        {
            "@type": "Person",
            "affiliation": "A Bright Color",
            "description": "Lauren is a developer with a background in design, hailing from Toledo, Ohio. Her focus: make understanding code scalability easier, to help create performant and accessible experiences. Outside of work: playing with her MIDI controller, finding the best shows, and cycling around the streets of Neukölln.",
            "familyName": "Dorman",
            "givenName": "Lauren",
            "jobTitle": "Development",
            "name": "Lauren Dorman",
            "sameAs": [
                "https://twitter.com/laurendorman?lang=en",
                "https://github.com/laurendorman"
            ],
            "url": "https://laurendorman.io/about/"
        },
        {
            "@type": "Person",
            "affiliation": "MDD",
            "description": "Jonathan is principal/CEO of MDD, a small web agency based in Atlanta, Georgia. He has been developing for the web since 2005 and Craft CMS since 2013. He used to have free time and cool hobbies until his second child arrived on December 6, 2017. He loves his work and his family.",
            "familyName": "Melville",
            "givenName": "Jonathan",
            "jobTitle": "Jonathan Melville",
            "name": "Jonathan Melville",
            "sameAs": [
                "https://twitter.com/jonmelville",
                "https://github.com/jonathanmelville"
            ],
            "url": "https://codemdd.io"
        },
        {
            "@type": "Person",
            "affiliation": "Newlevant",
            "description": "Marion has been writing software for over 40 years. She may not have seen it all, but she's seen many things, most of them more than once. Marion is the queen of twig macros, and the second most famous Newlevant.",
            "familyName": "Newlevant",
            "givenName": "Marion",
            "jobTitle": "Marion Newlevant",
            "name": "Marion Newlevant",
            "sameAs": [
                "https://twitter.com/marionnewlevant",
                "https://github.com/marionnewlevant/"
            ],
            "url": "http://marion.newlevant.com"
        },
        {
            "@type": "Person",
            "affiliation": "Working Concept",
            "description": "Matt's a web developer with a design background that's been building websites for the past decade. He loves learning, connecting things, and helping businesses solve multi-faceted, internet-shaped problems.",
            "familyName": "Stein",
            "givenName": "Matt",
            "jobTitle": "President/Designer/Developer",
            "name": "Matt Stein",
            "sameAs": [
                "https://twitter.com/mattrambles",
                "https://github.com/mattstein"
            ],
            "url": "https://workingconcept.com"
        },
        {
            "@type": "Person",
            "affiliation": "Build For Humans",
            "description": "Michael runs a small dev team in Texas. Code is his happy place. He wants to be a teacher when he grows up. When not writing code, he cooks with friends, travels all over the place, sings in the shower, and works out with the circus.",
            "familyName": "Rog",
            "givenName": "Michael",
            "jobTitle": "Michael Rog",
            "name": "Michael Rog",
            "sameAs": [
                "https://twitter.com/michaelrog",
                "https://github.com/TopShelfCraft"
            ],
            "url": "https://michaelrog.com"
        },
        {
            "@type": "Person",
            "affiliation": "Mildly Geeky",
            "description": "Patrick Harrington has been obsessed with the web ever since picking up a copy of HTML for Dummies at age 13. After working for larger Boston-area agencies, he started his own company in 2011 and specializes in Craft CMS.",
            "familyName": "Harrington",
            "givenName": "Patrick",
            "jobTitle": "President, Founder",
            "name": "Patrick Harrington",
            "sameAs": [
                "https://twitter.com/p_harrington83",
                "https://github.com/mildlygeeky"
            ],
            "url": "https://mildlygeeky.com"
        }
    ],
    "author": {
        "@id": "https://devmode.fm/#identity"
    },
    "copyrightHolder": {
        "@id": "https://devmode.fm/#identity"
    },
    "creator": {
        "@id": "https://nystudio107.com/#creator"
    },
    "description": "devMode.fm is a bi-weekly podcast dedicated to the tools, techniques, and technologies used in modern web development. Each episode, we have a cadre of hosts discussing the latest hotness, pet peeves, and technologies we use every day. We all come from a Craft CMS background, but we'll be focusing on other cool frontend development technologies as well.",
    "director": [
        {
            "@type": "Person",
            "affiliation": "nystudio107",
            "description": "Andrew Welch has been in the tech business since he was 15 years old.  After a stint running a software company for a couple of decades, he's now immersing himself in doing consulting to help businesses use technology effectively.",
            "familyName": "Welch",
            "givenName": "Andrew",
            "jobTitle": "Demolitions Expert",
            "name": "Andrew Welch",
            "sameAs": [
                "https://twitter.com/nystudio107",
                "https://github.com/nystudio107"
            ],
            "url": "https://nystudio107.com"
        },
        {
            "@type": "Person",
            "affiliation": "Mildly Geeky",
            "description": "Patrick Harrington has been obsessed with the web ever since picking up a copy of HTML for Dummies at age 13. After working for larger Boston-area agencies, he started his own company in 2011 and specializes in Craft CMS.",
            "familyName": "Harrington",
            "givenName": "Patrick",
            "jobTitle": "President, Founder",
            "name": "Patrick Harrington",
            "sameAs": [
                "https://twitter.com/p_harrington83",
                "https://github.com/mildlygeeky"
            ],
            "url": "https://mildlygeeky.com"
        }
    ],
    "image": {
        "@type": "ImageObject",
        "height": 1800,
        "url": "http://devmode.test/assets/site/devmode_light-itunes.jpg",
        "width": 1800
    },
    "inLanguage": "en-us",
    "mainEntityOfPage": "http://devmode.test/",
    "name": "devMode.fm",
    "url": "https://devmode.fm/"
}
</script>

This RadioSeries schema gives the indi­vid­ual episode con­text in terms of what series it belongs to, and also re-uses the addPersonArray() macro to add all of the peo­ple who have ever been on the show as a host as part of the RadioSeries.

Addi­tion­al things that could be done:

Google Struc­tured Data Test­ing Tool link → dev​Mode​.fm Episode Page

Link Fin.

These exam­ples show sev­er­al real-world uses of JSON-LD struc­tured data, but even here, more could be done!

I think it makes sense to get the low-hang­ing fruit first, and address the con­nec­tions & con­text that might be dif­fi­cult for Google to deter­mine on its own.

Hope­ful­ly these anno­tat­ed exam­ples of how JSON-LD struc­tured data is actu­al­ly used in the wild will be help­ful to you.

Cre­ate your own cus­tom JSON-LD struc­tured data by prag­mat­i­cal­ly pick­ing the schemas and prop­er­ties that best fit” your web­site, pay­ing a lit­tle extra spe­cial atten­tion to the schemas that Google consumes.

Enjoy your struc­tured data!