Skip to content

Working with variants – mental model and invariants

This document explains how variant-aware actions behave in the bulk editor. It is the conceptual guide that complements the implementation status in filters-variants-actions-status.md.

Variant & option terminology

To stay aligned with Shopify’s current Admin and Storefront APIs, we use the following terms consistently:

  • Product option (option name) – Labels such as Size, Color, or Material. Maps to Shopify’s ProductOption.name.
  • Option value – Specific values for a product option, e.g. Small, Red, Cotton. Maps to ProductOptionValue.name. In code you’ll see these stored on variants as option1/option2/option3.
  • Variant – A specific combination of option values (for example Size = Small, Color = Red). Maps to ProductVariant.
  • Variant title – The human-readable label for a variant (e.g. Red / Small). Shopify exposes this as ProductVariant.title; in our DSL the field variantName refers to the same combined option values.

When the UI or docs mention “variant title contains …”, it refers to matching against this combined option-value string.

1. Core invariants

  • Invariant V1 – Variant scope on matching products only
    By default, all variants on products that match the top-level product filter are eligible to be edited.

    • If you choose a variant-aware action (Price, SKU, Barcode, Edit/Delete/Replace Variants) and do not configure a variant filter, the job edits every variant on each matching product.
  • Invariant V2 – Product filter is the hard gate
    Products that do not match the product filter are never edited, even if a variant-level rule would otherwise match some of their variants.

    • The product filter decides which products “enter the job”.
    • Variant filters can only narrow or shape the set of variants within those products, not expand the job to new products.

These invariants should be preserved in:

  • UI copy (summaries and helper text).
  • Backend behavior (preview + execution).
  • Tests (Vitest + Playwright).

For the current implementation details, see filters-variants-actions-status.md (section “2. Variant targeting behavior”).

2. Two-step model: product filter → variant filter

  • Step 1 – Product filter (Filter products card)

    • Controls which products are included in the job.
    • Uses the main filter builder and is backed by the DSL + Shopify search query compilation.
  • Step 2 – Variant filter (per action)

    • Optional, lives inside the action card (e.g., “Variants to edit: All variants on each matching product [Change]”).
    • Controls which variants on those products are edited for that specific action.
    • Uses a variant-only DSL (position, options, SKU/barcode, variant price, etc.).

3. How variant filters refine edits

Given a fixed set of matching products (from Step 1):

  • No variant filter configured → all variants on each matching product are edited (V1).
  • Variant filter configured (e.g., “Variant position equals 2”, or “Option 2 equals Red”) → only variants that satisfy the variant DSL on each matching product are edited.
  • At no point can a variant filter cause a product outside the product filter to be modified (V2).

4. Action-specific semantics (VA‑1/2/Clone, SKU/Barcode, Options)

Given the invariants above, the specific variant actions behave as follows:

  • VA‑1 – Edit Variants

    • Applies the variant DSL to each matching product.
    • Only variants that match the DSL on matching products are edited.
    • Example: “Variant position equals 2” updates only the second variant on each matching product.
    • The Edit Variants action updates option values (Option 1/2/3) only. Updating option values effectively renames the variant (for example, changing Option 2 value from Red to Crimson for all matching variants). Use the Price, SKU, or Barcode actions (with variant filters) to change those fields on specific variants.
  • VA‑2 – Delete Variants

    • Applies the variant DSL per matching product.
    • Only matching variants are deleted; other variants on the product remain.
    • If the DSL effectively targets “all variants” on a product, the runner must first ensure the product keeps at least one variant (for example by creating a default variant) so the product itself is not deleted.
  • VA‑3 – Clone Variants & Options (replace)

    • Replaces all options and variants on each matching product with those from a selected source product.
    • This is a destructive action and ignores variant filters (the product filter still gates which products are affected).
    • Inventory quantities are not copied in v1; SKU/barcode and variant media are optional toggles.
  • Option-only actions (no variant changes)

    • Rename option updates the option label (e.g., SizeDimensions) without changing variant option values.
    • Reorder options changes the option order (Option 1/2/3) and therefore the variant ordering in admin/UI.
    • Reorder option values updates the ordering of values for a single option (variants are re-sorted accordingly).
  • SKU / Barcode actions + “blank SKU” helper

    • When the “blank SKU” (or equivalent) helper is active, only variants with a blank SKU on matching products are edited.
    • Summary copy should clearly indicate that the scope is constrained (for example, “Only variants with blank SKU on each matching product”).

5. Preview behavior

  • Product preview (Step 1)

    • The product preview table that belongs to the Filter products card does not need to update when the variant filter in the action panel changes.
    • It continues to show which products match the top-level product filter.
  • Action preview (per action)

    • Any action-specific preview (e.g., previewing changes in the action card or previewing variant rows) must reflect only the variants that match the variant DSL on the already-matching products.
    • This keeps execution and action preview aligned: if a variant is shown as being edited in the action preview, the job should actually edit it, and vice versa.

Zero‑match behavior:

  • If, after applying product + variant filters, zero variants match:
    • The job is allowed to run and complete as a no‑op, and
    • The UI should surface a clear summary (for example, “No variants matched your filters; no changes were applied.”).

6. Selection modes

  • Earlier “selection mode” presets (e.g., explicit First/Last variant toggles) were removed from the UI.
  • All variant targeting is now expressed via the variant filter DSL (for example, “Variant position equals 1” instead of a dedicated “First variant only” mode).
  • Tests and docs should reference these behaviors in terms of DSL rules rather than preset names.

7. Where this shows up in the product

  • UI

    • Variants to edit summary in the action cards (default text vs after clicking Change).
    • Variant Filter & Selection panels for Edit/Delete/Replace Variants and for SKU/Barcode actions.
  • Code

    • Product-level filtering: filter DSL + compileToShopifyQuery + preview/runner selection.
    • Variant-level filtering: variantPreviewFilters.ts, filterVariants / matchesVariantFilter, and the *VariantFilter fields on ActionConfig.
  • Tests

    • Unit: tests/variantSelection.test.ts, tests/variantPreviewFilters.test.ts.
    • E2E: tests/bulk-variants/variant-actions.pw.spec.ts, tests/bulk-tasks/sku-and-barcode.pw.spec.ts, and variant-related flows in tests/non-bulk/filters-and-usage.pw.spec.ts.

When adding or changing variant behavior, update this document first (invariants + mental model), then align filters-variants-actions-status.md, code, and tests with it.