Ecommerce SEO

How to Add Product Schema to Shopify Stores

R Ron Tsantker · · 4 min read
How to Add Product Schema to Shopify Stores

This would be the quick and easy way to add product schema code to your shopify store.

———

Go to Online Store > Themes > Edit Code

Choose the “product.liquid” file.

Add the following code towards the top:

———

<script type=’application/ld+json’>

{

“@context”: “http://schema.org/”,

“@type”: “Product”,

“name”: “{{ product.title }}”,

“url”: “{{ shop.url }}{{ product.url }}”,

“image”: “{{ shop.url }}{{ product.featured_image }}”,

“description”: “{{ page_description }}”,

“offers”: {

“@type”: “Offer”,

“priceCurrency”: “{{ shop.currency }}”,

“price”: “{{ product.price | money_without_currency }}”,

“itemCondition”: “http://schema.org/NewCondition”,

“availability”: “http://schema.org/InStock”

}

}

</script>


For a much more comprehensive schema, you can use the following:

{%- comment -%}
This snippet structures the micro-data using JSON-LD specification. Please note that for Product especially,
the schema often changes. We try to output as much info as possible, but Google may add new requirements over time,
or change the format of some info
{%- endcomment -%}

{%- if request.page_type == 'product' -%}
  {%- assign days_product_price_valid_until = 10 | times: 86400 -%}

  {%- assign gtin_option = 'gtin' -%}
  {%- if product.selected_or_first_available_variant.barcode != blank -%}
    {%- assign is_barcode_available = true -%}
    {%- assign gtin_string_length = product.selected_or_first_available_variant.barcode | size -%}

    {%- if gtin_string_length == 8 or gtin_string_length == 12 or gtin_string_length == 13 or gtin_string_length == 14 -%}
      {%- assign is_valid_gtin_length = true -%}
      {%- assign gtin_option = gtin_option | append: gtin_string_length -%}
    {%- endif -%}
  {%- endif -%}

  {%- assign has_reviews = false -%}
  {%- assign rating_value = blank -%}
  {%- assign review_count = blank -%}
  {%- if product.metafields.spr.reviews != blank -%}
    {%- assign rating_value_review_array = product.metafields.spr.reviews | split: 'meta itemprop="ratingValue" content="' -%}
    {%- assign rating_value_arr = rating_value_review_array[1] |split: '"' -%}
    {%- assign rating_value = rating_value_arr[0] -%}
    {%- assign review_count_str_array = product.metafields.spr.reviews | split: 'meta itemprop="reviewCount" content="' -%}
    {%- assign review_count_arr = review_count_str_array[1] | split: '"' -%}
    {%- assign review_count = review_count_arr[0] -%}

    {%- unless review_count == blank -%}
      {%- assign has_reviews = true -%}
    {%- endunless -%}
  {%- endif -%}

  {%- capture main_entity_microdata -%}
    "@type": "Product",
    "offers": [
      {%- for variant in product.variants -%}
        {
          "@type": "Offer",
          "name": {{ variant.title | json }},
          "availability": {%- if variant.available -%}"https://schema.org/InStock"{%- else -%}"https://schema.org/OutOfStock"{%- endif -%},
          "price": {{ variant.price | money_without_currency | json }},
          "priceCurrency": {{ shop.currency | json }},
          "priceValidUntil": "{{ 'now' | date: '%s' | plus: days_product_price_valid_until | date: '%Y-%m-%d'}}",
          {%- if variant.sku != blank -%}
            "sku": {{ variant.sku | json }},
          {%- endif -%}
          "url": "{{ request.path }}{{ variant.url }}"
        }{% unless forloop.last %},{% endunless %}
      {%- endfor -%}
    ],
    {%- if is_barcode_available and is_valid_gtin_length %}
      "{{gtin_option}}": {{ product.selected_or_first_available_variant.barcode | json }},
      "productId": {{ product.selected_or_first_available_variant.barcode | json }},
    {%- elsif is_barcode_available %}
      "mpn": {{ product.selected_or_first_available_variant.barcode | json }},
      "productId": {{ product.selected_or_first_available_variant.barcode | json }},
    {%- endif %}
    {%- if has_reviews -%}
      "aggregateRating": {
        "@type": "AggregateRating",
        "ratingValue": "{{ rating_value }}",
        "reviewCount": "{{ review_count }}"
      },
    {%- endif %}
    "brand": {
      "name": {{ product.vendor | json }}
    },
    "name": {{ product.title | json }},
    "description": {{ product.description | strip_html | json }},
    "category": {{ product.type | json }},
    "url": "{{ request.path }}{{ product.url }}",
    "sku": {{ product.selected_or_first_available_variant.sku | json }},
    "image": {
      "@type": "ImageObject",
      "url": "https:{{ product.featured_image | img_url: '1024x' }}",
      "image": "https:{{ product.featured_image | img_url: '1024x' }}",
      "name": {{ product.featured_image.alt | json }},
      "width": "1024",
      "height": "1024"
    }
  {%- endcapture -%}
{%- elsif request.page_type == 'article' -%}
  {%- capture main_entity_microdata -%}
    "@type": "BlogPosting",
    "mainEntityOfPage": "{{ article.url }}",
    "articleSection": {{ blog.title | json }},
    "keywords": "{{ article.tags | join: ', ' }}",
    "headline": {{ article.title | json }},
    "description": {{ article.excerpt_or_content | strip_html | truncatewords: 25 | json }},
    "dateCreated": "{{ article.created_at | date: '%Y-%m-%dT%T' }}",
    "datePublished": "{{ article.published_at | date: '%Y-%m-%dT%T' }}",
    "dateModified": "{{ article.published_at | date: '%Y-%m-%dT%T' }}",
    "image": {
      "@type": "ImageObject",
      "url": "https:{{ article.image | img_url: '1024x' }}",
      "image": "https:{{ article.image | img_url: '1024x' }}",
      "name": {{ article.image.alt | json }},
      "width": "1024",
      "height": "1024"
    },
    "author": {
      "@type": "Person",
      "name": "{{ article.user.first_name | escape }} {{ article.user.last_name | escape }}",
      "givenName": {{ article.user.first_name | json }},
      "familyName": {{ article.user.last_name | json }}
    },
    "publisher": {
      "@type": "Organization",
      "name": {{ shop.name | json }}
    },
    "commentCount": {{ article.comments_count }},
    "comment": [
      {%- for comment in article.comments limit: 5 -%}
        {
          "@type": "Comment",
          "author": {{ comment.author | json }},
          "datePublished": "{{ comment.created_at | date: '%Y-%m-%dT%T' }}",
          "text": {{ comment.content | json }}
        }{%- unless forloop.last -%},{%- endunless -%}
      {%- endfor -%}
    ]
  {%- endcapture -%}
{%- endif -%}

{%- capture breadcrumb_entity_microdata -%}
  "@type": "BreadcrumbList",
  "itemListElement": [{
      "@type": "ListItem",
      "position": 1,
      "name": {{ 'general.breadcrumb.home' | t | json }},
      "item": "{{ shop.url }}"
    }

    {%- if request.page_type == 'product' -%}
      {%- if collection -%}
        ,{
          "@type": "ListItem",
          "position": 2,
          "name": {{ collection.title | json }},
          "item": "{{ shop.url }}{{ collection.url }}"
        }, {
          "@type": "ListItem",
          "position": 3,
          "name": {{ product.title | json }},
          "item": "{{ shop.url }}{{ product.url | within: collection }}"
        }
      {%- else -%}
        ,{
          "@type": "ListItem",
          "position": 2,
          "name": {{ product.title | json }},
          "item": "{{ shop.url }}{{ product.url }}"
        }
      {%- endif -%}
    {%- elsif request.page_type == 'collection' -%}
        ,{
          "@type": "ListItem",
          "position": 2,
          "name": {{ collection.title | json }},
          "item": "{{ shop.url }}{{ collection.url }}"
        }
    {%- elsif request.page_type == 'blog' -%}
        ,{
          "@type": "ListItem",
          "position": 2,
          "name": {{ blog.title | json }},
          "item": "{{ shop.url }}{{ blog.url }}"
        }
    {%- elsif request.page_type == 'article' -%}
        ,{
          "@type": "ListItem",
          "position": 2,
          "name": {{ blog.title | json }},
          "item": "{{ shop.url }}{{ blog.url }}"
        }, {
          "@type": "ListItem",
          "position": 3,
          "name": {{ blog.title | json }},
          "item": "{{ shop.url }}{{ article.url }}"
        }
    {%- elsif request.page_type == 'page' -%}
       ,{
          "@type": "ListItem",
          "position": 2,
          "name": {{ page.title | json }},
          "item": "{{ shop.url }}{{ page.url }}"
        }
    {%- endif -%}
  ]
{%- endcapture -%}

{% if main_entity_microdata != blank %}
  <script type="application/ld+json">
  {
    "@context": "http://schema.org",
    {{ main_entity_microdata }}
  }
  </script>
{% endif %}

{% if breadcrumb_entity_microdata != blank %}
  <script type="application/ld+json">
  {
    "@context": "http://schema.org",
    {{ breadcrumb_entity_microdata }}
  }
  </script>
{% endif %}

Our backlinks have companies ranking for 17,793 keywords in Google's top three.



Day one: position 32.
Today, number one on a 74,000-a-month keyword.

Same machine, a different client: a 135,000-a-month keyword also sits at number one.

Nearly six thousand of those are at number one — across twenty-six different client programs, not one lucky site.

Counted in Google Search Console across those twenty-six properties, at average position over the twenty-eight days to 4 August 2026. The long tail is in there — plenty of those queries are searched a handful of times a month. Our own hand-tracked set of commercial keywords is the stricter cut: 1,518 in the top three, 670 at number one.

Now it's self-serve, and it's priced wholesale.

Create Free Account

No card required. See every tool and every price before you spend anything.

The Profit Labs rank tracker dashboard, showing a card per client program with a daily position sparkline, average position, keyword count, and counts of keywords in the top three, top ten and top one hundred.
The live rank tracker, eight of twenty-six programs. Client names and domains are blurred — they didn't sign up to appear in an advert. Every position, count and delta is untouched.