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
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
# Release Notes for Variant Manager

## 3.0.0 - 2026-09-11

> {tip} Run `./craft variant-manager/attributes/backfill` to take advantage of new features.

### Added

- Added Variant Attribute and Attribute Option elements for the names and values your variants store, listed under “Variant Attributes” and “Attribute Options”.
- Added a display type per attribute, set on the attribute itself, and a field layout for the attribute and one for its options, set at “Settings” -> “Plugins” -> “Variant Manager” and stored in project config.
- Added element chips to the Variant Attributes field, which open an attribute or option in a slideout.
- Added a “Variant Attribute: {name}” filter for each attribute, on variant and product listings.
- Added a “Variant Attributes” utility, plus `variant-manager/attributes/backfill` and `variant-manager/attributes/orphans` commands, for populating and pruning the registry.
- Added the `variant-manager:manage-attributes` permission.
- Added `craft.variantManager.getAttributeRegistry()`, which pairs a product's attribute names and values with their elements.
- Added support for showing a variant's attribute values in its card and as an element index column.
- Added a “Bulk edit field” action to the Variants index, configured with `bulkEditableVariantFields`.
- Added `defaultVariantTableAttributes`, for extra default columns on the Variants index.
- Added an “Available Display Types” setting, at “Settings” -> “Plugins” -> “Variant Manager”, which narrows the display types an attribute can be set to. It can also be set with `availableDisplayTypes` in `config/variant-manager.php`.
- Added a “Default Display Type” setting, on the same screen, which is the display type a newly registered attribute is given. It can also be set with `defaultDisplayType`.

### Changed

- Attributes and options can no longer be deleted from the control panel. Use “Prune orphans”, which removes only the rows no variant uses.
- The `variant-manager:manage` permission now also gates bulk editing variants.
- A Money column written with thousands separators, such as `1,234.56`, now fails the import instead of being read as a smaller amount.
- `productFieldMap` and `variantFieldMap` now default to the `title`, `slug` and `status` product columns and the standard variant columns, so `config/variant-manager.php` is only needed to change them.

### Fixed

- Fixed a bug where import and export produced no variant columns without a `config/variant-manager.php` file.
- Fixed a bug where an empty `variantFieldMap` entry exported a CSV with no variant columns.
- Fixed the `src/config.php` template mapping `'price' => 'basePrice'`, which named the price column `price[default]` where the rest of the documentation says `basePrice[default]`.
- Fixed a CSV column for a standard variant field the map does not list, such as `enabled` or `isDefault`, failing the import with an unknown field error.
- Fixed a CSV with no `sku` column raising a PHP error instead of reporting the missing column.
- Fixed a renamed product column, such as `'Product Name' => 'title'`, being ignored on import and written twice on export.
- Fixed a bug where the “Export Product” button reported only the HTTP status of a failed export.
- Fixed a bug where importing a Money field could store a cent less than the CSV held, such as `19.99` becoming `19.98`, including on a straight export and reimport.
- Fixed a bug where importing a Money field in a zero-decimal currency, such as JPY, stored an amount 100 times too large.
- Fixed an error that occurred when a Money column held a value that was not a number.
- Fixed a bug where “Upload Product” failed on sites with a `cpTrigger` other than `admin`.

### Removed

- Removed inline editing of attribute values on a variant, along with the `variant-manager/product-variants/save-variant-attributes` action and its route. A value now changes for every variant using it, edited from the option's slideout, or for one variant through a CSV import.

## 2.1.0 - 2026-05-13

- Add optional `status` column for product imports and exports.
Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

A Craft CMS plugin that imports and exports Craft Commerce product **variants** from CSV files.

## What it does
## Overview

- 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 Variants element index.
- 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.
- 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.

## Requirements
Expand All @@ -25,7 +27,7 @@ composer require fostercommerce/variant-manager
./craft plugin/install variant-manager
```

See [`docs/installation.md`](./docs/installation.md) for the full installation and configuration guide.
See [`docs/installation.md`](./docs/installation.md) for the full installation and configuration guide, and [`docs/upgrade.md`](./docs/upgrade.md) if you are coming from 2.x.

## Importing

Expand All @@ -35,26 +37,37 @@ See [`docs/user-guide/importing.md`](./docs/user-guide/importing.md) and [`docs/

## Exporting

Two ways to export: from a single product's edit page (sidebar **Export Product** button), or from the **Variants** element index using the **Export Variant Data** action on a multi-select. 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.
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.

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

## Variant Attributes field
## Variant attributes

The plugin ships a **Variant Attributes** field type that you add to each product type's variant field layout. The field stores the option-name and option-value pairs from your CSV (Color: Red, Size: Small) as JSON on the variant, and exposes them to Twig for variant selectors and faceted filtering.
The plugin ships a **Variant Attributes** field type that you add to each product type's variant field layout. It stores the name and value pairs from your CSV (Color: Red, Size: Small) as JSON on the variant. Twig reads them for variant selectors and faceted filtering.

See [`docs/reference/field-type.md`](./docs/reference/field-type.md) for storage and Twig usage.
Every name and value your variants use also gets its own element. Attach a swatch image, a spec sheet or a note to it. Pick how a storefront renders it: dropdown, radio buttons, text buttons, image swatches, color swatches or lightswitch. Imports and exports are unchanged.

See [`docs/user-guide/variant-attributes.md`](./docs/user-guide/variant-attributes.md) and [`docs/reference/field-type.md`](./docs/reference/field-type.md).

## Permissions

In addition to `accessPlugin-variant-manager`:

- `variant-manager:import`, upload CSVs and create or update products and variants.
- `variant-manager:export`, export products from the product edit page or the variants index.
- `variant-manager:manage`, clear the activity log and manage plugin data.
- `variant-manager:export`, export products from the product edit page or the Commerce products index.
- `variant-manager:manage`, clear the activity log and bulk edit variant fields.
- `variant-manager:manage-attributes`, view and edit variant attributes and their options.

See [`docs/reference/permissions.md`](./docs/reference/permissions.md).

## License

Proprietary.

## Documentation

See [`docs/index.md`](./docs/index.md).

## Credits

Brought to you by [Foster Commerce](https://fostercommerce.com).
10 changes: 1 addition & 9 deletions docs/dev-guide/custom-queue.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Custom queue

How to keep Variant Manager's import jobs from delaying other Craft queue work. Audience: developers running production sites with high queue volume.
How to keep Variant Manager's import jobs from delaying other Craft queue work.

Bulk imports can generate thousands of import jobs. By default they go on Craft's main queue, which means other Craft work (search index rebuilds, image transforms, emails) can sit behind a long import batch.

Expand Down Expand Up @@ -64,11 +64,3 @@ Then run the worker for the custom queue separately:
```

See Craft's [custom queues guide](https://craftcms.com/docs/5.x/system/queue.html#custom-queues) for more on how Yii's queue components are wired up.

## Verifying it works

1. Upload a small CSV from **Variant Manager -> Dashboard**. The upload modal returns "File ... has been queued for processing" as usual.
2. With the main queue worker stopped, check the dashboard activity log; the new row stays in its pending state because the main queue does not own the job.
3. Start the custom queue worker: `./craft queue/run --queue=priorityQueue`.
4. Refresh the dashboard. The activity log row flips to the green-dot success state once the worker drains the import.
5. For the priority approach, push two jobs back to back (an import and a search index rebuild) and confirm the search rebuild runs first.
53 changes: 40 additions & 13 deletions docs/dev-guide/template-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

Twig helpers Variant Manager exposes on the `craft` variable for use in storefront templates.

## Reading the field on a variant

The field's value is accessible by its handle:

```twig
{% for attribute in variant.variantAttributes ?? [] %}
<li>{{ attribute.attributeName }}: {{ attribute.attributeValue }}</li>
{% endfor %}
```

Substitute the handle you gave the field for `variantAttributes`.

## `craft.variantManager.getAttributeOptions(product, only?)`

Returns the distinct attribute names and the unique values used across a product's variants. Useful for building variant pickers and faceted filters.
Expand All @@ -13,43 +25,58 @@ Parameters:

Returns an array of associative arrays, each with:

- `name`: the attribute name.
- `name`: the attribute name, as stored on the variant.
- `values`: a deduplicated array of every value used by any variant for that attribute.

### Example output
The result is plain strings, so it is safe to pass to `json_encode` for a JavaScript picker:

```twig
{% set attributeOptions = craft.variantManager.getAttributeOptions(product.id) %}
{{ attributeOptions | json_encode }}
{{ craft.variantManager.getAttributeOptions(product.id)|json_encode }}
```

Renders something like:

```json
[
{ "name": "Color", "values": ["Red", "Blue"] },
{ "name": "Size", "values": ["Small", "Medium", "Large"] }
]
```

### Example: build a radio picker for every attribute
## `craft.variantManager.getAttributeRegistry(product, only?)`

Returns the same names and values, each paired with the [registry](../user-guide/variant-attributes.md) elements that carry its metadata. Use this to render swatches, display names, or any field a developer added.

Takes the same parameters. Each entry adds:

- `attribute`: the attribute element, or `null` if the name is not registered yet. Carries the display type and the attribute's own fields.
- `options`: the option elements, keyed by the raw value. A value that is not registered is absent from this array.

These are elements, so read them in Twig rather than encoding them. `json_encode` inlines every public property of every element.

### Example: render by display type

```twig
{% set product = craft.products().id(30).one() %}
{% for attributeOptions in craft.variantManager.getAttributeRegistry(product) %}
{% set attribute = attributeOptions.attribute %}

{% for attribute in craft.variantManager.getAttributeOptions(product) %}
<fieldset>
<legend>{{ attribute.name }}</legend>
{% for value in attribute.values %}
<legend>{{ attribute ? attribute.title : attributeOptions.name }}</legend>

{% for value in attributeOptions.values %}
{% set option = attributeOptions.options[value] ?? null %}

<label>
<input type="radio" name="{{ attribute.name|kebab }}" value="{{ value }}">
{{ value }}
<input type="radio" name="{{ attributeOptions.name|kebab }}" value="{{ value }}">
{{ option ? option.title : value }}
</label>
{% endfor %}
</fieldset>
{% endfor %}
```

A name or value that has not been registered yet has no element, so fall back to the raw name and value.

`attribute.displayType` is one of `dropdown`, `radioButtons`, `textButtons`, `imageSwatches`, `colorSwatches` or `lightswitch`. Branch on it to pick how you render the options.

### Example: limit to specific attributes

```twig
Expand Down
2 changes: 1 addition & 1 deletion docs/dev-guide/twig-queries.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Querying variants

How to filter Commerce variants by their Variant Attributes field. Audience: developers building storefront templates or PHP code that queries variants.
How to filter Commerce variants by their Variant Attributes field.

Substitute the handle you gave your Variant Attributes field (`variantAttributes`, `myVariantAttributes`, anything you chose) for `variantAttributes` in the examples below.

Expand Down
2 changes: 1 addition & 1 deletion docs/examples/classic-tee-minimum.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
title,sku,basePrice,Attribute: Color,Attribute: Size
title,sku,basePrice[default],Attribute: Color,Attribute: Size
Classic Tee,,,,
,TEE-RED-S,19.99,Red,Small
,TEE-RED-M,19.99,Red,Medium
Expand Down
62 changes: 17 additions & 45 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,15 @@ composer require fostercommerce/variant-manager
./craft plugin/install variant-manager
```

In the CP you should see a **Variant Manager** nav item with two subnav entries: **Dashboard** and **Variants**.
In the CP you should see a **Variant Manager** nav item with four subnav entries: **Dashboard**, **Variants**, **Variant Attributes** and **Attribute Options**.

## 2. Configure

Create `config/variant-manager.php`:

```php
<?php

return [
'emptyAttributeValue' => '',
'attributePrefix' => 'Attribute: ',
'inventoryPrefix' => 'Inventory',
'activityLogRetention' => '30 days',
'productFieldMap' => [
'*' => [
'title' => 'title',
'slug' => 'slug',
'status' => 'status',
],
],
'variantFieldMap' => [
'*' => [
'title' => 'title',
'sku' => 'sku',
'inventoryTracked' => 'inventoryTracked',
'price' => 'basePrice',
'height' => 'height',
'width' => 'width',
'length' => 'length',
'weight' => 'weight',
],
],
];
```

All keys have defaults; you can skip the file entirely and revisit it once you know which fields you want to import.
The default `productFieldMap` and `variantFieldMap` already cover every column this walkthrough uses, so there is nothing to add. Create `config/variant-manager.php` when you want to map a custom field or rename a column. See the [configuration reference](./reference/configuration.md) for every key.

## 3. Add the Variant Attributes field

The plugin needs a place to store option name and value pairs (Color: Red, Size: Small) on each variant.

1. **Settings -> Fields -> New field**.
2. **Field Type**: **Variant Attributes**. Name it **Variant Attributes**, handle `variantAttributes`. No further settings needed.
3. **Commerce -> Settings -> Product Types -> {your product type} -> Variant Fields**. Drag the new field onto the layout. Save.

You only need one Variant Attributes field per product type's variant layout. Extras are ignored.
The plugin needs a place to store option name and value pairs (Color: Red, Size: Small) on each variant. Create a **Variant Attributes** field with the handle `variantAttributes` and add it to your product type's variant field layout. See [installation](./installation.md#add-the-variant-attributes-field) for the steps.

## 4. Build your first CSV

Expand Down Expand Up @@ -94,7 +56,15 @@ Check the variants tab:
- Each has its price set to 19.99 and inventory tracking on.
- Open one variant. Scroll to the Variant Attributes field; you should see Color and Size with the value for that variant.

## 7. Round-trip: export, edit, reimport
## 7. See the attributes the import registered

**Variant Manager -> Variant Attributes**. The import created `Color` and `Size`. **Variant Manager -> Attribute Options** lists `Red`, `Blue`, `Small` and `Medium`, each with the attribute it belongs to.

Open `Red`. Its **CSV Value** is read-only; its title is not. The **Used by** count shows how many variants store `Red`. Rename the title to `Crimson` and save. No variant changed, and a template reading `option.title` now shows `Crimson`.

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

## 8. Round-trip: export, edit, reimport

This is the workflow you will use to bulk-edit existing products.

Expand All @@ -103,17 +73,19 @@ This is the workflow you will use to bulk-edit existing products.
3. Change something. Bump the price on DEMO-RED-S to 21.99.
4. Save the file. **Do not rename it.**
5. Back to **Variant Manager -> Dashboard -> Upload Product** and pick the same file.
6. The modal recognises the existing product and asks "Are you sure you want to edit an existing product named \"Demo Shirt\"?" with a **Refresh variants** radio group. Leave it on **Update and remove extra variants** (the default) and click **Edit Product**.
6. The modal recognizes the existing product and asks "Are you sure you want to edit an existing product named \"Demo Shirt\"?" with a **Refresh variants** radio group. Leave it on **Update and remove extra variants** (the default) and click **Edit Product**.
7. Activity log shows another green-dot row. Reopen the product; DEMO-RED-S is now 21.99.

## 8. Where to go next
## 9. Where to go next

For deeper reading:

- [CSV format](./user-guide/csv-format.md), every column the import recognises, with formatting rules.
- [CSV format](./user-guide/csv-format.md), every column the import recognizes, with formatting rules.
- [Importing](./user-guide/importing.md), the upload flow in detail, including the choice between updating and replacing variants.
- [Exporting](./user-guide/exporting.md), single-product and bulk export.
- [Bulk import](./user-guide/bulk-import.md), uploading a zip of CSVs.
- [Troubleshooting](./user-guide/troubleshooting.md), when imports fail.
- [Variant Attributes field](./reference/field-type.md), how the attribute data is stored and read.
- [Variant attributes](./user-guide/variant-attributes.md), attaching swatches and notes to attribute values.
- [Template tags](./dev-guide/template-tags.md) and [recipes](./recipes/add-to-cart.md), using the attributes on the storefront.
- [Upgrading to 3.x](./upgrade.md), if you are coming from 2.x.
Loading
Loading