Generate Product Thumbnails from a WooCommerce Store in India Using HTML to Image
Build consistent product-card images from WooCommerce data with a server-side HTML-to-image workflow, complete code, troubleshooting, and a screenshot API option.
To generate composed product thumbnails from a WooCommerce store, retrieve the product fields and image URLs, place them into a fixed HTML/CSS card template, then render that HTML as PNG, JPEG, or WebP with a browser-based renderer. Keep WooCommerce and renderer credentials on your server, validate and escape product data, and save the resulting image with a predictable filename so it can be refreshed when a product changes.
First clarify what “thumbnail” means for your use case. WooCommerce already creates smaller display images for the storefront. A generated card image is a separate asset combining product photography with fields such as the name and price, useful when you need a designed image for another placement. The steps below implement the second workflow. They are an implementation pattern; confirm API access, theme behavior, and registered image sizes for your store before relying on it.
1. Choose native WooCommerce sizing or a generated card
WooCommerce distinguishes the single product image, catalog images in product loops, and smaller product thumbnails used in places such as carts and widgets. It generates display sizes from the uploaded original and recommends source images of at least 800 × 800 pixels. If your only goal is to change how existing product photos appear in the store, inspect the theme’s image settings and native image sizes first. A separate renderer is unnecessary for that job. See the WooCommerce product images documentation.
Use HTML-to-image when the output should be a designed composition: for example, a product photo inside a branded card with a product name and price. Keep the generated card dimensions separate from WooCommerce’s own catalog and thumbnail dimensions; test the target placement’s aspect ratio.
2. Choose how to read products
For a public catalog of published products, WooCommerce’s Store API provides product data without exposing private or draft products. For merchant-authorized or private catalog workflows, use the authenticated REST API. The REST products endpoint exposes product and image data and supports requesting a registered image size through image_size; if that size is not registered, the endpoint falls back to the full image. See the Store API products reference and REST API products reference.
Select only fields the card needs, such as name, permalink, price, and a suitable image URL. Handle missing images and prices. Prefer an appropriately sized registered image when possible so the renderer does not have to download a needlessly large source image.
3. Set up credentials and a server-side job
- Create WooCommerce REST API keys with only the access needed for the integration. Public published products can instead be read through the Store API.
- Keep WooCommerce keys and image-rendering API credentials in server-side environment configuration or a secret manager. Never place them in browser-delivered JavaScript.
- Run generation from a backend endpoint, worker, or scheduled job. Expose only the narrow operation needed by your storefront or content process.
- Choose a renderer that accepts HTML/CSS or a public URL and returns an image. For a composed card, submitting controlled HTML is generally easier to keep consistent than capturing an entire product page.
The HTML/CSS to Image API documents Basic authentication using an API ID and API key, and accepts HTML or a public URL for rendering. Treat the key like a password. See its API guide.
4. Fetch products and render a card
The following Python example shows the flow with WooCommerce REST API v3 and an HTML-to-image renderer. It creates one card for each product returned by the endpoint. Set the environment variables on the server first. The renderer request follows the documented Basic authentication model; check the renderer’s current API documentation for account-specific endpoint details and response behavior.
import base64
import html
import os
from pathlib import Path
import requests
WC_BASE = os.environ["WC_BASE_URL"].rstrip("/")
WC_KEY = os.environ["WC_CONSUMER_KEY"]
WC_SECRET = os.environ["WC_CONSUMER_SECRET"]
HCTI_ID = os.environ["HCTI_API_ID"]
HCTI_KEY = os.environ["HCTI_API_KEY"]
# Set this to the image-rendering API endpoint for your account.
RENDER_ENDPOINT = os.environ["HCTI_RENDER_ENDPOINT"]
OUTPUT_DIR = Path("generated-thumbnails")
OUTPUT_DIR.mkdir(exist_ok=True)
products_response = requests.get(
f"{WC_BASE}/wp-json/wc/v3/products",
params={"per_page": 20, "image_size": "woocommerce_thumbnail"},
auth=(WC_KEY, WC_SECRET),
timeout=30,
)
products_response.raise_for_status()
products = products_response.json()
def make_card(product):
name = html.escape(product.get("name") or "Untitled product")
permalink = html.escape(product.get("permalink") or "", quote=True)
images = product.get("images") or []
image_url = html.escape(images[0].get("src", "") if images else "", quote=True)
# REST API price values are represented in minor units; format using the
# store's currency settings if you need a localized display string.
raw_price = product.get("price")
price = html.escape(str(raw_price)) if raw_price not in (None, "") else "Price unavailable"
image_markup = (
f'<img src="{image_url}" alt="{name}">'
if image_url
else '<div class="image-fallback">Image unavailable</div>'
)
return f'''<!doctype html>
<html><head><meta charset="utf-8">
<style>
* {{ box-sizing: border-box; }}
body {{ margin: 0; font-family: Arial, sans-serif; color: #172033; }}
.card {{ width: 640px; height: 480px; padding: 28px; background: #fff;
display: grid; grid-template-rows: 1fr auto; gap: 18px; }}
.photo {{ min-height: 0; display: grid; place-items: center; background: #f3f5f8; }}
.photo img {{ width: 100%; height: 100%; object-fit: contain; }}
.image-fallback {{ color: #687386; }}
.name {{ font-size: 24px; line-height: 1.25; }}
.price {{ margin-top: 8px; font-size: 20px; font-weight: 700; }}
</style></head><body>
<a href="{permalink}"><article class="card">
<div class="photo">{image_markup}</div>
<div><div class="name">{name}</div><div class="price">{price}</div></div>
</article></a>
</body></html>'''
# Basic authentication is documented by the renderer. Confirm the request body
# and response field names for your account's API before running this adapter.
auth_value = base64.b64encode(f"{HCTI_ID}:{HCTI_KEY}".encode()).decode()
for product in products:
response = requests.post(
RENDER_ENDPOINT,
headers={"Authorization": f"Basic {auth_value}"},
json={"html": make_card(product), "viewport_width": 640,
"viewport_height": 480, "device_scale": 1, "format": "png"},
timeout=60,
)
response.raise_for_status()
result = response.json()
image_response = requests.get(result["url"], timeout=60)
image_response.raise_for_status()
filename = OUTPUT_DIR / f"product-{product['id']}.png"
filename.write_bytes(image_response.content)
print(f"Saved {filename}")
The example intentionally makes the renderer endpoint and response adapter explicit rather than assuming account-specific details. Implement the body and response parsing that your rendering provider documents. The WooCommerce request, escaping approach, fixed card template, timeouts, and per-product output pattern are shown as a starting point. If your renderer accepts raw HTML directly and returns image bytes, save those bytes instead of making the follow-up URL request.
Store currency and price formatting
Do not assume a raw price string is formatted for display. WooCommerce stores prices according to the store’s currency settings and API representations may use minor units or formatted fields depending on endpoint and field. For a customer-facing card, read the store currency configuration or a documented formatted price field and format consistently. Avoid hand-formatting a currency symbol without confirming the store currency and decimal rules.
Template details that prevent inconsistent cards
- Use fixed card width and height, with explicit image fit such as
object-fit: containwhen the full product photo should remain visible. - Choose
coveronly if cropping is intentional and acceptable for the product category. - Escape text for HTML and attribute contexts. Validate URLs and permit only expected schemes such as HTTPS before inserting image links.
- Handle products with no image, no price, unusually long names, or multiple images. Clamp text or allocate a predictable title area.
- Use stable fonts available to the renderer, or wait for fonts to load using the renderer’s readiness or delay controls.
5. Set rendering dimensions and delivery behavior
Use the same viewport, card dimensions, device scale, and output format for every product in a batch. The HTML/CSS to Image documentation describes rendering HTML or a public URL and supports outputs including PNG, JPG, WebP, or PDF. Its rendering controls include viewport dimensions, device scale, delay, readiness callbacks, and selector cropping; consult the create and render guide and URL-to-image guide for exact request parameters.
For cards, HTML input gives the most control over layout. URL capture is useful if the card already exists as a public web page, but the page’s current layout, cookie notices, dynamic content, and load timing can affect the result. Use a documented wait condition or selector crop when needed. Keep output files associated with a product ID and a template or source version so you can refresh them after relevant changes.
6. Run the workflow for a whole catalog
- Fetch products in pages rather than assuming one response contains the full catalog. Follow the API’s pagination and rate guidance.
- For each product, normalize the fields your template accepts and construct a stable cache key from product ID plus the fields that affect the image.
- Skip rendering when that key has not changed. Refresh when the name, selected image, displayed price, card template, or output settings change.
- Limit concurrent rendering requests to the capacity and rate limits of your WooCommerce host and renderer. Retry transient network failures and server errors with bounded exponential backoff; do not endlessly retry invalid credentials or malformed HTML.
- Write generated images to storage only after the render and image download succeed. Keep the previous valid image available until its replacement is complete.
These are reliability patterns, not claims about a particular store’s throughput. The cited documentation does not specify your store’s rate limits, renderer plan, or India-specific hosting behavior.
7. Security, performance, and cost
- Credentials: WooCommerce REST keys and renderer keys belong on the server. Restrict WooCommerce permissions to the integration’s needs and avoid logging secrets or full authenticated URLs.
- Untrusted data: Escape product names and other text, validate image URLs, and do not insert arbitrary product-supplied HTML or JavaScript into the template.
- Image transfer: Request a suitable registered WooCommerce image size when possible. Smaller source images reduce transfer and rendering work, while the original may be needed if the output is large or must preserve detail.
- Rendering: Reuse identical templates and settings, avoid unnecessary waits, and cache completed files. Render only products whose relevant inputs changed.
- Cost: A hosted renderer may charge based on its own plan and usage; verify current pricing with that provider. WooCommerce API access, storage, and compute costs depend on your environment. The sources here establish no India-specific price, latency, or hosting requirement.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Product list is empty or incomplete | The public Store API exposes published products; a page size or pagination limit may also restrict results. | Confirm the products are published, follow pagination, or use the authenticated REST API for authorized private catalog data. |
| 401 or 403 from WooCommerce | Missing, incorrect, or insufficiently permissioned REST credentials; host configuration can also affect authentication. | Check the site URL, key/secret pair, permissions, and server authentication setup. Keep the credentials out of client code. |
| Image is missing or unexpectedly large | The product has no image, the chosen image size is not registered, or the request fell back to full size. | Use a fallback card state; verify registered image sizes and the returned image URL before rendering. |
| Card text breaks the layout | Long names or unusual characters do not fit the assumed template. | Escape text, define line limits and overflow behavior, and test long names and non-ASCII product text. |
| Renderer returns a blank or partially loaded card | The image or fonts were not ready when capture began, or the request body does not match the provider’s API. | Check the provider’s exact body and response schema. Use a readiness callback, selector wait, or modest documented delay, and verify external assets are reachable by the renderer. |
| Renderer authentication fails | Wrong API ID/key, incorrect Basic authentication encoding, or an account-specific endpoint mismatch. | Recheck the renderer’s current API instructions and environment variables. Do not print the authorization header in logs. |
| Some generated files are truncated or corrupted | The job saved an error response or incomplete download as an image. | Check HTTP status and content type before writing, use timeouts, and replace the old file only after a successful render and download. |
| Currency or displayed price looks wrong | A raw API price was treated as a localized display value, or the card assumes the wrong currency formatting. | Use the store’s currency configuration or documented formatted values and test representative prices. |
9. Alternative: capture an existing product page
If the goal is a screenshot of the product page as visitors see it, capture the public product URL rather than building a card template. This includes the site layout and can therefore vary with the theme, responsive viewport, dynamic content, and overlays. For a designed thumbnail that combines selected fields, the controlled HTML card above is usually more predictable.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. Its single GET endpoint can return PNG, JPEG, WebP, or PDF. For an existing public product page, a request looks like this; replace the target with a public product URL. See the ScreenshotNeo API documentation for options. A page screenshot captures the page; it does not compose a custom product card from WooCommerce fields, so use the template workflow above when you need that layout.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000. Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month, no card required.
FAQ
Does HTML-to-image replace WooCommerce thumbnail settings?
No. WooCommerce image sizes control storefront display derivatives. HTML-to-image creates a separate rendered asset, such as a composed product card.
Can I generate cards for draft products?
The public Store API only exposes published products. Use an authorized REST API integration for private catalog workflows, and keep its credentials server-side.
Should I use PNG, JPEG, or WebP?
Choose according to the destination’s supported formats and your visual needs. The cited renderer documentation lists these image formats; verify the current output options and quality controls with your provider.
Does the India location change the implementation?
The documented API workflow does not establish India-specific technical or regulatory requirements. Confirm your own hosting, access, and operational requirements for the store and deployment.


