← Back to Blog
Image SEO

Shopify Alt Text Automation: A Complete Guide for Store Owners

Shopify stores often have thousands of product images with empty alt text fields. Here is how to automate alt text generation end-to-end — from the Admin API to bulk updates and ongoing sync.

Shopify gives every product image an alt text field. Most stores leave it empty. The reason is mundane: writing 5,000 alt texts by hand is not a project anyone volunteers for, and Shopify's native bulk editor is not built for descriptive content.

The result is a measurable loss of image search traffic and a Lighthouse accessibility score that never quite reaches 100. This guide covers the full automation path: Admin API authentication, image fetching, bulk alt text generation, and writing the results back without breaking your store.

Why Shopify Alt Text Matters

Shopify product pages are templated, which means the alt text is one of the few image-level signals you control. The same image, with and without a descriptive alt text, can be the difference between ranking in Google Images and never appearing there at all.

There is also an accessibility angle: screen readers read the alt text aloud. An empty alt attribute on a product image makes that product effectively invisible to visually impaired customers. The SEO benefit is the side effect; accessibility is the real reason.

Step 1: Set Up a Custom App with Admin API Access

For automation, you need a custom Shopify app with Admin API access. Public apps from the App Store will not give you the control you need for a one-off bulk migration.

  • In Shopify Admin, go to Settings → Apps and sales channels → Develop apps.
  • Create a new custom app, configure Admin API scopes: read_products and write_products are sufficient.
  • Install the app on your store and copy the Admin API access token. Store it in your environment, never in source code.
Bad Hardcoding the API token in a script file checked into git
Good Environment variable in .env, referenced via config('shopify.token')

Step 2: Fetch Images via the Admin API

Shopify exposes product images through the GraphQL Admin API. Paginate through products, extract each image's ID, URL, and current alt text, and queue the ones that need processing.

// PHP / Laravel example$query = <<<'GRAPHQL'query getProductImages($cursor: String) {  products(first: 50, after: $cursor) {    pageInfo { hasNextPage endCursor }    edges {      node {        id        title        images(first: 20) {          edges {            node { id url altText }          }        }      }    }  }}GRAPHQL;$response = Http::withHeaders([    'X-Shopify-Access-Token' => config('shopify.token'),])->post("https://{$shop}.myshopify.com/admin/api/2025-01/graphql.json", [    'query' => $query,    'variables' => ['cursor' => $cursor],]);

Watch the rate limits. Shopify uses a leaky-bucket model: 1,000 cost points per minute on Plus, 50 on standard. Each query has a cost — wrap your loop with a backoff that respects the extensions.cost field in every response.

Step 3: Filter Images That Need Alt Text

Do not regenerate alt text for images that already have meaningful descriptions. The filter rule is simple but worth getting right:

  • Empty altText — process.
  • altText equal to the file name (e.g. IMG_4823.jpg) — process, this is auto-generated junk.
  • altText equal to the product title — process, this is too generic and duplicates content Google already sees.
  • altText longer than 20 characters and not matching the patterns above — skip, this is likely human-written.

This filter typically cuts the input set by 20–40% on stores that have done any prior alt text work.

Step 4: Generate Alt Text with Product Context

The single biggest quality lever is passing the right context to the image analysis API. Shopify gives you the product title, type, vendor, and tags — bundle these into a context hint before sending the image URL.

// Build context from Shopify product fields$context = sprintf(    '%s — %s — product photography',    $product['title'],    $product['productType'] ?? '',);Http::withToken(config('lucidseo.api_key'))    ->post('https://lucidseo.net/api/photos/analyze', [        'url' => $image['url'],        'webhook_url' => route('webhook.shopify-alt'),        'context' => $context,        'metadata' => [            'shopify_image_id' => $image['id'],            'product_handle' => $product['handle'],        ],    ]);

Without context, an AI looking at a photo of a leather wallet might describe it as "brown leather object on white background". With the product title "Bifold Leather Wallet — Saddle Brown" passed as context, the same analysis returns "Saddle brown bifold leather wallet with stitched edges on white background". The difference in keyword relevance is enormous.

Step 5: Write Alt Text Back via Mutation

Shopify's productImageUpdate mutation updates a single image's alt text. Call it from your webhook handler when the AI analysis completes.

$mutation = <<<'GRAPHQL'mutation updateImageAlt($productId: ID!, $image: ImageInput!) {  productImageUpdate(productId: $productId, image: $image) {    image { id altText }    userErrors { field message }  }}GRAPHQL;Http::withHeaders([    'X-Shopify-Access-Token' => config('shopify.token'),])->post("https://{$shop}.myshopify.com/admin/api/2025-01/graphql.json", [    'query' => $mutation,    'variables' => [        'productId' => $productId,        'image' => [            'id' => $shopifyImageId,            'altText' => $generatedAltText,        ],    ],]);

Check userErrors on every response. Shopify silently truncates alt texts beyond a certain length and rejects updates on archived products — neither produces an HTTP error, only a userError entry.

Step 6: Handle Variants and Image Groups

Shopify products have a primary image and a gallery of additional images, plus variant-specific images (e.g. each colour gets its own photo). Decide upfront whether you want variant images to share alt text or have unique descriptions per variant.

For most stores, variant-specific alt text is worth the extra API calls — "Bifold Leather Wallet in Saddle Brown" and "Bifold Leather Wallet in Charcoal Black" target different long-tail searches. Generate them separately and write them back to the variant's image ID, not the product's primary image.

Step 7: Continuous Sync for New Products

After the backlog is cleared, you need ongoing processing. Shopify webhooks fire on products/create and products/update — subscribe to both, queue an analysis job for any image that arrives without alt text.

Route::post('/webhook/shopify/product-update', function (Request $request) {    // Verify Shopify HMAC signature first    foreach ($request->input('images', []) as $image) {        if (empty($image['alt'])) {            AnalyzeShopifyImageJob::dispatch(                $image['id'],                $image['src'],                $request->input('title'),            );        }    }});

From this point on, every new product gets alt text within seconds of being created — without anyone in your team thinking about it.

Common Pitfalls

  • Rate limit underestimation — Shopify's GraphQL cost model surprises teams used to REST. Always read extensions.cost in responses and back off when throttleStatus.currentlyAvailable drops below 100.
  • Image URL expiry — Shopify's CDN URLs are stable, but signed URLs from staging environments expire. Always re-fetch the URL at job execution time, not at queue time.
  • Alt text overwriting human work — even with filters, occasionally a human-written alt text looks like junk to the filter. Keep an undo log: store the previous alt text before overwriting.
  • Locale handling — multi-language Shopify stores need the Translations API to set alt text per locale, not just the primary altText field.

Cost Estimate for a Typical Shop

A Shopify store with 500 products and 4 images each has 2,000 images. After filtering (~30% already have decent alt text), you process around 1,400 images. On LucidSEO's Pro plan that fits within one month's included quota; on the Starter plan it costs around 44 € including overage. Either way, the cost is below a single hour of writer time.

How LucidSEO Fits Shopify Workflows

LucidSEO's image analysis API is built for exactly the integration pattern above — context-aware analysis, webhook callbacks with metadata pass-through, multi-language output for international stores, and rate limits that align with Shopify's own throughput characteristics.

{  "alt_text": "Saddle brown bifold leather wallet with stitched edges",  "description": "Handcrafted saddle brown bifold leather wallet with reinforced stitching — minimalist design",  "caption": "Everyday carry, built to last",  "keywords": ["leather wallet", "bifold wallet", "saddle brown", "minimalist EDC", "handcrafted accessories"]}

Getting Started

The free plan includes 50 analyses per month — enough to test the full Shopify integration on a single product collection before committing to a full catalogue migration.

LucidSEO Image Analysis API

Upload a photo, get back alt text, description, caption and keywords in seconds — fully automated via webhook.

  • WordPress, Webflow, custom CMS — integrates in minutes
  • 12+ languages — metadata in the language your clients search in
  • Free plan — 50 analyses/month, no credit card required
Start for free →

Action List

  1. Create a Shopify custom app with read_products and write_products scopes.
  2. Paginate through products via GraphQL and filter images that actually need alt text.
  3. Always pass product title and type as context — this is the biggest quality lever.
  4. Use productImageUpdate to write back, and check userErrors on every response.
  5. Subscribe to products/create and products/update webhooks for continuous sync.