Metaobjects Overview: Content Modeling
Learn to create custom content types with metaobjects for FAQs, team members, testimonials, and other structured content beyond products and collections.
Metaobjects let you create entirely new content types in Shopify. While metafields attach data to existing resources, metaobjects are standalone content entries with their own defined structure.
What Are Metaobjects?
Metaobjects are custom content types you define. Think of them as:
- Metafields are fields on products, collections, pages
- Metaobjects are independent entries with multiple fields
Use Cases
| Metaobject Type | Fields |
|---|---|
| Team Member | Name, photo, title, bio, social links |
| FAQ | Question, answer, category |
| Testimonial | Quote, author, rating, photo |
| Store Location | Name, address, hours, map coordinates |
| Size Guide | Product type, measurements table |
| Brand | Logo, description, website |
| Press Mention | Publication, quote, date, link |
Creating a Metaobject Definition
In Shopify Admin:
- Go to Settings → Custom data → Metaobjects
- Click Add definition
- Define your fields
Example: Team Member
Name: Team MemberType: team_member
Fields:- name (single_line_text, required)- photo (file_reference, image)- title (single_line_text)- bio (multi_line_text)- email (single_line_text)- linkedin_url (url)Example: FAQ Item
Name: FAQType: faq
Fields:- question (single_line_text, required)- answer (rich_text, required)- category (single_line_text)- order (number_integer)Example: Testimonial
Name: TestimonialType: testimonial
Fields:- quote (multi_line_text, required)- author_name (single_line_text, required)- author_title (single_line_text)- author_photo (file_reference, image)- rating (number_integer, 1-5)- product (product_reference)Accessing Metaobjects in Liquid
Via Metaobject Reference Metafield
Connect metaobjects to products or other resources:
{# Product has metafield: custom.testimonials (list of testimonial references) #}{%- assign testimonials = product.metafields.custom.testimonials.value -%}
{%- if testimonials.size > 0 -%} <div class="product-testimonials"> <h2>Customer Reviews</h2> {%- for testimonial in testimonials -%} <blockquote class="testimonial"> <p>{{ testimonial.quote }}</p> <footer> {%- if testimonial.author_photo -%} {{ testimonial.author_photo | image_url: width: 60 | image_tag }} {%- endif -%} <cite>{{ testimonial.author_name }}</cite> {%- if testimonial.author_title -%} <span>{{ testimonial.author_title }}</span> {%- endif -%} </footer> </blockquote> {%- endfor -%} </div>{%- endif -%}Via Section Settings
Use dynamic sources to connect metaobjects:
{ "name": "Team Section", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Meet Our Team" } ], "blocks": [ { "type": "team_member", "name": "Team Member", "settings": [ { "type": "text", "id": "name", "label": "Name" }, { "type": "image_picker", "id": "photo", "label": "Photo" }, { "type": "text", "id": "title", "label": "Title" }, { "type": "textarea", "id": "bio", "label": "Bio" } ] } ]}Merchants can connect each field to a metaobject’s fields via dynamic sources.
Metaobject Templates
Metaobjects can have their own pages! Create a template:
templates/metaobject/team_member.liquidOr with JSON:
{# templates/metaobject/team_member.json #}{ "sections": { "main": { "type": "metaobject-team-member", "settings": {} } }, "order": ["main"]}Team Member Template Section
{# sections/metaobject-team-member.liquid #}
{%- assign member = metaobject -%}
<article class="team-member-page"> <div class="container"> <div class="team-member-page__grid"> <div class="team-member-page__photo"> {%- if member.photo -%} {{ member.photo | image_url: width: 600 | image_tag }} {%- endif -%} </div>
<div class="team-member-page__content"> <h1>{{ member.name }}</h1>
{%- if member.title -%} <p class="team-member-page__title">{{ member.title }}</p> {%- endif -%}
{%- if member.bio -%} <div class="team-member-page__bio"> {{ member.bio | newline_to_br }} </div> {%- endif -%}
<div class="team-member-page__contact"> {%- if member.email -%} <a href="mailto:{{ member.email }}">Email</a> {%- endif -%}
{%- if member.linkedin_url -%} <a href="{{ member.linkedin_url }}" target="_blank" rel="noopener"> LinkedIn </a> {%- endif -%} </div> </div> </div> </div></article>
{% schema %}{ "name": "Team Member Page", "settings": []}{% endschema %}Listing Metaobjects
All Entries of a Type
You cannot directly iterate all metaobjects of a type in Liquid. Instead:
- Create a metafield on shop that references multiple metaobjects
- Use a section with blocks connected via dynamic sources
- Use the Storefront API (for headless/custom)
Shop Metafield Approach
{# Shop has metafield: custom.all_team_members (list of team_member references) #}{%- assign team = shop.metafields.custom.all_team_members.value -%}
<section class="team-grid"> <h2>Our Team</h2> <div class="team-grid__members"> {%- for member in team -%} <div class="team-card"> {%- if member.photo -%} {{ member.photo | image_url: width: 300 | image_tag }} {%- endif -%} <h3>{{ member.name }}</h3> <p>{{ member.title }}</p>
{%- if member.system.url -%} <a href="{{ member.system.url }}">Learn more</a> {%- endif -%} </div> {%- endfor -%} </div></section>Block-Based Approach
{# sections/team-section.liquid #}
<section class="team-section"> <div class="container"> <h2>{{ section.settings.heading }}</h2>
<div class="team-grid"> {%- for block in section.blocks -%} <div class="team-card" {{ block.shopify_attributes }}> {%- if block.settings.photo -%} {{ block.settings.photo | image_url: width: 300 | image_tag }} {%- endif -%} <h3>{{ block.settings.name }}</h3> <p>{{ block.settings.title }}</p> <p>{{ block.settings.bio }}</p> </div> {%- endfor -%} </div> </div></section>Merchants add blocks and connect each field to a team member metaobject.
FAQ Section with Metaobjects
{# sections/faq-metaobjects.liquid #}
{%- assign faqs = shop.metafields.custom.all_faqs.value -%}
{%- if faqs.size > 0 -%} <section class="faq-section"> <div class="container"> <h2>{{ section.settings.heading }}</h2>
<div class="faq-list"> {%- for faq in faqs -%} <details class="faq-item"> <summary>{{ faq.question }}</summary> <div class="faq-item__answer"> {{ faq.answer }} </div> </details> {%- endfor -%} </div> </div> </section>{%- endif -%}
{% schema %}{ "name": "FAQ Section", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Frequently Asked Questions" } ]}{% endschema %}Store Locations Example
{# Metaobject: store_location #}{# Fields: name, address, phone, hours, lat, lng, image #}
{%- assign locations = shop.metafields.custom.store_locations.value -%}
<section class="store-locator"> <h2>Find a Store</h2>
<div class="store-locator__grid"> {%- for location in locations -%} <div class="store-card"> {%- if location.image -%} {{ location.image | image_url: width: 400 | image_tag }} {%- endif -%}
<h3>{{ location.name }}</h3>
<address> {{ location.address | newline_to_br }} </address>
{%- if location.phone -%} <p> <a href="tel:{{ location.phone | remove: ' ' | remove: '-' }}"> {{ location.phone }} </a> </p> {%- endif -%}
{%- if location.hours -%} <div class="store-card__hours"> <strong>Hours:</strong> {{ location.hours | newline_to_br }} </div> {%- endif -%}
{%- if location.lat and location.lng -%} <a href="https://maps.google.com/?q={{ location.lat }},{{ location.lng }}" target="_blank" rel="noopener" class="button button--secondary" > Get Directions </a> {%- endif -%} </div> {%- endfor -%} </div></section>System Properties
Metaobjects have system properties:
{{ metaobject.system.handle }} {# URL handle #}{{ metaobject.system.id }} {# GID #}{{ metaobject.system.url }} {# Page URL (if template exists) #}Metaobject vs Metafield Decision
| Use Metafields When | Use Metaobjects When |
|---|---|
| Data belongs to a product | Content is independent |
| One-to-one relationship | Reusable across site |
| Simple values | Complex multi-field entries |
| e.g., Product warranty | e.g., Team members |
Best Practices
Define Clear Types
team_member # Not: person, member, staffstore_location # Not: location, store, placetestimonial # Not: review, quote, feedbackUse Descriptive Field Names
author_name # Not: name, authorpublish_date # Not: date, publishedfeatured_image # Not: image, imgOrder Fields Logically
Put required/important fields first in the definition.
Practice Exercise
Create metaobjects for:
- Team members with photo, name, title, bio
- FAQ items with question, answer, category
- Testimonials with quote, author, rating
Then create sections that:
- Display team grid from metaobjects
- Show FAQ accordion
- Render testimonials carousel
Key Takeaways
- Metaobjects are custom content types
- Define fields in Settings → Custom data
- Access via reference metafields on shop/products
- Templates in
metaobject/type.liquidfor standalone pages metaobject.systemfor handle, id, url- Block + dynamic sources for flexible sections
- Shop metafield lists to iterate all entries
What’s Next?
The next lesson covers App Blocks and Theme Compatibility for third-party integrations.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...