Personalized checkout and custom promotions with Shopify Scripts
I am having some issues raised with Google. I keep having the following issue: Parsing error: Missing '}' or object member name, specifically highlighted around the line in bold: {% for image in product.images %}.
I am also have a product snippet issue for Either "offers", "review", or "aggregateRating" should be specified.
would appreciate any help/suggestions.
below is my ld+json:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "Eyewear",
"item": "{{ shop.url }}/collections/spirit-eyewear"
},
{
"@type": "ListItem",
"position": 3,
"name": "{{ product.title }}",
"item": "{{ shop.url }}{{ product.url }}"
}
]
},
{
"@type": "Product",
"name": "{{ product.title }}",
"itemCondition": "https://schema.org/NewCondition",
"image": [
{% for image in product.images %}
"{{ image.src | img_url: '1024x1024' }}"{% unless forloop.last %},{% endunless %}
{% endfor %}
],
"description": "{{ product.description | strip_html | truncate: 300 | escape }}",
"sku": "{{ product.sku }}",
"mpn": "{{ product.metafields.custom.mpn }}",
"brand": {
"@type": "Brand",
"name": "{{ product.vendor }}"
},
{% if product.metafields.reviews.rating_value and product.metafields.reviews.review_count and product.metafields.reviews.review_count != '0' %}
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "{{ product.metafields.reviews.rating_value }}",
"reviewCount": "{{ product.metafields.reviews.review_count }}"
},
{% endif %}
{% if product.metafields.reviews.reviews_list.size > 0 %}
"review": [
{% for review in product.metafields.reviews.reviews_list %}
{
"@type": "Review",
"author": "{{ review.author }}",
"datePublished": "{{ review.date }}",
"reviewBody": "{{ review.body | strip_html | escape }}",
"reviewRating": {
"@type": "Rating",
"ratingValue": "{{ review.rating }}"
}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
],
{% endif %}
"offers": {
"@type": "Offer",
"url": "{{ shop.url }}{{ product.url }}",
"priceCurrency": "{{ shop.currency }}",
"price": "{{ product.price | money_without_currency }}",
"priceValidUntil": "{{ 'now' | date: '%Y-%m-%d' | date_modify: '+1 year' }}",
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "{{ product.shipping_price | default: '0' }}",
"currency": "{{ shop.currency }}"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "US"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": {
"@type": "QuantitativeValue",
"minValue": 1,
"maxValue": 2,
"unitCode": "DAY"
},
"transitTime": {
"@type": "QuantitativeValue",
"minValue": 1,
"maxValue": 4,
"unitCode": "DAY"
}
}
},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"name": "30 Day Return",
"url": "https://merhoki.com/policies/ ",
"applicableCountry": "US",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
},
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Current Promotions",
"itemListElement": [
{
"@type": "Offer",
"itemOffered": {
"@type": "Product",
"name": "{{ product.title }}",
"image": "{{ product.featured_image | img_url: '1024x1024' }}",
"description": "{{ product.description | strip_html | truncate: 160 | escape }}"
},
"priceCurrency": "{{ shop.currency }}",
"price": "{{ product.price | money_without_currency }}",
"eligibleQuantity": {
"@type": "QuantitativeValue",
"value": 2,
"unitCode": "C62"
},
"description": "Buy One, Get One Free – Automatically Applied at Checkout.",
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"url": "{{ shop.url }}{{ product.url }}"
}
]
},
"color": "{{ product.options_by_name['Color'].values | first }}",
"size": "{{ product.options_by_name['Size'].values | first }}",
}
]
}
</script>
Hi @avk9641
I am from Mageplaza - Shopify solution expert.
Problem 1: Parsing Error: Missing '}' or object member name
The root cause is because Liquid loops (like {% for image in product.images %}) are outputting inside JSON directly without ensuring valid JSON syntax.
In JSON, you can't have trailing commas after the last item.
If your {% for image in product.images %} loop outputs an extra comma, Google and other parsers will throw errors.
Your section:
"image": [
{% for image in product.images %}
"{{ image.src | img_url: '1024x1024' }}"{% unless forloop.last %},{% endunless %}
{% endfor %}
],
is mostly correct, but outside of context, if product.images is empty, it could still cause syntax issues.
SOLUTION:
Here's a safer way:
{% if product.images.size > 0 %}
"image": [
{% for image in product.images %}
"{{ image.src | img_url: '1024x1024' }}"{% unless forloop.last %},{% endunless %}
{% endfor %}
],
{% endif %}
Important: notice the comma after the array ], — this needs to be carefully placed.
If no images, you don't want image: [] appearing incorrectly.
Problem 2: Missing "offers", "review", or "aggregateRating"
Google requires that at least one of these fields exists in the Product schema:
You have them in your code...
BUT the problem is:
You are wrapping "aggregateRating" and "review" inside {% if ... %}, so if there are no reviews, that part won't be rendered at all.
And offers is always there, but if any Liquid errors cause syntax invalidation (e.g., broken commas or quotes), Google will not see it properly.
Hidden Problems in Your Code:
"size": "{{ product.options_by_name['Size'].values | first }}",
}
JSON must NOT have a trailing comma before the last }.
➔ Remove that final comma after "size"!
"url": "https://merhoki.com/policies/ ",
That space could break parsing. Remove it.
So, fixed version (partial), focusing on critical points:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "Eyewear",
"item": "{{ shop.url }}/collections/spirit-eyewear"
},
{
"@type": "ListItem",
"position": 3,
"name": "{{ product.title }}",
"item": "{{ shop.url }}{{ product.url }}"
}
]
},
{
"@type": "Product",
"name": "{{ product.title }}",
"itemCondition": "https://schema.org/NewCondition",
{% if product.images.size > 0 %}
"image": [
{% for image in product.images %}
"{{ image.src | img_url: '1024x1024' }}"{% unless forloop.last %},{% endunless %}
{% endfor %}
],
{% endif %}
"description": "{{ product.description | strip_html | truncate: 300 | escape }}",
"sku": "{{ product.sku }}",
"mpn": "{{ product.metafields.custom.mpn }}",
"brand": {
"@type": "Brand",
"name": "{{ product.vendor }}"
},
{% if product.metafields.reviews.rating_value and product.metafields.reviews.review_count and product.metafields.reviews.review_count != '0' %}
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "{{ product.metafields.reviews.rating_value }}",
"reviewCount": "{{ product.metafields.reviews.review_count }}"
},
{% endif %}
{% if product.metafields.reviews.reviews_list.size > 0 %}
"review": [
{% for review in product.metafields.reviews.reviews_list %}
{
"@type": "Review",
"author": "{{ review.author }}",
"datePublished": "{{ review.date }}",
"reviewBody": "{{ review.body | strip_html | escape }}",
"reviewRating": {
"@type": "Rating",
"ratingValue": "{{ review.rating }}"
}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
],
{% endif %}
"offers": {
"@type": "Offer",
"url": "{{ shop.url }}{{ product.url }}",
"priceCurrency": "{{ shop.currency }}",
"price": "{{ product.price | money_without_currency }}",
"priceValidUntil": "{{ 'now' | date: '%Y-%m-%d' | date_modify: '+1 year' }}",
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "{{ product.shipping_price | default: '0' }}",
"currency": "{{ shop.currency }}"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "US"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": {
"@type": "QuantitativeValue",
"minValue": 1,
"maxValue": 2,
"unitCode": "DAY"
},
"transitTime": {
"@type": "QuantitativeValue",
"minValue": 1,
"maxValue": 4,
"unitCode": "DAY"
}
}
},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"name": "30 Day Return",
"url": "https://merhoki.com/policies/",
"applicableCountry": "US",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
},
"color": "{{ product.options_by_name['Color'].values | first }}",
"size": "{{ product.options_by_name['Size'].values | first }}"
}
]
}
</script>
Please let me know if it works as expected!
Best regards
Mageplaza | Top-Rated Shopify Agency | Trusted by 230,000+ worldwide merchants
If our suggestion works for you, please give it a Like or mark it as a Solution!
Should you have any questions or concerns, feel free to contact us via consultant@mageplaza.com
Learn how to build powerful custom workflows in Shopify Flow with expert guidance from ...
By Jacqui May 7, 2025Did You Know? May is named after Maia, the Roman goddess of growth and flourishing! ...
By JasonH May 2, 2025Discover opportunities to improve SEO with new guidance available from Shopify’s growth...
By Jacqui May 1, 2025