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.
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.
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.
<script type="application/ld+json"> block, usually placed in <head> or at the end of <body>. The markup lives entirely outside the visible HTML. Google explicitly recommends this format.
itemscope, itemtype, itemprop) added directly to existing HTML elements. The schema is tangled into your markup, making it harder to maintain.
The decision is not really debatable for a site you control. JSON-LD wins on every practical dimension:
itemprop attributes scattered across hundreds of lines of HTML.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.
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:
"@context": "https://schema.org" — Declares the vocabulary. This tells any parser reading this JSON that the terms used (Article, headline, etc.) are defined at schema.org. Without it, the JSON is just an object; with it, it is Linked Data.
"@type": "Article" — Declares what kind of thing this object describes. This is the most important field. It must match a real type in the schema.org vocabulary.
"@id" — A URI that uniquely identifies this specific entity. It does not have to be a real URL, but using the canonical URL of the page (with a fragment identifier to distinguish multiple entities on the same page) is best practice. This is the key to building a connected entity graph — discussed in Module 3.2.
A property's value can be one of three forms. Knowing which to use is a common source of errors.
Plain strings, numbers, dates, or booleans.
"headline": "The Complete Technical SEO Guide",
"datePublished": "2024-03-15",
"wordCount": 4200,
"isAccessibleForFree": true
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"
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.
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:
<script> blocksEach 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>
@graph arrayAll 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.
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.
headline, image, author, datePublished, dateModified, publisher. Enables Top Stories carousel eligibility (requires AMP or Web Stories for Top Stories on mobile in many cases; standard articles get enhanced display).
name, image, description, sku, brand, offers (nested Offer), aggregateRating. Enables price, availability, and rating display in product rich results and Google Shopping.
mainEntity containing an array of Question objects, each with acceptedAnswer → Answer. Enables expandable FAQ accordions directly in the SERP.
name, step array of HowToStep objects. Enables step-by-step displays in search results.
itemListElement array of ListItem objects with position, name, item. Displays the breadcrumb path in the SERP URL line instead of the raw URL.
name, url, logo, address, telephone, sameAs. Contributes to the Knowledge Panel; LocalBusiness subtype adds hours, geo, and map data.
name, startDate, endDate, location, eventStatus, eventAttendanceMode. Enables event rich results with dates and location.
name, image, author, cookTime, recipeYield, recipeIngredient, recipeInstructions, aggregateRating. Enables rich recipe cards with images, ratings, and cook time.
name, description, thumbnailUrl, uploadDate, duration, contentUrl. Enables video rich results with thumbnail, duration, and chapters.
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.
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:
aggregateRating, the rating must be displayed to users.dateModified, the updated date should be verifiable from the page content.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.
Two primary tools exist for validating your markup:
search.google.com/test/rich-results) — Checks whether your page is eligible for Google-specific rich results. It also renders the page (including JavaScript), so you can catch issues where schema is injected client-side vs server-side. Use this as your primary validation tool.
validator.schema.org) — Validates against the full schema.org specification, not just the Google subset. Useful for catching property-level errors and for types Google does not surface as rich results but that still contribute to entity understanding.
A typical validation workflow:
@type casing. Schema.org types are PascalCase (Article, LocalBusiness), and properties are camelCase (datePublished, streetAddress). Casing errors cause the parser to silently ignore the type or property.
addressLocality remains valid; others like postalAddress as a standalone type were replaced). Always check the current schema.org documentation for the type you are implementing.
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.
Choose a real product page from any e-commerce site you can inspect.
Product.@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.You are ready to move to Module 3.2 when you can: