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 Size | Images per Product | Manual Time/Month |
|---|---|---|
| 100 products | 3 images | 15-20 hours |
| 500 products | 3 images | 75-100 hours |
| 1000+ products | 3 images | You 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:
- Shopify Flow + Zapier - No code, easiest setup
- Custom App - Most control, requires development
- 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:
- Go to Apps > Flow
- Create workflow
- Trigger: Product updated
- 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
| Element | Dynamic | Notes |
|---|---|---|
| Product name | Yes | Handle long names (truncate or shrink) |
| Price | Yes | Include currency symbol |
| Original price | Conditional | Show only if on sale (strikethrough) |
| Sale badge | Conditional | Show only when compare_at_price exists |
| Product photo | Yes | Pull from Shopify |
| "New" badge | Conditional | Based 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)
: 0Image dimensionsImage Dimensions
| Platform | Dimensions | Use |
|---|---|---|
| Shopify product | 2048 x 2048px | Main listing |
| Social sharing | 1200 x 630px | FB/Twitter |
| 1080 x 1080px | Social posts | |
| 1000 x 1500px | Pins |
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
- You set a sale (compare_at_price) in Shopify
- Webhook fires
- Image regenerates with sale badge
- Sale ends, you remove compare_at_price
- Webhook fires
- 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
| Component | Monthly 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:
- Design one product template in Imejis.io
- Choose your method: Flow+Zapier, custom app, or batch
- Test with 5 products to verify the flow works
- Roll out to full catalog gradually
- 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.

