Catalog Product — Update
Updates a catalog product (any of the 7 types). This is a partial patch — send only the fields you want to change. Omitted fields keep their current value.
Endpoint
| Endpoint | Method |
|---|---|
/api/admin/catalog/products/{id} | PUT |
Path parameters
| Field | Type | Required | Notes |
|---|---|---|---|
id | integer | yes | Product ID. |
Query parameters
| Param | Default | Notes |
|---|---|---|
locale | store default | The locale that translatable fields (name, description, short_description, meta fields, …) are written to. One locale per request — repeat the call with a different ?locale= to edit another translation. |
channel | store default | The channel used for channel-scoped attribute values. |
Editing attributes by code
Every attribute on the product's family is editable by its code, sent at the top level of the JSON body (snake_case). This includes the common fields and any family-specific ones:
| Field code | Notes |
|---|---|
name, url_key, short_description, description | Translatable — written to the requested ?locale=. |
meta_title, meta_keywords, meta_description | SEO fields (translatable). |
price, weight | Decimal as string. |
status, new, featured, visible_individually, guest_checkout | Boolean flags as 0 / 1. |
tax_category_id | Existing tax category id. |
color, size, brand, product_number, … | Any family-specific attribute, by its code. |
Translatable fields write to the requested locale only — pass ?locale=fr&channel=default to target the French translation; the other locales are untouched.
Replace-on-send relations
These fields replace the product's current set when present, and are preserved when omitted:
| Field | Notes |
|---|---|
categories | int[] — the product's category assignment. |
channels | int[] — the product's channel assignment. |
up_sells, cross_sells, related_products | int[] — product relation lists. |
Type-structure fields
The type-specific structure keys replace that structure when sent — send the full set. See the examples: dropdown for the verified payload of each:
| Type | Field | Notes |
|---|---|---|
| downloadable | downloadable_links, downloadable_samples | Keyed map of link/sample rows. |
| grouped | links | Keyed map of { associated_product_id, qty, sort_order }. |
| bundle | bundle_options | Keyed map of option groups (type ∈ radio / checkbox / select / multiselect) each with a products map. |
| configurable | variants | Keyed by variant product id (from the create response or detail variants[].id). Replace-semantics — send every variant to keep. |
| booking | booking | Object with type (default / appointment / event / rental / table) plus sub-type fields (slots / tickets / pricing). |
New-row key prefixes
Inside a nested structure a new row is keyed by a prefixed marker; a bare or numeric key is treated as an existing row id (and fails if it doesn't exist):
| Structure | New-row key prefix |
|---|---|
bundle_options option group | option_* |
bundle_options → products | product_* |
links (grouped) | link_* |
downloadable_links | link_* |
downloadable_samples | sample_* |
customizable_options | option_* (its prices → price_*) |
booking → tickets (event) | ticket_* |
variants (configurable) | the existing variant product id (numeric) |
Customizable options (including file type)
customizable_options (custom options) are a Bagisto simple & virtual-only feature — the edit form shows the Custom Options accordion only for those two types, and the API ignores the field on any other type. Configurable, bundle, grouped, and downloadable products do not support them.
Each option has a type: text, textarea, select, multiselect, checkbox, radio, date, datetime, time, or file. A file option also carries a supported_file_extensions value — a comma-separated list the admin defines (e.g. pdf or jpg,png,pdf). There is no fixed API list of formats; the allowed set is whatever that option specifies, and the storefront upload rejects any other extension. Storefront clients read these options (and their supported extensions) from the product's customizableOptions and upload a matching file via the shop customizable-file upload endpoint.
Sub-resources are not updated here
images, videos, inventories, and customer_group_prices are not handled by this endpoint — they have dedicated endpoints. If sent, they are ignored and noted in the warnings array on the response:
- Images →
POST /api/admin/catalog/products/{id}/images - Inventories →
PUT /api/admin/catalog/products/{productId}/inventories - Customer-group prices →
POST …/customer-group-prices
Response
200 OK returning the full product detail payload — same shape as GET /api/admin/catalog/products/{id}.
warnings is an array of human-readable strings; it is empty when nothing was dropped, and non-empty (naming each dropped sub-resource field and the endpoint it should be sent to instead) when sub-resource fields were stripped.
Errors
| HTTP | Cause |
|---|---|
401 Unauthorized | Missing or invalid admin Bearer token. |
403 Forbidden | Admin role lacks catalog.products.edit. |
404 Not Found | Product not found. |
422 Unprocessable Entity | Validation failure (duplicate SKU / url_key, invalid boolean, special_price ≥ price, invalid date range). |

