# Structured data

> Add JSON-LD structured data and Open Graph tags to a Shoplazza Theme so search engines can show rich results and social platforms render link previews.

Structured data and social meta tags help search engines and social platforms understand your pages. They power rich results (such as product price and availability in search) and link previews on social media. This guide covers adding JSON-LD structured data and Open Graph tags to your theme.

Both live in a page's HTML and are read by machines, not shown to customers:

- **Structured data** tells a search engine what a page is about — for a product, its price, availability, and rating — so the search result can show those details inline (a *rich result*) instead of just a title and link. The standard format is **JSON-LD**: a block of JSON inside a `<script type="application/ld+json">` tag, the way Google recommends providing it.
- **Social meta tags** tell a social platform what image, title, and description to show when someone shares your link (the *link preview* card). The standard format is **Open Graph** — `og:` `<meta>` tags, originally from Facebook and now read by most platforms.

For page titles, descriptions, and canonical URLs, see [Metadata](/docs/theme/theme-features/seo/metadata).

:::warning
Structured data must match the content that's visible on the page. If the markup reports a price, currency, or availability that differs from what customers see, search engines may ignore it or flag the page.
:::

## Add website structured data

On any page, you can describe the store itself with [WebSite](https://schema.org/WebSite) structured data:

```liquid
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "WebSite",
  "name": {{ shop.name | json }},
  "url": {{ shop.url | json }}
}
</script>
```

## Add product structured data

Output [Product](https://schema.org/Product) structured data as JSON-LD inside a `<script type="application/ld+json">` tag. Render it from a snippet included in the `<head>` of your layout, on product pages.

Select the variant to describe, then build the JSON from the [product](/docs/theme/references/liquid/objects/product), [variant](/docs/theme/references/liquid/objects/variant), [cart](/docs/theme/references/liquid/objects/cart), and [shop](/docs/theme/references/liquid/objects/shop) objects:

```liquid
{% assign variant = product.selected_or_first_available_variant %}
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "Product",
  "name": {{ product.title | json }},
  "url": {{ canonical_url | json }},
  "image": {{ product.image.src | prepend: 'https:' | json }},
  {% if product.description != blank %}"description": {{ product.description | strip_html | json }},{% endif %}
  {% if product.vendor != blank %}"brand": { "@type": "Brand", "name": {{ product.vendor | json }} },{% endif %}
  {% if variant.sku != blank %}"sku": {{ variant.sku | json }},{% endif %}
  {% if variant.barcode != blank %}"gtin": {{ variant.barcode | json }},{% endif %}
  "offers": {
    "@type": "Offer",
    "url": {{ canonical_url | json }},
    "priceCurrency": {{ cart.currency | json }},
    "price": {{ variant.price | times: 1 | json }},
    "itemCondition": "https://schema.org/NewCondition",
    "availability": "https://schema.org/{% if variant.available %}InStock{% else %}OutOfStock{% endif %}",
    "seller": { "@type": "Organization", "name": {{ shop.name | json }} }
  }
}
</script>
```

What each part does, and the details that keep the output valid and accurate:

* `product.selected_or_first_available_variant` picks the variant the shopper selected, or the first in-stock one; the `variant.*` fields describe it.
* `@context` and `@type` declare the schema.org vocabulary and the `Product` type; `offers` holds the purchasable price, item condition, stock status, and seller.
* The optional fields (`description`, `brand`, `sku`, `gtin`) are wrapped in `{% if %}` guards, so each is output only when it has a value — empty entries would make the JSON invalid.
* `image.src` returns a protocol-relative URL (`//…`), so prepend `https:` for an absolute URL.
* The `json` filter escapes and quotes each value — don't add your own quotes around it.
* `price` passes through `times: 1` so it outputs as a number (`33`), not a string (`"33"`).
* Set `priceCurrency` to [`cart.currency`](/docs/theme/references/liquid/objects/cart), not `shop.currency`. When a store sells in multiple currencies, `cart.currency` reflects the currency the customer is viewing, so the markup matches the displayed price.
* `url` uses the [canonical_url](/docs/theme/references/liquid/objects/canonical_url) object, the absolute URL of the current product page.
* Output `gtin` only when the variant's `barcode` holds a valid GTIN (UPC, EAN, or ISBN).

### Variable-price products

When a product's variants span a price range, describe it with an [AggregateOffer](https://schema.org/AggregateOffer) instead of a single `Offer`, using `price_min` and `price_max`:

```liquid
"offers": {
  "@type": "AggregateOffer",
  "priceCurrency": {{ cart.currency | json }},
  "lowPrice": {{ product.price_min | times: 1 | json }},
  "highPrice": {{ product.price_max | times: 1 | json }},
  "offerCount": {{ product.variants.size }}
}
```

## Add Open Graph and Twitter tags

Open Graph (`og:`) and Twitter Card meta tags control how a page looks when shared on social platforms. Add them to the `<head>`.

The most common Open Graph properties:

| Property | Description |
| --- | --- |
| `og:title` | Title shown in the preview |
| `og:type` | Object type — `website`, `article`, `product`, and so on |
| `og:image` | Image shown in the preview card |
| `og:url` | Canonical URL of the page |
| `og:description` | A one-to-two sentence summary |
| `og:site_name` | Name of your overall store |
| `og:locale` | Locale, such as `en_US` |

The first four (`og:title`, `og:type`, `og:image`, `og:url`) are required by the protocol; the rest are optional. For the full set of properties and object types, see the [Open Graph protocol](https://ogp.me/).

For example, Set the values from the current page's objects, falling back to store-level values:

```liquid
<meta property="og:type" content="{% if template.name == 'product' %}product{% else %}website{% endif %}">
<meta property="og:title" content="{{ page_title | default: shop.name | escape }}">
<meta property="og:description" content="{{ page_description | default: shop.description | strip_html | escape }}">
<meta property="og:url" content="{{ canonical_url | default: shop.url }}">
{% if product.image %}
  <meta property="og:image" content="https:{{ product.image.src | img_url: '1200x' }}">
  <meta property="og:image:secure_url" content="https:{{ product.image.src | img_url: '1200x' }}">
  <meta property="og:image:width" content="{{ product.image.width }}">
  <meta property="og:image:height" content="{{ product.image.height }}">
  <meta property="og:image:alt" content="{{ product.image.alt | escape }}">
{% endif %}

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ page_title | default: shop.name | escape }}">
<meta name="twitter:description" content="{{ page_description | default: shop.description | strip_html | escape }}">
```

How the tags resolve their values:

* `og:type` is `product` on product pages and `website` elsewhere, keyed off `template.name`.
* The `default` filter falls back to store-level values (`shop.name`, `shop.description`, `shop.url`) when the page sets none of its own.
* The `og:image` width, height, and alt text help platforms render the preview card; `twitter:card` set to `summary_large_image` requests a large preview image.

On product pages, add product-specific tags for price and availability. These use the `product:` namespace — a Facebook extension that applies when `og:type` is `product`, not the `og:` prefix. Use `cart.currency` so the currency matches the displayed price:

```liquid
{% if template.name == 'product' %}
  {% assign variant = product.selected_or_first_available_variant %}
  <meta property="product:price:amount" content="{{ variant.price }}">
  <meta property="product:price:currency" content="{{ cart.currency }}">
  <meta property="product:availability" content="{% if variant.available %}in stock{% else %}out of stock{% endif %}">
{% endif %}
```

## Validate your output

After adding structured data, test a live product page with [Google's Rich Results Test](https://search.google.com/test/rich-results) to confirm the markup is valid and eligible for rich results.
