Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/examples/classic-tee-complete.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
title,slug,status,sku,basePrice[default],inventoryTracked[default],promotable[default],weight,Inventory[main]: available,Attribute: Color,Attribute: Size
Classic Tee,classic-tee,enabled,,,,,,,,
,,,TEE-RED-S,19.99,1,1,150,42,Red,Small
,,,TEE-RED-M,19.99,1,1,150,38,Red,Medium
,,,TEE-BLUE-S,19.99,1,1,150,55,Blue,Small
,,,TEE-BLUE-M,19.99,1,1,150,60,Blue,Medium
6 changes: 6 additions & 0 deletions docs/examples/classic-tee-minimum.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
title,sku,basePrice,Attribute: Color,Attribute: Size
Classic Tee,,,,
,TEE-RED-S,19.99,Red,Small
,TEE-RED-M,19.99,Red,Medium
,TEE-BLUE-S,19.99,Blue,Small
,TEE-BLUE-M,19.99,Blue,Medium
124 changes: 124 additions & 0 deletions docs/recipes/field-maps-for-many-product-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Recipe: field maps for a catalog with many product types

How to organise `productFieldMap` and `variantFieldMap` on a store with a dozen product types that each have their own custom fields on top of a shared core. Audience: developers maintaining `config/variant-manager.php` on a multi-product-type Commerce site.

## The gotcha to know first

The plugin resolves `$map[$productTypeHandle] ?? $map['*']`. **There is no merge.** If a product type handle is listed in the map, only that entry is used; `'*'` is a fallback for unlisted types, not a base layer.

This means: as soon as you add one custom field for one product type, the entry for that product type must list **every other column** you want imported too, not just the new field. The wrong instinct is to add `'apparel' => ['careInstructions' => 'careInstructions']` thinking it gets merged onto the `'*'` defaults; in reality it replaces them, and `title`, `sku`, `basePrice`, etc. stop importing for `apparel` products.

## The pattern

Define shared field groups as PHP arrays at the top of the file, then spread them into each per-product-type entry. The file stays DRY, and every entry is explicit about its full field set so the no-merge behaviour cannot surprise you.

```php
<?php

$sharedProductFields = [
'title' => 'title',
'slug' => 'slug',
'status' => 'status',
'productCategory' => 'productCategory',
'additionalSearchKeywords' => 'additionalSearchKeywords',
];

$contentProductFields = [
'alternateHeading' => 'alternateHeading',
'productImages' => 'productImages',
'productContent' => 'productContent',
];

$sharedVariantFields = [
'title' => 'title',
'sku' => 'sku',
'inventoryTracked' => 'inventoryTracked',
'price' => 'basePrice',
'height' => 'height',
'width' => 'width',
'length' => 'length',
'weight' => 'weight',
];

return [
'productFieldMap' => [
'*' => [
...$sharedProductFields,
...$contentProductFields,
],
'boatLetters' => [
...$sharedProductFields,
...$contentProductFields,
'letterHeight' => 'letterHeight',
'letterWidth' => 'letterWidth',
'specialChars' => 'specialChars',
],
'customHitchCover' => [
...$sharedProductFields,
],
'customIcons' => [
...$sharedProductFields,
'iconImage' => 'iconImage',
'iconWidth' => 'iconWidth',
'iconHeight' => 'iconHeight',
],
'licensePlateFrames' => [
...$sharedProductFields,
'platePrice' => 'platePrice',
'plateLettersLowercase' => 'plateLettersLowercase',
'plateLettersUppercase' => 'plateLettersUppercase',
'letterStyle' => 'letterStyle',
'bulkPricing' => 'bulkPricing',
],
],
'variantFieldMap' => [
'*' => $sharedVariantFields,
'boatLetters' => [
...$sharedVariantFields,
'insertColor' => 'insertColor',
],
'customHitchCover' => [
...$sharedVariantFields,
'hitchCoverImage' => 'hitchCoverImage',
'finish' => 'finish',
],
'customIcons' => $sharedVariantFields,
'licensePlateFrames' => [
...$sharedVariantFields,
'productImages' => 'productImages',
],
],
];
```

A few choices worth calling out:

- **`$sharedProductFields` and `$sharedVariantFields`** carry the columns that every CSV in the catalog has (title, sku, status, price, dimensions). Touch these to add a column everywhere.
- **`$contentProductFields`** is a second group for product types that have content-page fields (heading, images, body). Product types that are not editable content pages (a hitch-cover variant catalog) skip it.
- **Per-product-type entries** spread the shared groups, then list each type's own custom fields explicitly. The right side of the arrow is the field handle on the product or variant; the left side is the CSV column header.
- **Aliases**: in the variant map, `'price' => 'basePrice'` lets the CSV use a friendlier column name (`price[default]`) while writing to Commerce's `basePrice` property. The shared group is the right place to keep a rename like this so every product type benefits.
- **A bare `$sharedVariantFields`** as the value is fine for product types that need nothing extra (`customIcons`, `general`). Spread is only needed when adding to the shared set.

## Adding a new product type

1. Add a new entry under `productFieldMap` keyed by the product type's handle.
2. Spread the shared group(s) the type needs.
3. List any custom product field handles the type adds.
4. Repeat for `variantFieldMap` if the variants of the type have custom fields.

Do not rely on the `'*'` fallback for a product type that has custom fields, because spreading the shared group plus the custom fields is the only way to import both.

## Adding a column to every product type

Add the column to the relevant shared group at the top of the file. Every entry that spreads that group picks it up on the next deploy. No per-entry edits.

## When to leave a product type out of the map

If a product type's CSV only uses the core columns (`title`, `sku`, `basePrice`, etc.), do not add an entry for it. The `'*'` fallback handles it.

Add an entry only when the product type needs at least one custom field. Once you do, that entry replaces `'*'` for that product type; remember to spread the shared groups in.

## Related

- [Configuration reference](../reference/configuration.md), every config key with defaults.
- [CSV format: column reference](../user-guide/csv-format.md#column-reference), how CSV headers map to product and variant fields.
2 changes: 2 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Expiry runs during Craft's garbage collection and via the `variant-manager/activ

Maps CSV column headers (left) to product properties or field handles (right). Keys at the top level are product type handles, with `'*'` matching any product type not otherwise listed.

Per-product-type entries do **not** inherit from `'*'`. The plugin picks one entry per import: the product type's own entry if it has one, otherwise `'*'`. List every column you want imported under each product type's entry, including the ones in `'*'`. For a DRY pattern, see [field maps for many product types](../recipes/field-maps-for-many-product-types.md).

Three keys have special handling:

- `title`: always treated as the product title. Required in row 2 of every CSV.
Expand Down
4 changes: 4 additions & 0 deletions docs/user-guide/csv-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Classic Tee,,,,

The first cell of row 2 (`Classic Tee`) is the product title. From row 3 onwards every row is one variant; leave the product title column empty on variant rows.

Download: [`classic-tee-minimum.csv`](../examples/classic-tee-minimum.csv). Rename it to `Classic Tee.csv` (or whatever title you want the product to have) before uploading.

A more complete file showing every column type:

```csv
Expand All @@ -49,6 +51,8 @@ Classic Tee,classic-tee,enabled,,,,,,,,
,,,TEE-BLUE-M,19.99,1,1,150,60,Blue,Medium
```

Download: [`classic-tee-complete.csv`](../examples/classic-tee-complete.csv). Rename to `Classic Tee.csv` before uploading.

## Column reference

Every column header falls into one of five groups. Variant Manager looks at the header text to decide which group a column belongs to.
Expand Down
Loading