Import

How to Bulk Import Shopify Metaobjects via CSV (Without Losing Your Mind)

Shopify's admin adds metaobject entries one at a time. Here's what the API route really involves, and the spreadsheet workflow we use to load hundreds of entries without touching a GID.

Shopify gives you Content > Metaobjects and an Add entry button. At the time of writing (October 2026), that’s the whole import story in the admin. If 300 size guides are sitting in a spreadsheet, the admin’s answer is 300 rounds of: click, fill six fields, pick a product from a modal, upload an image, save, go back.

So you have three options: keep clicking, write against the GraphQL Admin API, or use an app that reads a CSV. This guide covers all three honestly, including what the API route really involves (a GID for every reference, a lookup pass, rate limits), and the spreadsheet workflow we use on client stores to load a few hundred entries without breaking anything.

Definitions vs entries: what you’re actually importing

A metaobject has two parts, and an import only touches one of them.

  • The definition is the schema: the type (for example size_guide), the fields, each field’s key and type, validations like “required” or “max 50 characters”, and capabilities such as draft/active status. You manage it under Settings > Custom data.
  • Entries are the rows of data that follow that schema. They live under Content > Metaobjects.

A CSV import creates and updates entries, so the definition has to exist before you start. Each column in your file ends up in one field, and fields are identified by their key, not their display name. A field shown as “Chest (cm)” in the admin might have the key chest_cm. Open the definition and note the keys before you build anything.

Two ceilings are worth knowing up front: a definition can have up to 40 fields, and up to 1,000,000 entries.

Route 1: the admin, one entry at a time

For under about 20 entries, or a one-off fix, the admin is fine. Nothing to set up, nothing to break.

The cost isn’t typing. It’s the reference fields. Every product field opens a picker you search and click through, and every image field means an upload or a file-library search. Multiply your number of reference fields by your number of rows to see what you’re signing up for. A six-field size guide with one product and one image, times 300 entries, is 600 picker interactions before you’ve typed a single measurement.

Route 2: the GraphQL Admin API

If you have a developer, the API looks like the obvious answer. It works, but there’s more to it than one mutation.

metaobjectUpsert keys on type and handle

metaobjectUpsert takes a type and a handle. If an entry with that handle exists, it’s updated; if not, it’s created. That’s exactly the behavior you want for a spreadsheet import.

graphql

mutation UpsertSizeGuide($handle: MetaobjectHandleInput!, $metaobject: MetaobjectUpsertInput!) {
  metaobjectUpsert(handle: $handle, metaobject: $metaobject) {
    metaobject { id handle }
    userErrors { field message code }
  }
}

json

{
  "handle": { "type": "size_guide", "handle": "slim-fit-chinos" },
  "metaobject": {
    "fields": [
      { "key": "title", "value": "Slim Fit Chinos Size Guide" },
      { "key": "product", "value": "gid://shopify/Product/8123456789012" },
      { "key": "chest_cm", "value": "96" }
    ]
  }
}

Every value goes in as a string, including numbers.

The values vs fields trap

In the current API version, metaobjectUpsert accepts the data in two ways, and they behave very differently on an existing entry:

  • metaobject: { fields: [...] } patches. Fields you don’t send keep their values.
  • values: { ... } is a full replacement. Shopify’s docs are explicit: omitted keys are cleared.

Write your script with values, send only the three columns that changed, and you’ve just emptied every other field on every entry you updated. Pick the argument deliberately.

Every reference needs a GID

Reference fields store global IDs, not handles. A product field expects gid://shopify/Product/8123456789012. A file field expects the ID of a file already in Shopify, such as gid://shopify/MediaImage/123, so an image URL from your spreadsheet has to be uploaded first (for example with fileCreate, using the URL as the source) and you have to wait for that file to be ready.

That means a lookup pass before the import. For products, the Admin API has a lookup by handle:

graphql

query ProductId {
  productByIdentifier(identifier: { handle: "slim-fit-chinos" }) { id }
}

One lookup per distinct handle, cached, plus error handling for handles that don’t exist. Collections need the same treatment. None of it is hard, but it’s a second program you now own.

Rate limits and bulk operations

The GraphQL Admin API is limited by calculated cost, not request count. On a Standard plan the bucket refills at 100 points a second (200 on Advanced, 1,000 on Plus), and a mutation costs 10 points by default. A single-threaded script tops out around ten upserts a second before you count the lookups, and it needs to back off when it’s throttled.

For large jobs there’s bulkOperationRunMutation: you upload a JSONL file (up to 100 MB) with one set of variables per line, and Shopify runs the mutation for each line without applying the normal rate limit. Two things to know before you rely on it:

  • The operation must finish within 24 hours.
  • Lines aren’t processed in order. If entry B references entry A and both are in the same file, B can run first and fail.

Errors come back per line in a results file. A line that fails doesn’t stop the others, and nothing is rolled back.

Route 3: a CSV keyed on handles

This is the workflow we use most. The spreadsheet holds values people already know, the tool resolves them, and nothing is written until every row has been checked.

Export first

Even for a brand-new definition with no entries, start with an export. The file gives you the exact columns, how handles are written, and how each field expects its data. Building the header row by hand is where typos in field keys come from.

Fill references with values you already have

Reference columns take the handle or URL, not the title and not a GID:

csv

handle,title,product,collection,size_chart
slim-fit-chinos,Slim Fit Chinos Size Guide,slim-fit-chinos,mens-trousers,https://example.com/charts/chinos.png
denim-jacket,Denim Jacket Size Guide,denim-jacket,mens-jackets,https://example.com/charts/denim.png
linen-shirt,Linen Shirt Size Guide,linen-shirt-blue,mens-shirts,https://example.com/charts/linen.png
  • Product fields: the product handle, the last part of the product URL. classic-oxford-shirt, not “Classic Oxford Shirt” and not the full https://yourstore.com/products/... link.
  • Collection fields: the collection handle.
  • File and image fields: a direct URL to the file itself, ending in the file, not a share or preview page.
  • The entry handle: lowercase letters, numbers and hyphens. It’s the key that decides whether a row creates or updates.

Save as CSV, not a workbook

Save the file as CSV. In Excel, choose CSV UTF-8 (Comma delimited) rather than plain “CSV”, or accented characters can be corrupted on the way out. Before you upload, reopen the file and check that every exported column is still there and every value sits in the right column.

Map, review, then import

In Metaobjectify you pick the definition, upload or paste the rows, and match each column to a field. Column order and header names don’t have to match the definition, because you map them. Then the review step shows every row before anything changes:

  • rows that will create a new entry (their handle doesn’t exist yet),
  • rows that will update an existing entry (their handle matches),
  • rows that need attention, with the reason. For example: No product found with handle linen-shirt-blue, or Required field is empty.

Fix the flagged rows in the spreadsheet, upload again, and import when the count of problems is zero. When the job finishes you get the totals (processed, succeeded, failed) and the exact error for any failed row, and the job stays in the All Jobs list.

Five things that break a first import

  1. Duplicate handles in the file. Two rows with the same handle can’t both be right. This is the most common failed row we see. Sort the handle column before you upload and repeats sit next to each other.
  2. An edited handle. Change slim-fit-shirt-guide to slim-fit-shirt-guide-2 and you don’t rename the entry, you create a second one. Leave handles alone on rows you mean to update.
  3. A title or URL where a handle belongs. “Classic Oxford Shirt” and https://yourstore.com/products/classic-oxford-shirt both fail; classic-oxford-shirt works.
  4. A share link instead of a file URL. Cloud-drive share links usually point at a preview page, not the file.
  5. A file type the field doesn’t allow. A PDF in a field that only accepts images will be rejected. Check the field’s validation in the definition.

After the import: check the storefront, not just the job log

A clean job report means Shopify accepted the data. It doesn’t mean customers can see it.

  • Status. If the definition uses draft/active status, check what status your new entries have. In the admin, new entries default to Active. Shopify’s developer docs show entries created through the API on a publishable definition starting as Draft. Open the definition after the import and look.
  • Storefront access. A headless storefront needs the definition exposed to the Storefront API. Liquid themes can read it either way.
  • The 50-entry loop. A Liquid loop over a list of metaobjects returns at most 50 entries unless it’s wrapped in paginate. A size-guide index page that shows exactly 50 entries hasn’t lost any data; the theme needs pagination.

Then open five pages that should show the new data, on mobile, including one you’d expect to be unaffected.

Which route to pick

Route Good for What it costs you
Admin, one at a time Under ~20 entries, one-off fixes Time, mostly in product pickers and uploads
GraphQL Admin API Recurring syncs from another system, developer on hand A GID lookup pass, file uploads, rate limits, retry logic
CSV keyed on handles Spreadsheet data, merchants and agencies, repeat edits An app; a review step before each import

If the data starts life in a spreadsheet, keep it in a spreadsheet until the last step. For the click-by-click version of Route 3, see How it works. If you’ve already imported and now need to change hundreds of entries, read how to bulk edit metaobjects in Excel or Google Sheets. And for why references are the part that breaks, see GIDs vs handles.

Stop typing metaobjects in one at a time.

Export a definition to see the exact columns, fill it in your spreadsheet, and every row is checked before anything touches your store.