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

Product options

Options are managed via Extras → MiniShop3 → Settings → Options.

Starting with v1.10.0-beta1

The options management interface is fully on Vue 3 + PrimeVue. ExtJS windows and grids were removed together with all Processors/Settings/Option/* and Processors/Category/Option/* processors. All operations go through REST API /api/mgr/options/* and /api/mgr/categories/{id}/options/*.

Purpose

Options are an EAV (Entity-Attribute-Value) system for extra product attributes. Add arbitrary properties without changing the database schema:

  • Color, size, material
  • Technical specifications
  • Package contents
  • Any custom properties

Interface

The page has two columns:

  • Left — MODX category tree (only resources with class_key = msCategory). Category checkboxes are independent (checking a parent does not cascade to children and vice versa). Context menu (right-click on a node): refresh branch, expand/collapse subtree, "Select all" / "Clear selection" recursively on the branch. Category name search is available.
  • Right — options grid (PrimeVue DataTable). Toolbar filters: categories selected in the tree + "Group (modCategory)". Bulk actions — assign selected options to multiple categories at once, delete.

Create and edit open in a dialog: form on the left (key, caption, description, type, group, unit), category tree on the right for binding. For combobox / comboMultiple / comboColors a value editor with drag-drop sorting appears; for comboColors — built-in ColorPicker next to the hex field.

Option fields

FieldTypeDescription
keystringUnique option key (Latin letters, digits, _, -)
captionstringDisplay name
descriptiontextOption description
measure_unitstringUnit of measure (pcs, kg, cm)
modcategory_idintGroup (MODX category) for UI grouping. 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 textInputText
textareaMultiline textTextarea
numberfieldNumberInputNumber
datefieldDateDatePicker (YYYY-MM-DD)
checkboxCheckbox (Yes / No)Checkbox
comboBooleanYes / No dropdownSelect with two values
comboboxSingle select from listString list (drag-drop)Select
comboMultipleMultiple select from listString list (drag-drop)MultiSelect
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 the user enters any text (Enter, comma, or blur → chip). Autocomplete loads values already used for the same key on other products via /api/mgr/options/suggestions.

Category binding

Options appear only on products in bound categories. Binding options:

  • In the option edit dialog — check categories in the tree on the right.
  • On category card → Options tab — add the option to that category.
  • Bulk assign — select multiple options in the grid, click "Assign to categories", pick 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 should have a different label in a category than globally — set an override in the category options grid (inline edit on "Caption (for category)") or in the "Add option" dialog. Empty means "use global". Non-empty — shown in the manager (product form in that category) and on the storefront via OptionLoaderService::loadForProduct / loadForProducts.

Conflict resolution when a product is in multiple categories: if the product belongs to several categories and each has its own override, resolution order:

  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) — multiple rows with the same key per product.

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

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 use these endpoints (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/optionsOption list. Params: start, limit, modcategory_id, category_id, categories[]
GET/api/mgr/options/{id}Detail + categories map
POST/api/mgr/optionsCreate (key, caption, type, properties, categories, …)
PUT/api/mgr/options/{id}Update (partial)
DELETE/api/mgr/options/{id}Delete option (cascade product values via model hook)
DELETE/api/mgr/options/bulkBulk delete (ids[])
POST/api/mgr/options/bulk/assignAssign options[] to categories[]
GET/api/mgr/options/typesAvailable types (localized names)
GET/api/mgr/options/treeMODX category tree (only class_key = msCategory). Lazy by parent
GET/api/mgr/options/modcategoriesFlat modCategory list for "Group" filter
GET/api/mgr/options/suggestionsUnique values by key for comboOptions autocomplete (key, query, limit)

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:

pagetitlepriceoption_coloroption_size
T-shirt1500RedL
T-shirt1500BlueM

Options color and size are created automatically if missing. By default they are created as textfield — change the type later in the UI.