Home / Blog / Amazon Product mainImage vs the Gallery images[] Field: Why They Differ

Amazon Product mainImage vs the Gallery images[] Field: Why They Differ

productInfo.mainImage and productInfo.images[0] look like they should be the same picture. They come from two different parts of the Amazon page, can show a different image entirely, and mainImage can be a smaller stored file even when it is the same picture. A tested Python check flags both cases before a catalogue sync ships the wrong hero image.

November 01, 2026
4 min read
SellerMagnet Team
Share & Bookmark
Terminal running a Python script that compares an Amazon product's mainImage and gallery images[0] and flags a different picture or a smaller stored file

productInfo.mainImage is not productInfo.images[0]. In a SellerMagnet /api/amazon-product-lookup response, mainImage is the picture Amazon shows with the offer listing, while images is the product page's own image gallery, so the two fields can show a different picture, and even the same picture at a smaller stored size. A catalogue sync that assumes they match can silently ship a lower-resolution, or simply wrong, hero image.

Key Takeaways

  • productInfo.mainImage comes from the offer listing, not the image gallery - they are two different parts of the page.
  • images[] is deduplicated, highest-resolution gallery pictures in gallery order; mainImage is one picture from elsewhere.
  • mainImage can be the same picture as images[0] at a smaller stored size, or a completely different picture.
  • A hero-image sync should prefer images[0] and only fall back to mainImage when there is no gallery at all.
  • The mismatch costs nothing extra to check: both fields come from the one product-lookup credit you already pay for.

Where does productInfo.mainImage actually come from?

An Amazon product page shows its image gallery and its offer listing as two separate parts. The lookup's images field is the gallery: every picture it holds, at the highest resolution offered, without duplicates, in gallery order. mainImage is the picture shown with the offer listing instead, and Amazon can use a different file there: sometimes the same picture at a smaller stored size, sometimes a different picture altogether. videos follows the gallery too, up to two HLS playlists.

Where each image field is read from.
FieldSource on the pageWhat you get
productInfo.imagesProduct image galleryEvery gallery picture, highest resolution offered, deduplicated, in gallery order
productInfo.mainImageOffer listing pictureOne picture shown with the offers, can differ from the gallery and can be smaller
productInfo.videosProduct image galleryUp to two HLS playlists (.m3u8)
searchResults[].mainImageSearch result cardResult thumbnail, e.g. ._AC_UY218_
bestsellers[].mainImageBestseller cardCard thumbnail, same source as the card itself

A lookup response where mainImage is a smaller, different file than images[0] (trimmed)

{
  "success": true,
  "data": {
    "productInfo": {
      "asin": "B0CL61F39H",
      "images": [
        "https://m.media-amazon.com/images/I/71qZqX8nQBL._SL1500_.jpg",
        "https://m.media-amazon.com/images/I/61e8hPmeoYL._SL1500_.jpg"
      ],
      "mainImage": "https://m.media-amazon.com/images/I/41srF-iY93L._SL500_.jpg"
    }
  }
}

How do I detect the mismatch in Python?

One lookup returns both fields, so the check is free: compare the file ID in each URL - the filename segment before any size modifier - to tell a different picture from the same picture at a different size, then compare the ._SL<n>_ size segment itself (as in the image download guide) to see which one is bigger.

hero_source.py - pick the right hero and flag the two mismatch cases

"""Check whether productInfo.mainImage is safe to use as a catalogue's hero image, or whether it
should fall back to productInfo.images[0] (the gallery's own first picture).

productInfo.mainImage is the picture Amazon shows with the offer listing, a different part of the page
from the product image gallery that fills productInfo.images.
The two can show a different picture, and even when they show the same picture, mainImage can be a
smaller stored file than the gallery's own first entry. A catalogue sync that assumes
mainImage == images[0] can silently ship a lower-resolution (or simply different) hero image.
Helpers: sellermagnet.py and retry.py from https://sellermagnet-api.com/blog/amazon-product-api-python
"""
import re

from retry import get_with_retry
from sellermagnet import ApiError, new_session

MARKETPLACE = "ATVPDKIKX0DER"   # amazon.com

# The "._SL<n>_" / "._AC_SL<n>_" segment sets the longest side in pixels; missing it means "no size
# modifier on this URL" rather than "0 px", so None (not 0) is the right default for a bare file.
SIZE_RE = re.compile(r"\._(?:AC_)?SL(\d+)_")


def pixels(url: str | None) -> int | None:
    if not url:
        return None
    m = SIZE_RE.search(url)
    return int(m.group(1)) if m else None


def image_id(url: str | None) -> str | None:
    """The filename stem before any size segment, e.g. '41ECK5cY-2L' - identifies the stored file."""
    if not url:
        return None
    return url.rsplit("/", 1)[-1].split("._")[0].split(".")[0]


def hero_check(product: dict) -> dict:
    gallery = product.get("images") or []
    main = product.get("mainImage")
    first = gallery[0] if gallery else None
    if not main:
        return {"verdict": "no mainImage", "use": first, "gallery_count": len(gallery)}
    if not first:
        return {"verdict": "no gallery, mainImage only", "use": main, "gallery_count": 0}
    if image_id(main) != image_id(first):
        return {"verdict": "DIFFERENT IMAGE: mainImage is not images[0]", "use": first, "gallery_count": len(gallery)}
    main_px, first_px = pixels(main), pixels(first)
    if main_px is not None and first_px is not None and main_px < first_px:
        return {"verdict": f"SAME IMAGE, SMALLER FILE: mainImage {main_px}px < images[0] {first_px}px",
                "use": first, "gallery_count": len(gallery)}
    return {"verdict": "same image, same or unknown size", "use": first, "gallery_count": len(gallery)}


def audit(asin: str) -> dict:
    session = new_session()
    data = get_with_retry(session, "amazon-product-lookup", asin=asin, marketplaceId=MARKETPLACE)
    result = hero_check(data["productInfo"])
    result["asin"] = asin
    return result


def row(r: dict) -> str:
    return f"{r['asin']:<10}  gallery={r['gallery_count']:<2}  {r['verdict']}"


if __name__ == "__main__":
    for asin in ("B0CL61F39H", "B0EXAMPLE1", "B0EXAMPLE2", "B0EXAMPLE3"):
        try:
            print(row(audit(asin)))
        except ApiError as err:
            if err.status in (401, 403):
                raise
            print(f"{asin:<10}  error {err.status}: {err}")

Output for four ASINs (illustrative data)

B0CL61F39H  gallery=2   DIFFERENT IMAGE: mainImage is not images[0]
B0EXAMPLE1  gallery=1   SAME IMAGE, SMALLER FILE: mainImage 500px < images[0] 1000px
B0EXAMPLE2  gallery=0   no gallery, mainImage only
B0EXAMPLE3  error 404: 404: no such product on this marketplace
Blueprint table of three product-lookup image fields, their source on the Amazon page and what each one returns
images[] and videos share the gallery source; mainImage does not.

Which image should a catalogue sync actually use?

Prefer images[0] as the hero: it is the gallery's own first picture, already the highest resolution the page offers. Fall back to mainImage only when images is empty - some marketplaces return no gallery for a given ASIN, and mainImage is then the one picture available. Never write mainImage into a catalogue field named "hero image" or "primary image" without this check first: the sync keeps working (no error, no empty field), it just quietly stores the wrong, or a smaller, file.

Blueprint table of four example ASINs with their gallery count and the hero_source.py verdict for each
Two of the four verdicts are mismatches a naive sync would ship without noticing.

Spot-check one ASIN's two image fields with curl and jq

curl -sG "https://sellermagnet-api.com/api/amazon-product-lookup" \
  --data-urlencode "asin=B0CL61F39H" \
  --data-urlencode "marketplaceId=ATVPDKIKX0DER" \
  --data-urlencode "api_key=$SELLERMAGNET_API_KEY" \
  | jq '.data.productInfo | {mainImage, first: .images[0]}' 

Only the lookup itself costs a credit (one, regardless of how many image fields you read from the response); re-running the check costs nothing extra. For bulk catalogues, run it inside the worker pool from the bulk ASIN lookup guide.

What this check cannot tell you

A matching file ID and size does not prove the pictures are pixel-identical - Amazon can re-encode the same source image differently in each widget. Treat the check as a catalogue-quality flag, not a byte-for-byte guarantee, and always use images[0] when it exists.

Frequently Asked Questions

Why is productInfo.mainImage different from productInfo.images[0]?

They come from two different parts of the Amazon page: mainImage is the picture shown with the offer listing, images[] is the product image gallery. They can show a different picture.

Is productInfo.mainImage always smaller than images[0]?

No. It can be the same resolution, a smaller stored file of the same picture, or a different picture at any size. Compare both the file ID and the size segment to know which.

Which field should I use as a product's hero image?

images[0]: it is the gallery's own first, highest-resolution picture. Use mainImage only as a fallback when images is empty.

Does checking both fields cost extra credits?

No. Both fields come back on the same /amazon-product-lookup call, which costs one credit regardless of how many fields you read.

Can images[] be empty while mainImage is still set?

Yes, on some marketplaces or listings. That is the one case where mainImage is the only hero picture available, so fall back to it only then.

Bottom line: images[0] and mainImage are two different fields from two different parts of the Amazon page, not two names for the same picture. Compare the file ID first, the size segment second, prefer images[0], and fall back to mainImage only when there is no gallery at all. The product lookup page lists every field, and a free account includes 150 credits.

Ready to Extract Amazon Data at Scale?

Start building with SellerMagnet API today. Real-time product data, competitive pricing, and review analytics at your fingertips.

150 free API credits • No credit card required • Cancel anytime