# 2026-04-04 Schema.org JSON-LD Metatag Improvements for AEO

> **Session date:** 2026-04-04
> **Agents used:** Forge (orchestrator)
> **ClickUp tickets:** none
> **Branch:** make-package

---

## Summary

Extended the site's structured data output with rich Schema.org JSON-LD using the `schema_metatag` module submodules. Specific schemas were added for Story nodes (BlogPosting), Person nodes (Person), and automatic BreadcrumbList output was added for all content types. The homepage received `WebSite` and `Organization` schemas including social `sameAs` links and a dedicated logo. All values are populated via Metatag tokens. The goal is improved Answer Engine Optimisation (AEO) and Google Rich Results eligibility.

---

## Work Completed

### Forge — Schema.org JSON-LD via schema_metatag

**What was done:**
Enabled three `schema_metatag` submodules and created bundle-specific Metatag Default config entities to emit `BlogPosting`, `Person`, and `WebPage` (with `BreadcrumbList`) structured data as JSON-LD in the `<head>` of every page.

**Modules enabled:**
- `schema_article` — provides `BlogPosting` / `Article` schema tags
- `schema_person` — provides `Person` schema tags
- `schema_web_page` — provides `WebPage` + automatic `BreadcrumbList` tag
- `schema_web_site` — provides `WebSite` schema tag for the homepage
- `schema_organization` — provides `Organization` schema tag for the homepage

**Files changed:**
- `config/sync/core.extension.yml` — added all five schema submodules
- `config/sync/metatag.metatag_defaults.node__story.yml` — **new** — `BlogPosting` schema + standard meta/OG tags for Story content type
- `config/sync/metatag.metatag_defaults.node__person.yml` — **new** — `Person` schema + standard meta/OG tags for Person content type
- `config/sync/metatag.metatag_defaults.node.yml` — updated — added `WebPage` type + `BreadcrumbList` for all node content types
- `config/sync/metatag.metatag_defaults.front.yml` — updated — added `WebSite` + `Organization` schema for homepage
- `web/themes/custom/naomi_2025/schema_organization_logo.png` — **new** — dedicated Organization logo image for Google Knowledge Panel

---

## Schema Details

### node__story — BlogPosting

Emits a `BlogPosting` JSON-LD block with:

| Schema property | Token source |
|---|---|
| `@type` | `BlogPosting` (hardcoded) |
| `@id` | `[node:url]` |
| `name` | `[node:title]` |
| `headline` | `[node:field_display_title]` |
| `description` | `[node:field_summary]` |
| `image.url` | `[node:field_erm_page_image:entity:field_media_image:seo_image:url]` |
| `author` | Organization: `[site:name]` / `[site:url]` |
| `publisher` | Organization: `[site:name]` / `[site:url]` |
| `datePublished` | `[node:field_published_date:value]` |
| `dateModified` | `[node:field_updated_date:value]` |
| `mainEntityOfPage` | `[node:url]` |

Also includes a `WebPage` block with `BreadcrumbList` (auto-built from Drupal's breadcrumb manager).

---

### node__person — Person

Emits a `Person` JSON-LD block with:

| Schema property | Token source |
|---|---|
| `@type` | `Person` (hardcoded) |
| `@id` | `[node:url]` |
| `name` | `[node:title]` |
| `description` | `[node:field_summary]` |
| `url` | `[node:url]` |
| `email` | `[node:field_email]` |
| `jobTitle` | `[node:field_job_title]` |
| `image.url` | `[node:field_erm_headshot:entity:field_media_image:url]` |

Also includes a `WebPage` block with `BreadcrumbList`.

---

### node (all content types) — WebPage + BreadcrumbList

The generic node Metatag Default now emits a `WebPage` schema block with `breadcrumb: Yes`. This enables Drupal's breadcrumb manager to auto-generate a `BreadcrumbList` for every node page (page, project, award, etc.) that doesn't have a more specific schema override.

---

### front (homepage) — WebSite + Organization

Emits two JSON-LD blocks on the homepage for brand identity and AEO:

**WebSite:**

| Schema property | Value |
|---|---|
| `@type` | `WebSite` |
| `@id` | `[site:url]` |
| `name` | `[site:name]` |
| `url` | `[site:url]` |

**Organization:**

| Schema property | Value |
|---|---|
| `@type` | `Organization` |
| `@id` | `[site:url]` |
| `name` | `[site:name]` (`Square360, Inc.`) |
| `url` | `[site:url]` |
| `logo` | `ImageObject` → `/themes/custom/naomi_2025/schema_organization_logo.png` |
| `sameAs` | LinkedIn, GitHub, Drupal.org profile URLs (comma-separated) |

---

## Meta & OG Tags — Bundle Overrides

Both `node__story` and `node__person` are bundle-level Metatag overrides, which means they take **full precedence** over the parent `node` defaults — no merging occurs. Standard `description`, `og_description`, `og_image`, `og_title`, `title`, and `canonical_url` tags were explicitly added to both configs to prevent them going missing.

---

## Decisions Made

- **Author set to Organization, not Person** — Story content does not have a dedicated author field linked to a Person node. Using `[site:name]` / `[site:url]` as the `Organization` publisher/author is the safe default and compliant with Google's guidelines. Individual author linking can be revisited if an author reference field is added to Story.
- **`schema_web_page` added to both story and person configs** — Both content types emit a `WebPage` wrapper alongside their primary type. This is required for the `BreadcrumbList` to output correctly (it lives in the `schema_web_page` group).
- **Breadcrumb value `Yes`** — The `BreadcrumbList` property type is a select with a single "Yes" option; it auto-builds the list from the current route's active breadcrumb trail at render time.
- **PHP-serialized strings for nested objects** — `schema_metatag` stores complex nested objects (ImageObject, Organization) as PHP `serialize()` strings in the Metatag config YAML. These were generated programmatically via `lando php -r` to ensure correctness.
- **Dedicated Organization logo** — `/themes/custom/naomi_2025/schema_organization_logo.png` is a purpose-built logo for structured data. Kept separate from the seo-default OG image so each can be optimised independently (Google prefers logos ≤ 1920×1080, ideally 160×90 for Knowledge Panel).

---

## Testing

- Config imported successfully via `lando drush cim -y` with no errors.
- `lando drush ev` confirmed all expected schema tag keys are present on a live Story node (NID 29) and a live Person node (NID 2).
- A generic Page node confirmed only `WebPage` + `BreadcrumbList` tags are applied (no bleed from story/person schemas).
- Config export confirmed no drift between DB and `config/sync/`.

---

## Reference

- [JSON-LD Schema for Google Rich Results](https://www.joydeepdeb.com/blog/json-ld-schema.html)
- [Schema.org BlogPosting](https://schema.org/BlogPosting)
- [Schema.org Person](https://schema.org/Person)
- [Schema.org BreadcrumbList](https://schema.org/BreadcrumbList)
- [Schema.org Organization](https://schema.org/Organization)
- [Schema.org WebSite](https://schema.org/WebSite)
- [Google Rich Results Test](https://search.google.com/test/rich-results)
