Miva Merchant Modules and Development
Want to start an online store? We work with you from start to finish, from commerce platform to design to SEO.
Experience counts, and we have a lot.

SCHEMA JSON/LD: Automatic JSON/LD Schema Generator

Scot Ranney • June 27, 2024


This will always be updated to whatever Scot does to it. 

Schema.org: https://schema.org/ This is a good basic reference but doesn't always match up to what google is looking for.

Google reference: https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data Google reference is the final word.

Testing Tools: Google links to two kinds of tools, similar but different. Both are useful but again, will differ on what is legal schema. 

https://search.google.com/test/rich-results

https://validator.schema.org/

Reviews: You have to edit under the "do not edit" area to uncomment review stuff.

Settings: The mvt:capture settings area is the only other part that needs to be edited unless there are special circumstances.

Implementation: Put this in a readytheme content section with the code schema_generator and the name Microdata Generator (JSON/LD Schema)and then put it at the bottom of the global footer. Use the conditional if scot's blogger is in the store because scot's blogger creates it's own json/ld

<mvt:if expr="l.settings:page:code NE 'BLOG'">
	<mvt:item name="readytheme" param="contentsection( 'schema_generator' )" />
</mvt:if>

Latest schema generator script for the readytheme content section. Make sure you use the testing tools above 

<mvt:comment> 
#
# June 2024 json/ld
#
# custom json/ld schema generator by scots scripts
#
</mvt:comment>

<mvt:comment>
#
# SCHEMA SETTINGS: The settings below are unique for your store. Make sure everything is correct.
# Note on reviews: Shopper Approved and other javascript based external review systems have, or should have, their own schema meta data.
# Tess TGR Reviews: You need to turn reviews on at bottom of settings AND edit below the "do not edit blow" sign to uncomment the reviews stuff due to page item calls
#
</mvt:comment>

<mvt:capture variable="l.settings:store_name">Apples of Gold Jewelry</mvt:capture>

<mvt:capture variable="l.settings:store_url">https://applesofgold.com</mvt:capture>

<mvt:capture variable="l.settings:generic_store_description">Apples of Gold Jewelry is a fine jewelry company, specializing in 14K, 18K, 22K solid gold and platinum, in various collections, such as gold cross pendants for men and women, chains and necklaces, earrings, diamond engagement rings, wedding bands, personalized jewelry and Christian jewelry.</mvt:capture>

<mvt:capture variable="l.settings:return_policy_url">https://applesofgold.com/policies.html</mvt:capture>

<mvt:comment>
#
# Manually set logo image full domain url - logos aren't always set in readytheme or other store settings. This can also be a custom logo (larger, different aspect ratio) for metadata
#
</mvt:comment>

<mvt:capture variable="l.settings:logo_image_url">https://applesofgold.com/Merchant2/graphics/00000001/1/apples-of-gold-jewelry-logo-2024.png</mvt:capture>

<mvt:capture variable="l.settings:return_policy_days">45</mvt:capture>

<mvt:comment>
#
# In Store Returns: True or False
#
</mvt:comment>

<mvt:capture variable="l.settings:in_store_returns">False</mvt:capture>

<mvt:comment>
#
# Refund_Type is one of the following:
#
# ExchangeRefund
# FullRefund
# StoreCreditRefund
#
</mvt:comment>

<mvt:capture variable="l.settings:refund_type">FullRefund</mvt:capture>

<mvt:comment>
#
# return_exceptions are the situatinos and/or products that do not qualify for returns.
#
# Example: Personalized Jewelry, Custom Orders
#
</mvt:comment>

<mvt:capture variable="l.settings:return_exceptions">Personalized Jewelry, Custom Orders</mvt:capture>

<mvt:comment>
#
# set whether or not reviews are being used. By default it uses TG Review module
# set to 1 if using, 0 if not. Also uncomment the review stuff and make it work with whatever system is being used.
#
</mvt:comment>

<mvt:assign name="l._reviews_on" value="0" />

<mvt:comment>
#
#
#
# DO NOT EDIT BELOW
#
#
#
</mvt:comment>

<mvt:capture variable="l.settings:store_schema">
<script type="application/ld+json">
{
	"@context": "http://schema.org",
		  "@type": "OnlineStore",
		"name": "&mvt:store_name;",
		  "url": "&mvt:store_url;",
		  "image": "&mvt:logo_image_url;",
		  <mvt:if expr="NOT ISNULL trim(l.settings:category:metafield:description)">
			   "description": "<mvt:eval expr="encodejavascriptstring(l.settings:category:metafield:description)" />",
		  <mvt:elseif expr="NOT ISNULL trim(l.settings:page_metafields:description)">
			   "description": "<mvt:eval expr="encodejavascriptstring(l.settings:page_metafields:description)" />",
		  <mvt:else>
			  "description": "<mvt:eval expr="encodejavascriptstring(l.settings:generic_store_description)" />",
		  </mvt:if>
		  "telephone": "&mvt:store:phone;",
		  "address": "<mvt:eval expr="encodejavascriptstring(l.settings:store:address $ ', ' $ l.settings:store:city $ ' ' $ l.settings:store:state $ ' ' $ l.settings:store:zip )" />",

		  <mvt:if expr="l.settings:reviews:review_count GT 0 AND l._reviews_on EQ 1">
			  "aggregateRating": {
					"@type": "AggregateRating",
					"ratingValue": "&mvt:reviews:avg_rating;",
					"reviewCount": "&mvt:reviews:review_count;"
			  },
		  </mvt:if>

	"MerchantReturnPolicy": 
	  {
			"@type": "https://schema.org/MerchantReturnPolicy",
			 "merchantReturnLink": "&mvta:return_policy_url;",
		"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
			"merchantReturnDays": "&mvt:return_policy_days;",
			"returnPolicyCountry": "US",
		"returnMethod": "https://schema.org/ReturnByMail",
			"refundType": "&mvt:refund_type;",
			"inStoreReturnsOffered": "&mvt:in_store_returns;"
	  }
}
,
{
	"@type": "WebSite",
	"url": "&mvtj:store_url;"
<mvt:if expr="g.search">
,
	"potentialAction": {
		"@type": "SearchAction",
		"target": {
			"@type": "EntryPoint",
			"urlTemplate": "&mvtj:urls:SRCH:auto_sep;Search=&mvta:global:search;"
		},
		"query-input": "required name=Search"
	}
}
</mvt:if>
<mvt:if expr="NOT ISNULL l.settings:breadcrumbs:links">
,
	{
		<mvt:assign name="l.settings:schema_breadcrumb_links" value="l.settings:breadcrumbs:links" />
		<mvt:assign name="l.home_breadcrumb:link" value="l.settings:urls:SFNT:auto" />
		<mvt:assign name="l.home_breadcrumb:name" value="'Home'" />
		<mvt:assign name="l.null" value="miva_array_insert_var( l.settings:schema_breadcrumb_links, l.home_breadcrumb, 1 )" />
		"@type": "BreadcrumbList",
		"itemListElement": [
			<mvt:foreach iterator="schema_breadcrumb_link" array="schema_breadcrumb_links">
				<mvt:if expr="l.settings:schema_breadcrumb_link:name EQ 'Home'">
					<mvt:assign name="l.settings:schema_breadcrumb_link:link" value="l.settings:store_url" />
				</mvt:if>
				<mvt:if expr="l.pos1 GT 1">,</mvt:if>{
					"@type": "ListItem",
					"position": <mvt:eval expr="l.pos1" />,
					"name": "&mvtj:schema_breadcrumb_link:name;",
					"item": "&mvtj:schema_breadcrumb_link:link;"
				}
			</mvt:foreach>
		]
	}
</mvt:if>
</script>
</mvt:capture>

<!-- SCHEMA generator begin: JSON/LD Microdata -->

<mvt:comment>
|
| CTGY PAGE - cateogry display microdata
|
</mvt:comment>
<mvt:if expr="g.screen EQ 'CTGY' AND NOT ISNULL g.category_code">

<mvt:comment>
#
  <mvt:if EXPR = "{ l._reviews_on EQ 1 }">
	<mvt:item name="tgreviews" param="Load_Product_Reviews( 0, l.settings:reviews )" />
  </mvt:if>
#
</mvt:comment>

&mvt:store_schema;
<mvt:if expr="miva_array_elements(l.settings:category_listing:products) GT 0">
  <script type="application/ld+json">
  {
	  "@context": "http://schema.org",
	  "@type": "Collection",
	  "image": "&mvt:logo_image_url;",
	  "url": "&mvt:category:link;",
	  "name": "<mvt:eval expr="l.settings:category:name $ ' - ' $ encodejavascriptstring(g.store:name)" />",
  "description": "<mvt:eval expr="encodejavascriptstring(l.settings:category:metafield:description)" />",
  "offerCount": "<mvt:eval expr="miva_array_elements(l.settings:category_listing:products)" />",

	<mvt:if expr="l.settings:reviews:review_count GT 0 AND l._reviews_on EQ 1">
	"aggregateRating": {
		  "@type": "AggregateRating",
		  "ratingValue": "&mvt:reviews:avg_rating;",
		  "reviewCount": "&mvt:reviews:review_count;"
	},
	</mvt:if>

	"offers": 
	[
	  <mvt:foreach iterator="product" array="category_listing:products">  
	  {
		"@type": "Offer",
	"name": "<mvt:eval expr="encodejavascriptstring(l.settings:product:name)" />",
	"description": "<mvt:eval expr="encodejavascriptstring(l.settings:product:descrip)" />",
	"category": "<mvt:eval expr="encodejavascriptstring(l.settings:category:name)" />",
		<mvt:if expr="l.settings:product:inv_active">
		  "availability": "http://schema.org/InStock",
		</mvt:if>
		"price": "&mvt:product:price;",
		"priceCurrency": "USD",
		"identifier": "upc:&mvte:product:code;",
		"seller": "&mvte:global:store:name;",
		"itemCondition": "new"		
	  }
	 <mvt:if expr="NOT ISNULL l.settings:category_listing:products[l.pos1 + 1]">
			  ,
	 </mvt:if>	
  </mvt:foreach>  
	]	
  }
  </script>
</mvt:if>
<mvt:elseif expr="( (g.screen EQ 'PROD') AND NOT ISNULL g.product_code )">
<mvt:comment>
#
# PRODUCT
#
</mvt:comment>
&mvt:store_schema;
  
<mvt:assign name="l.settings:product:schema" value="l.settings:product" />
<mvt:if expr="g.variant_id">
		<mvt:do file="g.Module_Library_DB" name="l.success" value="ProductList_Load_Variant( l.settings:product:id, g.variant_id, l.variant_schema )" />
		<mvt:assign name="l.settings:product:schema:name" value="l.variant_schema[1]:name" />
		<mvt:assign name="l.settings:product:schema:price" value="l.variant_schema[1]:price" />
		<mvt:comment> SKU or Code? </mvt:comment>
		<mvt:if expr="len_var(l.variant_schema[1]:sku ) EQ 0">
			  <mvt:assign name="l.settings:product:schema:sku" value="l.variant_schema[1]:code" />
		<mvt:else>
			  <mvt:assign name="l.settings:product:schema:sku" value="l.variant_schema[1]:sku" />
	</mvt:if>
</mvt:if>
<mvt:comment> Capture starting at price customfield </mvt:comment>
<mvt:if expr="len_var( g.startprice ) GT 0">
	<mvt:assign name="l.settings:product:schema:price" value="g.startprice" />
</mvt:if>
<mvt:comment> Strip HTML </mvt:comment>
<mvt:assign name="l.settings:product:schema:desc" value="miva_html_strip( l.settings:product:schema:descrip, '' )" />
<mvt:comment> SKU or Code? </mvt:comment>
<mvt:if expr="len_var( l.settings:product:schema:sku ) EQ 0">
	<mvt:assign name="l.settings:product:schema:sku" value="l.settings:product:schema:code" />
</mvt:if>

<mvt:comment>
#
  <mvt:if EXPR = "{ l._reviews_on EQ 1 }">
	<mvt:item name="tgreviews" param="load_product_reviews( l.settings:product:id,l.settings:product:reviews )" />
  </mvt:if>
#
</mvt:comment>

<script type="application/ld+json">
{
"@context": "http://schema.org/",
"@type": "Product",
"name": "&mvtj:product:schema:name;",
<mvt:if expr="g.socialimage">
	"image": "&mvtj:global:socialimage;",
<mvt:else>
	"image": "&mvtj:global:domain:base_surl;&mvt:imagedata[1]:image:image;",
</mvt:if>
"description": "&mvtj:product:schema:desc;",
<mvt:if expr="l.settings:product:schema:sku">
	"sku": "&mvtj:product:schema:sku;",
	"mpn": "&mvtj:product:schema:sku;",
<mvt:elseif expr="l.settings:product:sku">
	"sku": "&mvtj:product:sku;",
	"mpn": "&mvtj:product:sku;",
<mvt:else>
	"sku": "&mvtj:product:code;",
	"mpn": "&mvtj:product:code;",
</mvt:if>
"brand": {
	"@type": "Brand",
	<mvt:if expr="NOT ISNULL l.settings:product:customfield_values:customfields:brand">
		"name": "&mvtj:product:customfield_values:customfields:brand;
	<mvt:else>
		"name": "&mvtj:store:name;"
	</mvt:if>
},
"category": "&mvt:category:name;",
<mvt:if expr="g.socialURL">
	"url": "&mvt:global:socialURL;",	
<mvt:else>
	"url": "&mvt:product:link;",
</mvt:if>
<mvt:comment>
#
# MULTI PRODUCTS
#
</mvt:comment>
<mvt:if expr="miva_array_elements(l.settings:schema_new_multiprod)">
	  "offers": 
	  [
		<mvt:foreach iterator="mproduct" array="schema_new_multiprod">  
		  {
			"@type": "Offer",
			"name": "<mvt:eval expr="encodejavascriptstring(l.settings:mproduct:name)" />",
			"description": "<mvt:eval expr="encodejavascriptstring(l.settings:mproduct:description)" />",
			<mvt:if expr="l.settings:mproduct:inv_active EQ 1">
			<mvt:if expr="l.availability_inv_level EQ 'low'">
						  "availability": "http://schema.org/LimitedAvailability",
			<mvt:elseif expr="l.availability_inv_level EQ 'out'">
						  "availability": "http://schema.org/OutOfStock",
			<mvt:else>
						  "availability": "http://schema.org/InStock",
			</mvt:if>
		<mvt:else>
			"availability": "https://schema.org/InStock",		 
			</mvt:if>
		<mvt:if expr="l.settings:attributemachine:product:inv_level">   
			<mvt:assign name="l.availability_inv_level" value="l.settings:attributemachine:product:inv_level"/>
			<mvt:do name="l.formatted_variant_price" file="g.Module_Root $ g.Store:currncy_mod:module" value="CurrencyModule_AddFormatting( g.Store:currncy_mod, l.settings:attributemachine:product:price )" />
			"price": "&mvt:attributemachine:product:price;",
		<mvt:else>
			"price": "&mvt:mproduct:price;",
		</mvt:if>
				"priceCurrency": "USD",
				"identifier": "upc:&mvte:mproduct:code;",
				"seller": "&mvte:global:store:name;",
				"url": "&mvt:product:link;",
				"itemCondition": "http://schema.org/NewCondition"
		  }
		  <mvt:if expr="NOT ISNULL l.settings:schema_new_multiprod[l.pos1 + 1]">
				  ,
		  </mvt:if>	
		</mvt:foreach>		
	  ]	
<mvt:else>
<mvt:comment> 
# 
# SINGLE PRODUCT
# 
</mvt:comment>
"offers": {
	"@type": "Offer",
	<mvt:if expr="l.settings:product:inv_active EQ 1">
		<mvt:if expr="l.availability_inv_level EQ 'low'">
					  "availability": "http://schema.org/LimitedAvailability",
		<mvt:elseif expr="l.availability_inv_level EQ 'out'">
					  "availability": "http://schema.org/OutOfStock",
		<mvt:else>
					  "availability": "http://schema.org/InStock",
		</mvt:if>
	<mvt:else>
		"availability": "https://schema.org/InStock",
	</mvt:if>
	<mvt:if expr="l.settings:attributemachine:product:inv_level">
		<mvt:assign name="l.availability_inv_level" value="l.settings:attributemachine:product:inv_level"/>
		<mvt:do name="l.formatted_variant_price" file="g.Module_Root $ g.Store:currncy_mod:module" value="CurrencyModule_AddFormatting( g.Store:currncy_mod, l.settings:attributemachine:product:price )" />
		"price": "&mvt:attributemachine:product:price;",
	<mvt:else>
		"price": "&mvt:product:price;",
	</mvt:if>
		"priceCurrency": "USD",
	<mvt:if expr="l.settings:product:sku">
			"identifier": "upc:&mvte:product:sku;",
	<mvt:else>
			"identifier": "upc:&mvte:product:code;",
	</mvt:if>
		"seller": "&mvte:global:store:name;",
	"itemCondition": "http://schema.org/NewCondition",
		"priceValidUntil": "<mvt:eval expr="s.dyn_tm_year + 1 $ '-' $ padl(s.dyn_tm_mon,'0',2) $ '-' $ padl(s.dyn_tm_mday,'0',2)" />",
		<mvt:if expr="g.socialURL">
		"url": "&mvt:global:socialURL;"
		<mvt:else>
		"url": "&mvt:product:link;"
		</mvt:if>	  
	  }
</mvt:if>
<mvt:if expr="miva_array_elements(l.settings:product:reviews:reviews) GT 0 AND l._reviews_on EQ 1">   
,
	  "aggregateRating": {
		  "@type": "AggregateRating",
		  "ratingValue": "&mvt:product:reviews:product_rating;",
		  "reviewCount": "&mvt:product:reviews:review_count;"
	  },
  "review": [
	<mvt:foreach iterator="review" array="product:reviews:reviews">
	  {
			"@type": "Review",
			"author": "<mvt:eval expr="encodejavascriptstring(l.settings:review:name)" />",
			"datePublished": "&mvt:review:formatted_created;",
			"description": "<mvt:eval expr="encodejavascriptstring(l.settings:review:summary)" />",
			"name": "Customer Review",
			"reviewRating": {
			  "@type": "Rating",
			  "bestRating": "10",
			  "ratingValue": "&mvt:review:rating",
			  "worstRating": "1"
			}
	 }
	   <mvt:if expr="NOT ISNULL l.settings:product:reviews:reviews[l.pos1 + 1]">
			  ,
	   </mvt:if>
	]
	</mvt:foreach>
</mvt:if>	 
}
</script>
<mvt:comment>
|
| SFNT PAGE - storefront microdata
|
</mvt:comment>
<mvt:elseif expr="g.screen EQ 'SFNT'">

<mvt:comment>
#
  <mvt:if EXPR = "{ l._reviews_on EQ 1 }">
	<mvt:item name="tgreviews" param="Load_Product_Reviews( 0, l.settings:reviews )" />
  </mvt:if>  
#
</mvt:comment>

&mvt:store_schema;
<mvt:comment>
|
| CATCH-ALL - all other pages. We can easily add custom schema for individual pages here based on product code, category code, or screen code at a later date.
|
</mvt:comment>
<mvt:else>
  <script type="application/ld+json">
  {
	  "@context": "http://schema.org",
	  "@type": "WebPage",
	  "telephone": "&mvt:store:phone;",
	 "address": "<mvt:eval expr="encodejavascriptstring(l.settings:store:address $ ', ' $ l.settings:store:city $ ' ' $ l.settings:store:state $ ' ' $ l.settings:store:zip )" />",
	  "image": "&mvt:logo_image_url;",
	  "url": "&mvt:urls:_self:auto;",
  <mvt:if expr="l.settings:page_metafields:title">
	  "name": "<mvt:eval expr="encodejavascriptstring(l.settings:page_metafields:title)" />",
  <mvt:else>
		"name": "<mvt:eval expr="encodejavascriptstring(l.settings:page:name)" />",
  </mvt:if>
	"description": "<mvt:eval expr="encodejavascriptstring(l.settings:page_metafields:description)" />"
  }
  </script>
</mvt:if>  
<!-- SCHEMA generator end -->

https://www.scotsscripts.com/mvblog/schema-jsonld-automatic-jsonld-schema-generator.html

mvkb_schema