Theme Settings and Extensibility Advanced 10 min read

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 TypeFields
Team MemberName, photo, title, bio, social links
FAQQuestion, answer, category
TestimonialQuote, author, rating, photo
Store LocationName, address, hours, map coordinates
Size GuideProduct type, measurements table
BrandLogo, description, website
Press MentionPublication, quote, date, link

Creating a Metaobject Definition

In Shopify Admin:

  1. Go to Settings → Custom data → Metaobjects
  2. Click Add definition
  3. Define your fields

Example: Team Member

Name: Team Member
Type: 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: FAQ
Type: faq
Fields:
- question (single_line_text, required)
- answer (rich_text, required)
- category (single_line_text)
- order (number_integer)

Example: Testimonial

Name: Testimonial
Type: 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.liquid

Or 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:

  1. Create a metafield on shop that references multiple metaobjects
  2. Use a section with blocks connected via dynamic sources
  3. 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 WhenUse Metaobjects When
Data belongs to a productContent is independent
One-to-one relationshipReusable across site
Simple valuesComplex multi-field entries
e.g., Product warrantye.g., Team members

Best Practices

Define Clear Types

team_member # Not: person, member, staff
store_location # Not: location, store, place
testimonial # Not: review, quote, feedback

Use Descriptive Field Names

author_name # Not: name, author
publish_date # Not: date, published
featured_image # Not: image, img

Order Fields Logically

Put required/important fields first in the definition.

Practice Exercise

Create metaobjects for:

  1. Team members with photo, name, title, bio
  2. FAQ items with question, answer, category
  3. Testimonials with quote, author, rating

Then create sections that:

  • Display team grid from metaobjects
  • Show FAQ accordion
  • Render testimonials carousel

Key Takeaways

  1. Metaobjects are custom content types
  2. Define fields in Settings → Custom data
  3. Access via reference metafields on shop/products
  4. Templates in metaobject/type.liquid for standalone pages
  5. metaobject.system for handle, id, url
  6. Block + dynamic sources for flexible sections
  7. 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...