Shopify Variant Image CSV Validator: Find Mapping Risks Before Import

Shopify Variant Image CSV Validator: Find Mapping Risks Before Import

Rewarx practical lab · November 3, 2026 · Synthetic fixtures

Shopify Variant Image CSV Validator: Find Mapping Risks Before Import

This read-only CSV preflight reports missing image assignments, duplicate variant identities, suspicious cross-product references and malformed URLs before you import. It does not modify your file or call Shopify. It uses Python’s CSV parser, so quoted commas and multiline cells are handled as CSV rather than split text.

The current Shopify contract

Shopify’s current columns include URL handle, SKU, Product image URL and Variant image URL. Older aliases remain compatible. Product image URLs and variant image URLs serve different roles. Shopify warns that included blank columns can overwrite existing data; omitted columns behave differently. Option columns also have dependencies. Read the complete current CSV specification.

Treat community complaints as diagnostic leads. The variant-assignment discussion includes replies showing that expanding the variant list still permits assignment. It does not prove that Shopify removed per-variant images across the platform. An older CSV-import discussion describes a different reported failure and is not a current platform-wide status report.

What this validator checks

FindingMeaningReview action
BLANK_IMAGE_REVIEWA variant row has an included but blank image fieldDetermine whether the mapping is intentionally blank and whether overwrite is enabled.
DUPLICATE_VARIANT_KEYHandle and option name/value tuple appeared earlierCheck that the CSV has not duplicated the same variant.
REUSED_SKU_REVIEWSKU appears on more than one variant rowMay be intentional; verify identity rather than deleting rows.
CROSS_PRODUCT_IMAGE_REVIEWImage URL occurs only in another product gallery in this CSVSuspicious, but not proof of the wrong image.
INVALID_URLSyntactically invalid HTTP(S) URLFix syntax; reachability is a separate check.
OPTION_DEPENDENCYVariant row lacks Option1 identity fieldsReview official import dependencies before import.

Run it on a copy of your export

python shopify_variant_csv_preflight.py products.csv --output findings.csv

Save the full code below. Current column names are preferred; supported aliases include Handle, Variant SKU, Image Src and Variant Image. When both current and legacy names occur together, the report flags ambiguity. Additional gallery rows are not automatically treated as missing variants. Starting physical line numbers are reported, including when a quoted cell spans more than one line.

Complete CSV validator
"""Read-only Shopify CSV review. No network/API writes and no automatic fixes.
python shopify_variant_csv_preflight.py products.csv --output findings.csv
Rows identify physical starting line numbers (including multiline quoted cells).
"""
import argparse,csv
from collections import defaultdict
from pathlib import Path
from urllib.parse import urlsplit

ALIASES={'handle':['URL handle','Handle'],'sku':['SKU','Variant SKU'],
         'gallery':['Product image URL','Image Src'],'image':['Variant image URL','Variant Image']}
for n in range(1,4):
    ALIASES['name'+str(n)]=[f'Option{n} name',f'Option{n} Name']
    ALIASES['value'+str(n)]=[f'Option{n} value',f'Option{n} Value']

def valid_url(v):
    try:
        u=urlsplit(v);return u.scheme in ('http','https') and bool(u.hostname) and not u.username and not u.password and not any(c.isspace() for c in v)
    except ValueError:return False

def validate(path):
    issues=[];rows=[]
    def add(line,code,detail):issues.append({'line':line,'code':code,'detail':detail})
    with open(path,newline='',encoding='utf-8-sig') as f:
        reader=csv.DictReader(f);headers=reader.fieldnames or []
        if len(set(headers))!=len(headers):add(1,'DUPLICATE_HEADER','Duplicate column names are ambiguous')
        mapping={k:next((a for a in aliases if a in headers),None) for k,aliases in ALIASES.items()}
        for k,aliases in ALIASES.items():
            if sum(a in headers for a in aliases)>1:add(1,'AMBIGUOUS_ALIAS',k+': both current and legacy columns present')
        if not mapping['handle']:add(1,'MISSING_HANDLE_HEADER','Use URL handle or legacy Handle')
        if not mapping['image']:add(1,'IMAGE_COLUMN_OMITTED','Existing variant image field is not included; no image assignment supplied')
        line=1
        for raw in reader:
            start=line+1;line=reader.line_num
            if None in raw or any(v is None for v in raw.values()):add(start,'MALFORMED_ROW','Field count differs from headers')
            r={k:(raw.get(col) or '').strip() if col else '' for k,col in mapping.items()};r['line']=start
            r['variant']=bool(r['sku'] or any(r['value'+str(n)] for n in range(1,4)))
            rows.append(r)
    galleries=defaultdict(set)
    for r in rows:
        if r['gallery'] and r['handle']:galleries[r['gallery']].add(r['handle'])
    keys={};skus={}
    for r in rows:
        line=r['line'];h=r['handle'];img=r['image']
        if not h:add(line,'MISSING_HANDLE','Product identifier is blank; not inferred from the previous row')
        for field in ['gallery','image']:
            if r[field] and not valid_url(r[field]):add(line,'INVALID_URL',field+' is not a syntactically valid HTTP(S) URL')
        if not r['variant']:continue  # Additional gallery rows need not define a variant.
        if mapping['image'] and not img:add(line,'BLANK_IMAGE_REVIEW','Missing assignment; when overwriting, a blank included field may clear existing data')
        if not r['name1'] or not r['value1']:add(line,'OPTION_DEPENDENCY','Variant identity needs Option1 name and value; review before import')
        for n in range(1,4):
            if bool(r['name'+str(n)])!=bool(r['value'+str(n)]):add(line,'OPTION_PAIR','Option name/value pair incomplete: '+str(n))
        key=(h,)+tuple((r['name'+str(n)],r['value'+str(n)]) for n in range(1,4))
        if key in keys:add(line,'DUPLICATE_VARIANT_KEY','Same handle/options first appeared at line '+str(keys[key]))
        else:keys[key]=line
        if r['sku']:
            if r['sku'] in skus:add(line,'REUSED_SKU_REVIEW','SKU also used at line '+str(skus[r['sku']])+': inspect intent')
            else:skus[r['sku']]=line
        if img in galleries and h not in galleries[img]:add(line,'CROSS_PRODUCT_IMAGE_REVIEW','URL occurs only in another product gallery in this CSV; may be intentional')
    return issues

def main():
    p=argparse.ArgumentParser();p.add_argument('csv');p.add_argument('--output',required=True);a=p.parse_args()
    if Path(a.csv).resolve()==Path(a.output).resolve():raise SystemExit('Output must not replace input')
    issues=validate(a.csv)
    with open(a.output,'w',newline='',encoding='utf-8') as f:
        writer=csv.DictWriter(f,fieldnames=['line','code','detail']);writer.writeheader();writer.writerows(issues)
    print(f'{len(issues)} review findings; no changes made to input')

if __name__=='__main__':main()

Copyable synthetic fixture

URL handle,Title,SKU,Option1 name,Option1 value,Product image URL,Variant image URL
tee,"Tee, cotton",R-S,Color,Red,https://example.com/red.png,https://example.com/red.png
tee,,R-M,Color,Blue,,https://example.com/red.png
tee,,B-S,Color,Green,,
tee,,B-S,Color,Green,,file:///wrong.png
shoe,Shoe,SH,Color,Black,https://example.com/shoe.png,https://example.com/red.png
tee,,,,,https://example.com/detail.png,

Save as products.csv, then run the command above. The red image is intentionally reused for two option values: that alone creates no error. The tool cannot infer color meaning from a URL or pixels. It checks structural risk, leaving visual verification to a person.

Actual six-row fixture report
line,code,detail
4,BLANK_IMAGE_REVIEW,"Missing assignment; when overwriting, a blank included field may clear existing data"
5,INVALID_URL,image is not a syntactically valid HTTP(S) URL
5,DUPLICATE_VARIANT_KEY,Same handle/options first appeared at line 4
5,REUSED_SKU_REVIEW,SKU also used at line 4: inspect intent
6,CROSS_PRODUCT_IMAGE_REVIEW,URL occurs only in another product gallery in this CSV; may be intentional

Test results and limits

The combined image/CSV suite passed 15 tests on Python 3.9.6. CSV tests cover current and legacy columns, quoted commas, multiline line numbers, shared images, duplicate keys, blank fields, invalid URLs, gallery-only rows and cross-product references. The six-row demonstration produced five review findings. These are local synthetic results.

No development store import was performed. A clean report cannot establish Shopify import success, correct theme rendering, URL accessibility or semantic color accuracy. The cross-product check only knows galleries present in this file, not your full store. Missing SKU is not inherently an error: option identity still matters. Run the full published test suite.

Use the findings as a review queue

  • Keep the exported original and avoid changing row order during review.
  • Resolve structural duplicate identities first, then review intentional shared images.
  • Treat blank included fields as a review trigger, not a command to invent replacements.
  • Check the real image against the selected option values. A URL containing “red” does not prove a red product.
  • Use a development store or controlled import preview before applying changes to production. This article has not verified that final step.

Related practical guides

  • Check feed image dimensions
  • Test metadata preservation
  • Shopify CSV image-import troubleshooting
  • Catalog tool workflow comparison

Sources checked November 3, 2026

  • Official Shopify CSV documentation
  • Community assignment question
  • Historical CSV-import discussion

Rewarx workflow entry

Inspect the Rewarx background-removal entry page. Its primary button leads to Studio authentication. The validators and metadata lab here run locally; this article does not claim the Studio performs these checks or preserves metadata automatically.

Check HTTP delivery before CSV import

An image URL may return HTML, a permission error or a redirect instead of the asset. Use the read-only image HTTP diagnostic and local server fixtures to record the response, then return to this guide for mapping checks. Rewarx already supports Shopify import; the local CSV preflight described here is a separate utility, and its fixture tests do not validate the existing product importer.

https://www.rewarx.com/blogs/shopify-variant-image-csv-validator

Rewarx Studio | AI-Powered Product Photography & Image Generator

Turn snapshots into professional, high-converting product photos in batches. Cut costs by 90% and launch your collection in minutes.

Create Stunning Product Photos in Batches

Rewarx Studio is fine-tuned to understand the material physics and lighting requirements of 20+ specialized industries, including electronics, cosmetics, fashion, jewelry, home decor, and beverages.

Our virtual photography studio provides precise control over lighting, depth, and material textures. Perfect for high-end catalog shots, Etsy, Amazon, Shopify, and eBay sellers.

The Full AI Production Suite

  • AI Photography Studio: Professional virtual photography with precise control over lighting and textures.
  • AI Lookalike Creator: Match the aesthetic, lighting, and composition of any reference photo.
  • AI Model Studio: Integrate professional human models with your products naturally with realistic shadows.
  • AI Ghost Mannequin: Create a 3D "Invisible" mannequin effect showing inner linings and volume.
  • AI Mockup Generator: Apply patterns and graphics onto 3D items with absolute physical accuracy.
  • AI Group Shot Studio: Cohesively synthesize multiple products into a single scene with perfect lighting.
  • AI Product Page Builder: Generate conversion-optimized listing asset sets in a single click.
  • AI Commercial Ad Poster: Combine product focal points with premium typography for high-converting ads.

Corporate Headquarters

Rewarx Limited, Suite 400, 548 Market Street, San Francisco, CA 94104, United States. Email: studio@rewarx.com