How to Add CollectionPage Schema in Shopify: Step by Step
How to add CollectionPage schema in Shopify requires inserting JSON-LD structured data that identifies collection pages as curated product groupings.

This schema type helps search engines understand your collection hierarchy, product relationships, and category organization, improving how Google displays your store’s collections in search results.
CollectionPage schema complements Product schema by establishing clear relationships between products and their categories. Shopify doesn’t include CollectionPage schema by default, making manual implementation necessary for optimal collection page SEO.
What Is CollectionPage Schema and Why Add It to Shopify?
CollectionPage schema is structured data that identifies collection pages as organized product listings within your store’s taxonomy. This schema type signals to search engines that a page represents a curated group of products, not a generic list or search results.
Benefits of CollectionPage schema in Shopify SEO:
- Category hierarchy recognition – Search engines understand how collections relate to parent categories and subcategories
- Product discovery paths – Google traces relationships between individual products and their category groupings
- Collection-level rich results – Improved eligibility for enhanced search displays showing product counts and collection attributes
- Faceted navigation support – Filtered collection views maintain schema integrity without creating duplicate content issues
CollectionPage schema works alongside Product and Breadcrumb schema to create complete site structure documentation for search engines.
What Properties Should CollectionPage Schema Include?
Complete CollectionPage schema requires five core properties that define the collection’s identity and relationship to your store. Required properties:
- @type: CollectionPage – Identifies the schema type
- @id – Unique identifier URL for the collection page
- url – Canonical URL of the collection
- name – Collection title
- description – Collection description text
Recommended properties:
- mainEntity – References the ItemList of products in the collection
- breadcrumb – Links to BreadcrumbList schema showing collection hierarchy
- numberOfItems – Total product count in collection
- isPartOf – References parent collection or website for nested categories
The mainEntity property connects CollectionPage schema to an ItemList containing the products, creating explicit product-to-collection relationships.
How to Add Basic CollectionPage Schema to Shopify
Basic implementation establishes CollectionPage identity without advanced features like product listings or pagination.
Step 1: Access Collection Template
Navigate to your theme’s collection template:
- Go to Online Store > Themes
- Click Actions > Edit code
- Open Templates/collection.liquid
- For OS 2.0 themes, edit sections/main-collection.liquid
Step 2: Insert Basic CollectionPage Schema
Add this JSON-LD code before the closing </body> tag or in the template head:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ collection.url }}#collectionpage",
"url": "{{ shop.url }}{{ collection.url }}",
"name": "{{ collection.title | escape }}",
"description": "{{ collection.description | strip_html | strip_newlines | truncate: 300 | escape }}",
"numberOfItems": {{ collection.products_count }}
}
</script>
This creates basic CollectionPage schema using Shopify’s collection object. The @id property includes #collectionpage fragment to create unique identifiers.
Step 3: Add Collection Metadata
Enhance the schema with additional collection properties:
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ collection.url }}#collectionpage",
"url": "{{ shop.url }}{{ collection.url }}",
"name": "{{ collection.title | escape }}",
"description": "{{ collection.description | strip_html | strip_newlines | truncate: 300 | escape }}",
"numberOfItems": {{ collection.products_count }},
{% if collection.image %}
"image": {
"@type": "ImageObject",
"url": "https:{{ collection.image | img_url: 'master' }}",
"width": {{ collection.image.width }},
"height": {{ collection.image.height }}
},
{% endif %}
"inLanguage": "{{ shop.locale }}"
}
The image property adds visual identification, while inLanguage signals the collection’s primary language for international stores.
How to Add CollectionPage Schema with Product ItemList
Advanced implementation includes an ItemList of products within the collection, creating explicit product-collection relationships.
Step 1: Create ItemList Structure
Add mainEntity property containing ItemList:
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ collection.url }}#collectionpage",
"url": "{{ shop.url }}{{ collection.url }}",
"name": "{{ collection.title | escape }}",
"description": "{{ collection.description | strip_html | strip_newlines | truncate: 300 | escape }}",
"mainEntity": {
"@type": "ItemList",
"numberOfItems": {{ collection.products_count }},
"itemListElement": [
{% for product in collection.products limit: 20 %}
{
"@type": "ListItem",
"position": {{ forloop.index }},
"url": "{{ shop.url }}{{ product.url }}"
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
}
This lists the first 20 products in the collection. The limit prevents schema blocks from becoming excessively large on collections with hundreds of products.
Step 2: Add Product Details to ItemList
Expand ListItem entries with product information:
"itemListElement": [
{% for product in collection.products limit: 20 %}
{
"@type": "ListItem",
"position": {{ forloop.index }},
"item": {
"@type": "Product",
"@id": "{{ shop.url }}{{ product.url }}#product",
"url": "{{ shop.url }}{{ product.url }}",
"name": "{{ product.title | escape }}",
"image": "https:{{ product.featured_image | img_url: 'grande' }}",
"offers": {
"@type": "Offer",
"price": "{{ product.price | divided_by: 100.0 }}",
"priceCurrency": "{{ shop.currency }}",
"availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}"
}
}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
Each ListItem now contains complete Product schema, establishing clear product-to-collection relationships.
Step 3: Handle Empty Collections
Add conditional logic for collections with no products:
{% if collection.products_count > 0 %}
"mainEntity": {
"@type": "ItemList",
"numberOfItems": {{ collection.products_count }},
"itemListElement": [
{% for product in collection.products limit: 20 %}
{
"@type": "ListItem",
"position": {{ forloop.index }},
"url": "{{ shop.url }}{{ product.url }}"
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
{% endif %}
The {% if %} conditional prevents empty ItemList objects that trigger schema validation errors.
How to Add CollectionPage Schema for Nested Collections
Nested collection structures (parent/child hierarchies) require additional schema properties to document relationships.
Step 1: Identify Parent Collections
Use collection metafields to store parent collection handles:
{% if collection.metafields.custom.parent_collection %}
{% assign parent_handle = collection.metafields.custom.parent_collection %}
{% assign parent_collection = collections[parent_handle] %}
{% endif %}
This retrieves the parent collection object using the handle stored in a custom metafield.
Step 2: Add isPartOf Property
Reference the parent collection in schema:
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ collection.url }}#collectionpage",
"url": "{{ shop.url }}{{ collection.url }}",
"name": "{{ collection.title | escape }}",
{% if parent_collection %}
"isPartOf": {
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ parent_collection.url }}#collectionpage",
"name": "{{ parent_collection.title | escape }}",
"url": "{{ shop.url }}{{ parent_collection.url }}"
},
{% endif %}
"description": "{{ collection.description | strip_html | strip_newlines | truncate: 300 | escape }}"
}
The isPartOf property creates hierarchical relationships: Subcategory isPartOf Category isPartOf Department.
Step 3: Implement Multi-Level Hierarchy
For stores with multiple hierarchy levels, extend the logic:
{% if parent_collection %}
{% if parent_collection.metafields.custom.parent_collection %}
{% assign grandparent_handle = parent_collection.metafields.custom.parent_collection %}
{% assign grandparent_collection = collections[grandparent_handle] %}
{% endif %}
"isPartOf": {
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ parent_collection.url }}#collectionpage",
"name": "{{ parent_collection.title | escape }}",
"url": "{{ shop.url }}{{ parent_collection.url }}",
{% if grandparent_collection %}
"isPartOf": {
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ grandparent_collection.url }}#collectionpage",
"name": "{{ grandparent_collection.title | escape }}",
"url": "{{ shop.url }}{{ grandparent_collection.url }}"
}
{% endif %}
},
{% endif %}
This supports three-level hierarchies: Product Category > Department Category > Store.
How to Handle Paginated Collections in Schema
Collections with multiple pages require additional properties to indicate pagination state.
Step 1: Detect Pagination
Check if the collection uses pagination:
{% if paginate.pages > 1 %}
{# pagination exists #}
{% endif %}
Shopify’s paginate object provides page count and current page number.
Step 2: Add Pagination Properties
Wrap CollectionPage schema in pagination context:
{% paginate collection.products by 24 %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ collection.url }}?page={{ paginate.current_page }}#collectionpage",
"url": "{{ shop.url }}{{ collection.url }}{% if paginate.current_page > 1 %}?page={{ paginate.current_page }}{% endif %}",
"name": "{{ collection.title | escape }}{% if paginate.current_page > 1 %} - Page {{ paginate.current_page }}{% endif %}",
{% if paginate.next %}
"relatedLink": "{{ shop.url }}{{ collection.url }}?page={{ paginate.next.url | split: '=' | last }}",
{% endif %}
"description": "{{ collection.description | strip_html | strip_newlines | truncate: 300 | escape }}"
}
</script>
{% endpaginate %}
This adds page numbers to @id and url properties, preventing duplicate schema across paginated pages.
Step 3: Limit ItemList to Current Page
Only include products from the current page in ItemList:
"mainEntity": {
"@type": "ItemList",
"numberOfItems": {{ collection.products_count }},
"itemListElement": [
{% for product in paginate.collection %}
{
"@type": "ListItem",
"position": {{ forloop.index | plus: paginate.current_offset }},
"url": "{{ shop.url }}{{ product.url }}"
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
The position calculation (forloop.index + paginate.current_offset) maintains accurate position numbers across pages.
How to Handle Filtered Collection Views
Collection filtering (by color, size, price) requires careful schema handling to avoid duplicate content issues.
Step 1: Detect Filter Parameters
Check for filter parameters in the URL:
{% if collection.filters.size > 0 %}
{% assign has_filters = true %}
{% else %}
{% assign has_filters = false %}
{% endif %}
Step 2: Use Canonical URLs in Schema
Always reference the unfiltered collection URL in schema:
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ collection.url }}#collectionpage",
"url": "{{ shop.url }}{{ collection.url }}",
"name": "{{ collection.title | escape }}",
"description": "{{ collection.description | strip_html | strip_newlines | truncate: 300 | escape }}"
}
Never include filter parameters (?color=blue&size=large) in @id or url properties. Schema should represent the canonical collection, not filtered views.
Step 3: Adjust Product Count for Filters
When filters are active, reflect the filtered product count:
"numberOfItems": {% if has_filters %}{{ collection.products.size }}{% else %}{{ collection.products_count }}{% endif %}
This shows filtered product counts in the ItemList while maintaining canonical URLs.
How to Add CollectionPage Schema with Review Aggregates
Collections can display aggregate review data summarizing product reviews across all items in the category.
Step 1: Calculate Collection Review Average
Loop through collection products to compute aggregate rating:
{% assign total_rating = 0 %}
{% assign total_reviews = 0 %}
{% for product in collection.products %}
{% if product.metafields.judgeme.rating %}
{% assign rating = product.metafields.judgeme.rating | times: 1.0 %}
{% assign reviews = product.metafields.judgeme.review_count | times: 1 %}
{% assign total_rating = total_rating | plus: rating %}
{% assign total_reviews = total_reviews | plus: reviews %}
{% endif %}
{% endfor %}
{% if total_reviews > 0 %}
{% assign collection_avg_rating = total_rating | divided_by: total_reviews %}
{% endif %}
This calculates a simple average of all product ratings in the collection.
Step 2: Add AggregateRating to Schema
Include the computed rating in CollectionPage schema:
{% if total_reviews > 0 %}
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "{{ collection_avg_rating | round: 1 }}",
"reviewCount": {{ total_reviews }},
"bestRating": "5",
"worstRating": "1"
},
{% endif %}
Collection-level aggregate ratings help search engines understand overall category quality.
How to Combine CollectionPage Schema with Breadcrumb Schema
Linking CollectionPage and BreadcrumbList schema creates comprehensive site structure documentation.
Step 1: Create Breadcrumb Schema
Add BreadcrumbList schema to the collection template:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"@id": "{{ shop.url }}{{ collection.url }}#breadcrumb",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "{{ collection.title | escape }}",
"item": "{{ shop.url }}{{ collection.url }}"
}
]
}
</script>
Step 2: Reference Breadcrumb in CollectionPage
Link CollectionPage schema to the BreadcrumbList:
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ collection.url }}#collectionpage",
"url": "{{ shop.url }}{{ collection.url }}",
"name": "{{ collection.title | escape }}",
"breadcrumb": {
"@id": "{{ shop.url }}{{ collection.url }}#breadcrumb"
},
"description": "{{ collection.description | strip_html | strip_newlines | truncate: 300 | escape }}"
}
The breadcrumb property references the BreadcrumbList @id, connecting the two schema types.
How to Validate CollectionPage Schema
Testing ensures CollectionPage schema validates correctly and qualifies for enhanced search features
Step 1: Use Google Rich Results Test
Validate collection page schema:
- Visit search.google.com/test/rich-results
- Enter collection page URL
- Click Test URL
- Wait for validation results
Look for “CollectionPage detected” confirmation. The tool may not show preview snippets for CollectionPage schema, but absence of errors confirms valid implementation.
Step 2: Validate with Schema.org Validator
Check schema syntax and specification compliance:
- Go to validator.schema.org
- Select Fetch URL tab
- Enter collection URL
- Click Run Test
This validator provides detailed feedback on schema structure, property types, and specification compliance.
Step 3: Monitor in Google Search Console
Search Console doesn’t have a dedicated CollectionPage report, but monitors schema errors:
- Open Search Console
- Navigate to Enhancements
- Check Unparsable structured data report
- Look for collection page URLs with errors
Fix any reported errors within 30 days to maintain schema health.
How to Fix Common CollectionPage Schema Errors
Missing Required Property: URL
Error message: “Missing field ‘url’ (required)” Cause: The url property is missing or empty. Fix: Always include the full collection URL:
"url": "{{ shop.url }}{{ collection.url }}"
Include {{ shop.url }} prefix to create absolute URLs (https://store.com/collections/shoes) not relative paths (/collections/shoes).
Duplicate @id Values Across Pages
Error message: “Duplicate @id detected” Cause: Paginated pages use identical @id values. Fix: Include page numbers in @id for paginated collections:
"@id": "{{ shop.url }}{{ collection.url }}{% if paginate.current_page > 1 %}?page={{ paginate.current_page }}{% endif %}#collectionpage"
This creates unique identifiers for each paginated page.
Invalid numberOfItems Value
Error message: “Value for numberOfItems must be a number” Cause: numberOfItems property contains non-numeric value or missing. Fix: Use Shopify’s products_count without quotes:
"numberOfItems": {{ collection.products_count }}
Don’t wrap in quotes: “numberOfItems”: “{{ collection.products_count }}” creates a string, not a number.
FAQs About CollectionPage Schema in Shopify
Does CollectionPage Schema Improve Collection Page Rankings?
CollectionPage schema doesn’t directly impact rankings as a ranking factor, but influences search performance through three indirect mechanisms that matter for Shopify stores.
- First, CollectionPage schema helps search engines understand site structure and category relationships. When Google comprehends that /collections/mens-shoes is a curated product grouping within /collections/shoes, it better evaluates topical relevance for category-level queries.
- Second, the schema enables more accurate indexing of collection pages versus individual product pages. Without CollectionPage markup, Google may misinterpret collection pages as product listing pages or search results, affecting how they compete for rankings.
- Third, clear category hierarchy through isPartOf properties helps search engines distribute authority through your site structure. Collections with explicit parent-child relationships via schema pass contextual relevance signals more effectively.
Real-world impact: Stores implementing CollectionPage schema typically see 5-15% traffic increases to collection pages within 60-90 days, primarily from improved indexing of category-level queries rather than direct ranking boosts. The effect is strongest for stores with deep collection hierarchies (Department > Category > Subcategory). CollectionPage schema works best when combined with Product and Breadcrumb schema to create comprehensive site structure documentation. Isolated CollectionPage implementation without supporting schema types delivers minimal impact.
Should CollectionPage Schema Include All Products or Just Visible Products?
CollectionPage schema should include only products visible on the current page, not the entire collection inventory. This aligns with how search engines perceive page content and prevents schema blocks from becoming excessively large.
For collections displaying 24 products per page with pagination, the ItemList should contain only those 24 products. Use Shopify’s paginate feature to limit the product loop:
{% paginate collection.products by 24 %}
"itemListElement": [
{% for product in paginate.collection %}
{
"@type": "ListItem",
"position": {{ forloop.index | plus: paginate.current_offset }},
"url": "{{ shop.url }}{{ product.url }}"
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
{% endpaginate %}
The numberOfItems property, however, should reflect the total collection count, not just visible products:
"numberOfItems": {{ collection.products_count }}
This creates accurate schema: the page contains X products (itemListElement count) from a collection of Y total products (numberOfItems). For collections with hundreds of products, limiting ItemList to 20-30 products prevents performance issues. Search engines understand that schema represents a sample of the collection, not exhaustive inventory. Collections without pagination can include all products in ItemList if the count remains under 100. Beyond that threshold, consider implementing pagination to improve page load performance and schema manageability.
How Does CollectionPage Schema Handle Automated Collections in Shopify?
Automated collections (based on product tags, type, vendor, or price conditions) work identically with CollectionPage schema as manual collections. Shopify’s collection object provides the same properties regardless of collection creation method. The schema implementation doesn’t distinguish between manual and automated collections:
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ shop.url }}{{ collection.url }}#collectionpage",
"url": "{{ shop.url }}{{ collection.url }}",
"name": "{{ collection.title | escape }}",
"numberOfItems": {{ collection.products_count }}
}
This code works for both collection types because {{ collection.products_count }} dynamically reflects current product matches, updating automatically when products meet or stop meeting automated conditions. For automated collections, product counts and ItemList contents update without schema code changes. When a product gains a tag matching an automated collection’s rules, it appears in that collection’s schema automatically on next page load. One consideration: automated collections with volatile membership (products frequently entering/leaving) may show different numberOfItems values when Google crawls versus when you test. This isn’t a problem – schema should always reflect current collection state, and fluctuating product counts are expected for dynamic collections. Smart collections using complex condition rules (price between X and Y, tagged with A OR B, vendor equals C) behave identically. Shopify’s collection object abstracts the underlying logic, making schema implementation consistent across all collection types.
Can CollectionPage Schema Reference Products from Multiple Collections?
CollectionPage schema should only include products that actually appear in the specific collection, not products from other related or similar collections. Each CollectionPage schema represents one collection’s boundaries. Incorrect implementation referencing external products:
{% comment %} Don't do this {% endcomment %}
"itemListElement": [
{% for product in collection.products %}
{# products from this collection #}
{% endfor %}
{% for product in collections['related-collection'].products %}
{# products from different collection #}
{% endfor %}
]
This creates inaccurate schema misrepresenting the collection’s actual contents. Correct implementation focuses solely on current collection:
"itemListElement": [
{% for product in collection.products limit: 24 %}
{
"@type": "ListItem",
"position": {{ forloop.index }},
"url": "{{ shop.url }}{{ product.url }}"
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
For collection pages showing “Related Collections” or “You Might Also Like” sections, don’t include those products in CollectionPage schema. The schema documents the primary collection only, not ancillary recommendations. If you want to show relationships between collections, use the isPartOf or hasPart properties to indicate parent-child collection hierarchies, not to merge product inventories across collections.
How Often Should You Update CollectionPage Schema Code?
CollectionPage schema code requires updates only when Shopify changes template variables, you restructure collection hierarchies, or schema.org releases new specifications. The schema content (product counts, titles, descriptions) updates automatically through Liquid variables. Review CollectionPage implementation biannually to ensure: Liquid variables output correctly – Test {{ collection.products_count }}, {{ collection.title }}, and {{ collection.url }} produce expected values. Shopify occasionally updates how these variables behave, particularly during major platform updates. Pagination logic remains functional – Verify paginated collections still generate unique @id values per page. Changes to Shopify’s pagination system may require schema adjustments. Nested collection logic matches current hierarchy – If you add or remove collection hierarchy levels, update isPartOf logic to reflect new structures. Three-level hierarchies need different code than two-level hierarchies. Immediate updates needed when:
- Implementing collection hierarchies – Adding parent/child relationships requires isPartOf properties and hierarchy detection logic
- Changing pagination settings – Modifying products-per-page counts affects ItemList generation and position calculations
- Migrating to OS 2.0 themes – New theme architecture may require relocating schema from template to section files
- Adding filtered navigation – Implementing filters requires canonical URL handling to prevent duplicate schema
Unlike product data that changes frequently, collection structures remain stable. Once implemented correctly, CollectionPage schema needs minimal maintenance because Liquid variables handle content updates automatically.Need expert help with Shopify SEO? HiAgency’s technical SEO specialists implement schema markup, optimize site architecture, and deliver data-driven strategies that improve organic visibility. Get in touch to discuss your Shopify SEO performance.

Comments
Post a Comment