UK payments & ecommerce

Bulk-edit a Shopify catalogue with the Admin API and AI

Change prices, metafields, tags and redirects across thousands of products without breaking the storefront: read before you write, run it as one bulk mutation over a JSONL file, and keep the export that lets you undo it.

Ritesh AgarwalSep 16, 20269 min read
One API call reaches every product: change risk by the safety net you skipped, from no export down to no final check

Direct answer

Never run a catalogue-wide change straight against live data. Export the current state first, change one thing at a time, and keep that export as your rollback. For a repeatable, rule-based edit, prices, metafields, tags or redirects, script it against the GraphQL Admin API: read with a bulk query, write with bulkOperationRunMutation over a JSONL file, and let an AI agent draft the mutation and the file while a scoped, short-lived token does the work. For a one-off migration a non-developer will run, reach for Matrixify and its dry-run, because the native CSV import cannot touch metafields. Under both, one rule holds: a bulk write reaches every product in a single call, so the snapshot and the dry run are not optional extras.

SnapshotExport before you writeA bulk query to a JSONL file you keep. No snapshot, no rollback.
Dry runProve it on a subsetTen products or a dev store, read the userErrors, then apply the full file.
NeverNot one blind mutationA catalogue-wide write with no export and no dry run is the incident.

Key takeaways

  1. A bulk write reaches every product in one call. Export the before-state first; that export is your only rollback.
  2. There is no single mutation for variant prices or inventory. Wrap productVariantsBulkUpdate in a bulk operation over a JSONL file, one line per product.
  3. Dry-run on a development store or a small subset, read the per-line userErrors, then apply the full file.
  4. Redirects are catalogue data too: a mis-mapped column can point hundreds of URLs at the wrong place. Validate every target before import.
  5. Script the repeatable change; buy Matrixify for the one-off migration a non-developer runs. Native CSV import cannot set metafields.

What a bulk edit actually touches

Our earlier guides put the Shopify theme under version control with an AI agent and gave that agent a scoped credential rather than a login. This one is about the other half of the job: changing the data behind the storefront in bulk, safely, when there are thousands of products and one afternoon to do it. It sits in our UK payments and ecommerce work, where a wrong price or a broken redirect is not a cosmetic bug, it is lost revenue.

Shopify gives you a handful of things you will end up changing across the whole catalogue, and an honest constraint on each.

01 · Prices & inventoryOne product per call

productVariantsBulkUpdate updates the variants of a single product, so a catalogue price change is one JSONL line per product, not one request. Inventory has no bulk-operation path at all; it is set per item and location.

02 · MetafieldsmetafieldsSet, 25 at a time

One call sets up to 25 metafields, atomically, so a partial write inside a call cannot happen. Native CSV import cannot create or update metafields, which is exactly why bulk metafield work goes through the API or a paid tool.

03 · Tags & redirectsIdempotent, except the redirect

tagsAdd, tagsRemove and collection publishing are safe to repeat. urlRedirectCreate is the sharp one: a redirect points somewhere, and a wrong target is live SEO damage the moment it saves.

The native CSV importer is not the bulk tool you think it is. It cannot create or update metafields, and on a standard plan it caps SKU imports at roughly a thousand a day unless you are on Shopify Plus. On real migrations we reach for Matrixify precisely because it handles metafields and redirects the built-in importer leaves behind, and because its dry-run shows the errors before they land.

So the first decision is how you run the change, and it is genuinely a decision. A one-off migration a client will run themselves belongs in a tool with a dry-run and a spreadsheet, not a bespoke script. A change you will make every week, driven by a rule and wired into other systems, belongs in a scoped Admin API script you own. Choose by how often it runs.

A scoped Admin API script
  • Repeatable and rule-based: same logic, new data, every week
  • Reads from and writes to other systems, feeds and models
  • An AI agent can draft the mutation and the JSONL file
  • You own the dry run, the logging and the rollback
vs
Matrixify or a CSV
  • One-off migration or a change a non-developer will run
  • Reaches metafields and redirects the native importer cannot
  • A built-in dry-run that lists errors and warnings first
  • Priced by tier; scale it up to migrate, then scale it back down

The safe bulk-change pipeline

SAFE BULK-CHANGE PIPELINE · GREEN IS THE STEP THAT SAVES YOUExport beforebulkOperationRunQuerythe JSONL snapshotBuild & validateone line per itemcheck the schemaDry runsubset or dev storeread the userErrorsApplybulkOperationRunMutationstaged JSONL uploadVerify & keepdiff vs snapshotsnapshot = the undoundoRollback = re-apply the before-snapshotno snapshot, no undo — the export is the safety netthe token limits the blast radius; the snapshot and the dry run limit the damage inside itFIG. 01SAFE BULK-CHANGE PIPELINE
Fig. 01 A bulk write reaches every product in one call. The export, the dry run and the kept snapshot are what make that reversible; skip them and there is no undo button.scroll →

Every safe bulk change we run is the same five steps, and the three that matter are the ones people skip when they are in a hurry. Export the before-state to a JSONL file and keep it. Build the change file and validate its schema. Dry-run it, on a development store or a slice of ten products, and read the userErrors it returns. Only then apply the full file. And when it is done, diff the result against the snapshot and keep that snapshot somewhere you can find it, because it is the undo button. The scoped token limits how far a mistake can reach; the snapshot and the dry run limit the damage inside that reach.

Whatever runs the job, it should carry all of this, every time:

A before-state exportField-scoped writesA dry run on a subsetPer-line error handlingA kept rollback fileRate-limit backoff

How a bulk mutation runs

HOW ONE BULK MUTATION RUNS · JSONL IN, JSONL OUT, ASYNCstagedUploadsCreatereserve an upload URLUpload JSONLone line = one inputbulkOperationRunMutationthe mutation per linePoll statuscurrentBulkOperationRUNNING → COMPLETEDResults JSONLsuccesses + userErrorsre-run only the failed linesWHAT THROTTLES A BULK JOBRate limit · GraphQL100 / 200 / 1000 / 2000 pts/sby plan · one query ≤ 1,000 ptsmetafieldsSet≤ 25 metafields per callatomic · ≤ 260 pointsConcurrency≤ 5 bulk mutations per shopsince API 2026-01FIG. 02HOW A BULK MUTATION RUNS
Fig. 02 One bulk mutation is asynchronous: a JSONL file in, a JSONL file of results out. Re-run only the lines that returned userErrors, and design the job around the three limits that actually throttle it.scroll →

A bulk mutation is asynchronous, and that shape is the whole trick. You reserve an upload with stagedUploadsCreate, POST a JSONL file where each line is one input, and run bulkOperationRunMutation, which executes your mutation once per line. Then you poll currentBulkOperation until it reports COMPLETED and download a results JSONL, line by line, with successes and userErrors separated. The recovery model falls out of that: re-run only the lines that failed. Shopify: bulk import with the GraphQL Admin API

Three limits decide how you shape the job. The GraphQL Admin API restores 100 points a second on a standard plan, 200 on Advanced, 1,000 on Plus and 2,000 on Enterprise, and no single query may cost more than 1,000 points, so a naive loop throttles itself long before it throttles the store. Shopify: API rate limits. metafieldsSet takes at most 25 metafields per call. And since API version 2026-01 a shop can run up to five bulk mutations at once, where older versions allowed one of each type, so parallelism is a version decision, not a guess. Inventory is the exception with no bulk path, which is where a throttled cron that updates a batch on a schedule earns its place over a single doomed request.

ChangeHow it is writtenThe limit that bitesHow you roll it back
Variant pricesproductVariantsBulkUpdate in a bulk operationOne product per call, so one JSONL line per productRe-apply the exported prices
InventorySet per item and location; no bulk-operation pathThrottle a cron against the point budgetRe-apply the exported quantities
MetafieldsmetafieldsSet25 per call, atomic per callRe-apply, or delete the keys you set
Tags & collectionstagsAdd, tagsRemove, publishIdempotent, but check publication rulesRemove the tags you added
RedirectsurlRedirectCreate and urlRedirectUpdateA wrong target is live SEO damageDelete the ones you created, re-import the old set
Apply a price change over a JSONL file, then re-run only the failed linesjavascript
// bulk-price.mjs: apply a price change to many products in one bulk mutation.
// Step 0, always: export the before-state with bulkOperationRunQuery and keep it.
// price.jsonl, one line per product, each line one productVariantsBulkUpdate input:
//   {"productId":"gid://shopify/Product/123",
//    "variants":[{"id":"gid://shopify/ProductVariant/456","price":"19.99"}]}

// 1. Reserve an upload with stagedUploadsCreate, POST price.jsonl to it, keep the path.
// 2. Run the mutation over every line of the file:
const VARIANT_UPDATE = `mutation call($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkUpdate(productId: $productId, variants: $variants) {
    userErrors { field message }
  }
}`;

const run = await admin(`mutation bulk($path: String!, $q: String!) {
  bulkOperationRunMutation(mutation: $q, stagedUploadPath: $path) {
    bulkOperation { id status }
    userErrors { field message }
  }
}`, { path: stagedUploadPath, q: VARIANT_UPDATE });

// 3. Poll currentBulkOperation until COMPLETED, download the results JSONL, and
//    re-run only the lines that returned a userError. The step-0 export is the
//    rollback: re-apply it exactly the same way if the change was wrong.

What breaks in production

For this guide we went through our own Shopify catalogue work. Client names are withheld and described by sector. These are the bulk-change failures that actually cost time, and the rule each one taught.

2026 · Parts retailer306 right-priced pages, 1,072 with the content

A merged import split price and content across roughly 1,378 products: 306 pages had the correct price but no description or image, the rest had the content but the old price.

Read and export the catalogue before you write to it. Dedupe by preserving content, never blind-delete.
2025 · Parts retailerThe obvious price mutation was already dead

productVariantUpdate was gone, and productVariantsBulkUpdate turned out to be one product per call, useless for variant-level SKUs until wrapped in a JSONL bulk operation.

Confirm the mutation and your ID granularity first. The shape of your SKUs decides the shape of the file.
2025 · Parts retailerNo bulk mutation for inventory

Inventory had no bulk path, so the naive version was one REST call per item per location across tens of thousands of SKUs, straight into the rate limit.

When there is no bulk path, throttle a cron against measured throughput. Do not hammer the API and hope.
2026 · Training provider859 broken URLs from one CSV column

A migration's redirect import mis-mapped a column and pushed spreadsheet notes in as redirect targets, so hundreds of live URLs pointed at nonsense.

A redirect points somewhere. Dry-run the import and validate the target of every row before it goes live.
2025 · Fashion retailerThe free bulk editor that stops at ten

Every free App Store bulk editor capped at 10 products per file, and the sale and regular prices had no common pattern to formula.

Free tiers are demos. Real catalogues need a script or a paid tool, and irregular data needs a mapping, not a formula.
2025 to 2026 · Pet-food retailer“Import limited: 672 of 1,072, 400 failed”

Migration imports repeatedly hit row and daily caps and reported partial success that was easy to read as done.

Read the failure count and reconcile against the snapshot. An import that finished is not an import that succeeded.
  1. Export before you write, or you cannot roll back. A Shopify mutation has no undo. The before-state JSONL is the only thing that turns a mistake into a re-run instead of an incident.
  2. Dry-run against the userErrors, not against your confidence. Run the file on a subset or a development store and read what comes back per line. The errors are cheaper to find before the full run than after it.
  3. Treat redirects and handles as catalogue data. Changing a handle changes a URL. Validate every redirect target, and generate the redirect when the handle moves, or search rankings pay for it.

The Catalogue Change Risk Score

Before a bulk change runs, this is the triage we use. It does not measure whether the change is a good idea. It measures how bad an afternoon it could turn into, and whether to run it or build the safety net first.

Catalogue Change Risk Score0–12
+4The change runs straight against live products with no before-state export
+3No dry run on a subset or a development store before the full run
+2The selector is untested or broad, so the write may hit more products than intended
+2No per-line error handling, so a partial failure leaves the catalogue half-changed
+1Handles or redirects change with no validation of where each one points
0–2 · Run itSnapshot taken, dry run clean, errors handled. Apply, then reconcile against the export.
3–6 · Build the net firstTake the export, dry-run on ten products, add per-line error handling, then re-score.
7–12 · StopYou cannot undo this. Build the snapshot and the dry run before a single write lands.

A scripted price change with an export, a dry run on a dev store and per-line error handling scores zero or one. The same change typed straight into a live store with no export, because it was quicker, scores nine before it has touched a product.

A real boundary: the API will not bulk everything

The boundary that catches every high-SKU store is that Shopify’s convenient bulk tools stop short of what you actually need. On a UK caravan, motorhome and campervan parts retailer we run, with tens of thousands of variant-level SKUs, the tidy mutation, productVariantsBulkUpdate, only handles one product per call, and there is no bulk mutation for inventory at all. That is not a bug to work around, it is the shape of the platform, and the honest answer is to build the pipeline the API rewards.

01Export the before-statebulkOperationRunQuery to a JSONL you keep
02Build the JSONLOne line per product, only the changed fields
03Dry run and read errorsSubset or dev store; fix the JSONL
04Apply and keep the snapshotbulkOperationRunMutation; re-run failures

What our production record shows is that this pipeline scales. On that store we read roughly 36,000 products through a single bulk query, and a throttled price and inventory job moved about 12,000 products in five to six minutes, which extrapolates to a full six-figure catalogue in under an hour, with the rate limit a non-issue because bulk operations restore fast. It is the same discipline the team brings to a five-year custom Shopify build for an EU beauty brand: read first, change deliberately, keep the way back.

Recommendations by business type

High-SKU retailer, weekly price feedsA scoped script, not a plugin

Read the catalogue, build a JSONL from the supplier feed, dry-run it, and apply through a bulk mutation. Put it behind a scoped, short-lived token and a schedule, and keep every export.

Brand migrating off WooCommerce or MagentoMatrixify, then scale the plan back down

The native importer cannot do metafields and caps SKUs a day. Migrate with Matrixify and its dry-run, generate redirects for every changed handle, then drop back to a free tier once the move is done.

Store letting an AI agent make changesAgent drafts, scoped token applies

Let the agent write the mutation and the JSONL and read the docs, but hand it a read scope to inspect and a single write scope per job, never a login. The dry run and the export are still yours.

Small catalogue, a few dozen productsDo not overbuild it

A spreadsheet and the native CSV import, or a careful hour in the admin, beats a bulk pipeline you will run twice. Reach for the API when the count, the frequency or the metafields make it worth it.

Our ruleA bulk write reaches every product in one call. If you cannot show the export you took before it and the dry run you ran on it, you are not ready to press go.

Frequently asked questions

What is the safest way to change thousands of Shopify products at once?
Export the current state first with a bulk query, build a JSONL file that carries only the fields you are changing, dry-run it on a small subset or a development store, then apply it with bulkOperationRunMutation. Keep the export: re-applying it is the only rollback you get, because a Shopify mutation cannot be undone.
Can I bulk-update variant prices with one Admin API call?
Not with a single mutation. productVariantsBulkUpdate updates the variants of one product per call, and the older productVariantUpdate is gone. For a whole catalogue you wrap productVariantsBulkUpdate inside a bulkOperationRunMutation driven by a JSONL file with one line per product, uploaded through stagedUploadsCreate.
Is there a bulk mutation for inventory?
No. Inventory is set per item and location, with no bulk-operation path. At scale you run a throttled job, a cron that updates a batch every few seconds, sized against your plan's point budget rather than fired as one giant request.
Should I use a script or an app like Matrixify?
For a one-off migration or a spreadsheet a non-developer will run, Matrixify and its dry-run import are the faster, safer choice, and it reaches metafields that the native CSV import cannot touch. For a repeatable, rule-based change wired into other systems, a scoped Admin API script wins. We use both, and pick by whether the job runs once or every week.
How do I roll back a bad bulk change?
You do not undo it, you re-apply the previous values, so a rollback only exists if you exported the before-state first. That is why the snapshot is step one and not an afterthought. Redirects need the same treatment: export the current set before you import a new one.

Primary sources

Published 16 Sep 2026Reviewed 16 Sep 2026Reviewer Appycodes Editorial Team

Technical and operational guidance, not legal advice.

The engagements this writing comes from

UK · Europe · Worldwide

selected work

Where these numbers came from

Production systems with the delivery figures attached.

Creoate product interface01
B2B commerce

The engineering partnership behind a cross-border wholesale marketplace.

Next.js storefront, Python ingestion pipelines, DynamoDB data layer and AWS infrastructure.

8+ yearsone team, still shipping
Ontick product interface02
Event technology

A commission-free ticketing platform built for ownership and scale.

Multi-organiser commerce, Stripe instalments and two native apps in one connected platform.

£2M+processed since launch
Easyship product interface03
Global logistics

Embedded product engineering for a global shipping platform.

Rate, tax and duty calculators, server-rendered courier pages and a custom MongoDB CMS.

550+couriers on the calculator
TEFL.ie product interface04
Education & training

A course-commerce and learning platform wired into one system.

WordPress and WooCommerce, a Moodle LMS, Stripe deposits and Zoho CRM, tied together with Zapier automation.

8 yrsengineer & run, since 2017
All White Laser product interface05
Medical aesthetics

A bespoke platform that finances aesthetic machines on Direct Debit.

A lead-to-billing system on GoCardless Direct Debit, provider certification, and a React Native app for machine owners.

9 yrsbuild & run, since 2017
Decofetch product interface06
Luxury commerce

A custom furniture marketplace engineered from storefront to infrastructure.

Server-rendered Next.js commerce over a Laravel API, bespoke operations tooling and re-architected AWS infrastructure.

0→livecustom, front to back
BA Engine Room product interface07
AI operations

An AI-native operating system that runs a consultancy lead to invoice.

Discovery briefs, e-signed contracts, Stripe deposits, delivery milestones and time tracking in one operational system.

0→1built from the ground up
PlusHeat product interface08
Home services

A conversion platform for a growing UK boiler-cover provider.

Custom plan configuration, postcode-qualified lead journeys, CRM synchronisation and campaign landing pages.

5 yrsweb partner since 2021
Léonia product interface09
Beauty commerce

A custom Shopify store for a French beauty brand.

Custom theme, customer accounts, loyalty rewards, referrals and gift-with-purchase offers.

5 yrspartners since 2021
Shutters 365 product interface10
Home improvement

Made-to-measure shutters with live pricing.

A seven-step product builder with live previews, sample orders and supplier tools.

7-stepconfigurator, live pricing
Bloc Ads Manager product interface11
Advertising

A self-service advertising platform for venues.

Campaign creation, audience targeting, in-app ads and reporting linked to venue check-ins.

check-insclosed-loop attribution
Bloc product interface12
Social events

An events app with the tools to run it.

Mobile app, backend, advertising tools, a digital marketplace and website.

4+ yrsone team, five codebases
Zonely product interface13
Social mobile

Two mobile apps for real-time companionship.

Customer and buddy apps with per-minute billing, wallets, moderation and admin tools.

2 appsconsumer + buddy, iOS & Android
Player Profile Hub product interface14
Grassroots football

Player profiles and discovery for youth football.

Verified profiles, video highlights, coach discovery and safeguarding on web and mobile.

0→1built from the ground up
DeepSpatial product interface15
Geospatial AI

Websites and a talent platform for DeepSpatial.

Corporate and investor pages, the Xploor talent platform and ongoing releases on AWS Amplify.

2 yrsone team, ongoing
Yippee Malta product interface16
Travel

Tour bookings with a custom mobile-first checkout.

A multilingual website connected to the booking API, with deposits, coupons and affiliate tracking.

90+core web vitals, mobile & desktop
Professional Energy product interface17
Energy brokerage

One platform for tenders, contracts and accounts.

Supplier tenders, contract management, brokerage accounting and client records.

100+suppliers in one tender

Tell us what you are trying to build.

A thirty-minute call with the engineer who would run it.

Book a call