IntermediateUpdated Jan 28, 2026
Building a Product Image Generator for Shopify
Create an automated system that generates professional product images for your Shopify store using AI, including background removal, lifestyle shots, and batch processing.
SC
Sarah Chen
Developer Advocate
18 min read
Introduction
Manual product photography is expensive and time-consuming. In this tutorial, you'll build an automated system that generates professional product images for Shopify stores using AI.
By the end, you'll have a working system that:
- Fetches products from Shopify
- Generates multiple image variations
- Uploads images back to Shopify
- Runs as a scheduled job or on-demand
Prerequisites- Node.js 18+
- Shopify Partner account or development store
- Abstrakt API key
Project Setup
mkdir shopify-image-generator cd shopify-image-generator npm init -y npm install @shopify/shopify-api node-fetch dotenv
Create .env:
ABSTRAKT_API_KEY=abs_your_key_here SHOPIFY_STORE_URL=your-store.myshopify.com SHOPIFY_ACCESS_TOKEN=shpat_xxxxx
Shopify API Setup
First, let's create a Shopify client:
javascript
// lib/shopify.js
import { shopifyApi, LATEST_API_VERSION } from '@shopify/shopify-api';
const shopify = shopifyApi({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET,
scopes: ['read_products', 'write_products'],
hostName: process.env.SHOPIFY_STORE_URL,
apiVersion: LATEST_API_VERSION,
});
export async function getProducts(limit = 50) {
const response = await fetch(
`https://${process.env.SHOPIFY_STORE_URL}/admin/api/2024-01/products.json?limit=${limit}`,
{
headers: {
'X-Shopify-Access-Token': process.env.SHOPIFY_ACCESS_TOKEN,
'Content-Type': 'application/json',
},
}
);
const data = await response.json();
return data.products;
}
export async function updateProductImage(productId, imageUrl) {
const response = await fetch(
`https://${process.env.SHOPIFY_STORE_URL}/admin/api/2024-01/products/${productId}/images.json`,
{
method: 'POST',
headers: {
'X-Shopify-Access-Token': process.env.SHOPIFY_ACCESS_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
image: { src: imageUrl }
}),
}
);
return response.json();
}Abstrakt Image Generator
javascript
// lib/image-generator.js
const ABSTRAKT_API_URL = 'https://api.abstrakt.one/v1';
export async function generateProductImage(product, style = 'standard') {
const prompt = buildPrompt(product, style);
const response = await fetch(`${ABSTRAKT_API_URL}/models/flux-pro/run`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ABSTRAKT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: {
prompt,
image_size: { width: 1024, height: 1024 },
num_images: 1,
},
}),
});
const result = await response.json();
return result.result.images[0].url;
}
function buildPrompt(product, style) {
const basePrompt = `Professional product photography of ${product.title}`;
const styles = {
standard: `${basePrompt}, clean white background, soft studio lighting,
e-commerce style, high detail, centered composition`,
lifestyle: `${basePrompt} in a modern lifestyle setting,
natural lighting, aspirational context, editorial photography style`,
minimal: `${basePrompt}, minimalist composition,
negative space, subtle shadows, premium feel`,
closeup: `${basePrompt}, macro detail shot,
showing texture and quality, sharp focus, professional lighting`,
};
return styles[style] || styles.standard;
}
export async function generateVariations(product) {
const styles = ['standard', 'lifestyle', 'closeup'];
const images = await Promise.all(
styles.map(style => generateProductImage(product, style))
);
return images;
}Main Processing Script
javascript
// index.js
import 'dotenv/config';
import { getProducts, updateProductImage } from './lib/shopify.js';
import { generateVariations } from './lib/image-generator.js';
async function processProduct(product) {
console.log(`Processing: ${product.title}`);
try {
// Generate image variations
const imageUrls = await generateVariations(product);
// Upload each image to Shopify
for (const url of imageUrls) {
await updateProductImage(product.id, url);
console.log(` Uploaded image: ${url.slice(0, 50)}...`);
}
return { success: true, product: product.title, images: imageUrls.length };
} catch (error) {
console.error(` Error: ${error.message}`);
return { success: false, product: product.title, error: error.message };
}
}
async function main() {
console.log('Fetching products from Shopify...');
const products = await getProducts(10); // Start with 10 for testing
console.log(`Found ${products.length} products`);
const results = [];
for (const product of products) {
// Skip products that already have enough images
if (product.images?.length >= 3) {
console.log(`Skipping ${product.title} (already has images)`);
continue;
}
const result = await processProduct(product);
results.push(result);
// Rate limiting - wait between products
await new Promise(resolve => setTimeout(resolve, 2000));
}
// Summary
const successful = results.filter(r => r.success).length;
console.log(`\nCompleted: ${successful}/${results.length} products processed`);
}
main().catch(console.error);Adding Quality Control
javascript
// lib/quality-check.js
export async function checkImageQuality(imageUrl) {
// Basic checks - in production, use a proper image analysis service
const response = await fetch(imageUrl);
const blob = await response.blob();
// Check file size (should be substantial for quality images)
if (blob.size < 50000) {
return { pass: false, reason: 'Image too small' };
}
return { pass: true };
}
export async function generateWithRetry(product, style, maxAttempts = 3) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const imageUrl = await generateProductImage(product, style);
const quality = await checkImageQuality(imageUrl);
if (quality.pass) {
return imageUrl;
}
console.log(` Retry ${attempt + 1}: ${quality.reason}`);
}
throw new Error('Failed to generate quality image after retries');
}Batch Processing with Progress
javascript
// lib/batch-processor.js
export class BatchProcessor {
constructor(batchSize = 5, delayMs = 1000) {
this.batchSize = batchSize;
this.delayMs = delayMs;
this.results = [];
}
async process(items, processor) {
const batches = this.chunk(items, this.batchSize);
for (let i = 0; i < batches.length; i++) {
console.log(`\nBatch ${i + 1}/${batches.length}`);
const batchResults = await Promise.all(
batches[i].map(item => processor(item))
);
this.results.push(...batchResults);
// Progress update
const completed = (i + 1) * this.batchSize;
const percent = Math.min(100, (completed / items.length) * 100);
console.log(`Progress: ${percent.toFixed(0)}%`);
// Delay between batches
if (i < batches.length - 1) {
await this.delay(this.delayMs);
}
}
return this.results;
}
chunk(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}Webhook for Real-Time Processing
javascript
// webhook-server.js
import express from 'express';
import { processProduct } from './index.js';
const app = express();
app.use(express.json());
// Shopify webhook for new products
app.post('/webhooks/products/create', async (req, res) => {
const product = req.body;
// Verify webhook (in production, verify HMAC)
console.log(`New product: ${product.title}`);
// Process asynchronously
processProduct(product)
.then(result => console.log('Processed:', result))
.catch(err => console.error('Error:', err));
// Respond immediately
res.status(200).send('OK');
});
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});Running the System
One-Time Batch Processing
node index.js
Scheduled Processing (Cron)
# Run daily at 2 AM 0 2 * * * cd /path/to/project && node index.js >> /var/log/shopify-images.log 2>&1
Webhook Server
node webhook-server.js
Cost Optimization
| Strategy | Savings |
|---|---|
| Skip products with images | ~30% |
| Use flux-schnell for drafts | ~50% |
| Cache similar products | ~20% |
| Batch during off-peak | ~10% |
Next Steps
- Add image variation A/B testing
- Implement brand style consistency
- Create a Shopify app UI
- Add manual approval workflow
Complete Code
Find the complete source code on GitHub.
#shopify#ecommerce#product-photography#automation#nodejs