Most size guides start life as an image pasted into a product description, or an HTML table on a page someone duplicated forty times. Then one supplier changes its sizing and you’re editing forty pages, hoping you found them all, while the products that embed the table in their description quietly keep the old numbers.
Metaobjects fix the duplication: one “Slim fit chart” entry, used by every product that needs it. The hard part is the migration, because the data you have is in the wrong shape. It’s HTML inside product descriptions, or a supplier sheet with “Chest (in)” in one tab and “chest_cm” in another. This guide covers how to model the chart, how to extract what you already have, and how to load it by product handle without retyping a row.
Pick the shape before touching the data
Every hour spent here saves a re-import later. Decide three things: how many entries, which way the link points, and how the measurements are stored.
One entry per chart, not per product
If 120 products share the same chart, that’s one entry. The test is simple: if you’d change it once and expect all 120 products to update, it’s one entry. Products that look similar but have genuinely different measurements get their own.
Which way the link points
There are two ways to connect a chart to a product, and they’re not equal.
- Product → entry. A product metafield of type
metaobject_reference(for examplecustom.size_guide) points at the chart. The product template reads its chart directly. This is the model Liquid is built for. - Entry → product. The chart entry has a
product_referencefield. This is easy to fill from a spreadsheet (one column of product handles), but Liquid can’t ask “which chart points at this product?”, so on its own it doesn’t help the product page.
Use product → entry for shared charts. For stores where every product has its own chart, there’s a shortcut that needs no product metafield at all: give each entry the same handle as its product and look it up directly.
liquid
{% assign guide = metaobjects.size_guide[product.handle] %}
{% if guide %}
<h2>{{ guide.title.value }}</h2>
{{ guide.size_chart.value | image_url: width: 1200 | image_tag: loading: 'lazy' }}
{% endif %}
Shopify allows 20 unique handle lookups per metaobject type per page render. That’s plenty for a product page, but don’t use this pattern inside a collection grid of 48 products.
If you go with product → entry, the template reads the metafield instead:
liquid
{% assign guide = product.metafields.custom.size_guide.value %}
How to store the measurements
Three workable options, with real trade-offs:
| Option | Field setup | Good | Watch out for |
|---|---|---|---|
| Chart image | file_reference (image) |
Fastest to migrate if you already have accurate images | Text in an image isn’t searchable, accessible or easy to update; one image per language or unit system |
| JSON blob | json field holding the rows |
Compact; one field; translatable through the API | Nothing validates the inner shape; editors can’t safely change it in the admin |
| Row entries | A size_chart_row definition (size, chest, waist, length) referenced from the chart with list.metaobject_reference |
Every number is typed and validated; renders as a real, accessible table | More entries; needs a two-pass import (rows first, then charts) |
Our default on client stores: start with the image if the images exist and are correct, and plan the move to row entries when the chart data needs to be edited regularly or shown in more than one unit system.
Units
Shopify has a dimension type that stores a value with a unit, written over the API as {"value": 25.0, "unit": "centimeters"}. It’s precise, but awkward to fill in a spreadsheet. For CSV-driven workflows, plain number fields named by unit (chest_cm, waist_cm) are easier to fill, sort and check, and a decimal field with a min/max validation will reject a value that was obviously entered in the wrong unit.
Specs: check category metafields first
Before you build a custom “Material” or “Neckline” field, check what Shopify already gives you. Assign a product category from Shopify’s standard product taxonomy and you get category metafields such as fabric, color, neckline and sleeve length, backed by metaobject entries Shopify provides. Those standard definitions don’t count toward your store’s definition limit, and they’re understood by the rest of Shopify. Build custom definitions for what’s genuinely specific to your catalog: fit notes, care instructions, compatibility tables.
Get the data out of where it lives now
Tables inside product descriptions
The product CSV export from Shopify’s admin includes each product’s description as HTML in the Body (HTML) column. If your charts are tables inside those descriptions, a short script can pull them out and, more usefully, group identical ones:
python
# pip install pandas lxml
import csv, hashlib
from io import StringIO
import pandas as pd
charts = {}
with open("products_export.csv", encoding="utf-8") as f:
for row in csv.DictReader(f):
body = row.get("Body (HTML)") or ""
if "<table" not in body:
continue
table = pd.read_html(StringIO(body))[0]
key = hashlib.sha1(table.to_csv(index=False).encode()).hexdigest()[:10]
chart = charts.setdefault(key, {"table": table, "products": []})
chart["products"].append(row["Handle"])
for key, chart in charts.items():
print(key, len(chart["products"]), "products, e.g.", chart["products"][:3])
The output tells you how many distinct charts you really have. It’s usually far fewer than the number of products, and the near-duplicates (one chart with a typo in one cell) show up as separate keys you can merge by hand.
Duplicated pages
One page per chart maps cleanly to one entry per chart. List the pages, note which products link to each, and you have both the entries and the product links.
Supplier sheets and PIM exports
Normalize before importing, not after:
- Rename columns to your field keys.
- Standardize size labels (
SvsSmallvssmall). - Strip units out of numeric cells:
42, not42 cm. A number field will reject the second one, which is good, but it’s quicker to fix in bulk in the sheet. - Convert inches to centimeters (or the reverse) in a helper column, not by hand.
Build the import file
Pick a handle convention you can generate, not invent. Something like {brand}-{fit}-{category} (northline-slim-trousers) keeps handles predictable, lowercase and hyphenated. Then the file is one row per chart:
csv
handle,title,fit_notes,size_chart,chest_cm,waist_cm
northline-slim-trousers,Northline Slim Trousers,Cut close through the thigh,https://example.com/charts/northline-slim.png,,76
northline-regular-shirts,Northline Regular Shirts,Room through the chest,https://example.com/charts/northline-regular.png,104,
harbor-relaxed-knits,Harbor Relaxed Knits,Size down for a closer fit,https://example.com/charts/harbor-relaxed.png,112,
- The image column holds a direct URL to the file.
- If your definition links the chart to products (entry → product), that column holds the product handle, never the product title or full URL.
- Leave a cell empty only when the field genuinely has no value for that chart.
Import in the right order
- Definitions. Create or update them under Settings > Custom data. Add the product metafield definition (
custom.size_guide, type metaobject reference tosize_guide) if you’re using product → entry. - Row entries, if you chose that model. Charts can’t reference rows that don’t exist yet.
- Chart entries.
- The product-side links. For a few dozen products, set the metafield in the product admin. For hundreds, use your product import tool or the API’s
metafieldsSetmutation with the entry’s GID.
Run the first ten rows on their own, check them in the theme preview, then import the rest. Ten rows is enough to catch a wrong field mapping or a template that reads the wrong key.
Check it on the storefront
- Open five products that use five different charts, on a phone.
- Open one product that shouldn’t have a chart, and check the section hides cleanly instead of rendering an empty heading.
- If the definition uses draft/active status, confirm the entries are Active.
- If you’re on a headless storefront, confirm the definition is available to the Storefront API.
Then remove the old tables from product descriptions, but only after the new section has been live for long enough that you’d notice if something was missing.
Not sure whether a piece of spec data belongs in a metafield or a metaobject? Metafields vs metaobjects walks through the decision. For why the import file uses handles rather than IDs, see GIDs vs handles.