A catalog script: read rows, map columns to named layers, POST batches of 50, retry failures. Product story: /use-cases/product-banners.
Overview
If you run an online store with more than a few dozen products, you know the pain: every product needs a banner for the category page, a variant for the sale, a version for the newsletter, and yet another crop for social ads. Multiply that by hundreds or thousands of SKUs and manual design stops being an option — it becomes the bottleneck that quietly caps your marketing velocity.
This post walks through building a product banner generator for e-commerce using jsontoimg: a JSON-in, image-out API. You design one template, connect your product catalog, and generate on-brand banners for every SKU automatically — including sale badges, dynamic pricing, and seasonal variants. By the end, you’ll have a pipeline that turns a product record into a finished banner in under a second.
Why Manual Banner Design Doesn’t Scale
A typical store with 500 SKUs and four seasonal promotions per year needs roughly 2,000+ banner variations annually — before you count A/B test variants or per-platform crops. The three common approaches all break at that volume:
- Design tools (Figma/Canva) — a designer spends 15–30 minutes per banner; 2,000 banners is 500–1,000 hours of work a year. It’s also error-prone: prices change, badges go stale, and files drift out of sync with the catalog.
- Headless browsers (Puppeteer/Playwright) — you get full control, but you’re now running rendering infrastructure: servers, memory limits, font loading, and constant maintenance. For a store that just wants banners, it’s a second product to operate.
- Static templates in the frontend (HTML/CSS + screenshots) — brittle, hard to keep pixel-perfect across browsers, and every layout change means re-engineering.
The alternative is a template-based image API: define the design once, send JSON per product, get an image back. No designers burned on repetitive work, no rendering infrastructure to run.
The Architecture: Catalog → JSON → Banner
The whole system has four moving parts:
Product catalog (Shopify / WooCommerce / your DB)
│ product data: name, price, image, badge, category
▼
JSON payload → jsontoimg API
│ renders template with your data
▼
Banner image (PNG/JPG)
│ stored on S3 / R2 / your CDN
▼
Served on category pages, emails, ads, social
The beauty is that the catalog is already structured data. A product *is* a JSON object: name, price, compare-at price, image URL, badge text. That’s exactly what a template-based API consumes.
Step 1: Design Your Product Banner Template
First, design a template in jsontoimg with placeholders for everything that changes per product. A standard product banner is 1200×628 (works for category headers, ads, and social). Your template should include:
- Background — brand color or gradient, optionally a subtle seasonal treatment
- Product image — the hero visual, with a placeholder for the image URL
- Product name — bold headline text
- Price / compare-at price — dynamic pricing with currency
- Sale badge — a “30% OFF” or “NEW” pill that appears conditionally
- Category or store logo — for branding consistency
In jsontoimg the template is defined as structured JSON with dynamic placeholders. Conceptually it looks like this:
{
"width": 1200,
"height": 628,
"background": { "type": "solid", "color": "#f7f7f8" },
"layers": [
{
"type": "image",
"source": "named layer product_image",
"x": 80, "y": 80, "width": 480, "height": 480
},
{
"type": "text",
"text": "named layer product_name",
"x": 620, "y": 140, "size": 44, "weight": "bold", "color": "#111827",
"max_width": 480, "wrap": true
},
{
"type": "text",
"text": "named layer price",
"x": 620, "y": 320, "size": 52, "weight": "bold", "color": "#0f766e"
},
{
"type": "text",
"text": "named layer compare_at_price",
"x": 620, "y": 390, "size": 28, "color": "#9ca3af",
"strikethrough": true
},
{
"type": "badge",
"text": "named layer badge",
"visible": "named layer has_badge",
"x": 620, "y": 80, "background": "#dc2626", "color": "#ffffff"
}
]
}
Notice the design decisions baked in: text has a max_width with wrapping so long product names never overflow, the badge is conditional via visible, and the price uses a larger, brand-colored style so the discount pops. Design once, reuse for every SKU.
Step 2: Generate Banners for Your Catalog
Once the template exists, generating banners is a single API call per product. Here’s a minimal Node.js example that pulls products from any source and renders a banner for each:
const products = await fetchCatalog(); // Shopify, WooCommerce, your DB
const banner = await fetch("https://app.jsontoimg.com/api/v1/render?wait=60", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer $JSONTOIMG_API_KEY",
},
body: JSON.stringify({
template: "product-banner-v1",
format: "png",
layers: {
product_image: { src: products[0].image_url },
product_name: { text: products[0].title },
price: { text: `$${products[0].price}` },
badge: { text: products[0].on_sale ? "SALE" : "" },
},
}),
});
const result = await banner.json();
// result.assetUrl → GET /api/v1/renders/{id}/asset
For hundreds of SKUs, send them as a batch — jsontoimg accepts an array and returns an array, so one request covers many products:
{
"jobs": [
{
"template": "product-banner-v1",
"format": "png",
"layers": {
"product_name": { "text": "Wireless Headphones" },
"price": { "text": "$79.99" },
"badge": { "text": "30% OFF" }
}
},
{
"template": "product-banner-v1",
"format": "png",
"layers": {
"product_name": { "text": "Mechanical Keyboard" },
"price": { "text": "$129.00" }
}
}
]
}
Step 3: Automate the Triggers
A banner generator is only valuable if it runs without you. Wire the render call into the events your store already emits:
- Product created/updated (webhook) — Shopify and WooCommerce both fire webhooks on product changes. Listen for
products/createandproducts/update, render the banner, and store the URL back on the product record. - Scheduled jobs (cron) — re-render sale banners nightly so prices and badges never go stale.
- Workflow tools (n8n / Zapier / Make) — trigger a render when a spreadsheet row is added; handy for seasonal campaigns where the promo list lives outside your store.
- CI/CD for marketing — on every deploy of a campaign, regenerate the full set of ad creatives.
Here’s a minimal webhook handler for a store product update:
app.post("/webhooks/products/update", async (req, res) => {
const product = req.body;
const image = await renderBanner(product); // Step 2 logic
await saveBannerUrl(product.id, image.image_url); // back to catalog or CDN
res.sendStatus(200);
});
Step 4: Store and Serve
Save generated banners to object storage (S3, Cloudflare R2, or any CDN) and reference the URL — don’t store blobs in your database. A few serving tips:
- Cache aggressively — banners change when prices change, not per page view. Cache with a short TTL (e.g., 15–60 minutes) so a sale update propagates without hammering the API.
- Version by template — include the template version in the filename (
banner-v1-{sku}.png) so a template redesign doesn’t invalidate old URLs mid-campaign. - Generate on demand, cache forever — compute the banner URL from the SKU + template version; render only when the image doesn’t exist yet. This gives you instant banners for 10K SKUs with a tiny render footprint.
If the banners feed a shopping surface rather than only your own storefront, the specs are not yours to choose. Google Merchant Center’s image requirements set minimum dimensions and prohibit promotional overlays on the main product image, which is a constraint worth encoding in the template rather than discovering at review time. For the trigger side, Shopify’s webhook topics are the canonical list of the events worth re-rendering on — a product update or an inventory change, rather than a nightly full sweep. On delivery, web.dev’s format guidance favours WebP or AVIF for storefront imagery, and Google’s image SEO documentation covers the filename and alt-text habits that make generated banners findable.
Best Practices for Product Banners
- Watch platform image specs — Amazon recommends at least 1,000 px on the longest side for zoom (500 px minimum), while Google Shopping moves to a 500×500 px minimum in January 2027. Design templates at a base size that comfortably exceeds marketplace minimums and export downscaled variants.
- Test with real variable content — render a few banners with the longest product names in your catalog, non-Latin characters, and missing images. The templates that break are the ones you didn’t test.
- Handle missing images gracefully — products without photos happen. Give the template a placeholder fallback so a banner renders instead of erroring.
- Keep badges conditional — a badge layer with
visible: falsecosts nothing when the product isn’t on sale, and keeps the design clean. - Log failures and retry — batch renders should tolerate partial failures: log the failed SKUs, retry once, and alert if the same SKU fails twice.
Beyond Banners: The Same Pipeline, Every Asset
Once the banner pipeline exists, the same catalog JSON powers every other visual: email hero images, social posts, ad variants for A/B tests, and certificate-style assets. You’re not building one automation — you’re building the rendering layer for your entire store’s visual content. (For the full catalog of scenarios, see the Product Banners use case page.)
Key Takeaways
- Manual design and headless browsers both break at e-commerce scale — a template-based image API is purpose-built for this job.
- A product record is already JSON; that’s the exact input a template-based API needs.
- Design one template with wrapped text, conditional badges, and dynamic pricing; then generate for every SKU in batch.
- Automate triggers via store webhooks, cron, or n8n so banners stay fresh without human intervention.
- Cache aggressively, version your templates, and test with real catalog data before launch.
FAQ
How do I keep product banners in sync with the catalog?
Trigger a render from the event that changed the data — a price update, a stock change, a new SKU — rather than regenerating the whole catalog on a schedule. Only the rows that changed cost credits.
Can I render banners for a whole catalog in one run?
Yes, in chunks of 50 jobs per batch request. A 2,000-product catalog is 40 sequential batch calls, which is a script you can leave running.
Where should the generated banners live?
Store the rendered file and reference it by URL from your storefront. Re-rendering an unchanged product on every page view wastes credits for a file that never changed.
What does a catalog-wide refresh cost?
One credit per banner. Two thousand products is 2,000 images, so a full refresh fits inside a Pro month and an incremental daily sync costs a handful of credits.
Related Resources
- Product Banners use case page
- Auto-Generate Open Graph Images for Your Blog
- Getting Started with jsontoimg: Your First API Call
- Batch Image Generation: 50 Jobs at a Time
Zoom out: How JSON-to-Image Conversion Works maps the whole template, named-layer, and render-job architecture.