Skip to content

Commerce Foundations is our pre-built Craft Commerce store. See what it includes

Craft CMS plugins

Variant Manager

A Craft CMS plugin for managing Craft Commerce variants as combinations of attributes and options.

Configuration reference

Every setting Variant Manager reads from config/variant-manager.php. The file is multi-environment aware; nest values under environment names if you need per-environment overrides. The src/config.php file that ships with the plugin is a template to copy, not a loaded default.

Example config file#

<?php

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

Settings#

emptyAttributeValue#

  • Type: string
  • Default: ''

The value stored for an attribute whose cell is empty. Set it to a value such as None or N/A if a literal empty string might confuse storefront filters.

attributePrefix#

  • Type: string
  • Default: 'Attribute: '

The prefix that marks an attribute column in a CSV. A column whose header starts with this string is mapped to a Variant Attributes entry whose name is the rest of the header. Default behavior: Attribute: Color becomes attribute Color.

A blank value fails the import and the export, because the import cannot distinguish an attribute column from any other column.

Changing this is a breaking change for any existing CSVs. Keep it consistent across your store.

inventoryPrefix#

  • Type: string
  • Default: 'Inventory'

The prefix that marks an inventory column. The full column pattern is {prefix}[locationHandle]: totalName, so with the default prefix a column is Inventory[main]: available.

activityLogRetention#

  • Type: string, int, null, or false
  • Default: '30 days' (the plugin’s settings default; the example config.php ships '1 week')

How long to keep activity log entries.

  • String values use PHP relative-time format: 1 hour, 1 day, 1 week, 1 month, 1 year.
  • Integer values are interpreted as a number of days.
  • false or null disables expiry; logs grow until cleared by hand.

Expiry runs during Craft’s garbage collection and via the variant-manager/activities/clear console command.

productFieldMap#

  • Type: array
  • Default: ['*' => ['title' => 'title', 'slug' => 'slug', 'status' => 'status']]. Set the key to replace that map, not to add to it.

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 pattern without repeated blocks, see field maps for many product types.

Three keys have special handling:

  • title: names the export column only. The import reads the product title from the first cell of row 2. See CSV format.
  • slug: generated from the title for new products if missing.
  • status: read as enabled (default) or disabled. Any value other than disabled (case-insensitive, trimmed) imports as enabled.

Other entries write to product custom fields by handle. See supported field types below.

A handle absent from the product type’s field layout is left out of that product’s export, header and cell together. Under '*', that happens for any handle only some product types have. An import skips that column for the same reason.

Example per-product-type map:

'productFieldMap' => [
    '*' => [
        'title' => 'title',
        'slug' => 'slug',
        'status' => 'status',
    ],
    'apparel' => [
        'title' => 'title',
        'slug' => 'slug',
        'status' => 'status',
        'careInstructions' => 'careInstructions',
        'fabricNotes' => 'fabricNotes',
    ],
],

variantFieldMap#

  • Type: array
  • Default: ['*' => ['title' => 'title', 'sku' => 'sku', 'inventoryTracked' => 'inventoryTracked', 'basePrice' => 'basePrice', 'height' => 'height', 'width' => 'width', 'length' => 'length', 'weight' => 'weight']]. Set the key to replace that map, not to add to it.

An empty map for a product type fails the import and the export with “No variant fields are mapped”.

Same shape as productFieldMap, but maps to variant properties or field handles. The '*' catch-all applies to product types not otherwise listed.

Standard cross-site variant properties:

  • title, enabled, isDefault, sku, width, height, length, weight.

Per-site variant properties (use the column suffix [siteHandle]):

  • basePrice, inventoryTracked, availableForPurchase, freeShipping, promotable, minQty, maxQty.

For what a missing column or an empty cell writes, see per-site Commerce columns.

The Variant Attributes field handle does not need to be in this map. The plugin discovers it from the product type’s variant field layout.

Example variant map that adds a custom notes field for one product type:

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

return [
    'variantFieldMap' => [
        '*' => $defaults,
        'apparel' => array_merge($defaults, [
            'notes' => 'notes',
            'releaseDate' => 'releaseDate',
        ]),
    ],
];

defaultVariantTableAttributes#

  • Type: list<string>
  • Default: []

Extra columns shown by default on Variant Manager -> Variants. Each entry is a variant field handle or table attribute, appended to the plugin’s own defaults.

bulkEditableVariantFields#

  • Type: list<string>
  • Default: []

Variant field handles the Bulk edit field action can set. inventoryTracked is accepted alongside custom field handles. For when the action appears, see the Variants index.

availableDisplayTypes#

  • Type: list<string>
  • Default: ['*']

Display types offered in the Display Type menu on an attribute. While '*' is in it, every type is offered. Use it to hide the ones your templates do not render:

return [
    'availableDisplayTypes' => ['dropdown', 'textButtons', 'imageSwatches'],
];

Valid values are dropdown, radioButtons, textButtons, imageSwatches, colorSwatches, and lightswitch. An unrecognized value is skipped. If the list is empty, or has only unrecognized values, only dropdown is offered.

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

This setting is also editable at Variant Manager -> Settings. 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 tab. While this is empty, no product type offers it.

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

A product type also needs a Variant Attributes field in its variant field layout, because 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 Variant Manager -> Settings. A value here overrides what that screen saves, and the control shows a warning saying so.

defaultDisplayType#

  • Type: string
  • Default: 'dropdown'

Display type given to an attribute the first time it is registered. The New attribute slideout sets its own type instead. Attributes that already exist keep the type they have.

Takes the same values as availableDisplayTypes. An unrecognized value, or a type availableDisplayTypes doesn’t offer, falls back to the first type availableDisplayTypes offers. This setting is also editable at Variant Manager -> Settings, where the menu offers the types availableDisplayTypes allows. A known type it doesn’t offer stays selected, with a warning naming the type new attributes get. A value here overrides what that screen saves, and the screen says so.

Supported field types#

When productFieldMap or variantFieldMap maps a column to a custom field, the import writes these types:

Field type CSV value format
Plain Text Raw text.
Number Raw number.
Date Any date string PHP can parse (2026-03-15, 2026-03-15 14:30). Exported in ATOM format.
Lightswitch 1, true, yes, or on for on, and any other value for off. An empty cell sets the field’s default.
Money Decimal value (15.00), parsed in the field’s currency. Thousands separators fail the import.
Entries Comma-separated sectionHandle:slug (articles:summer-launch,faqs:returns).
Assets Comma-separated volumeHandle:path/to/file.jpg. Numeric asset IDs are also accepted.
Other relation fields Comma-separated slugs.

Multi-environment overrides#

Because Variant Manager’s config file is multi-environment aware, you can nest settings under environment names:

return [
    '*' => [
        'attributePrefix' => 'Attribute: ',
        'activityLogRetention' => '30 days',
    ],
    'dev' => [
        'activityLogRetention' => false,
    ],
    'production' => [
        'activityLogRetention' => '90 days',
    ],
];

For the resolution order, see Craft’s config files documentation.

We can take it from here.

We take on the Craft and Craft Commerce sites you built, so you can get back to building. Introduce a client who signs a management contract and you get a $3,000 partnership fee.

How the Dev Partnership Program works
See if we’re a fit