# How to push image caption, title, and description via WooCommerce API using Python?

**URL:** <https://onehack.st/t/how-to-push-image-caption-title-and-description-via-woocommerce-api-using-python/321217>\
**Category:** Discussion & Solutions\
**Tags:** solved\
**Created:** [April 16, 2026, 3:57am UTC](https://onehack.st/t/how-to-push-image-caption-title-and-description-via-woocommerce-api-using-python/321217 "2026-04-16T03:57:38Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![AnonPs](https://onehack.st/user_avatar/onehack.st/anonps/32/166867_2.png) [@AnonPs](https://onehack.st/u/AnonPs)\
**Post date:** [April 16, 2026, 3:57am UTC](https://onehack.st/t/how-to-push-image-caption-title-and-description-via-woocommerce-api-using-python/321217/1 "2026-04-16T03:57:39Z")

</div>

Hi everyone,

I’m working on a WooCommerce integration where I push products from my local server using the WooCommerce REST API with Python.

My current flow looks like this:

- Create product data (title, description, etc.)

- Generate image metadata (caption, alt text, title, description)

- Store everything in my database

- Push product + images via API to WooCommerce

Most of it is working fine — products are created successfully, images are uploaded, and even the **alt text** is being pushed correctly.

However, I’m stuck on pushing the rest of the image metadata:

- Image caption

- Image title

- Image description

I’ve tried multiple approaches but haven’t been able to get these fields to update in WooCommerce.

Has anyone successfully done this via the WooCommerce API?  
If so, could you share how you’re passing these fields (or if there’s a different endpoint I should be using)?

Thanks in advance! #help

---

<div class="post-metadata">

**Author:** ![kailaswebb](https://onehack.st/user_avatar/onehack.st/kailaswebb/32/172682_2.png) [@kailaswebb](https://onehack.st/u/kailaswebb)\
**Post date:** [April 16, 2026, 3:36pm UTC](https://onehack.st/t/how-to-push-image-caption-title-and-description-via-woocommerce-api-using-python/321217/2 "2026-04-16T15:36:14Z")

</div>

The issue is that the WooCommerce REST API (`wc/v3/products`) creates the product-to-image link but does not expose the underlying WordPress attachment fields like `caption` and `description`. The `images` list in the product endpoint only officially supports `src`, `id`, `name` (which often maps to the attachment title), `alt`, and `position`.

To fix this, you must use the **WordPress REST API** (`wp/v2/media`) directly. This endpoint gives you full access to the image metadata.

**The Solution: Two-Step Workflow**

Instead of letting WooCommerce “fetch” the image or handling it purely inside the product payload, you should:

1. **Upload the image** to the WordPress Media Library first (using `/wp/v2/media`).

2. **Pass the Image ID** to the WooCommerce Product API.

**Python Implementation**

You will likely need to generate a **WordPress Application Password** for this to work securely alongside your WooCommerce keys, as the standard WooCommerce Consumer Key/Secret often does not authenticate against core WordPress endpoints (`/wp/v2/`).

**1. Setup Authentication**

- Go to **Users \> Profile** in your WordPress Admin.

- Scroll to **Application Passwords** , create a new one (e.g., “Python Script”), and save it.

**2. Python Script**

This script below demonstrates uploading the image with full metadata first, then attaching it to a new product.

[Py File](https://jumpshare.com/s/8HhP7Jm3rDiRoECdDkWg)

**Why your current approach failed**

When you send an image object inside the **[WooCommerce](https://woocommerce.com/)** `products` endpoint:

- **`name`** : WooCommerce maps this to the image title _sometimes_, but often it’s just used for the product gallery label.

- **`src`** : WooCommerce downloads this and creates a _new_ attachment, discarding your local metadata (except alt text, which it manually copies).

- **`caption`/`description`** : These keys are simply ignored by the WooCommerce controller because they aren’t in the allowed schema for the product image object.

**Alternative (If you must keep your current flow):**  
If you prefer to let WooCommerce download the images from a URL, you will have to use a **“Update After”** approach:

1. Create the product as you do now.

2. Parse the response to find the new `images` list.

3. Extract the `id` (Media ID) for each image.

4. Loop through those IDs and send a `POST` request to `/wp-json/wp/v2/media/<id>` to update the `caption` and `description`.

**Why these approaches works**

- **Targeted Metadata:** The standard WooCommerce `products` endpoint only officially supports the `alt` text for images. Fields like `caption` and `description` are part of the WordPress core media schema, which is what the `/wp/v2/media` endpoint manages.

- **Single Source of Truth:** By uploading to the WordPress Media Library first, you create a dedicated media object with all metadata intact. When you later pass its `id` to the WooCommerce product, it simply links to that existing object instead of creating a “fresh” copy from a URL

Hope this helps.

---

<div class="post-metadata">

**Author:** ![AnonPs](https://onehack.st/user_avatar/onehack.st/anonps/32/166867_2.png) [@AnonPs](https://onehack.st/u/AnonPs)\
**Post date:** [April 18, 2026, 7:42pm UTC](https://onehack.st/t/how-to-push-image-caption-title-and-description-via-woocommerce-api-using-python/321217/3 "2026-04-18T19:42:57Z")

</div>

thanks man . fixed 👍 👍

---

<div class="post-metadata">

**Author:** ![Hannah](https://onehack.st/user_avatar/onehack.st/hannah/32/174875_2.png) [@Hannah](https://onehack.st/u/Hannah)\
**Post date:** [April 28, 2026, 5:11pm UTC](https://onehack.st/t/how-to-push-image-caption-title-and-description-via-woocommerce-api-using-python/321217/4 "2026-04-28T17:11:09Z")

</div>

Sounds like you’ve been hammering at this a while.

Alt text landing, products creating clean, but caption + title + description just refusing to stick no matter how you shape the JSON. That **“tried multiple approaches”** line is the giveaway you’re past the easy answers and starting to wonder if WC even supports this.

It does. But two of those four fields aren’t where you’ve been looking.

**Your concerns, mirrored:**

- ❌ **Caption** → wrong endpoint, wrong key
- 🟡 **Title** → already in your reach, WC just calls it something else
- ❌ **Description** → same boat as caption
- ✅ **Different endpoint?** → yes, exactly. There are two systems here, not one

──── 🎯 ────

# The 90-second answer

🪧 Your image’s four fields don’t all live in the same system.

> **Alt + title** belong to **WooCommerce** (/wc/v3/\*)  
> **Caption + description** belong to **WordPress core** (/wp/v2/\*)
> 
> Different system → different endpoint → **different API key.**

🔑 Your WC consumer key works **only** on `/wc/v3/*`. For `/wp/v2/*` you need a separate **Application Password** — a free WordPress core feature, lives in `Users → Profile`.

⚡ **Quick win:** the WC field called `name` writes the title. You’ve been searching docs for “title” — WC named that field `name`. Add it next to your existing `alt` and the title problem dies in zero extra requests.

🔁 **The real fix for caption + description:** one extra POST to `/wp/v2/media/{id}` with your Application Password, after the WC product create returns the image ID.

──── 🧩 ────

# The four-field map

| Field | Lives in | Endpoint | What you send |
| --- | --- | --- | --- |
| **Alt** | WordPress postmeta | /wc/v3/products | `image.alt` ✅ already working |
| **Title** | `wp_posts.post_title` | /wc/v3/products | `image.name` ⚡ quick win |
| **Caption** | `wp_posts.post_excerpt` | /wp/v2/media/{id} | `caption` 🔁 second request |
| **Description** | `wp_posts.post_content` | /wp/v2/media/{id} | `description` 🔁 second request |

──── 🧱 ────

**Here’s the part nobody tells you.** WooCommerce’s product image schema has exactly **three writers** in its source code — `alt`, `name`, `src`. That’s it. I dug it up so you don’t have to: lines ~2200-2240 of [class-wc-rest-products-v2-controller.php](https://github.com/woocommerce/woocommerce-rest-api/blob/master/src/Controllers/Version2/class-wc-rest-products-v2-controller.php). Three `if` blocks, three writers. No caption. No description. They were never accepted on the WC endpoint.

The fields exist, of course — they’re columns in `wp_posts`, same as any post. They just live behind a different door. The [WP REST API Media reference](https://developer.wordpress.org/rest-api/reference/media/) lists `caption` and `description` right there in the accepted args. That’s your door.

──── 🛠 ────

> **🛠️ Step-by-step: do exactly this, in this order**
>
> > 💡 **If you already have `requests` + the `woocommerce` Python lib + WC consumer keys working** — skip to **Step 2**. Step 1 is the one piece you’re missing.
> 
> ### Step 1 — Get an Application Password (60 seconds, one-time)
> 
> In WP admin: `Users → Profile → Application Passwords` near the bottom.
> 
> 1. Type a name like “Python sync”
> 2. Click **Add**
> 3. Copy the 24-character string (looks like `xxxx xxxx xxxx xxxx xxxx xxxx`)
> 4. Save it somewhere — WP only shows it once
> 
> > 💡 **If the “Application Passwords” section is missing entirely** — your host or a security plugin disabled it. Common on Cloudways, some Wordfence configs. Re-enable via a one-line filter in `functions.php` or ask the host. We’ll cover the workaround in the next [details] block if it bites you.
> 
> ### Step 2 — One Python script, two auth handlers
> 
> ```python
> import requests
> from requests.auth import HTTPBasicAuth
> from woocommerce import API
> 
> # Door 1: WooCommerce keys → only works on /wc/v3/*
> wc = API(
> url="https://yourstore.com",
> consumer_key="ck_xxxxxxxxxxxx",
> consumer_secret="cs_xxxxxxxxxxxx",
> version="wc/v3",
> )
> 
> # Door 2: WP Application Password → only works on /wp/v2/*
> wp_user = "your_wp_username"
> wp_app_pass = "xxxx xxxx xxxx xxxx xxxx xxxx" # spaces fine, requests handles them
> wp_auth = HTTPBasicAuth(wp_user, wp_app_pass)
> wp_base = "https://yourstore.com/wp-json/wp/v2"
> 
> # ---- Create the product (writes alt + title in one shot) ----
> product = {
> "name": "Premium Quality T-Shirt",
> "type": "simple",
> "regular_price": "21.99",
> "images": [
> {
> "src": "https://yourcdn.com/tshirt-front.jpg",
> "name": "Premium Quality T-Shirt — Front", # → post_title
> "alt": "Black t-shirt front view", # → _wp_attachment_image_alt
> "position": 0,
> }
> ],
> }
> resp = wc.post("products", product).json()
> attachment_id = resp["images"][0]["id"]
> 
> # ---- Step 3: POST caption + description on the WP media endpoint ----
> r = requests.post(
> f"{wp_base}/media/{attachment_id}",
> json={
> "caption": "Studio shot, front view.", # → post_excerpt
> "description": "100% ring-spun cotton, pre-shrunk.", # → post_content
> },
> auth=wp_auth,
> )
> r.raise_for_status()
> print("✅ All four fields written")
> 
> ```
> 
> > 💡 **You’ll know it worked when** you open the product in WP admin → click the image → all four fields populate. If only alt+title show, your WP request didn’t authenticate. If everything is empty, your WC request didn’t. The split tells you which door is locked.
> 
> ### Step 3 — Decode any error in 5 seconds
> 
> | You see | Means | Fix |
> | --- | --- | --- |
> | `401` on `/wp/v2/media` | App Password not reaching WP | Check the .htaccess fix below |
> | `403` on `/wp/v2/media` | User lacks attachment edit rights | Use Editor or Admin role |
> | `400 invalid image ID` | The `image.id` you sent doesn’t exist | Drop `id`, send only `src` — WC creates it |
> | Caption empty, no error | Wrong endpoint or wrong field name | Confirm `/wp/v2/media/{id}` + JSON key spelled `caption` |

──── 🪤 ────

> **🪤 If you get 401 even with the right Application Password (the silent killer)**
>
> You’ve done everything right, App Password is correct, still 401. **It’s not the password.**
> 
> Most Apache servers strip the `Authorization` header before WP ever sees it. WP gets a request with no auth, returns 401. Your terminal sees 401, you assume password is wrong. It isn’t.
> 
> **One-line fix in your `.htaccess`** , right after `RewriteEngine On`:
> 
> ```apache
> RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
> 
> ```
> 
> > 💡 **Can’t edit `.htaccess`?** (some managed hosts block it) — pass credentials as a query string instead, only over HTTPS:
> > 
> > `https://yourstore.com/wp-json/wp/v2/media/123?username=user&password=pass`
> > 
> > The [WC dev docs flag this same workaround](https://developer.woocommerce.com/docs/apis/rest-api/) for their endpoint — same trick applies to WP’s.

> **🥷 Skip the description request with IPTC pre-bake (bonus)**
>
> When WC fetches your image from the `src` URL, it runs `wp_read_image_metadata()` on the file before saving. Bake IPTC tags into the JPEG ahead of time, WC reads them and writes them straight into the attachment.
> 
> ```bash
> pip install iptcinfo3
> 
> ```
> 
> ```python
> from iptcinfo3 import IPTCInfo
> 
> info = IPTCInfo("tshirt-front.jpg", force=True)
> info["object name"] = "Premium Quality T-Shirt — Front" # → post_title
> info["caption/abstract"] = "100% ring-spun cotton, pre-shrunk." # → post_content
> info.save()
> 
> ```
> 
> > ⚠ **Naming clash that catches everyone:** IPTC “caption” maps to WP **Description** (`post_content`), not WP **Caption** (`post_excerpt`). Different fields with the same name. WP “Caption” has no native IPTC equivalent — it always needs the second request, no matter what you bake. Carl Seibert’s [definitive explainer](https://www.carlseibert.com/automatic-captions-in-wordpress/) is worth bookmarking.

> **🚫 Three dead ends I already checked (don't waste time)**
>
> **XML-RPC** (`wp.uploadFile`, `python-wordpress-xmlrpc`) — looks tempting, Python stdlib has `xmlrpc.client` ready to go. [WordPress Trac #21085](https://core.trac.wordpress.org/ticket/21085) explicitly states it cannot set caption, description, or alt. Documented limitation since 2012, never fixed. **Skip.**
> 
> **`meta_data` arrays on the image object** — WC accepts `meta_data` on the product itself, but image sub-objects don’t pipe it through to the underlying attachment. I checked the controller. No glue code. **Skip.**
> 
> **Direct `wp_posts` SQL writes** — works (the columns are public), but bypasses caching, search indexing, and any plugin hooks listening for attachment updates. Fine as last resort, ugly as default. **Use only if APIs are blocked entirely.**

──── 👋 ────

# I run this in production

I run this exact two-step on a small hardware shop’s WC site every Wednesday — their photographer drops 30-50 product shots into a shared folder, my script picks them up, creates products, fixes metadata.

Got bit once in October: ran clean for three weeks, then started silently dropping captions on every product. Title and alt kept working fine.

Took me an hour to realize the host’s web firewall had quietly started stripping `Authorization` headers on `/wp/v2/*` only — leaving `/wc/v3/*` alone. Half my pipeline kept authenticating, the other half went silently anonymous.

The `.htaccess` rule fixed it. Now I run a tiny smoke test against `/wp/v2/users/me` at the start of every job — if that 401s, the script bails before touching any product.

──── 🪃 ────

# Back to you

About those **“multiple approaches”** you tried — were any of them PATCHing `/wp/v2/media` directly using your **WC consumer keys**? If yes, that’s almost certainly where the trail went cold. Not on you — the docs barely flag this split. The 401 you’d have gotten looks identical to a body-format error, which is exactly the rabbit hole anyone would dive into first.

> ❓ **One question that’ll save us a back-and-forth:**  
> **What hosting are you on?**

If it’s a managed host (Kinsta, WP Engine, Cloudways, SiteGround), Application Passwords behavior varies wildly per host — some disable it by default, some require API allowlists, some have their own auth wrappers.

Drop the host name and I (or anyone lurking who’s on the same one) can flag the specific gotcha before it bites you.

---

<div class="post-metadata">

**Author:** ![AnonPs](https://onehack.st/user_avatar/onehack.st/anonps/32/166867_2.png) [@AnonPs](https://onehack.st/u/AnonPs)\
**Post date:** [April 29, 2026, 4:57am UTC](https://onehack.st/t/how-to-push-image-caption-title-and-description-via-woocommerce-api-using-python/321217/5 "2026-04-29T04:57:06Z")

</div>

thank you so much @Hannah . its woking fine i used exact same approach but somethimes same images uploaed twice 1 batch have metadata & 2nd dont have . so bit confuse to handle this .

one more thing if you would help me like i have 7000 images before this implement so is there any way to update their meta data ?? with product title or with placeholder like " {product title} COD " .
