Skip to product information
1 of 1
Published:2026.09.14

[Horizon] Reflect metafield content in structured data (JSON-LD) too

[Horizon] Reflect metafield content in structured data (JSON-LD) too

Applications

  • SEO関連
View full details

[Why structured data still matters now] In 2026, Shopify introduced UCP (Universal Commerce Protocol) as a dedicated channel for AI agents to fetch product info for agentic commerce — a purpose-built API endpoint where agents can pull price, inventory, and product detail. But UCP is only used by agents that deliberately implement the protocol (every request requires JSON-RPC formatting and an agent profile). Search engine crawlers that don't support UCP, and any AI agent that just fetches the page directly, still rely on the structured data (schema.org / JSON-LD) already on the page.

A structured-data specialist makes the same point.
"Structured data remains the main foundation. Before an AI agent can buy a product or book a service, it has to find it."

In the previous TIPS (showing different collapsible-row content per product or per category), we used product metafields and a metaobject to control what's visible on the page. That's only the "visible" side of things, though — it isn't reflected in the structured data (schema.org / JSON-LD) that search engines and AI agents read. This TIPS shows how to dynamically merge that same metafield content into the structured data too.

[Important] The visible content and the structured data must always come from the same value. Avoid designing a separate description specifically "for AI agents" that differs from what's shown on the page. Google explicitly states that structured data must be a true representation of the content on the page, and that you must not mark up content that isn't visible to users. This implementation naturally satisfies that principle, because it reuses the exact same metafield/metaobject value that's already shown on the page.

Whatever is shown on the page also flows into the structured data (JSON-LD)

Why Horizon's built-in mechanism isn't enough on its own

Horizon's structured data is generated with a single line: {{ closest.product | structured_data }}. This filter returns a complete JSON string that already includes price, inventory, and variants — but it just returns it as-is, with no way to add fields afterward. And since Liquid has no filter to parse a string back into JSON (no equivalent of JSON.parse in other languages), there's no way to decompose and rebuild that string in Liquid alone.

That <script type="application/ld+json"> tag itself lives inside the core file sections/product-information.liquid — but you don't need to touch it or add an id to it. Inside the new block, document.currentScript (the currently-executing script element itself) lets you walk up to .closest('.shopify-section') and find the existing ld+json script within the same section, without modifying the core file at all. Because of this mechanism, the block must be placed somewhere inside the "Product information" section — placing it in a different section means no ld+json exists in that section, so it silently does nothing.

The "extra properties" side doesn't depend on DOM ordering (e.g. being adjacent to anything) at all. The JSON island carries a unique id="schema-extra-properties-{{ block.id }}", and document.getElementById looks it up directly — so no matter how many other blocks (including ones with their own script tags) sit before or after it in the same section, it always finds its own.

How it works: one metafield, two destinations

The solution: merge into the JSON-LD in the browser

We leave the filter's output untouched, and separately have Liquid output just the "extra properties" we want to add as JSON. On page load, JavaScript merges the two together. Google officially states that it can process structured data present in the DOM at render time, so this approach is correctly picked up by both rich results and AI agents.

・schema.org's Product type has no dedicated property for "extra info added via a metafield," so we use additionalProperty (an array of PropertyValue objects), which exists exactly for this purpose.

・Google also notes that dynamically-generated markup can be crawled somewhat less often and less reliably. Fast-changing fields like price and inventory should stay in the filter's static output untouched; appending supplementary info the way we do here keeps the impact limited.

Setup

[Key point] This never touches a core file (sections/product-information.liquid or any other Horizon-provided file). You add one new private block and attach it to the "Product information" section in the theme editor — that's the whole change.

◆1. In the code editor, create schema-additional-property.liquid in the blocks directory and paste in the code (shown after purchase).

New file: blocks/schema-additional-property.liquid

◆2. In the theme editor, open a product page, and under the "Product information" section's "Add block," add the "Reflect additional info in structured data" block. It must be placed somewhere inside the "Product information" section — but the exact position within it (relative to other blocks, nesting depth) doesn't matter. This block renders nothing visible.

◆3. In the config section at the top of the code, rewrite the metafield keys to match your own store.

schema_product_description: converts a rich-text product metafield to plain text via the metafield_text filter
schema_category_guide_text: does the same for the rich-text field inside the metaobject referenced by a metaobject-reference metafield

[Note] A rich-text metafield is NOT correctly converted to plain text by .value or strip_html alone. (During testing, this actually produced the raw rich-text data structure instead of plain text.) Always use the metafield_text filter.

How to verify it

After deploying, open the product page, open DevTools via "Inspect," and run the following in the Console to see the merged content directly.

JSON.parse(document.querySelector('script[type="application/ld+json"]').textContent).additionalProperty

[Note] "View Page Source" will NOT show this. The merge happens via JavaScript rewriting the DOM after the page loads, so it never appears in the initial HTML the server returns. Always check the post-execution DOM via "Inspect" or the console.

Customization

This example uses a product metafield and a metaobject-reference metafield, but you can reflect any other metafield the same way by adding another entry to the additionalProperty array.

Notes

・Google itself notes that dynamically-generated structured data can be somewhat less crawl-frequent and reliable. Don't use this approach for fast-changing data like price or inventory
・A metafield with no value simply doesn't output an additionalProperty entry, so no empty strings are left behind in the schema

See this TIPS for how to show different collapsible-row content per product or per category in the first place.

References:
Google: Generate Structured Data with JavaScript
Google: General Structured Data Guidelines
Shopify: Build commerce agents with UCP
How Structured Data, WebMCP, and UCP Form the Trinity of Agentic E-Commerce

After making a purchase (all items are ¥0), you will be able to view the sample code.

If you have already made a purchase, please login here.

Sample Codes

Test Theme :Horizon 4.1.4以降

blocks/schema-additional-property.liquid(新規作成)