apicreateproduct JSON request body create or update single or bulk optional images ZIP copy/paste docs
POST /api/createproduct is the supported endpoint for this payload. Send JSON as application/json, or use multipart/form-data with payload + images_zip to import product images — see Product images (optional ZIP upload).
Create-or-update behavior
The endpoint creates a new product by default. If id is provided, that product is updated instead.
If no id is provided but the submitted sku already exists, the existing SKU product is updated instead of returning a duplicate-SKU error.
On update, omitted product fields are kept as-is; attributes and variations are resynced only when those fields are included in the request.
Product images (optional ZIP upload)
In addition to the JSON body, you can attach a ZIP archive of image files in the same request. Products are created or updated from JSON first; then images from the ZIP are imported and linked to products or variations by SKU (case-insensitive).
When to use
- JSON-only requests (
Content-Type: application/json) work exactly as before — no ZIP required. - To import images together with products, send
multipart/form-datawith a JSON string inpayloadplus the ZIP file inimages_zip. - You can also send a ZIP when updating existing products: as long as the SKU in the filename matches a product or variation in the database, images will be assigned even if the JSON update fails for unrelated reasons.
How to name image files inside the ZIP
Each image filename must encode the target SKU. Supported formats: JPG, JPEG, PNG, GIF, WebP.
Use the product or variation SKU as the filename (before the extension).
API-TEST-001.jpg
VAR-PARENT-001-RED.png
When a product needs a gallery (more than one image), add a double underscore and a sequence number before the extension. Numbers determine gallery order (__1 first, then __2, etc.).
API-TEST-001__1.jpg
API-TEST-001__2.jpg
API-TEST-001__3.png
Do not mix numbered and unnumbered names for the same SKU in one ZIP — use either SKU.ext or SKU__N.ext consistently.
- Files may be at the ZIP root or inside folders; only the basename (e.g.
images/API-TEST-001__1.jpg→API-TEST-001__1.jpg) is used for SKU matching. - Ignore macOS metadata folders (
__MACOSX) — they are skipped automatically. - SKUs in filenames must match the
skufield from your JSON payload (product SKU or variationvariations[].sku).
Example ZIP contents
For a bulk import with products CHAIR-001, CHAIR-001-RED (variation), and CHAIR-001-BLUE (variation):
product-images.zip
├── CHAIR-001__1.jpg → main product gallery (image 1)
├── CHAIR-001__2.jpg → main product gallery (image 2)
├── CHAIR-001-RED.png → variation image
└── CHAIR-001-BLUE.png → variation image
How to send images with the API
Use POST /api/createproduct with Content-Type: multipart/form-data and two parts:
payload— the same JSON you would send as the raw body (must includekey)images_zip— the ZIP file (field name must be exactlyimages_zip)
curl example
curl -X POST "https://YOUR-DOMAIN/api/createproduct" \
-F 'payload={
"key": "YOUR_API_KEY",
"products": [
{
"name": "Office chair",
"category": 1,
"sku": "CHAIR-001",
"attributes": [
{
"name": "Color",
"create_if_missing": 1,
"type": 1,
"multiselect": 0,
"values": [
{ "value": "Red", "add": "#ff0000" },
{ "value": "Blue", "add": "#0000ff" }
],
"selected": ["Red", "Blue"]
}
],
"variation_features_by_name": ["Color"],
"variations": [
{ "sku": "CHAIR-001-RED", "name": "Red", "combo": { "Color": "Red" } },
{ "sku": "CHAIR-001-BLUE", "name": "Blue", "combo": { "Color": "Blue" } }
]
}
]
}' \
-F "images_zip=@product-images.zip"
JavaScript (FormData) example
const payload = {
key: "YOUR_API_KEY",
products: [
{ name: "Office chair", category: 1, sku: "CHAIR-001" }
]
};
const form = new FormData();
form.append("payload", JSON.stringify(payload));
form.append("images_zip", zipFileInput.files[0]);
const res = await fetch("/api/createproduct", {
method: "POST",
body: form
});
const data = await res.json();
Do not set Content-Type manually when using FormData — the browser adds the correct multipart boundary.
How images are assigned after import
- Variation SKU (checked first): the first image is stored on
product_variations.image. - Product SKU: all images become the product gallery —
main_image(first),image_gallery(comma-separated ids), and theproduct_imagepivot table. - If a SKU in the ZIP is not found, that group is reported in
images.errorswith"sku not found".
Image import in the response
When a ZIP was uploaded, the response includes an images summary (inside data for bulk/single success responses, or at the top level when data is a plain error string):
{
"status": "ok",
"data": {
"created": [ /* ... */ ],
"errors": [],
"count_created": 1,
"count_errors": 0,
"images": {
"assigned": [
{
"sku": "CHAIR-001",
"type": "product",
"id": 123,
"image_ids": [45, 46],
"count": 2
},
{
"sku": "CHAIR-001-RED",
"type": "variation",
"id": 88,
"product_id": 123,
"image_ids": [47],
"count": 1
}
],
"errors": [],
"skipped": 0,
"count_assigned": 2,
"count_errors": 0
}
}
}
Recommended workflow: create products in JSON (bulk products[] with all SKUs and variations), then attach the ZIP so every filename SKU already exists when images are processed.
Top-level JSON fields
keyYour API key.
products
If provided, the endpoint creates or updates multiple products in one request.
Each item in products[] uses the same fields as the single-product payload (everything documented below),
just without the top-level key.
- These “child” products should be created with
"visibility": 2. - In bulk mode, put those “child” products first in the
productsarray, and put “parent” products (that reference them) last.
Reason: parent products may reference children by id/sku during creation; ordering ensures referenced products exist already.
Backward compatibility
If products is missing, the request is treated as a single product payload (old behavior),
where fields like name and category are top-level.
Example bulk request (children first, parents last):
{
"key": "YOUR_API_KEY",
"products": [
{
"name": "Child product (used in Included/Related)",
"category": 1,
"visibility": 2,
"sku": "CHILD-001"
},
{
"name": "Parent product",
"category": 2,
"attributes": [
{
"name": "Included products",
"type": 4,
"create_if_missing": 1,
"selected": ["CHILD-001"]
}
]
}
]
}
Bulk response:
"status": "ok"when all items were created or updated"status": "partial"when some items succeeded and some failed"status": "error"when none of the items succeeded
{
"status": "partial",
"data": {
"created": [ /* Created or updated Product objects */ ],
"errors": [
{ "index": 1, "error": "required not set" }
],
"count_created": 1,
"count_errors": 1
}
}
Example bulk request (1st product becomes a Related product on the 2nd):
- The “child” product is created with
"visibility": 2. - The “parent” product references it via the Related products attribute (
type: 5) using the child SKU inselected. - Order matters: create the referenced product first.
{
"key": "YOUR_API_KEY",
"products": [
{
"name": "Child product (will be referenced as Related)",
"category": 1,
"visibility": 2,
"sku": "CHILD-REL-001"
},
{
"name": "Parent product (has Related products attribute)",
"category": 1,
"sku": "PARENT-REL-001",
"attributes": [
{
"name": "Related products",
"type": 5,
"create_if_missing": 1,
"selected": ["CHILD-REL-001"]
}
]
}
]
}
idExisting product ID. If provided, the endpoint updates that product. If the ID is invalid, the request returns id invalid and does not create a duplicate product.
nameProduct name (base/default). In bulk mode this field belongs to each products[] item.
categoryCategory ID or name. In bulk mode this field belongs to each products[] item.
- If int and category id does not exist → system falls back to the first category in DB.
- If string → case-insensitive search by name; if not found, a new category is created and used.
skuIf missing/empty on create, a unique SKU is generated. If provided and it already belongs to an existing product, that product is updated. If updating by id, the SKU must not belong to a different product.
priceIf object, it will be JSON-encoded and saved. Typical structure:
{
"Supplier A": {
"1": [100, 110, 0, 5],
"10": [90, 95, 0, 0]
}
}
Meaning:
"1","10"= quantity breaks / tiers[cost, sale_price, delivery, discount]- index 0 = cost
- index 1 = sale_price
- index 2 = delivery
- index 3 = discount (percent, default
0when omitted — backwards compatible with 3-element tiers)
- If you provide
"variations": [...], the product is treated as variable. - Base product
priceis forced to JSON[]when missing/empty/null/"0"to avoid breaking JSON consumers.
urlSupplier product URLs list. If array, it will be JSON-encoded and saved.
If missing/empty/"null" it defaults to:
[""]
Example:
["https://example.com/product/api-test-001"]
namesLocalized names by language code:
{ "en": "Name", "lt": "Pavadinimas" }
descriptionMain (default) short description saved into products.description.
*Required in the sense that the system expects a main description field; if you don't provide it,
the API will attempt to fill it from descriptions using the rules below.
descriptionlMain (default) long description saved into products.descriptionl.
*Required in the sense that the system expects a main long description field; if you don't provide it,
the API will attempt to fill it from descriptionls using the rules below.
descriptionsLocalized short descriptions by language code (used for additional languages / addlangs).
- If
descriptionis provided → it is used as the main short description. - Else if
descriptions[mainlang]exists → it is copied intodescription. - Else if any
descriptionsvalue exists → the first available value is copied intodescription. If its language key is notmainlang, it is also kept indescriptions.
If you only have one description available, you can send it in any language — it will still populate description.
descriptionlsLocalized long descriptions (often HTML) by language code (used for additional languages / addlangs).
- If
descriptionlis provided → it is used as the main long description. - Else if
descriptionls[mainlang]exists → it is copied intodescriptionl. - Else if any
descriptionlsvalue exists → the first available value is copied intodescriptionl. If its language key is notmainlang, it is also kept indescriptionls.
withpvmDefaults to 0.
NEW: Type 4 “Selectable products” (Included products only)
This setting applies ONLY to type 4 attributes (Included products).
Meaning in proposal UI (Step 2)
When enabled, included products added via this attribute are selectable — the user can select or unselect these add-on products in the proposal frontend.
When disabled, included products are included without the ability to exclude them in the proposal frontend.
How to set it
You can set it in two ways:
attributes[].selectable_products(type 4 only): sets the attribute’s flag when creating/updating the attribute (true|false,1|0,"on")attributes[].selectable(type 4 only): alias forselectable_products
Note: type4_selectable_products is not a product field. If a client sends it at top-level, it is ignored.
For backward compatibility you may also send attributes[].type4_selectable_products as an alias.
The standalone /api/createattribute endpoint accepts the same aliases when creating a type 4 attribute.
When this flag is enabled and the attribute adds included products to a proposal, those generated child entries get selectable = 1 and are selected by default.
NEW: Step-2 selection mode for Included/Related products (type 4/5)
This setting affects ONLY proposal “Step 2” selection behavior (when adding product/variation to proposal):
"single": user can select at most 1 item (or none)"multiple": user can select many (current behavior)
This setting does NOT change how many included/related items can be stored on the product itself.
You can set it in two ways:
1) type45_select_modes_by_name
object, optional — Map FeatureName → "single" | "multiple"
{
"Included products": "single",
"Related products": "multiple"
}
2) attributes[].select_mode
string, optional (type 4/5 only) — Per-attribute override (wins over type45_select_modes_by_name)
Allowed values: "single" or "multiple"
NEW: Chaining Included/Related product options (Step‑2 “inline” options)
What “chaining” means
If a product has a type 4 or 5 feature (Included / Related products) and the user selects a “child” product in proposal Step‑2,
the UI can show that child product’s own options inline (child attributes like type 0/1/2/3, and even its own type 4/5).
Those inline choices are saved onto the created proposal lines.
- Create/update the child product normally, including its own
attributesselections. - On the parent product, create a type
4/5feature and setselectedto include the child SKU(s) or ids. - Optional: use
type45_select_modes_by_nameorattributes[].select_modeto force single‑select for the child list. - Optional: use
type45_required_features_by_nameto make an Included/Related feature required in Step‑2. - Optional: use
step2_visibility_rules_by_nameto show/hide Step‑2 features based on other selections.
Defaults: the inline option controls are prefilled from the child product’s saved selections (the same values you send in the child’s attributes[].selected).
Example: Parent includes a child, child has its own options
{
"key": "YOUR_API_KEY",
"products": [
{
"name": "Child: Backpack (has its own options)",
"category": 1,
"visibility": 2,
"sku": "CHILD-BACKPACK-001",
"attributes": [
{
"name": "Length (cm)",
"create_if_missing": 1,
"type": 3,
"type3_calc_target": 1,
"selected": "200"
},
{
"name": "Color",
"create_if_missing": 1,
"type": 1,
"multiselect": 0,
"values": [
{ "value": "Black", "add": "#111827" },
{ "value": "White", "add": "#ffffff" }
],
"selected": ["Black"]
},
{
"name": "Needs packaging?",
"create_if_missing": 1,
"type": 0,
"multiselect": 0,
"values": ["Yes", "No"],
"selected": ["No"]
}
]
},
{
"name": "Parent: Bundle product",
"category": 1,
"sku": "PARENT-BUNDLE-001",
"type45_select_modes_by_name": {
"Included products": "single"
},
"type45_required_features_by_name": {
"Included products": true
},
"attributes": [
{
"name": "Included products",
"create_if_missing": 1,
"type": 4,
"selectable_products": true,
"selected": ["CHILD-BACKPACK-001"]
}
]
}
]
}
Attributes / features (attributes)
attributesDefines product attributes (features), selected values, and optionally creates missing features/options.
Each attributes[] item can contain
id(int, optional): existingproduct_features.idname(string, optional): feature name (case-insensitive match)use_existing(0|1, optional): 1 = do not create; if not found → errorcreate_if_missing(0|1, optional): 1 = create feature if missingtype(int, required when creating): see Feature typesmultiselect(0|1, optional; only for type 0/1 when creating)use_as_variation_default(0|1, optional; only for type 0/1 when creating)visibility(int, optional; when creating)default(string|null, optional; when creating)type3_calc_target(1|2 or structured scalar — optional): calculation mode for type 3. Stored per variation/add column semantics same as admin UI; numeric values are normalized like type 3selected.required(optional; type 4 and 5 only) — see Type 4/5 required (Step 2). Overridestype45_required_features_by_namefor that feature when both are present.
values) type 0/1 only
values(array, optional)- Each element can be a string:
"Red" - Or an object:
{ "value": "Red", "add": "#ff0000", "price": 10.5, "cost": 2.0 }
selected)
selected is optional; format depends on feature type.
Alias: you may send value instead of selected on the same attribute object — both use the same parsing rules below.
Feature types (type)
0: Selectable value (options)1: Selectable color (options, usuallyadd= hex color)2: Custom text3: Amount calculation / numeric-like custom value4: Included products5: Related products
selected formats by feature type
Type 0 / 1 (selectable options)
Single label or id:
"Red"
12
Multiple:
["Red", "Blue"]
[12, 15]
Type 2 (custom text)
Preferred: a plain string or number (converted to string).
"Cotton"
Structured payloads are also accepted and normalized to one string:
- Single-element list unwraps:
["Cotton"]→"Cotton" - Objects/maps: first matching key (in this order) —
value,amount,selected,val,number,text(recursive). - Example:
{ "value": "Cotton blend" } - Other objects/arrays are stored as JSON text.
Type 3 (calc / numeric-like)
Preferred: string or number (stored as string).
"15"
15
Structured payloads use the same normalization as type 2:
{ "amount": 15 }or{ "value": "15" }[15]→"15"type3_calc_targeton the attribute row accepts1/2or the same structured forms when needed.- Unrecognized associative shapes fall back to
json_encode(...)as the stored value.
Type 4 / 5 (included / related products)
selected can be any mix of:
- product id:
123 - product sku:
"SKU-123" - product ref:
"product_123" - variation sku:
"SKU-123-RED"(resolved to the exact product variation) - variation ref:
"variation_456" - embedded product object (created if SKU does not exist):
{ "name": "New Related", "category": "Spare parts", "sku": "REL-NEW-001" }
You can also nest arrays; they will be flattened. If a plain string SKU does not match a product SKU, the API tries to match it as a variation SKU.
Variations
Variation configuration
variation_features_by_nameFeature names that form the variation combination, e.g.:
["Color", "Size"]
preset_features_by_nameFeature names that are “preset” (not part of the combo).
Variations list
variationsEach item can contain:
sku(string, required): variation SKUname(string, optional)combo(object, required): map feature name → option label. It does not need to include every variation feature for every variation; one variation may use all 3 attributes, another may use only 2, and another may use only 1.prices(object, optional): same format as top-levelpricetags(string, optional): comma-separated tag idshidden_features_by_name(array of strings, optional)
If variations are provided, the system automatically marks the product as variable and also auto-selects all variation-axis values (type 0/1) on the base product, so attributes won’t appear empty in the edit UI.
NEW: Per-variation bulk product blocks (Included mandatory / Included selectable / Related)
Each variation can define up to three product lists used in proposal Step 2 when that variation is added to an offer. These are configured per variation in the admin bulk-variation editor; the API uses the same fields.
Order in Step 2: mandatory included → selectable included → related → then parent type 4/5 attributes.
Selectable in proposal is determined by which block a product is in — not by the mode number:
included_mandatory_products and related_products are not user-toggleable;
included_selectable_products behaves like selectable add-ons (selectable = 1).
Fields on each variations[] item
included_mandatory_productsProducts always included with this variation (mandatory). Same ref formats as type 4 selected (see below).
included_mandatory_products_modeStep 2 selection rules for this block:
0— multiple, not required1— multiple, required (at least one checked)2— single, not required3— single, required
included_mandatory_products_titleCustom Step 2 card title. Empty = system default (“Included mandatory products”).
included_selectable_productsIncluded products the user may toggle on/off in proposal Step 2.
included_selectable_products_modeSame mode values as mandatory block.
included_selectable_products_titleCustom Step 2 card title. Empty = system default (“Included selectable products”).
related_productsRelated / add-on products for this variation (shown in the related lane). Same ref formats as type 5 selected.
related_products_modeSame mode values as above.
related_products_titleCustom Step 2 card title. Empty = system default (“Related products”).
Product ref formats (all three blocks)
Each list accepts the same mix as type 4/5 selected:
- product id:
123 - product sku:
"INC-A-001" - product ref:
"product_123" - variation sku:
"SKU-RED-S" - variation ref:
"variation_456" - embedded product object (created if SKU missing):
{ "name": "Addon", "category": 1, "sku": "ADD-001", "visibility": 2 }
Arrays may be nested; they are flattened. Plain string SKUs that are not product SKUs are tried as variation SKUs. In bulk products[] mode, create referenced child products before the parent (same as type 4/5).
Legacy fields (still supported)
If you send the old single included list, it is migrated automatically:
included_products— same ref formats as aboveincluded_products_mode— if0or2, products go toincluded_selectable_products; if1or3, toincluded_mandatory_productsincluded_products_title— copied to the target split block
Legacy is used only when neither included_mandatory_products nor included_selectable_products is set.
Example: variable product with all three blocks
{
"key": "YOUR_API_KEY",
"products": [
{
"name": "Mandatory addon",
"category": 1,
"visibility": 2,
"sku": "VAR-MANDATORY-001"
},
{
"name": "Selectable addon",
"category": 1,
"visibility": 2,
"sku": "VAR-SELECT-001"
},
{
"name": "Related addon",
"category": 1,
"visibility": 2,
"sku": "VAR-RELATED-001"
},
{
"name": "Variable parent",
"category": 1,
"sku": "VAR-PARENT-001",
"attributes": [
{
"name": "Color",
"create_if_missing": 1,
"type": 1,
"multiselect": 0,
"values": [
{ "value": "Red", "add": "#ff0000" },
{ "value": "Blue", "add": "#0000ff" }
],
"selected": ["Red", "Blue"]
}
],
"variation_features_by_name": ["Color"],
"variations": [
{
"sku": "VAR-PARENT-001-RED",
"name": "Red",
"combo": { "Color": "Red" },
"prices": { "Supplier": { "1": [100, 110, 0, 5] } },
"included_mandatory_products": ["VAR-MANDATORY-001"],
"included_mandatory_products_mode": 3,
"included_mandatory_products_title": "Required parts",
"included_selectable_products": ["VAR-SELECT-001"],
"included_selectable_products_mode": 0,
"included_selectable_products_title": "Optional extras",
"related_products": ["VAR-RELATED-001"],
"related_products_mode": 2,
"related_products_title": "You may also need"
},
{
"sku": "VAR-PARENT-001-BLUE",
"name": "Blue",
"combo": { "Color": "Blue" },
"prices": { "Supplier": { "1": [105, 115, 0, 0] } },
"included_selectable_products": ["VAR-SELECT-001"],
"related_products": ["VAR-RELATED-001"]
}
]
}
]
}
Omit any block you do not need. Modes and titles are optional; defaults apply when omitted.
Step-2 visibility rules (optional)
step2_visibility_rules_by_nameMap of TargetFeatureName → list of prerequisites:
{
"Size": [
{ "feature": "Color", "value": "Red" }
]
}
Minimal valid JSON
{
"key": "YOUR_API_KEY",
"name": "My product",
"category": 1
}
Full example JSON (attributes + variations + included + related + new selection options)
{
"key": "YOUR_API_KEY",
"name": "API Test Product (Attributes + Variations + Included + Related)",
"category": "Spare parts",
"sku": "API-TEST-001",
"type45_select_modes_by_name": {
"Included products": "single",
"Related products": "multiple"
},
"type45_required_features_by_name": {
"Included products": true,
"Related products": false
},
"price": {
"Supplier A": {
"1": [100, 110, 0, 5],
"10": [90, 95, 0, 0]
}
},
"url": ["https://example.com/product/api-test-001"],
"names": {
"en": "API Test Product",
"lt": "API Test Produktas"
},
"descriptions": {
"en": "Created to test apicreateproduct payload: attributes, variations, included + related products.",
"lt": "Sukurta testuoti apicreateproduct payload: atributai, variacijos, įtraukti + susiję produktai."
},
"descriptionls": {
"en": "<ul><li>Line 1</li><li>Line 2</li></ul>"
},
"attributes": [
{
"name": "Color",
"create_if_missing": 1,
"type": 1,
"multiselect": 0,
"values": [
{ "value": "Red", "add": "#ff0000" },
{ "value": "Blue", "add": "#0000ff" }
],
"selected": ["Red"]
},
{
"name": "Size",
"create_if_missing": 1,
"type": 0,
"multiselect": 0,
"values": ["S", "M", "L"],
"selected": ["M"]
},
{
"name": "Material",
"create_if_missing": 1,
"type": 2,
"selected": "Cotton"
},
{
"name": "Setup fee",
"create_if_missing": 1,
"type": 3,
"type3_calc_target": 1,
"selected": "15"
},
{
"name": "Included products",
"create_if_missing": 1,
"type": 4,
"select_mode": "single",
"selectable_products": true,
"selected": [
{ "name": "Included Item A", "category": 1, "sku": "INC-A-001" },
{ "name": "Included Item B", "category": 1, "sku": "INC-B-001" }
]
},
{
"name": "Related products",
"create_if_missing": 1,
"type": 5,
"select_mode": "multiple",
"selected": [
{ "name": "Related Item A", "category": 1, "sku": "REL-A-001" },
{ "name": "Related Item B", "category": 1, "sku": "REL-B-001" }
]
}
],
"variation_features_by_name": ["Color", "Size"],
"preset_features_by_name": ["Material"],
"variations": [
{
"sku": "API-TEST-001-RED-S",
"name": "Red / S",
"combo": { "Color": "Red", "Size": "S" },
"prices": {
"Supplier A": {
"1": [110, 120, 0, 0],
"10": [95, 105, 0, 0]
}
},
"included_mandatory_products": ["INC-A-001"],
"included_mandatory_products_mode": 1,
"included_mandatory_products_title": "Required included",
"included_selectable_products": ["INC-B-001"],
"included_selectable_products_mode": 0,
"related_products": ["REL-A-001"],
"related_products_mode": 0,
"hidden_features_by_name": ["Material"]
},
{
"sku": "API-TEST-001-BLUE-M",
"name": "Blue / M",
"combo": { "Color": "Blue", "Size": "M" },
"prices": {
"Supplier A": {
"1": [120, 130, 0, 0],
"10": [105, 115, 0, 0]
}
},
"included_selectable_products": ["INC-B-001"],
"related_products": ["REL-A-001", "REL-B-001"],
"hidden_features_by_name": []
}
],
"step2_visibility_rules_by_name": {
"Size": [
{ "feature": "Color", "value": "Red" }
]
}
}
Type 4/5 required features (proposal Step 2)
For attributes with type 4 (Included products) or 5 (Related products), you can mark the block as required in the proposal Step 2 picker:
the user must satisfy that block’s rules (same idea as ticking “required” for that Included/Related feature in the admin product editor).
Two ways to set it
type45_required_features_by_name
Keys must match the "name" of a type 4 or 5 row in the same request’s attributes[] array (after trimming; spelling and accents must match exactly).
Example:
{
"Included products": true,
"Related products": false,
"Volai su rėmeliais": true
}
- If a key does not match any type 4/5 attribute
namein this request, it is ignored. - Only attributes present in this request’s
attributesare updated from the map.
attributes[].required
Set on the attribute object. If both type45_required_features_by_name and attributes[].required apply to the same feature, attributes[].required wins.
{
"name": "Included products",
"create_if_missing": 1,
"type": 4,
"required": true,
"selected": ["CHILD-SKU-001"]
}
Truthy / falsy values
Required is on when the value is: true, 1, "1", "true", "on", "yes" (strings are compared case-insensitively). Other values are treated as not required.
How it is stored
The API sets products.type45_required_features to a JSON array of product_features.id values (as strings), e.g. ["12","34"], listing which type 4/5 features are required — same as the admin UI.
Do not send a top-level type45_required_features field in the request; it is not read from the client.
Create vs update
- Create: if no required flag is resolved from
attributes/type45_required_features_by_name, nothing required is stored (null). - Update: if this call resolves at least one required flag, the stored list is replaced from that resolution. If this call resolves none, the product keeps its previous
type45_required_features.
Bulk products[]
Each product object may include its own type45_required_features_by_name and type 4/5 rows in attributes; rules apply per product.
Together with type45_select_modes_by_name
Required is separate from single vs multiple selection. Use type45_select_modes_by_name or attributes[].select_mode for single / multiple Step 2 behaviour.
type45_required_features_by_name + type45_select_modes_by_name is under Example: Parent includes a child, child has its own options earlier on this page.