← Back to Course Index

Module 3.1 — The Schema.org Model: JSON-LD, Vocabulary, and Structured Data Foundations

Structured data is the layer of your HTML that speaks directly to machines. Where a human reader infers that a star rating belongs to a product, a crawler has to guess — unless you tell it explicitly. Schema.org and JSON-LD together give you a precise, standard language for making those relationships unambiguous. This module builds the conceptual and technical foundation you need before touching a single line of schema markup.


1. What Schema.org Actually Is

Schema.org is a collaborative, open vocabulary launched in 2011 by Google, Microsoft, Yahoo, and Yandex. It defines a shared hierarchy of types and properties that describe the things on your pages: products, events, people, organizations, recipes, articles, and hundreds more.

Think of it as a data dictionary. The dictionary does not care how you embed it in a page — that is a separate question answered by the serialization format. Schema.org just defines what things exist and what properties they can have.

2. The Three Serialization Formats

You can embed Schema.org vocabulary in HTML in three ways. You need to know all three exist, but you will almost always choose one.

3. Why JSON-LD Is the Right Choice

The decision is not really debatable for a site you control. JSON-LD wins on every practical dimension:

4. The Schema.org Type Hierarchy

Schema.org is organized as a class hierarchy. Every type inherits properties from its parent types. Understanding this inheritance prevents both over-specification and under-specification.

The root of the hierarchy is Thing. Everything inherits from it. Some key branches:

Thing
├── CreativeWork
│   ├── Article
│   │   ├── NewsArticle
│   │   └── BlogPosting
│   ├── Recipe
│   ├── HowTo
│   └── WebPage
│       ├── ItemPage          ← for product pages
│       └── FAQPage
├── Event
├── Intangible
│   ├── Offer
│   ├── AggregateRating
│   ├── Rating
│   ├── BreadcrumbList
│   └── ListItem
├── Organization
│   └── LocalBusiness
│       └── Restaurant
├── Person
└── Product

Because Article inherits from CreativeWork which inherits from Thing, an Article can use properties defined at any of those three levels. For example, name is a Thing property and is therefore valid on every type. author is a CreativeWork property, valid on Article and all its siblings.

5. Anatomy of a JSON-LD Block

Before writing any specific type, you need to understand the structural components every JSON-LD block shares.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "@id": "https://example.com/blog/technical-seo-guide#article",
  "headline": "The Complete Technical SEO Guide",
  "datePublished": "2024-03-15",
  "dateModified": "2024-11-01",
  "author": {
    "@type": "Person",
    "name": "Jordan Ellis",
    "url": "https://example.com/authors/jordan-ellis"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Example Media",
    "logo": {
      "@type": "ImageObject",
      "url": "https://example.com/logo.png"
    }
  },
  "image": "https://example.com/images/seo-guide-hero.jpg",
  "description": "A comprehensive guide to technical SEO for intermediate and advanced practitioners."
}
</script>

Let's break down each structural keyword:

6. Properties: Scalar Values, URLs, and Nested Types

A property's value can be one of three forms. Knowing which to use is a common source of errors.

6a. Scalar values

Plain strings, numbers, dates, or booleans.

"headline": "The Complete Technical SEO Guide",
"datePublished": "2024-03-15",
"wordCount": 4200,
"isAccessibleForFree": true

6b. URL values

Some properties expect a URL. You can pass a plain string URL or wrap it in a URL type object. For most properties, a plain string is sufficient and cleaner.

"image": "https://example.com/images/hero.jpg"

6c. Nested type objects

Many properties expect not a value but an entire entity as their value. The author property, for example, does not just want a name string — it wants a Person or Organization object.

"author": {
  "@type": "Person",
  "name": "Jordan Ellis",
  "url": "https://example.com/authors/jordan-ellis",
  "sameAs": [
    "https://twitter.com/jordanellis",
    "https://linkedin.com/in/jordanellis"
  ]
}

This nesting is what makes structured data powerful. You are not just labeling a page — you are describing a graph of related entities and their relationships.

7. Multiple Types on One Page

A single page often represents more than one type. A product page might carry a Product, a BreadcrumbList, and an Organization. You have two options:

Option A: Multiple separate <script> blocks

Each type gets its own block. Clean and easy to maintain independently.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Wireless Headphones Pro",
  ...
}
</script>

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  ...
}
</script>

Option B: A single block with a @graph array

All types live in a single block under a @graph key. This is the preferred approach when entities need to reference each other via @id, which is the pattern used for building a connected entity graph.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Product",
      "@id": "https://example.com/headphones-pro#product",
      "name": "Wireless Headphones Pro",
      "brand": {
        "@id": "https://example.com/#organization"
      }
    },
    {
      "@type": "Organization",
      "@id": "https://example.com/#organization",
      "name": "Example Electronics",
      "url": "https://example.com"
    },
    {
      "@type": "BreadcrumbList",
      "@id": "https://example.com/headphones-pro#breadcrumb",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "https://example.com"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "Headphones",
          "item": "https://example.com/headphones"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "Wireless Headphones Pro",
          "item": "https://example.com/headphones-pro"
        }
      ]
    }
  ]
}
</script>

Notice how the Product's brand property does not repeat the organization data — it simply references it by @id. This is called entity referencing and is the foundation of the entity graph pattern covered in Module 3.2.

8. Core Types and Their Rich Result Eligibility

Not every schema type earns a visual rich result in the SERP. Google supports a defined set of types for rich results. Below is a map of the types you will implement most often, their primary properties, and the rich result they can trigger.

9. Eligibility vs. Guarantee

This is one of the most important mental models in structured data work. Valid schema markup makes you eligible for a rich result. It does not guarantee one.

Google decides whether to show a rich result based on multiple signals beyond your markup:

A clean validation result in the Rich Results Test means you have removed all technical barriers. Whether Google chooses to render the rich result is a separate editorial decision.

10. The Golden Rule: Structured Data Must Match Visible Content

Google's structured data guidelines are explicit: do not mark up content that is not visible to the user on the page. This is treated as a form of cloaking and can result in a manual action that suppresses rich results across your entire site.

Practical implications:

When you generate schema programmatically from CMS data (which you will do in both platform tracks), you must ensure the CMS fields driving the schema are the same fields driving the visible template output. These should never diverge.

11. Validation Tools

Two primary tools exist for validating your markup:

A typical validation workflow:

  1. Author or generate your JSON-LD block.
  2. Paste the raw JSON into JSONLint or your editor to confirm it is syntactically valid JSON.
  3. Test the live URL in the Rich Results Test. Check the "Detected structured data" panel to confirm all nested types were parsed correctly.
  4. Test in the Schema Markup Validator to catch any property-level warnings the Rich Results Test does not surface.
  5. Use Google Search Console's "Enhancements" reports over time to monitor eligibility and catch regressions at scale.

12. Common Mistakes to Avoid

13. Module Summary

You now understand the schema.org vocabulary model, why JSON-LD is the correct serialization choice, the anatomy of a JSON-LD block including @context, @type, and @id, the core types that are eligible for rich results, and the critical constraint that all marked-up content must be visible to users. You also know the difference between validation eligibility and a guaranteed rich result.

In Module 3.2, you will take these primitives and use nesting and @id referencing to build a connected entity graph across a page and across a site — the pattern that moves structured data from "individual markup" to a deliberate entity architecture.


Hands-On Exercise

Choose a real product page from any e-commerce site you can inspect.

  1. Open the page's source HTML and check whether it already has structured data. If so, copy the existing markup and evaluate it against the current schema.org documentation for Product.
  2. Author a @graph-based JSON-LD block from scratch that includes: a Product (with at least name, image, description, sku), a nested Offer (with price, priceCurrency, availability), a BreadcrumbList, and an Organization referenced via @id.
  3. Lint the JSON for syntax validity.
  4. Test the block in both the Rich Results Test and the Schema Markup Validator. Resolve every error and note every warning.
  5. Cross-reference every value you marked up against the actual visible page content. Flag any discrepancies.

Milestone Check

You are ready to move to Module 3.2 when you can: