BigCommerce to Shopify Migration: Modifiers First

A BigCommerce to Shopify migration is mostly a catalogue-mapping job, and three things decide how hard it is: modifier options, which have no Shopify equivalent; variant options, where Shopify allows three per product and BigCommerce publishes no per-product option limit; and your BigCommerce URL format, which decides how many 301s you need. Audit those three before you move a single product.

Everything else, from pages to tax settings, is ordinary setup work that Shopify’s general migration guide walks through in order. The catalogue is where BigCommerce stores carry decisions that do not survive the trip.

What is the difference between BigCommerce variants and modifiers?

BigCommerce splits product options into two kinds. Its Variants and Modifiers article puts it plainly: variants are combinations of option values that make a distinct product with its own SKU, while modifiers let the shopper personalise or add to the base product. “SKUs cannot be assigned to modifiers,” and “Inventory cannot be tracked for individual product modifiers.”

Shopify has the first concept and not the second. A Shopify variant is the same idea as a BigCommerce variant: a SKU, a price, stock. Shopify has nothing that sits on the product, changes the price, and is not a variant.

So the variants usually port cleanly, and the modifiers are where the migration time goes.

BigCommerce’s modifier type enum, from the Catalog API reference, is date, checkbox, file, text, multi_line_text, numbers_only_text, radio_buttons, rectangles, dropdown, product_list, product_list_with_images and swatch. Each value can carry adjusters.price, with an adjuster of relative or percentage and an adjuster_value. That is the field to look for, because a modifier that changes the price is a different problem from one that only collects text.

Where do BigCommerce modifiers go on Shopify?

Sort them into three groups.

Free-text and file modifiers with no price effect (a monogram, an engraving line, an uploaded logo) become line item properties: inputs in the product form whose values travel with the cart line onto the order. Our line item properties walkthrough covers the form markup and how the values reach fulfilment.

Choice modifiers that add a fixed charge (gift wrap for a few pounds, an extended warranty) need a decision. Turning them into variants doubles the variant count for every yes/no choice, which runs into the three-option limit fast. The usual alternatives are a separate add-on product the theme puts in the cart alongside the main one, or a Shopify Function that adjusts the line at checkout. Neither is a copy of the BigCommerce behaviour, so choose per modifier.

Percentage adjusters and product_list modifiers are the ones to argue about before you build anything. A percentage surcharge on top of a variant price has no direct equivalent, and a product list modifier is really a bundle. Treat both as design work, not data migration.

Will my BigCommerce variants fit Shopify’s limits?

Probably, but check. Shopify’s variant documentation says each product can have up to three options and up to 2,048 variants. The 2,048 figure is recent: the Shopify changelog raised it from 100 for all merchants on 15 October 2025, with a warning that apps not on the current GraphQL product APIs “may have a downgraded or broken experience” above 100 variants.

BigCommerce’s platform limits cap a product at 600 SKUs and 250 values per option. So the variant count always fits under Shopify’s 2,048. The option count is the one that bites. A BigCommerce product with size, colour, material and finish has four variant options, and Shopify will not take it.

The fix for a four-option product is to merge two options into one (“Oak / Matt”) or split the product into several, one per material. Both change how the product page looks, so the merchant has to sign off. A migration app will not make that call for you.

Check the apps you plan to keep as well. If any of them still read products through the REST Admin API, a product above 100 variants may break them even though Shopify itself accepts it.

How do I find the problem products before migrating?

Ask the BigCommerce Catalog API rather than a CSV export. GET /stores/{store_hash}/v3/catalog/products accepts an include parameter that embeds variants and modifiers in each product, authenticates with an X-Auth-Token header, and paginates with page and limit (default 50) plus a meta.pagination.total_pages count, all per the products endpoint reference.

Each variant’s option_values entries carry an option_display_name and a label, according to the variants reference. Count the distinct option names and you have the option count Shopify cares about.

This script lists every product with more than three options, more than 2,048 variants, or any modifier, flags modifiers that adjust the price, and writes a draft redirect CSV as it goes:

// audit.mts: run with `node audit.mts` (Node 22.18+ strips the types)
import { writeFile } from "node:fs/promises";

type OptionValue = { option_display_name: string; label: string };
type Variant = { sku: string; option_values: OptionValue[] };
type Modifier = {
  type: string;
  display_name: string;
  option_values: { label: string; adjusters?: { price?: { adjuster: string | null } } }[];
};
type Product = {
  id: number;
  name: string;
  custom_url: { url: string; is_customized: boolean };
  variants: Variant[];
  modifiers: Modifier[];
};

const STORE = process.env.BC_STORE_HASH;
const TOKEN = process.env.BC_TOKEN;
const BASE = process.env.BC_API_BASE ?? "https://api.bigcommerce.com";

async function* products(): AsyncGenerator<Product> {
  for (let page = 1; ; page++) {
    const url =
      `${BASE}/stores/${STORE}/v3/catalog/products` +
      `?include=variants,modifiers&limit=50&page=${page}`;
    const res = await fetch(url, {
      headers: { "X-Auth-Token": TOKEN ?? "", Accept: "application/json" },
    });
    if (!res.ok) throw new Error(`${res.status} on page ${page}`);
    const body = await res.json();
    yield* body.data as Product[];
    if (page >= body.meta.pagination.total_pages) return;
  }
}

function handleFrom(path: string): string {
  const segments = path.split("/").filter(Boolean);
  return (segments.at(-1) ?? "").toLowerCase();
}

const redirects: string[] = ["Redirect from,Redirect to"];
const problems: string[] = [];

for await (const p of products()) {
  const optionNames = new Set(
    p.variants.flatMap((v) => v.option_values.map((o) => o.option_display_name)),
  );
  if (optionNames.size > 3) {
    problems.push(`${p.id} ${p.name}: ${optionNames.size} variant options`);
  }
  if (p.variants.length > 2048) {
    problems.push(`${p.id} ${p.name}: ${p.variants.length} variants`);
  }
  for (const m of p.modifiers) {
    const priced = m.option_values.some((v) => v.adjusters?.price?.adjuster);
    problems.push(
      `${p.id} ${p.name}: modifier "${m.display_name}" (${m.type})` +
        (priced ? " adjusts price" : ""),
    );
  }

  const from = p.custom_url.url;
  const to = `/products/${handleFrom(from)}`;
  if (from.replace(/\/$/, "") !== to) redirects.push(`${from},${to}`);
}

console.log(problems.join("\n"));
await writeFile("redirects.csv", redirects.join("\n") + "\n");

Run it with BC_STORE_HASH and BC_TOKEN set, using an API account with read access to products. BC_API_BASE is only there so you can point it at a stub server while testing. The variant check will rarely fire given BigCommerce’s own 600 cap, but it costs one line and catches a limit change on either side.

The handle logic assumes each Shopify product keeps the last segment of its BigCommerce URL as its handle. If your import tool generates handles from product titles instead, swap handleFrom for a lookup against the handles you actually created.

How many redirects does a BigCommerce to Shopify move need?

That depends on the product URL setting under Settings, URL Structure. BigCommerce’s SEO fields article lists four formats: SEO Optimized (Short) at /product-name, SEO Optimized (Long) at /products/product-name, SEO Optimized (Category) at /category/product-name, and Custom with placeholders.

Shopify serves every product at /products/{handle}. A store on the Long format with handles that match its BigCommerce slugs already lines up, which is why the script skips paths that equal their target. Short and Category stores need a redirect for every product, and Category URLs depend on each product’s first category, so export them after the catalogue is frozen.

The product’s custom_url.is_customized flag tells you which URLs someone set by hand rather than letting BigCommerce generate them. Those are the ones most likely to break the “last segment is the handle” assumption, so check them by eye.

Shopify’s URL redirect rules allow 100,000 redirects on standard plans and 20,000,000 on Plus, warn that URLs containing query strings may not redirect as expected, and only fire when the old URL no longer loads a page. Categories, brand pages and web pages need their own rows; our WooCommerce redirects guide covers building and testing the full map, and the method carries over.

What about customers?

Shopify’s customer import page is blunt: “you can’t migrate customer passwords from another online store using a CSV.” Every customer account has to be reactivated with an invite. Plan the email and the timing as part of cutover, because a returning customer who cannot log in looks to them like a broken shop.

What I would do

Run the audit first, before choosing a migration app or quoting a timeline. A catalogue with no modifiers and no four-option products is a fast move, and an app plus a redirect import will carry most of it. A catalogue where half the products have price-adjusting modifiers is a rebuild of the product page and possibly the checkout, and it should be scoped and priced as one. If the modifiers are what the business sells (made-to-order furniture, personalised print), test the Shopify version of the worst product with the merchant before committing to the platform at all.

Whoooop runs Shopify migrations and builds, including the product-page and Functions work that BigCommerce modifiers usually turn into.

Need this built properly?

Get in touch