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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ composer.lock
.php_cs.cache
.idea
.DS_Store
.twig-parse.php
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# Release Notes for Variant Manager

## 4.0.0 - 2026-09-14
## 4.0.0 - 2026-09-15

> {tip} This release removes the `VariantAttributeOption` element type. See [upgrading to 4.x](./docs/upgrade.md) before updating.

### Added

- Added the Variant Maker, which builds a product's variants from the attributes and options you pick, with a preview of what generating would change.
- Added a “Variant Maker Product Types” setting, for choosing which product types offer the Variant Maker. No product type offers it by default.
- Added a “SKU Partial” and a “Price Modifier” to each attribute option, which the Variant Maker assembles into a generated variant's SKU and price.
- Added a `variant-manager/variant-maker/plan` command, for previewing a plan from the command line.
- Added drag ordering to the “Variant Attributes” listing, for arranging an attribute's options in the order a storefront should render them.
- Added a `resave/variant-attributes` command, for re-saving attributes and options and rewriting their search keywords.

Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ A Craft CMS plugin that imports and exports Craft Commerce product **variants**
- Imports a CSV to create or update a Craft Commerce product and its variants.
- Bulk-imports many products at once from a zip of CSVs, each file becoming its own product.
- Exports a product to CSV from the product edit page, or many products at once from the Commerce products index.
- Adds a **Variant Attributes** field that stores option name and value pairs (Color, Size, Material) on each variant for filtering on the storefront.
- Lets you build a color picker or size swatch in Twig, from swatch images, spec sheets or notes you attach to any attribute value.
- Adds a **Variant Attributes** field that stores option name and value pairs (Color, Size, Material) on each variant for filtering on the storefront, and lets you attach a swatch image, spec sheet or note to any value to build a color picker or size swatch in Twig.
- Generates a product's full variant matrix in the control panel from attribute options you pick (three sizes and four colors become twelve variants, each with its own SKU).
- Sets one field on many variants at once from the Variants index.
- Logs each import and export, with configurable retention, in a dashboard activity feed.

Expand All @@ -35,6 +35,14 @@ Upload a CSV (or a zip of CSVs) from **Variant Manager -> Dashboard**. The CSV's

See [`docs/user-guide/importing.md`](./docs/user-guide/importing.md) and [`docs/user-guide/csv-format.md`](./docs/user-guide/csv-format.md).

## Variant Maker

Builds a product's variants from attributes and options you already have, for stores with no ERP, PIM or CSV feed. Pick the attributes to combine and set what each variant should get. The preview lists every combination and what generating would change, before anything is written. An option can carry a SKU partial and a price modifier, which the generated variant's SKU and price are assembled from.

Off for every product type until you turn it on at **Settings -> Plugins -> Variant Manager**.

See [`docs/user-guide/variant-maker.md`](./docs/user-guide/variant-maker.md).

## Exporting

Two ways to export: the sidebar **Export Product** button on a product's edit page, or the **Export Variant Data** action on a multi-select at **Commerce -> Products**. A single product downloads as one CSV; multiple products download as a zip. Exported CSVs are shaped so they can be reimported without edits to the column headers.
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Coming from 2.x or 3.x? See [upgrading](./upgrade.md).
- [Importing](./user-guide/importing.md), uploading single CSVs and zip batches.
- [Exporting](./user-guide/exporting.md), getting CSVs out of Commerce to edit.
- [Variant attributes](./user-guide/variant-attributes.md), attaching swatches, notes and other fields to attribute values.
- [Variant Maker](./user-guide/variant-maker.md), building a product's variant matrix from attributes and options.
- [Troubleshooting](./user-guide/troubleshooting.md), when an import does not behave the way you expected.

**Building on top of the plugin?** See the [developer guide](./dev-guide/), [recipes](./recipes/), and [reference](./reference/):
Expand Down
2 changes: 2 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ Those values are the defaults, so the file above changes nothing. See [configura

Attribute display types and field layouts are set in the CP instead, at **Settings -> Plugins -> Variant Manager**, and stored in project config. See [variant attributes](./user-guide/variant-attributes.md).

The same screen carries **Variant Maker product types**, which decides where the Variant Maker tab appears. No product type offers it by default. See [Variant Maker](./user-guide/variant-maker.md).

## Add the Variant Attributes field

The plugin ships a **Variant Attributes** field type. Add it to every Commerce product type whose variants you want to import or export by attribute.
Expand Down
20 changes: 19 additions & 1 deletion docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ return [
'attributePrefix' => 'Attribute: ',
'inventoryPrefix' => 'Inventory',
'activityLogRetention' => '30 days',
'variantMakerProductTypes' => [],
'productFieldMap' => [
'*' => [
'title' => 'title',
Expand Down Expand Up @@ -185,7 +186,24 @@ Valid values are `dropdown`, `radioButtons`, `textButtons`, `imageSwatches`, `co

An attribute already set to a type this list omits keeps it, and the menu still shows it, so nothing is rewritten on the next save. Change that attribute and the omitted type is gone from its menu.

This setting is also editable at **Settings** -> **Plugins** -> **Variant Manager**. Setting it here disables that control, since a config file overrides what the control panel saves.
This setting is also editable at **Settings** -> **Plugins** -> **Variant Manager**. A value here overrides what that screen saves, and the control shows a warning saying so.

### `variantMakerProductTypes`

- Type: `list<string>`
- Default: `[]`

Handles of the Commerce product types whose products offer the [Variant Maker](../user-guide/variant-maker.md) tab. While this is empty, no product type offers it.

```php
return [
'variantMakerProductTypes' => ['catalog', 'apparel'],
];
```

A product type also needs a Variant Attributes field in its variant field layout, since that is where a generated variant stores its combination. Listing a product type without one leaves the tab hidden.

This setting is also editable at **Settings** -> **Plugins** -> **Variant Manager**. A value here overrides what that screen saves, and the control shows a warning saying so.

### `defaultDisplayType`

Expand Down
21 changes: 21 additions & 0 deletions docs/reference/console-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,24 @@ Deletes them. Anything a variant has started using since the scan is skipped. An
| `--batchSize` | `500` | Variants read per batch. |

See [variant attributes](../user-guide/variant-attributes.md).

## `variant-manager/variant-maker/plan`

Print what generating a set of combinations would do to a product's variants.

```sh
./craft variant-manager/variant-maker/plan 4054 --select="Container Size=1 Quart,1 Gallon;CSP Color=Custom"
```

The first argument is the product ID. Prints one line per combination with its status, the SKU and the price, and any SKU warning under it. Saves nothing.

The selection is read from `--select` rather than the product's saved builder.

| Option | Default | Description |
|--------|---------|-------------|
| `--select` | `''` | Attributes and values to combine, as `Name=A,B;Other Name=C`. |
| `--mode` | `add` | One of `add`, `update` or `replace`. Only `replace` differs here, since the command includes every property on new variants only. |
| `--skuFormat` | none | SKU format. Blank uses the default variant SKU followed by each option. |
| `--basePrice` | none | Price each combination starts from. Blank uses the product's default variant price. |

See [Variant Maker](../user-guide/variant-maker.md).
25 changes: 25 additions & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Roadmap

Internal working notes. Not linked from the docs index.

## Variant Maker

### Create attributes and options from the builder

Today a builder row can only pick from attributes and options the registry already holds. Creating either one means leaving the product and going to **Variant Manager -> Variant Attributes** first.

Deferred 2026-09-15: creating them elsewhere first is little enough work that it does not justify a second creation path in the builder.

If it is picked up, the shape to match is Craft's element select, which offers a create option inside the selection modal. The row partial already renders two `forms.elementSelect` fields, so the work is a create action for `VariantAttribute` that respects the row's `attributeId` criteria, plus the field layout an attribute or option carries.

### Editable preview table

The preview renders what generating would do. Making its cells editable would let a merchant correct one title, SKU or price before generating, the way the CSV importer lets them edit a file.

Deferred 2026-09-15.

Open questions if it is picked up:

- Edits are keyed by combination. A changed builder row invalidates some of them, so either the edits merge by combination key and survive, or they are discarded whenever the plan changes. Merging is the useful behavior and the harder one.
- Editing turns the plan into stored state. It currently derives from the settings on every request, which is what keeps the preview honest about the current builder.
- A large plan is already one HTML response with no pagination. Editable cells multiply the cost per row.
2 changes: 2 additions & 0 deletions docs/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ If anything writes attribute values to variants outside the control panel, run t
./craft variant-manager/attributes/backfill
```

4.0.0 adds the [Variant Maker](./user-guide/variant-maker.md). No product type offers it until you turn it on at **Settings -> Plugins -> Variant Manager**, so an upgrade changes nothing on its own.

## Upgrading to 3.x

Existing templates keep working. This page is the setup the update needs, plus one change to the Variant Attributes field.
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/variant-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Values come from the variant's own stored data, so the card costs no extra queri

**Display Type** is set on the attribute itself, at **Variant Manager -> Variant Attributes**. It tells your storefront how to render the options: dropdown, radio buttons, text buttons, image swatches, color swatches or lightswitch. It does not change anything in the control panel. Dropdown is the default.

**Available Display Types**, at **Settings** -> **Plugins** -> **Variant Manager**, narrows that menu to the types your templates render. It applies to every attribute. A developer can also set it in [`availableDisplayTypes`](../reference/configuration.md#availabledisplaytypes), which disables the control panel field.
**Available Display Types**, at **Settings** -> **Plugins** -> **Variant Manager**, narrows that menu to the types your templates render. It applies to every attribute. A developer can also set it in [`availableDisplayTypes`](../reference/configuration.md#availabledisplaytypes), which overrides what that screen saves.

**Default Display Type**, on the same screen, is what a new attribute is given when an import first registers it. Attributes that already exist keep the type they have.

Expand Down
131 changes: 131 additions & 0 deletions docs/user-guide/variant-maker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Variant Maker

Build a product's whole variant matrix in the control panel, from attributes and options you already have.

This suits stores with no ERP, PIM or CSV feed.

## Turning it on

Off for every product type by default.

Go to **Settings -> Plugins -> Variant Manager** and check the product types that should offer it. A developer can also set [`variantMakerProductTypes`](../reference/configuration.md#variantmakerproducttypes), which overrides what that screen saves.

The product type also needs a Variant Attributes field in its variant field layout. Without one the tab stays hidden, even when the product type is checked.

A saved product then shows a **Variant Maker** tab, last in the row. A brand new product shows the tab with a note to save first.

## Attributes to combine

One row per attribute. Pick the attribute, then pick as many of its options as you want.

Every option in a row is combined with every option in every other row. Three sizes, four colors and two finishes make 24 combinations.

The options list is limited to the attribute chosen in that row, so changing a row's attribute clears the options under it.

A row needs both an attribute and at least one option. Leave either empty and the product will not save, with the row number named in the error.

Row order sets the order the SKU partials assemble in, for the default SKU format below.

## What to do with existing variants

This is the only control over whether an existing variant is rewritten.

| Mode | What it does |
|------|--------------|
| **Add missing only** | Creates the combinations that do not exist yet. Never touches an existing variant. |
| **Add and update existing** | Also rewrites existing variants with every included property. |
| **Replace: add, update and delete** | Does both, and deletes any variant whose combination is not in the generated set. |

Replace deletes variants. The preview lists each one as **Delete** before you generate.

## Variant properties

Each property is a row in the table with two controls:

- **Include**: whether the Variant Maker sets this property. Off means a new variant gets Commerce's default and an existing variant keeps what it has. A new variant is always titled either way, falling back to its combination.
- **Value**: a format, a number or a switch, depending on the property.

SKU and price are always included, so their switches are on and disabled. Commerce requires both on every variant.

| Property | Value |
|----------|-------|
| Title | Format. Hidden where the product type builds variant titles from its own format. |
| SKU | Format. |
| Price | The price each combination starts from, before its options' price modifiers. |
| Track inventory | Switch. |
| Stock | Number. Shown only while **Track inventory** is included and on. |
| Allow out of stock purchases | Switch. Shown on the same condition as Stock. |
| Available for purchase | Switch. |
| Free shipping | Switch. |
| Promotable | Switch. |

These are not set here, so edit them on the Variants tab after generating: dimensions, weight, tax and shipping categories, minimum and maximum quantity, promotional price, and custom fields.

Where the store has more than one inventory location, an **Inventory location** menu appears under the table. Stock is written to the location you pick.

### Title and SKU formats

In a format, `{Attribute Name}` becomes the option chosen under that attribute. Anything else is used as typed, so a product code goes in literally.

```text
M110-{Container Size}-{CSP Color}-{Mortar Type}
```

Each attribute contributes its option's **SKU Partial** where the option has one, and the option's own value where it does not. Set SKU partials at **Variant Manager -> Variant Attributes**, or from the slideout that opens when you click an option chip.

Leave a format blank for the default:

- A title is the combination joined by a slash: `1 Quart / Custom / Type N`.
- A SKU is the product's default variant SKU followed by each option, joined by dashes. Spaces become dashes.

A format you type is used exactly as written, spaces included.

### Price modifiers

Each option can carry a **Price Modifier**, set in the same place as its SKU partial. A combination's price is the base price plus the modifier of every option in it. Both are held to two decimal places.

## Preview

The table below the form updates as you change the builder. It lists every combination and what generating would do to it.

| Status | Meaning |
|--------|---------|
| **Create** | No variant has this combination yet. |
| **Update** | A variant exists, the mode allows updates, and at least one included property would change it. A stock count on its own counts. |
| **Unchanged** | A variant exists and nothing the Variant Maker manages would change. |
| **Delete** | Replace mode only. The variant's combination is not in the generated set. |

A column shows `old -> new` only where the run would write that value. An excluded property shows **Commerce default** on a Create row, and **Kept** on every other row. The Stock column instead shows **Unlimited** where inventory is not tracked, and **None** where it is tracked with no count set.

The preview reads the builder as it stands on screen, not what was last saved.

### SKU warnings

Commerce requires every SKU to be unique across the store and no longer than 255 characters. A row that breaks either rule is flagged in red under its SKU, and **Generate variants** refuses to run while any row is flagged.

| Warning | Cause |
|---------|-------|
| Another row builds this same SKU | Two combinations resolve to the same SKU, or one matches a variant on this product the run leaves alone. Usually a format with no `{Attribute Name}` token, or two options sharing a SKU partial. |
| A variant on another product already uses this SKU | The SKU is taken elsewhere in the store. Comparison ignores case. |
| Longer than 255 characters | The assembled SKU is over the limit Commerce enforces. |

## Saving and generating

Saving the product saves the builder. It does not create variants.

The settings are stored per product, so each product keeps its own attributes, mode and properties.

**Generate variants** runs the job in the background and returns you to the page. Watch **Variant Manager -> Dashboard** for the result. It records how many variants were created, updated and deleted, or the error if the run failed.

Generating uses the saved settings, so save the product before pressing it. A product with unsaved changes says so instead of running.

The whole run is one transaction. If any variant fails to save, nothing is written and the activity log records why.

The preview does not refresh itself when the job finishes. Reload the page to see the new variants.

## Related

- [Variant attributes](./variant-attributes.md), where SKU partials and price modifiers are set
- [Activity log](./activity-log.md), where a generation result is recorded
- [Console commands](../reference/console-commands.md), previewing a plan from the command line
- [Configuration reference](../reference/configuration.md#variantmakerproducttypes)
Loading
Loading