SudoMock
Mockups
DocsPricing
Request a demo
Get Started

Start rendering mockups in minutes

From PSD to product mockup in one API call.

Get Free API Key→Browse Templates
Public status pagePublished API pricingProduct mockup API500 one-time API credits

Site Navigation

SudoMock

Turn reusable PSD templates, artwork, and editable text into production-ready product images through REST API, no-code workflows, native storefront apps, or AI.

SudoMock is a registered DBA
1209 Mountain Rd Pl NE, Ste N
Albuquerque, NM 87110, USA

Product

  • Pricing
  • Dashboard
  • PSD Mockups
  • Changelog
  • Start Free

Developers

  • API Overview
  • MCP Server
  • Quick Start
  • Render API
  • Upload PSD
  • Authentication
  • Error Handling
  • PSD Preparation
  • Integrations

Mockups & Tools

  • All Mockups
  • Apparel Mockups
  • Drinkware Mockups
  • Wall Art Mockups
  • Home & Living
  • By Platform
  • Search Mockups
  • Background Remover
  • Free Tools

Resources

  • All Use Cases
  • T-Shirt Mockup Generator for Print on Demand
  • Tote Bag Mockup Generator for Sustainable Brands
  • Automate Mug at Scale with SudoMock
  • Blog
  • Roadmap
  • All Alternatives
  • vs Placeit
  • vs DynamicMockups
  • vs Adobe Photoshop API
  • vs Static Mockup Generators
  • vs AI Image Editors

Company

  • About
  • Contact
  • Brand Assets
  • System Status (opens in new tab)
  • Feature Requests (opens in new tab)
Blog

Latest Articles

View all →
  • 01WooCommerce Product Placeholder Image: Change It, Then Replace It
  • 02Amazon Product Image Requirements: The Rules and Where a Mockup Fits
  • 03Print on Demand Ornaments: A Mockup for Every Holiday Design
  • 04Make.com Mockup Scenario: Render Each New Google Sheets Row on Your Own PSD or Product Photo

© 2026 SudoMock. All rights reserved.

PrivacyTermsRefundsCookies[email protected]
  1. Blog
  2. /
  3. Tutorial

WooCommerce Product Placeholder Image: Change It, Then Replace It

Change WooCommerce's placeholder under WooCommerce > Settings > Products, where the Placeholder image field takes an attachment ID or an image URL. To take a product off the placeholder, give it a main image: render one from your template and send its URL as the first entry of images in one REST update.

Published Nov 3, 2026·6 min read·Beck

A WooCommerce product update request beside a grey no-image placeholder card and the two SudoMock renders that replace it: a framed botanical print and a white t-shirt with a mushroom badge

Key takeaways

  • A product with gallery images but no main image still shows the placeholder in the shop grid.
  • WooCommerce names a downloaded image after its URL and rejects one without an image extension.
  • export_label names the render file, so the media library gets a readable file name.

On this page

  • Change the placeholder image in WooCommerce settings
  • Find every product that shows the placeholder
  • Render a main image for each product
  • Set the render as the product's main image
  • Run the whole catalog
  • Next step
  • Frequently asked questions
  • Sources

Change the placeholder image in WooCommerce settings

Open WooCommerce > Settings > Products and stay on the General tab. The Placeholder image field sits in the Shop pages section.

The field's help text in the WooCommerce 11.2.1 settings source (read November 3, 2026) says what it does: "This is the attachment ID, or image URL, used for placeholder images in the product catalog. Products with no image will use this." Leave it empty and WooCommerce falls back to its own grey placeholder.webp.

A theme or a small plugin can set the same thing in code. wc_placeholder_img_src() passes the final URL through the woocommerce_placeholder_img_src filter:

PHP
add_filter( 'woocommerce_placeholder_img_src', function () {
return 'https://example.com/wp-content/uploads/brand-placeholder.jpg';
} );

Make the placeholder the same shape as your product images. At WooCommerce's defaults the shop grid uses a 300 px wide thumbnail cropped 1:1 and the product page shows the main image 600 px wide, uncropped, so a square image of at least 600 px covers both. A theme can fix these sizes; otherwise they are set under Appearance > Customize > WooCommerce > Product Images.

Find every product that shows the placeholder

A product shows the placeholder when it has no main image. Through the REST API that is a product whose images array comes back empty, so read the catalog 100 products a page and keep those.

WooCommerce's get_image() reads only the main image, then the parent product's image for a variation, then the placeholder (abstract-wc-product.php, 11.2.1). The shop grid and the admin product list draw thumbnails through it. This is the request for one page:

Shell
curl -X GET "https://example.com/wp-json/wc/v3/products?per_page=100&page=1" \
-u "$WC_CONSUMER_KEY:$WC_CONSUMER_SECRET"

The response headers carry X-WP-TotalPages, so a script knows when to stop (WooCommerce REST API docs, pagination). Products listed before their photos exist, or imported from CSV rows with an empty Images column, come back this way.

One case hides from that check. A product with gallery images and no main image lists the gallery in images and still shows the placeholder in the shop grid, while its product page opens on the first gallery image (product-image.php, 11.2.1). In the admin product list its thumbnail is the grey placeholder, which is the quickest way to spot it.

Render a main image for each product

Give each product type one template and send one render request per product, with that product's own design in the template's smart object. The render comes back as a file URL you can pass straight to WooCommerce.

On November 3, 2026 we ran this for two example products: a framed botanical print and a white t-shirt with a mushroom badge. Each came from its own PSD template, with the design file in the template's print area, rendered at 1024 px as JPG. This is the t-shirt request, with a second smart object that sets the shirt color:

curl -X POST https://api.sudomock.com/api/v1/renders \
-H "x-api-key: $SUDOMOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mockup_uuid": "e532c503-fe20-4653-a301-af9f7a934609",
"smart_objects": [
{ "uuid": "39865245-8d99-42e9-b1fc-3713fa4dbc94",
"asset": { "url": "https://example.com/designs/mushroom-badge.png", "fit": "fit" } },
{ "uuid": "ea565e8a-2b2b-46a1-b181-ff19d0e18eac",
"color": { "hex": "#FFFFFF", "blending_mode": "multiply" } }
],
"export_options": { "image_format": "jpg", "image_size": 1024 },
"export_label": "mushroom-badge-tee-white"
}'
const response = await fetch('https://api.sudomock.com/api/v1/renders', {
method: 'POST',
headers: {
'x-api-key': process.env.SUDOMOCK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"mockup_uuid": "e532c503-fe20-4653-a301-af9f7a934609",
"smart_objects": [
{
"uuid": "39865245-8d99-42e9-b1fc-3713fa4dbc94",
"asset": {
"url": "https://example.com/designs/mushroom-badge.png",
"fit": "fit"
}
},
{
"uuid": "ea565e8a-2b2b-46a1-b181-ff19d0e18eac",
"color": {
"hex": "#FFFFFF",
"blending_mode": "multiply"
}
}
],
"export_options": {
"image_format": "jpg",
"image_size": 1024
},
"export_label": "mushroom-badge-tee-white"
}),
});
const data = await response.json();
console.log(data);
import os
import requests
response = requests.post(
"https://api.sudomock.com/api/v1/renders",
headers={
"x-api-key": os.environ["SUDOMOCK_API_KEY"],
"Content-Type": "application/json",
},
json={
"mockup_uuid": "e532c503-fe20-4653-a301-af9f7a934609",
"smart_objects": [
{
"uuid": "39865245-8d99-42e9-b1fc-3713fa4dbc94",
"asset": {
"url": "https://example.com/designs/mushroom-badge.png",
"fit": "fit",
},
},
{
"uuid": "ea565e8a-2b2b-46a1-b181-ff19d0e18eac",
"color": {
"hex": "#FFFFFF",
"blending_mode": "multiply",
},
},
],
"export_options": {
"image_format": "jpg",
"image_size": 1024,
},
"export_label": "mushroom-badge-tee-white",
},
)
print(response.json())
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
payload := []byte(`{
"mockup_uuid": "e532c503-fe20-4653-a301-af9f7a934609",
"smart_objects": [
{
"uuid": "39865245-8d99-42e9-b1fc-3713fa4dbc94",
"asset": {
"url": "https://example.com/designs/mushroom-badge.png",
"fit": "fit"
}
},
{
"uuid": "ea565e8a-2b2b-46a1-b181-ff19d0e18eac",
"color": {
"hex": "#FFFFFF",
"blending_mode": "multiply"
}
}
],
"export_options": {
"image_format": "jpg",
"image_size": 1024
},
"export_label": "mushroom-badge-tee-white"
}`)
req, err := http.NewRequest("POST", "https://api.sudomock.com/api/v1/renders", bytes.NewBuffer(payload))
if err != nil {
panic(err)
}
req.Header.Set("x-api-key", os.Getenv("SUDOMOCK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
<?php
$payload = <<<'JSON'
{
"mockup_uuid": "e532c503-fe20-4653-a301-af9f7a934609",
"smart_objects": [
{
"uuid": "39865245-8d99-42e9-b1fc-3713fa4dbc94",
"asset": {
"url": "https://example.com/designs/mushroom-badge.png",
"fit": "fit"
}
},
{
"uuid": "ea565e8a-2b2b-46a1-b181-ff19d0e18eac",
"color": {
"hex": "#FFFFFF",
"blending_mode": "multiply"
}
}
],
"export_options": {
"image_format": "jpg",
"image_size": 1024
},
"export_label": "mushroom-badge-tee-white"
}
JSON;
$ch = curl_init('https://api.sudomock.com/api/v1/renders');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . getenv('SUDOMOCK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $payload,
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

The file URL is in data.print_files[0].export_path. Ours ended in mushroom-badge-tee-white_bb9b264c-4610-49d1-8687-32337a20e4db.jpg: the export_label you send (letters, digits, hyphens and underscores, up to 100 characters) starts the file name, and the format sets the extension. Use the mockup_uuid and smart object uuid values of your own template in place of ours: the upload response returns them, and GET /api/v1/psd-mockups lists every template on your account. The framed print is one smart object on a poster template with "fit": "crop", so the artwork fills the frame edge to edge.

Framed botanical art print with coral and white leaves on a deep blue background, leaning on a stone shelf, rendered from a framed print PSD templateFramed print, rendered November 3, 2026 at 1024 px

White crew neck t-shirt with a round mushroom badge print on the chest, worn outside a cafe, rendered from a t-shirt PSD templateT-shirt, rendered November 3, 2026 at 1024 px

The designs were made for this example. With no PSD of your own, the template library has crew neck t-shirts and a framed print ready to render. A store that photographs its own blanks can save one product photo as a mockup and render each design on it instead.

Set the render as the product's main image

Send the render URL as the first entry of images in one PUT to the product. WooCommerce downloads the file into the media library and makes the first entry the main image.

Shell
curl -X PUT https://example.com/wp-json/wc/v3/products/412 \
-u "$WC_CONSUMER_KEY:$WC_CONSUMER_SECRET" \
-H "Content-Type: application/json" \
-d '{
"images": [
{ "src": "https://cdn.sudomock.com/mockup-assets/renders/e532c503-fe20-4653-a301-af9f7a934609/mushroom-badge-tee-white_bb9b264c-4610-49d1-8687-32337a20e4db.jpg",
"alt": "Mushroom badge print on a white crew neck t-shirt" }
]
}'

The products controller in 11.2.1 makes entry 0 the main image and every later entry the gallery, and it writes alt to the attachment. The list replaces what the product had. For a product that already has gallery images, send the render first and the existing gallery IDs after it: [{ "src": "..." }, { "id": 812 }, { "id": 813 }].

Where this step usually fails is the URL. wc_rest_upload_image_from_url() names the file after the last part of the URL, query string removed, and accepts it only with an image extension: jpg, jpeg, jpe, gif, png, bmp, tif, tiff, ico or webp. WooCommerce's CSV importer downloads the Images column through the same function, so the rule holds there too, and a failed download stops that row.

src you sendWhat WooCommerce does
https://example.com/images/render?id=8812Names the file render, finds no image extension and answers 400 woocommerce_product_image_upload_error, message starting "Invalid image"
https://cdn.sudomock.com/.../mushroom-badge-tee-white_bb9b264c-....jpgSaves mushroom-badge-tee-white_bb9b264c-....jpg in the media library and sets it as the main image

A render URL always ends in its format's extension, so it passes. Set the image in the same run as the render, as the script below does.

Run the whole catalog

A script joins the three calls: it lists the products with no image, renders each from its template and sets the result. Map products to templates by SKU prefix or category, and keep each product's design file URL in a list keyed by SKU. Save it as fill-images.mjs and run it with Node 18 or newer.

JavaScript
import { readFile } from "node:fs/promises";
const STORE = "https://example.com/wp-json/wc/v3";
const WC_AUTH = "Basic " + Buffer.from(`${process.env.WC_CONSUMER_KEY}:${process.env.WC_CONSUMER_SECRET}`).toString("base64");
const designs = JSON.parse(await readFile("designs.json", "utf8")); // { "TEE-MUSHROOM": "https://.../mushroom-badge.png" }
// One PSD template per product type, keyed by SKU prefix, with your own UUIDs
const templates = {
TEE: { mockup: "e532c503-fe20-4653-a301-af9f7a934609", area: "39865245-8d99-42e9-b1fc-3713fa4dbc94", fit: "fit", what: "white crew neck t-shirt",
color: { uuid: "ea565e8a-2b2b-46a1-b181-ff19d0e18eac", hex: "#FFFFFF" } },
PRINT: { mockup: "091de3df-148d-4946-bbfb-ff62603a7e6a", area: "44f6a757-e4c7-410e-b2b6-59a00dc9c87f", fit: "crop", what: "framed art print" },
};
async function productsWithoutImage() {
const found = [];
for (let page = 1; ; page++) {
const res = await fetch(`${STORE}/products?per_page=100&page=${page}`, { headers: { Authorization: WC_AUTH } });
found.push(...(await res.json()).filter((p) => p.images.length === 0));
if (page >= Number(res.headers.get("x-wp-totalpages"))) return found;
}
}
async function render(product, t) {
const res = await fetch("https://api.sudomock.com/api/v1/renders", {
method: "POST",
headers: { "x-api-key": process.env.SUDOMOCK_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
mockup_uuid: t.mockup,
smart_objects: [
{ uuid: t.area, asset: { url: designs[product.sku], fit: t.fit } },
...(t.color ? [{ uuid: t.color.uuid, color: { hex: t.color.hex, blending_mode: "multiply" } }] : []),
],
export_options: { image_format: "jpg", image_size: 1024 },
export_label: product.sku.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").slice(0, 100),
}),
});
if (res.status === 429) {
await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") || 5) * 1000));
return render(product, t);
}
const body = await res.json();
if (!res.ok) throw new Error(`${product.sku}: ${res.status} ${JSON.stringify(body)}`);
return body.data.print_files[0].export_path;
}
for (const product of await productsWithoutImage()) {
const t = templates[product.sku.split("-")[0]];
if (!t || !designs[product.sku]) continue; // no template or design for this SKU yet
try {
const src = await render(product, t);
const res = await fetch(`${STORE}/products/${product.id}`, {
method: "PUT",
headers: { Authorization: WC_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({ images: [{ src, alt: `${product.name}, ${t.what}` }] }),
});
console.log(product.id, product.sku, res.status);
} catch (err) {
console.error(product.id, product.sku, err.message); // logged, the run moves on
}
}

Each product gets its own PUT because WooCommerce downloads the file inside that request. A 429 from the render API carries Retry-After, and the script waits that long before it asks again (rate limits). The SKU, cleaned to letters, digits, hyphens and underscores, becomes the export label, and a product that fails is logged and skipped. Variations without an image of their own show the parent's image, so filling the parent takes the placeholder off them too; to give each color its own picture, the variation gallery walkthrough covers the variation endpoint.

The average SudoMock render finishes in under a second. Plans start at $25/month. Subscriptions from $0.002 per render.

Without code, an n8n workflow or a Make scenario on a schedule runs the same chain: an HTTP step lists the products, the SudoMock step renders and a second HTTP step sends the PUT.

Next step

Start with Upload a PSD: the dashboard takes PSD files up to 300 MB and the API has no file size limit. Run the GET on your store and count the empty images arrays. Then render one product with the request above, PUT it, and check the shop grid before you run the script on the rest. The render reference lists every field. For a shopper who designs their own product on the product page, the WooCommerce integration adds a customizer with a live preview.

Frequently asked questions

Where is the placeholder image setting in WooCommerce?

It is under WooCommerce > Settings > Products, on the General tab, in the Shop pages section. The Placeholder image field takes an attachment ID from the media library or a full image URL. With the field empty, WooCommerce shows its own grey placeholder.

Why does my WooCommerce product still show the placeholder image?

The shop grid and the admin product list show the product's main image, and gallery images do not stand in for it there. A product with gallery images but no main image keeps the placeholder in the grid, while its product page opens on the first gallery image. Set a main image, or send the image you want first in the images list of a REST update.

Why did WooCommerce reject my product image URL?

WooCommerce downloads the file and names it after the last part of the URL, then accepts it only with an image extension such as jpg, png or webp. A URL that ends without an extension fails with an Invalid image error. Use a direct file URL that ends in the image's extension.

What size should a WooCommerce product image be?

At WooCommerce's default settings the shop grid shows thumbnails 300 pixels wide, cropped square, and the product page shows the main image 600 pixels wide, uncropped. A square image of at least 600 pixels covers both. A theme can set its own sizes.

Do product variations need their own image?

A variation without its own image shows the parent product's image, so giving the parent a main image takes the placeholder off its variations too. Give each color its own image when shoppers should see the color they pick.

Sources

  1. woocommerce/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php at 11.2.1 · woocommerce/woocommerce (opens in new tab), GitHub
  2. woocommerce/plugins/woocommerce/includes/wc-product-functions.php at 11.2.1 · woocommerce/woocommerce (opens in new tab), GitHub
  3. woocommerce/plugins/woocommerce/includes/abstracts/abstract-wc-product.php at 11.2.1 · woocommerce/woocommerce (opens in new tab), GitHub
  4. woocommerce/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php at 11.2.1 · woocommerce/woocommerce (opens in new tab), GitHub
  5. woocommerce/plugins/woocommerce/includes/wc-rest-functions.php at 11.2.1 · woocommerce/woocommerce (opens in new tab), GitHub
  6. WooCommerce REST API Documentation - WP REST API v3 (opens in new tab)
  7. Render a PSD mockup (opens in new tab), SudoMock Docs
  8. Rate limits and concurrency limits (opens in new tab), SudoMock Docs
  9. Create a mockup from a product photo (opens in new tab), SudoMock Docs
  10. Upload a PSD (opens in new tab), SudoMock Docs
  11. woocommerce/plugins/woocommerce/includes/customizer/class-wc-shop-customizer.php at 11.2.1 · woocommerce/woocommerce (opens in new tab), GitHub
  12. woocommerce/plugins/woocommerce/templates/single-product/product-image.php at 11.2.1 · woocommerce/woocommerce (opens in new tab), GitHub
  13. woocommerce/plugins/woocommerce/includes/import/abstract-wc-product-importer.php at 11.2.1 · woocommerce/woocommerce (opens in new tab), GitHub
B

Beck

CTO

View all posts

On this page

  • Change the placeholder image in WooCommerce settings
  • Find every product that shows the placeholder
  • Render a main image for each product
  • Set the render as the product's main image
  • Run the whole catalog
  • Next step
  • Frequently asked questions
  • Sources

Related articles

Lighthouse t-shirt rendered in white, navy and heather gray, on a model and as a flat lay, beside the variation request that sets each gallery
Tutorial

WooCommerce Variation Image Galleries: Fill Each Color by API

Fill the native WooCommerce variation galleries by API: render each color from a PSD and a product photo, upload, then set gallery_image_ids.

Oct 6, 2026 · 5 min read

The input fields of the WooCommerce product-create ability next to the REST call that adds images by URL, with one sunset design rendered on a sand, a black and a forest green tee from one PSD template
Tutorial

WooCommerce MCP: Create Products With Images From Your Own Template

The WooCommerce abilities behind the MCP Adapter create products without images. Render each color from your PSD with SudoMock and attach it by REST.

Oct 23, 2026 · 7 min read

A plain canvas tote product photo turned into a mockup with POST /api/v1/photo-mockups, with mushroom, sunset and botanical designs rendered on the same bag
Tutorial

How to Make Your Own Mockup from Any Product Photo

One photo of your own product becomes a reusable mockup: the create call, the render call and real renders of four designs on a canvas tote.

Oct 2, 2026 · 7 min read

Your own PSD, rendered via API

Render your own PSD through the API. Start with 500 one-time credits, no card.

Start Free View Docs

Render your own PSD via API with 500 one-time credits.

Start Free · 500 credits, no card