Skip to content
  1. Extras
  2. MiniShop3
  3. Manager interface
  4. Settings
  5. Options

Product options ​

Open Extras → MiniShop3 → Settings → Options.

Starting with v1.10.0-beta1

Options UI is on Vue 3 + PrimeVue. Old ExtJS windows and processors Processors/Settings/Option/*, Processors/Category/Option/* are removed. All operations go through /api/mgr/options/* and /api/mgr/categories/{id}/options/*.

Purpose ​

Options store product attributes (EAV): color, size, material, any key of your own. The component does not change core MODX tables.

Interface ​

Options tab ​

  • Left: MODX category tree (class_key = msCategory). Checkboxes are independent: checking a parent does not select its children. The context menu works on a branch (refresh, expand or collapse, select or clear selection), and there is a search by name.
  • Right: options grid. Filters: selected categories and group (option_group_id). Bulk actions: assign options to categories, delete.

Create and edit dialog: form on the left (key, caption, description, type, msOptionGroup group, unit), category tree on the right for binding. For combobox / comboMultiple / comboColors there is a value editor with drag-and-drop ordering. For comboColors, a ColorPicker sits next to the hex field.

Option groups tab ​

The tab manages msOptionGroup: create, edit, delete, and reorder groups by dragging. Groups live in table ms3_option_groups (name, description, sort_order). This is not modCategory: categories from other packages no longer clutter the list.

An option has an option_group_id field. Deleting a group only unlinks its options (option_group_id = NULL) — the options themselves stay.

Breaking changes (v1.11)

Existing groups are migrated during the upgrade. Two things you have to fix by hand:

  • in chunks, replace {$option.category_name} and {$option.category} with {$option.group_name};
  • in your own code, call /api/mgr/option-groups instead of the removed /api/mgr/options/modcategories.

Option fields ​

FieldTypeDescription
keystringUnique option key (Latin letters, digits, _, -)
captionstringDisplay name
descriptiontextOption description
measure_unitstringUnit of measure (pcs, kg, cm)
option_group_idint / nullmsOptionGroup group. Optional
typestringValue type (see below)
propertiesJSONExtra settings (for list-based types)

Option types ​

Type is stored in msOption.type as lowerCamelCase. All 10 supported types:

typeDescriptionValue editor in settingsUI on product card
textfieldSingle-line text—InputText
textareaMultiline text—Textarea
numberfieldNumber—InputNumber
datefieldDate—DatePicker (YYYY-MM-DD)
checkboxCheckbox (Yes / No)—Checkbox
comboBooleanYes / No dropdown—Select with two values
comboboxSingle select from listString list, ordered by draggingSelect
comboMultipleMultiple select from listString list, ordered by draggingMultiSelect
comboColorsMultiple select with colorsList {value, name=hex} + ColorPickerMultiSelect with color squares
comboOptionsFree-form tags with autocomplete— (values accumulate when saving products)PrimeVue InputChips + suggestions from previously entered values

properties structure for list types ​

combobox, comboMultiple:

json
{
  "values": ["S", "M", "L", "XL"]
}

comboColors (hex stored in name, display label in value):

json
{
  "values": [
    { "value": "Red", "name": "#FF0000" },
    { "value": "Blue",   "name": "#0000FF" }
  ]
}

comboOptions does not require a preset list. On the product card you type any text — Enter, a comma, or a click outside the field commits the value. Autocomplete takes values of the same key from other products via /api/mgr/options/suggestions.

Category binding ​

Options appear only on products in bound categories. There are three ways to bind:

  • In the option dialog — check categories in the tree on the right.
  • On the category card, Options tab — add the option there.
  • In the options grid — select several options and click "Assign to categories".

Per-category caption / description override ​

Starting with v1.10.0-beta1

The "option ↔ category" link (msCategoryOption) has its own caption and description.

If an option needs a different caption in this category, set it in the category options grid (inline edit in the "Caption (for category)" column) or in the "Add option" dialog. An empty field means the global caption is used. A filled one shows up in the manager on products of that category and on the storefront via OptionLoaderService::loadForProduct / loadForProducts.

A product in several categories. If the parent and the additional categories have different captions, this order applies:

  1. Product parent category (msProduct.parent)
  2. Lower msCategoryOption.position
  3. Lower category_id (stable tiebreak)

Via PHP ​

php
/** @var \MiniShop3\Model\msOption $option */
$option = $modx->getObject(\MiniShop3\Model\msOption::class, ['key' => 'color']);
$option->setCategories([5, 10, 15]); // Category IDs

// Via service (with caption/description override support):
$optionService = $modx->services->get('ms3_option_service');
$optionService->addOptionToCategory(
    optionId: $option->get('id'),
    categoryId: 5,
    defaultValue: 'Red',
    active: true,
    position: 0,
    caption: 'Upholstery color',       // override for this category
    description: null
);

Product option values ​

Values are stored in ms3_product_options (product_id, key, value). For multi-value types (comboMultiple, comboColors, comboOptions) a product gets several rows with the same key.

Adding a value ​

php
$modx->services->get('ms3_option_service')->saveProductOptions(
    productId: 123,
    options: [
        'color' => 'Red',            // single value
        'size' => ['S', 'M', 'L'],   // multi value
    ],
    removeOther: true                     // remove keys not listed in $options
);

Getting values ​

Standard path — via OptionLoaderService:

php
$loader = $modx->services->get('ms3_option_service')->getLoader();

// Single product (with per-category caption override applied)
$data = $loader->loadForProduct(123);
// $data = [
//   'color'         => ['Red'],
//   'color.caption' => 'Upholstery color',  // override from msCategoryOption (if set)
//   'size'          => ['S', 'M'],
//   ...
// ]

// Catalog (batch, no N+1)
$byProduct = $loader->loadForProducts([123, 124, 125]);

Displaying options ​

msOptions snippet ​

Lists options for filtering:

fenom
{'msOptions' | snippet : [
    'tpl' => 'tpl.msOptions.row',
    'parents' => 5
]}

msProductOptions snippet ​

Shows options for a specific product:

fenom
{'msProductOptions' | snippet : [
    'product' => $id,
    'tpl' => 'tpl.msProductOptions.row'
]}

On the product page ​

Options are available in the template through the $options variable:

fenom
{if $options?}
<div class="product-options">
    {foreach $options as $key => $value}
    <div class="option">
        <span class="option-name">{$key}:</span>
        <span class="option-value">{$value}</span>
    </div>
    {/foreach}
</div>
{/if}

Options in the cart ​

When adding a product to the cart you can pass selected options:

JavaScript (Web API) ​

javascript
await ms3.cartAPI.add(123, 1, { color: 'Red', size: 'L' })

Display in cart ​

Options are stored on the cart line and available in the chunk:

fenom
{if $options?}
    {foreach $options as $key => $value}
        <small>{$key}: {$value}</small>
    {/foreach}
{/if}

REST API ​

All UI operations go through the Manager API (/assets/components/minishop3/connector.php, action MiniShop3\Processors\Api\Router). Permissions: mssetting_save for options, mscategory_save for category binding.

Options ​

MethodPathDescription
GET/api/mgr/optionsList. Params: start, limit, option_group_id (0 = no group), category_id, categories[]
GET/api/mgr/options/{id}One option + categories map
POST/api/mgr/optionsCreate (key, caption, type, option_group_id, properties, categories, …)
PUT/api/mgr/options/{id}Update (partial)
DELETE/api/mgr/options/{id}Delete option (product values are deleted as a cascade)
DELETE/api/mgr/options/bulkBulk delete (ids[])
POST/api/mgr/options/bulk/assignAssign options[] to categories[]
GET/api/mgr/options/typesType list
GET/api/mgr/options/treemsCategory category tree (lazy by parent)
GET/api/mgr/options/suggestionsUnique values for comboOptions (key, query, limit)
GET/api/mgr/option-groupsGroup list
POST/api/mgr/option-groupsCreate group
GET / PUT / DELETE/api/mgr/option-groups/{id}Read, update, delete
PUT/api/mgr/option-groups/positionsOrder after drag-and-drop
DELETE/api/mgr/option-groups/bulkBulk delete

Category bindings ​

MethodPathDescription
GET/api/mgr/categories/{category_id}/optionsOptions bound to category (with global_caption/global_description + category_caption/category_description override)
POST/api/mgr/categories/{category_id}/optionsAdd option to category (option_id, value, active, required, caption, description)
PUT/api/mgr/categories/{category_id}/options/{option_id}Partial update of binding (value / active / required / position / caption / description)
DELETE/api/mgr/categories/{category_id}/options/{option_id}Remove binding
POST/api/mgr/categories/{category_id}/options/sortSave new order (option_ids[])
POST/api/mgr/categories/{category_id}/options/bulkBulk actions: activate / deactivate / require / unrequire / remove for option_ids[]
POST/api/mgr/categories/{category_id}/options/duplicateCopy all bindings from another category (category_from), skipping existing

Option import ​

When importing products from CSV, options are created automatically from columns with the option. prefix — a dot, not an underscore:

pagetitlepriceoption.coloroption.size
T-shirt1500RedL
T-shirt1500BlueM

Options color and size are created automatically if missing. The default type is textfield — you can change it later in the UI.