Ten metaobjects, three intent clusters.
Six previously specified in the original schema docs, four newly identified through working the data-to-design mapping. The newly-identified four (product_video, faq_item, glossary_term, distributor) are specified in full below — same level of detail as the originals.
| Metaobject | Status | Purpose | Feeds |
|---|---|---|---|
| product_document | Specified | Files attached to products — manuals, datasheets, drawings, certifications. Replaces the 46-PDF-per-product scrape with explicit, reusable entries. | D · I · L · M |
| product_relationship | Specified | Editorial parent-curated links between products — required accessories, replacement parts, upgrade paths. | D · I |
| product_spec_group | Specified | Spec category container — Performance, Electrical, Mechanical, etc. The grouping for spec rows. | D · E · F |
| product_spec_row | Specified | Single structured spec value with US/metric units, comparable/filterable flags, variant override support. | A · D · E · F · G · K |
| product_video | New | Structured video metadata. Replaces iframe embeds — searchable, filterable, emits JSON-LD VideoObject. | D · H · I · L |
| faq_item | New | Discrete question-answer pairs. Indexable individually, attachable to products and collections, emits FAQPage schema. | J · D · E |
| glossary_term | New | Single technical term with definition, examples, related products and articles. Emits DefinedTerm schema. | J · I |
| distributor | New | A single distributor with location, contact, partner tier. Drives the map, search, and list on Mock O. | O |
| ai_policy | Specified | Single-entry site-wide AI metadata. Renders meta tags in <head> on every page. | ALL · <head> |
| ai_crawler_policy | Specified | Multi-entry per-crawler policies. Drives the robots.txt template and the /llms.txt Cloudflare Worker. | ALL · robots.txt |
Phase 1 ceiling is ten metaobject types. Phase 2 brings four more: company_stat, company_milestone, finder_intent_path / finder_step, learn_category. v1 ships those as hardcoded section blocks; promotion to metaobjects is a refactor, not new work.
product_document
A file attached to a product, variant, or the shop — manual, datasheet, drawing, install guide, certification, SDS, warranty, calibration cert. Replaces the prior scrape-and-attach-PDFs pattern that left some products with 46 attachments duplicating the same six PDFs. Each entry is created once and referenced from wherever it applies.
| Field | Type | Req | Description |
|---|---|---|---|
| title | single_line_text | ✓ | Display title (e.g., "Tube Oil Skimmer Installation Manual"). |
| document_type | single_line_text | ✓ | Drives section grouping on PDP and filtering on support hub.manual · datasheet · drawing · install_guide · troubleshooting · warranty · sds · certification · calibration_cert · spec_sheet · case_study · white_paper · catalog · regulatory |
| file | file_reference | — | PDF/document file. Either this or external_url is required. |
| external_url | url | — | External hosted document (e.g., third-party datasheet on a manufacturer's site). |
| language | single_line_text | ✓ | ISO 639-1 code. Drives language-filtered surfacing for multilingual stores.en · es · fr · de · it · ja · zh · ... |
| version | single_line_text | — | Document version (e.g., "Rev. 3.2"). Surfaces as badge on document card. |
| revision_date | date | ✓ | Last update. Sorts the support hub guides by recency. Emits as dateModified. |
| valid_until | date | — | Auto-expire date for certifications, calibration certs. |
| file_size_bytes | number_integer | — | Auto-derived from file; manual override for external URLs.DERIVED FROM FILE |
| description | multi_line_text | — | Short blurb under document title on PDP card. ~80 chars. |
| audience | list.single_line_text | — | Who the document is for. Drives audience-filtered surfacing.installer · engineer · buyer · shop_floor · maintenance · operator · qa · safety |
| thumbnail | file_reference | — | Override thumbnail; if blank, auto-generated from page 1 of PDF at render time. |
title: "Tube Oil Skimmer Manual" document_type: "manual" file: files/zebra-tube-oil-skimmer-manual-v3.pdf language: "en" version: "Rev. 3.2" revision_date: "2025-08-14" file_size_bytes: 2147483 // 2.1 MB description: "Installation, operation, and maintenance for the ZVA family." audience: ["installer", "maintenance", "shop_floor"] thumbnail: // auto-generated from PDF page 1
product.theme.documents with selectedVariant.theme.documents at render, deduping by metaobject id. file_size_bytes is computed on-save by a metaobject hook; external URLs need manual entry. The 51 unique documents in the current catalog become ~60 entries with audience tags and revision dates.
product_relationship
Editorial parent-curated link between a product and another product or variant. The parent product owns the relationship — "the ZVA8-08 requires a ZT8-08 tube as a consumable." Reverse-lookup happens via the fits_products metafield on the child (the part), not via a metaobject.
| Field | Type | Req | Description |
|---|---|---|---|
| target_product | product_reference | ✓ | The other end of the relationship. Required. |
| target_variant | variant_reference | — | Optional variant-level specificity (e.g., "fits ZVA8-08 specifically, not ZVA8-101"). If blank, applies to all variants of target_product. |
| relationship_type | single_line_text | ✓ | Drives section grouping on PDP ("Required parts" vs "Recommended accessories" vs "Upgrade").required_accessory · recommended_accessory · replacement_part · consumable · upgrade · alternative · complementary · accessory |
| display_label | single_line_text | — | Override the auto-derived label (e.g., "Recommended" instead of "Recommended accessory"). |
| notes | multi_line_text | — | Editorial note shown on the relationship card (e.g., "Replace every 2,000 hours of operation"). |
| display_priority | number_integer | — | Sort order within relationship_type group. Lower numbers first. Defaults to 100. |
theme.related_products with this metaobject. The part (a tube) lists which variants it fits via theme.fits_products, which is a plain list.variant_reference — no metaobject needed. PDP's parts-and-accessories section queries both directions and merges. Variant-granular compatibility means a part can declare "fits ZVA8-08 only" without affecting other reach variants of the same family.
product_spec_group
Category container for spec rows. Each group is a section header in the PDP spec table ("Performance", "Electrical", "Mechanical & materials"). Defining groups as a metaobject means the order and labels are admin-editable without code changes, and new groups can be added when product categories require them.
| Field | Type | Req | Description |
|---|---|---|---|
| label | single_line_text | ✓ | Display name (e.g., "Performance", "Electrical"). |
| key | single_line_text | ✓ | Machine-readable identifier (e.g., performance). Stable across language; used to match spec rows to groups. |
| description | multi_line_text | — | Subhead under the group label on the PDP spec table. |
| display_order | number_integer | ✓ | Order on PDP spec table. Defaults: performance=10, electrical=20, mechanical=30, materials=40, certifications=50, service=60. |
| icon | file_reference | — | Optional SVG icon for group header. |
// Six default groups created at theme install: performance "Performance" (capacity, reach, flow rate, throughput) electrical "Electrical" (voltage, frequency, amperage, power) mechanical "Mechanical & materials" (mount style, materials, weight, dimensions) certifications "Certifications" (UL, CE, NIST, ISO, OSHA) connectivity "Connectivity" (protocols, APIs, network) [for automation products] service "Service & warranty" (warranty term, country of origin, MOQ, lead time)
product.theme.specs, grouping them by row.group.key, then rendering groups in display_order. A product with no connectivity rows simply doesn't render that group.
product_spec_row
A single specification value — Reach is 8 inches, Voltage is 115 VAC, Capacity is 1 qt/h. Has US and metric values with units, comparable/filterable flags, optional variant-level overrides, and a stable key for cross-product alignment so Compare and the collection filter rail can match equivalent rows across different products.
| Field | Type | Req | Description |
|---|---|---|---|
| label | single_line_text | ✓ | Display label (e.g., "Reach", "Capacity", "Voltage"). |
| key | single_line_text | ✓ | Machine identifier for cross-product alignment (e.g., reach_depth, voltage_primary). Stable across products. |
| group | metaobject_reference | ✓ | References a product_spec_group. |
| value_text | single_line_text | — | Free-text value for non-numeric specs ("Stainless steel", "USA · Solon, OH"). Mutually exclusive with the number fields. |
| value_number_us | number_decimal | — | Numeric value in US units. Required if unit_us set. |
| value_number_metric | number_decimal | — | Numeric value in metric units. If only one is set, the other is computed at render via a canonical conversion map. |
| unit_us | single_line_text | — | Canonical US unit string.in · ft · lb · oz · qt · gal · gph · gpm · hp · °F · psi · ... |
| unit_metric | single_line_text | — | Canonical metric unit string.mm · cm · m · kg · g · L · L/h · kW · °C · kPa · ... |
| value_range_min | number_decimal | — | Lower bound for range values (e.g., "Reach 8–101 in"). Optional. |
| value_range_max | number_decimal | — | Upper bound for range values. |
| comparable | boolean | ✓ | If true, surfaces in compare table, card spec pills, featured-product callout. Defaults to true. |
| filterable | boolean | ✓ | If true, surfaces in collection filter rail facets. Defaults to false (avoid facet pollution). |
| display_priority | number_integer | — | Order within group. Lower numbers first. |
| notes | multi_line_text | — | Footnote shown beneath the row on PDP (e.g., "Measured at 70 °F ambient"). |
| applies_to_variants | list.variant_reference | — | If set, this row only renders when one of these variants is selected. Used for variant-level overrides at the product level. |
| auto_derived | boolean | — | If true, value is computed from variant options (e.g., the Reach number comes from the option1 value) rather than entered manually. Defaults false. |
Two ways to express variant-specific spec values. Option A · per-variant metafield: attach a complete row to variant.theme.specs; render-time merge replaces the product-level row by matching key. Option B · applies_to_variants: keep all rows at product level, use the applies_to_variants field on rows that only apply to specific variants. Option A is cleaner for fully variant-driven specs (Reach changes per variant). Option B is better for sparse overrides (only the 220V variant has a different motor amperage).
label: "Capacity" key: "capacity" group: → product_spec_group "performance" value_number_us: 1 unit_us: "qt/h" value_number_metric: 0.95 unit_metric: "L/h" comparable: true filterable: false // continuous-range, doesn't facet well display_priority: 10 notes: "Measured at 70 °F ambient with light tramp oil load"
metaobjectCreate in batched mutations. Estimated ~12 hours of human work for 120 products.
product_video
Structured metadata for a video associated with a product, variant, or shop. Replaces the current pattern of raw YouTube iframe embeds inside product descriptions — those are unsearchable, can't emit JSON-LD VideoObject for rich-result eligibility, and can't be filtered by audience or topic. The AI Search corpus already has clean source data for ~5 videos per major product.
| Field | Type | Req | Description |
|---|---|---|---|
| title | single_line_text | ✓ | Display title (e.g., "How to install the Sidewinder tube skimmer"). |
| video_type | single_line_text | ✓ | Drives section placement and filtering.install · demo · overview · tutorial · troubleshooting · case_study · spec_walkthrough · unboxing |
| platform | single_line_text | ✓ | Where the video is hosted.youtube · vimeo · self_hosted · wistia |
| video_id | single_line_text | ✓ | Platform's video ID (e.g., dQw4w9WgXcQ for YouTube). |
| watch_url | url | — | Computed from platform + video_id if not provided.DERIVED FROM PLATFORM + ID |
| embed_url | url | — | Computed iframe-embeddable URL.DERIVED FROM PLATFORM + ID |
| duration_seconds | number_integer | ✓ | Total duration in seconds. Used for VideoObject.duration (ISO 8601 PT format on emit) and the card duration badge. |
| thumbnail | file_reference | — | Override thumbnail. If blank, fetched from platform at render. |
| description | multi_line_text | ✓ | Short description for cards and VideoObject.description. |
| transcript | rich_text | — | Full transcript for accessibility, SEO, and on-page rendering. Highly recommended for indexability. |
| published_date | date | ✓ | When the video was first published. Emitted as VideoObject.uploadDate. |
| audience | list.single_line_text | — | Same enum as product_document.audience — installer, engineer, buyer, etc. |
| applies_to_variants | list.variant_reference | — | If set, the video only renders on PDP when one of these variants is selected. |
| chapters | json | — | Optional chapter markers [{label, t}]. Emitted as VideoObject.hasPart Clip entries. |
title: "How to install the ZVA8 Sidewinder tube skimmer" video_type: "install" platform: "youtube" video_id: "abc123XYZ" duration_seconds: 187 // 3:07 description: "Mounting options, voltage check, first-run procedure." published_date: "2024-11-12" audience: ["installer", "shop_floor"] chapters: [ { label: "Mounting options", t: 0 }, { label: "Voltage check", t: 75 }, { label: "First run", t: 142 } ]
/Users/dylanburkey/active/execution/client/zebraskimmers/zebra-product-images already structures video data with the same shape — title, platform, video_id, watch_url, embed_url, thumbnail_url. Migration is a transform-and-bulk-create from products_full.json. Transcripts are not in the source data; generating them via Workers AI is a Phase 1.5 enhancement.
faq_item
A single question-and-answer pair. Each FAQ becomes a discrete metaobject entry — individually searchable, attachable to a specific product or collection or the global FAQ page, emits cleanly as FAQPage structured data. Replaces the rich-text FAQ page (which dedupes badly across PDPs and isn't indexable per-question).
| Field | Type | Req | Description |
|---|---|---|---|
| question | single_line_text | ✓ | The question text. Renders as the accordion heading. |
| answer | rich_text | ✓ | The answer body. Rich text — supports inline links to products, articles, and glossary terms. |
| category | single_line_text | ✓ | Section grouping on the FAQ page.ordering · shipping · payment · installation · maintenance · warranty · returns · technical · parts · international · account |
| display_order | number_integer | ✓ | Sort within category. Lower numbers first. |
| products | list.product_reference | — | If set, the FAQ also appears on these PDPs in a per-product FAQ block. Empty = global FAQ only. |
| collections | list.collection_reference | — | If set, the FAQ appears on these collection pages in a category-level FAQ block. |
| related_terms | list.metaobject_reference | — | References glossary_term entries — for "see also" cross-links. |
| last_updated | date | ✓ | Freshness signal. Surfaces as dateModified in structured data. |
| display_priority | number_integer | — | Promote a high-value FAQ to the top of its category irrespective of display_order. Defaults to 0. |
faq_item.products, OR a product can declare its FAQs via product.theme.faqs. Theme renderer queries both directions and dedupes by metaobject id. The PDP renders only FAQs where the product is referenced (either way). Global FAQ page aggregates all shop.theme.faq_items grouped by category.
glossary_term
A single technical term — "Coalescer", "Tramp oil", "Sump", "Refractometer". Defines the term, points to products and articles that exemplify it, and cross-links related terms. Indexable as a discrete unit so technical buyers searching for terminology land directly on the glossary page with the term highlighted. Emits as DefinedTerm structured data.
| Field | Type | Req | Description |
|---|---|---|---|
| label | single_line_text | ✓ | Primary term (e.g., "Coalescer", "Coolant concentration"). |
| abbreviation | single_line_text | — | Acronym or short form (e.g., "CNC", "VFD"). Surfaces alongside label and as additional indexable token. |
| alternate_names | list.single_line_text | — | Synonyms or variants (e.g., for "Tramp oil" — ["leak oil", "way oil contamination"]). Used in search and "also known as" labels. |
| category | single_line_text | ✓ | Section grouping on glossary page.equipment · process · material · measurement · regulation · general · application |
| definition | rich_text | ✓ | Primary definition. Rich text supports inline links to related products and other terms. |
| examples | multi_line_text | — | "In a typical CNC sump, a coalescer separates emulsified tramp oil from water-soluble coolant before disposal." Renders as a callout below the definition. |
| related_terms | list.metaobject_reference | — | Self-referential. Cross-links to other glossary_term entries. |
| related_products | list.product_reference | — | Products that exemplify the term. "Coalescer" links to the Z-17 Muscle Coalescer. |
| related_articles | list.article_reference | — | Learn articles that explain the term in depth. The glossary card invites readers into the long-form content. |
| citations | rich_text | — | Sources / references for definition. Footnote-style under the entry. |
glossary_term doesn't need a shop-level metafield binding. The glossary page route queries the Storefront API directly for all entries of the type. Cross-linking from PDPs and articles uses inline anchor tags that point at /learn/glossary?focus=coalescer — the entry handle becomes the deep link.
distributor
A single distributor or reseller. Drives the map, the searchable list, and the regional contact routing on Mock O. The current state of the truth (68 distributors across 42 countries) lives in a spreadsheet; promoting it to a metaobject makes the list admin-editable, the map auto-rendered, and adds the metafield bindings needed for "find a distributor near you" features.
| Field | Type | Req | Description |
|---|---|---|---|
| name | single_line_text | ✓ | Display name of the company. |
| legal_name | single_line_text | — | Legal entity name if different from display name (for contracts, agreements page). |
| continent | single_line_text | ✓ | Region grouping on the distributor list.north_america · south_america · europe · asia · africa · oceania · middle_east |
| country | single_line_text | ✓ | ISO 3166-1 alpha-2 code (e.g., US, DE, JP). |
| region | single_line_text | — | State, province, or sub-region (e.g., "California", "Ontario", "Bayern"). |
| city | single_line_text | ✓ | Primary city. |
| latitude | number_decimal | ✓ | For map pin placement. Decimal degrees, WGS-84. |
| longitude | number_decimal | ✓ | For map pin placement. Decimal degrees, WGS-84. |
| service_areas | list.single_line_text | — | Postal codes, states, or country codes the distributor serves beyond their city. Drives "find a distributor near you" zip lookup. |
| primary_phone | single_line_text | ✓ | With country code, E.164 format preferred (e.g., +1 216 555 0100). |
| single_line_text | ✓ | Primary contact email. | |
| website | url | — | Distributor's own site. |
| product_categories | list.single_line_text | — | Which product lines they carry (matches collection handles).skimmers · automation · refractometers · parts · accessories |
| contact_name | single_line_text | — | Primary rep at the distributor. |
| contact_title | single_line_text | — | Rep's title (e.g., "Sales Manager"). |
| partner_tier | single_line_text | — | Partner level. Surfaces as a badge on the distributor card.authorized · certified · master · regional · exclusive |
| partner_since | date | — | Partnership start date. |
| notes | multi_line_text | — | Internal-only notes (not rendered on storefront). |
continent for display, and renders the map by reading latitude and longitude per entry. Client-side filter ("near me" by zip code) reads service_areas against the user's input. Internal-only notes field never reaches the storefront — useful for tracking renewal dates, contract terms, or contact history that admin staff need but customers shouldn't see.
ai_policy
Single-entry shop-level AI metadata. Renders meta tags in <head> on every page, drives the /llms.txt Cloudflare Worker, and provides JSON-LD isAccessibleForFree and licensing annotations. Admin-editable so policy changes don't require a code deploy.
| Field | Type | Req | Description |
|---|---|---|---|
| content_license | single_line_text | ✓ | License declaration.all_rights_reserved · cc_by · cc_by_sa · cc_by_nc · cc0 · custom |
| training_allowed | boolean | ✓ | Whether AI training is allowed on site content. Renders ai-training-allowed meta tag. |
| attribution_required | boolean | ✓ | If true, citation/attribution requested for AI surfacing of content. |
| description | multi_line_text | ✓ | Site-wide description for ai-description meta tag. ~160 chars optimal. |
| summary_short | single_line_text | — | Short version for SERP-style AI surfaces. ~60 chars. |
| keywords | list.single_line_text | — | Site-wide topic tags (e.g., ["oil skimmer", "CNC coolant", "tramp oil removal"]). |
| product_type | single_line_text | — | Primary product category. Renders ai-product-type. |
| industry | single_line_text | — | Industry vertical (e.g., "manufacturing", "machining", "fluid handling"). |
| llms_url | url | — | Path to /llms.txt. Renders link rel. |
| ai_info_url | url | — | Path to an AI-policy-detail page if one exists. |
| structured_data_formats | list.single_line_text | — | Which structured-data schemas the site emits (Product, Article, FAQPage, DefinedTerm, VideoObject, ...). |
| max_image_preview | single_line_text | — | Robots directive — large recommended. |
| last_reviewed_date | date | ✓ | When admin last confirmed policy is current. |
ai-* meta tags aren't honored by any major crawler currently, but they're cheap insurance and signal good faith. The effective controls live in robots.txt (driven by ai_crawler_policy below) and the JSON-LD emission. The Cloudflare Worker at /llms.txt reads this metaobject via the Admin API on cold start and caches for 5 minutes.
ai_crawler_policy
Per-crawler allow/deny rules. Each entry is one AI crawler (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, etc.) and its policy. Drives the dynamic /robots.txt.liquid template — admin can disable a specific crawler without a code deploy. Seeded with 12 named crawlers at theme install, all allow-by-default given Zebra's permissive policy.
| Field | Type | Req | Description |
|---|---|---|---|
| user_agent | single_line_text | ✓ | Exact user-agent token (e.g., GPTBot, ClaudeBot, Google-Extended). |
| display_name | single_line_text | ✓ | Friendly name for admin UI (e.g., "OpenAI GPT", "Anthropic Claude"). |
| enabled | boolean | ✓ | Master toggle. Renders Disallow-everything if false. |
| allow_paths | list.single_line_text | — | Explicit allow rules. Defaults to ["/"] when enabled. |
| disallow_paths | list.single_line_text | — | Explicit disallow rules (e.g., ["/cart", "/account", "/admin"]). |
| crawl_delay | number_integer | — | Seconds between requests. Most crawlers ignore but harmless to set. |
| notes | multi_line_text | — | Why this policy was set (e.g., "Disabled 2025-Q3 due to scraping volume"). |
// 12 default crawlers, all enabled, attached at theme install:
GPTBot OpenAI · ChatGPT browsing & training
ClaudeBot Anthropic Claude
Claude-Web Anthropic Claude · web fetch
PerplexityBot Perplexity
Google-Extended Google Gemini training opt-in
GoogleOther Google research / dev crawlers
Bingbot Microsoft Copilot via Bing index
CCBot Common Crawl
Applebot Apple Intelligence
Applebot-Extended Apple LLM training
Meta-ExternalAgent Meta AI
Bytespider ByteDance / TikTok
robots.txt.liquid template iterates the bound list and emits a User-agent block per crawler with its allow/disallow paths. Crawlers not in the list fall through to the default User-agent: * block. Admin can disable a crawler by flipping enabled to false; the change appears in the next /robots.txt serve (no cache).
The metafield bindings, by owner.
Every metaobject reaches the storefront through a metafield attached to a Shopify primitive — product, variant, collection, article, or shop. This is the exhaustive list of metafields the theme needs to read. Each is namespaced under theme so they're scoped to this theme and don't collide with anything else the merchant has installed.
| PRODUCT | |||
| product | theme.documents | list.metaobject_reference | Manuals, datasheets, drawings. References product_document. |
| product | theme.specs | list.metaobject_reference | Structured specifications. References product_spec_row. |
| product | theme.related_products | list.metaobject_reference | Curated relationships. References product_relationship. |
| product | theme.fits_products | list.variant_reference | Compatibility — which variants this product fits. No metaobject; native variant refs. |
| product | theme.videos | list.metaobject_reference | Install, demo, tutorial videos. References product_video. |
| product | theme.faqs | list.metaobject_reference | Per-product FAQ block. References faq_item. |
| product | theme.overview_rich | rich_text_field | Optional structured overview for PDP. Falls back to descriptionHtml. |
| VARIANT | |||
| variant | theme.documents | list.metaobject_reference | Variant-specific documents (e.g., 220V variant gets a different manual). |
| variant | theme.specs | list.metaobject_reference | Variant-specific spec overrides. Merge-by-key with product-level theme.specs. |
| variant | theme.videos | list.metaobject_reference | Variant-specific video overrides. |
| COLLECTION | |||
| collection | theme.faqs | list.metaobject_reference | Per-collection FAQ block. References faq_item. |
| collection | theme.fyf_banner | single_line_text · json | Find Your Fit banner copy override for this collection. |
| collection | theme.featured_filters | list.single_line_text | Spec-row keys to promote to the top of the filter rail. Override defaults. |
| ARTICLE (Shopify Blog) | |||
| article | editorial.standfirst | multi_line_text | Editorial subtitle / dek beneath the article title. Renders larger than body. |
| article | editorial.reviewer | single_line_text | "Reviewed by" credit (e.g., a P.E. or technical authority). |
| article | related.products | list.product_reference | Products referenced by the article. Renders at the foot. |
| article | related.terms | list.metaobject_reference | Glossary terms the article touches. Cross-link block. |
| SHOP (singletons & aggregations) | |||
| shop | theme.ai_policy | metaobject_reference | Single-entry ai_policy. Rendered on every page in <head>. |
| shop | theme.ai_crawler_policies | list.metaobject_reference | All crawler policies. Drives /robots.txt. |
| shop | theme.shop_documents | list.metaobject_reference | Site-wide documents — catalog, warranty terms, return form. Surfaces on support hub. |
| shop | theme.faq_items | list.metaobject_reference | Global FAQ aggregation. Drives /learn/faq. |
| shop | theme.featured_product | product_reference | Homepage featured product callout. |
| shop | theme.featured_install_videos | list.metaobject_reference | Featured install videos on the support hub. |
| shop | theme.featured_learn_article | article_reference | Featured article on the Learn hub. |
| shop | theme.popular_parts_models | list.variant_reference | Quick-pick model pills on Find Parts by Model (Mock K). |
| shop | theme.brand_tagline · theme.hero_* | single_line_text · multi_line_text | Homepage editorial copy. ~5 fields total. |
| shop | theme.about_* · theme.warranty_* · theme.contact_* | various | Editorial copy for About, Warranty, Contact pages. ~15 fields total across these pages. |
Creating these in Shopify — GraphQL mutations.
Every metaobject and metafield gets defined via a single Admin API mutation, run once per environment. The full create script is a Node script that posts these mutations sequentially with retries. Below is the pattern for one metaobject definition and the corresponding metafield definition; the full script applies the same shape to all ten metaobject types and all twenty-plus metafield definitions.
1. Create the metaobject definition
mutation CreateProductDocumentDefinition { metaobjectDefinitionCreate(definition: { type: "product_document" name: "Product document" description: "A PDF or document attached to a product." fieldDefinitions: [ { key: "title" name: "Title" type: "single_line_text_field" required: true }, { key: "document_type" name: "Document type" type: "single_line_text_field" required: true validations: [ { name: "choices" value: "[\"manual\",\"datasheet\",\"drawing\",\"install_guide\",\"troubleshooting\",\"warranty\",\"sds\",\"certification\",\"calibration_cert\",\"spec_sheet\",\"case_study\",\"white_paper\",\"catalog\",\"regulatory\"]" } ] } /* ... remaining 10 field definitions ... */ ] access: { storefront: PUBLIC_READ } displayNameKey: "title" }) { metaobjectDefinition { id type } userErrors { field message } } }
2. Create the metafield definition that binds the metaobject to products:
mutation CreateProductDocumentsMetafield { metafieldDefinitionCreate(definition: { name: "Documents" namespace: "theme" key: "documents" description: "Manuals, datasheets, drawings attached to this product." type: "list.metaobject_reference" ownerType: PRODUCT validations: [ { name: "metaobject_definition_id" value: "gid://shopify/MetaobjectDefinition/123456789" // the ID returned by the metaobjectDefinitionCreate mutation above } ] access: { admin: MERCHANT_READ_WRITE, storefront: PUBLIC_READ } }) { createdDefinition { id key } userErrors { field message } } }
3. Wire-up sequence for the full data layer — order matters because some metaobjects reference others:
// Stage 1 — base metaobjects (no inter-references) 1. product_spec_group // seeded with 6 entries after creation 2. product_document 3. product_video 4. glossary_term // self-reference allowed, no other deps 5. distributor 6. ai_policy // single-entry · seed 1 entry after 7. ai_crawler_policy // seeded with 12 entries // Stage 2 — metaobjects with references to Stage 1 8. product_spec_row // references product_spec_group 9. product_relationship // references product, variant 10. faq_item // references glossary_term // Stage 3 — metafield definitions (after all metaobjects exist) 11. product.theme.{documents, specs, related_products, fits_products, videos, faqs, overview_rich} 12. variant.theme.{documents, specs, videos} 13. collection.theme.{faqs, fyf_banner, featured_filters} 14. article.{editorial.standfirst, editorial.reviewer, related.products, related.terms} 15. shop.theme.{ai_policy, ai_crawler_policies, shop_documents, faq_items, featured_product, featured_install_videos, featured_learn_article, popular_parts_models, brand_tagline, hero_*, about_*, warranty_*, contact_*}
scripts/create-data-layer.mjs runs all three stages with retry-on-rate-limit and an idempotency check (skips if the definition already exists). Same script targets either the Zebra production store or the sellable-theme demo store via env var. Estimated runtime ~2 minutes per environment. Same script reverses for teardown via --rollback flag — important for the demo store where definitions change as we iterate. Token scopes needed: write_metaobject_definitions, write_metaobjects, write_metafields, plus write_products for the seeding step that attaches the seeded entries to sample products.