Shopify Product Image Automation: Sale Badges, Price Overlays & More

Shopify Product Image Automation: Sale Badges, Price Overlays & More

A Shopify store owner told me she spent every Sunday preparing product images for the week. New arrivals needed price tags. Sale items needed badges. Seasonal products needed themed overlays.

Eight hours. Every week. Just on images.

Her product data already existed in Shopify. Prices, sale status, inventory levels. All sitting there. She was manually transferring that data into Photoshop overlays.

That's exactly what automation fixes. Connect your Shopify catalog to an image API, and product images update themselves when your data changes.

Here's how to build it.

Why automate shopify product imagesWhy Automate Shopify Product Images?

Manual product image work doesn't scale. The math gets ugly fast:

Catalog SizeImages per ProductManual Time/Month
100 products3 images15-20 hours
500 products3 images75-100 hours
1000+ products3 imagesYou need a team

And that's just creation. What about when prices change? Sale periods? New variants?

What automation handles:

  • Price overlays that update when prices change
  • Sale badges that appear/disappear automatically
  • "New" tags based on creation date
  • "Low Stock" warnings based on inventory
  • Variant-specific images (color, size labels)

One template, unlimited product images. Your catalog changes, your images follow.

The architectureThe Architecture

Shopify has webhooks for product events. We'll use those to trigger image generation.

Shopify Product Update → Webhook → Image API → Storage → Back to Shopify (or CDN)

You have options for the middle layer:

  1. Shopify Flow + Zapier - No code, easiest setup
  2. Custom App - Most control, requires development
  3. External Cron - Batch processing approach

I'll cover all three.

Method 1 shopify flow zapier no codeMethod 1: Shopify Flow + Zapier (No Code)

Shopify Flow is built-in automation for Shopify Plus. Combined with Zapier, it handles most use cases without code.

Step 1 set up the zapStep 1: Set Up the Zap

Trigger: Webhooks by Zapier > Catch Hook

Copy the webhook URL. You'll use this in Shopify Flow.

Action: Code by Zapier (JavaScript)

const response = await fetch("https://render.imejis.io/v1/YOUR_TEMPLATE_ID", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    product_name: inputData.title,
    price: inputData.price,
    compare_at_price: inputData.compare_at_price,
    product_image: inputData.image_url,
    show_sale_badge: inputData.compare_at_price ? "true" : "false",
  }),
})
 
// API returns image directly - we need to handle the binary
const imageBuffer = await response.arrayBuffer()
 
// For Zapier, we'll base64 encode it
output = {
  image_base64: Buffer.from(imageBuffer).toString("base64"),
}

Action: Upload to Cloudinary (or your CDN)

Action: Send webhook back to Shopify (or update via API)

Step 2 create shopify flowStep 2: Create Shopify Flow

In Shopify admin:

  1. Go to Apps > Flow
  2. Create workflow
  3. Trigger: Product updated
  4. Action: Send HTTP request to your Zapier webhook

Include product data in the request:

{
  "title": "{{ product.title }}",
  "price": "{{ product.price }}",
  "compare_at_price": "{{ product.compare_at_price }}",
  "image_url": "{{ product.featured_image.src }}"
}

Now product updates trigger image regeneration automatically.

Method 2 custom shopify app most flexibleMethod 2: Custom Shopify App (Most Flexible)

For full control, build a custom app. This runs on your own server and handles everything.

The app structureThe App Structure

shopify-image-app/
├── server.js          # Express server
├── webhooks/
│   └── product.js     # Webhook handler
├── services/
│   └── imageGen.js    # Imejis.io integration
└── package.json

Webhook handlerWebhook Handler

// webhooks/product.js
import { generateProductImage } from "../services/imageGen.js"
 
export async function handleProductUpdate(req, res) {
  const product = req.body
 
  // Skip if no relevant changes
  if (!shouldRegenerateImage(product)) {
    return res.status(200).send("Skipped")
  }
 
  try {
    const imageUrl = await generateProductImage(product)
 
    // Optionally update product metafield with new image URL
    await updateProductMetafield(product.id, "generated_image", imageUrl)
 
    res.status(200).send("Generated")
  } catch (error) {
    console.error("Image generation failed:", error)
    res.status(500).send("Error")
  }
}
 
function shouldRegenerateImage(product) {
  // Logic to determine if image needs updating
  // Could check price changes, sale status, etc.
  return true
}

Image generation serviceImage Generation Service

// services/imageGen.js
import fs from "fs"
 
export async function generateProductImage(product) {
  const response = await fetch(
    `https://render.imejis.io/v1/${process.env.TEMPLATE_ID}`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.IMEJIS_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        product_name: product.title,
        price: formatPrice(product.variants[0].price),
        original_price: product.variants[0].compare_at_price
          ? formatPrice(product.variants[0].compare_at_price)
          : null,
        product_image: product.images[0]?.src,
        is_on_sale: !!product.variants[0].compare_at_price,
        is_new: isNewProduct(product.created_at),
      }),
    }
  )
 
  // API returns image directly
  const imageBuffer = await response.arrayBuffer()
 
  // Upload to your CDN (Cloudinary, S3, etc.)
  const cdnUrl = await uploadToCDN(
    Buffer.from(imageBuffer),
    `product-${product.id}.png`
  )
 
  return cdnUrl
}
 
function formatPrice(price) {
  return `$${parseFloat(price).toFixed(2)}`
}
 
function isNewProduct(createdAt) {
  const thirtyDaysAgo = new Date()
  thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
  return new Date(createdAt) > thirtyDaysAgo
}

Register webhooksRegister Webhooks

// server.js
 
import { Shopify } from "@shopify/shopify-api"
import express from "express"
 
import { handleProductUpdate } from "./webhooks/product.js"
 
const app = express()
 
// Verify Shopify webhook signature
app.use("/webhooks", Shopify.Webhooks.Registry.process)
 
app.post("/webhooks/products/update", handleProductUpdate)
 
// Register webhooks on app install
Shopify.Webhooks.Registry.register({
  path: "/webhooks/products/update",
  topic: "PRODUCTS_UPDATE",
  webhookHandler: handleProductUpdate,
})

Method 3 batch processing with cronMethod 3: Batch Processing with Cron

For stores that don't need real-time updates, batch processing is simpler.

The scriptThe Script

// scripts/sync-shopify-images.js
import Shopify from "shopify-api-node"
 
const shopify = new Shopify({
  shopName: process.env.SHOP_NAME,
  apiKey: process.env.SHOPIFY_API_KEY,
  password: process.env.SHOPIFY_PASSWORD,
})
 
async function processAllProducts() {
  let params = { limit: 50 }
 
  do {
    const products = await shopify.product.list(params)
 
    for (const product of products) {
      await generateAndStoreImage(product)
      // Rate limit
      await sleep(1000)
    }
 
    params = products.nextPageParameters
  } while (params)
}
 
async function generateAndStoreImage(product) {
  const needsImage = !product.metafields?.find(
    (m) => m.key === "generated_image"
  )
 
  if (!needsImage) return
 
  const response = await fetch(
    `https://render.imejis.io/v1/${process.env.TEMPLATE_ID}`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.IMEJIS_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        product_name: product.title,
        price: `$${product.variants[0].price}`,
        product_image: product.images[0]?.src,
      }),
    }
  )
 
  const imageBuffer = await response.arrayBuffer()
  const cdnUrl = await uploadToCDN(Buffer.from(imageBuffer))
 
  // Store URL in metafield
  await shopify.metafield.create({
    namespace: "generated",
    key: "product_image",
    value: cdnUrl,
    type: "url",
    owner_resource: "product",
    owner_id: product.id,
  })
 
  console.log(`Generated: ${product.title}`)
}
 
processAllProducts()

Run daily or weekly via cron or GitHub Actions.

Template design for e commerceTemplate Design for E-commerce

Product image templates need to handle real-world data variations.

Essential elementsEssential Elements

ElementDynamicNotes
Product nameYesHandle long names (truncate or shrink)
PriceYesInclude currency symbol
Original priceConditionalShow only if on sale (strikethrough)
Sale badgeConditionalShow only when compare_at_price exists
Product photoYesPull from Shopify
"New" badgeConditionalBased on created_at date

Handling price displayHandling Price Display

Your template logic should handle:

{
  "price": "$79.99",
  "original_price": "$99.99", // null if not on sale
  "show_sale_badge": true,
  "discount_percent": "20%"
}

Calculate the discount in your code:

const discount = originalPrice
  ? Math.round((1 - price / originalPrice) * 100)
  : 0

Image dimensionsImage Dimensions

PlatformDimensionsUse
Shopify product2048 x 2048pxMain listing
Social sharing1200 x 630pxFB/Twitter
Instagram1080 x 1080pxSocial posts
Pinterest1000 x 1500pxPins

Create separate templates for each platform if needed.

Displaying generated imagesDisplaying Generated Images

Option 1 product metafieldsOption 1: Product Metafields

Store the generated image URL in a product metafield, then display it in your theme:

{% if product.metafields.generated.product_image %}
  <img src="{{ product.metafields.generated.product_image }}" alt="{{ product.title }}">
{% else %}
  <img src="{{ product.featured_image | img_url: 'large' }}" alt="{{ product.title }}">
{% endif %}

Option 2 separate image sectionOption 2: Separate Image Section

Add generated images as an additional product image via the API:

await shopify.productImage.create(product.id, {
  src: cdnUrl,
  alt: `${product.title} - Social Image`,
  position: product.images.length + 1,
})

Option 3 social meta tags onlyOption 3: Social Meta Tags Only

If you only need images for social sharing, update your theme's <head>:

{% if product.metafields.generated.og_image %}
  <meta property="og:image" content="{{ product.metafields.generated.og_image }}">
{% endif %}

Sale period automationSale Period Automation

Here's the real power: images that change with your sales.

The flowThe Flow

  1. You set a sale (compare_at_price) in Shopify
  2. Webhook fires
  3. Image regenerates with sale badge
  4. Sale ends, you remove compare_at_price
  5. Webhook fires
  6. Image regenerates without badge

Zero manual image work for sales.

Scheduled salesScheduled Sales

For scheduled sales, use Shopify Flow:

Trigger: Scheduled time (sale start)
Action: Update product compare_at_price
→ This triggers your image generation webhook

And another flow for sale end.

Cost analysisCost Analysis

ComponentMonthly Cost
Shopify (Basic)$39
Imejis.io Pro$24.99
Cloudinary (Free)$0
Added Cost$24.99

For a 500-product store updating images weekly, that's about 5 cents per image. Compare to:

  • Freelance designer: $15-50 per image
  • Your time: Whatever you value it at

Check Imejis.io pricing for exact rates.

Common issuesCommon Issues

Images look pixelatedImages Look Pixelated

Cause: Source product images are low resolution. Fix: Ensure your Shopify product images are at least 2048x2048px.

Sale badge showing on wrong productsSale Badge Showing on Wrong Products

Cause: compare_at_price not cleared properly. Fix: Audit your products for stale compare_at_price values.

Webhook timeoutWebhook Timeout

Cause: Image generation takes too long. Fix: Process async. Acknowledge webhook immediately, generate in background.

app.post("/webhooks/products/update", (req, res) => {
  res.status(200).send("OK") // Respond immediately
 
  // Process in background
  setImmediate(() => {
    handleProductUpdate(req.body)
  })
})

Getting startedGetting Started

Your path:

  1. Design one product template in Imejis.io
  2. Choose your method: Flow+Zapier, custom app, or batch
  3. Test with 5 products to verify the flow works
  4. Roll out to full catalog gradually
  5. Set up sale automation for hands-off promotions

Start with your best-selling products. Get the template right, then expand.

E-commerce success is about efficiency. Every hour you're not manually editing images is an hour you can spend on marketing, customer service, or product sourcing.

Start automating product images with Imejis.io

FaqFAQ

Will this slow down my shopify storeWill this slow down my Shopify store?

No. Generated images are stored on a CDN and load just like regular images. There's no runtime processing when customers view products.

Can i use this with shopifys free themesCan I use this with Shopify's free themes?

Yes. The automation works independently of your theme. You just need to add the code to display generated images (using metafields or product images).

What happens during high traffic sales eventsWhat happens during high-traffic sales events?

If you're using webhooks, queue your image generation to avoid overwhelming the API. For Black Friday-level traffic, pre-generate sale images before the event starts.

Can i generate images for product variantsCan I generate images for product variants?

Yes. Create a template that includes variant info (size, color) and generate separate images for each variant. You'll need to loop through variants in your code.

How do i handle products without imagesHow do I handle products without images?

Your template should have a fallback. Either use a placeholder image or skip generation for products without photos.