Skip to content
  1. Extras
  2. MiniShop3
  3. Snippets
  4. msProducts

msProducts

Snippet for outputting a list of products. Based on pdoTools and supports all of its filtering, sorting, and pagination features.

Parameters

Main

ParameterDefaultDescription
tpltpl.msProducts.rowChunk for each product
limit10Number of products per page
offset0Skip this many products
depth10Search depth in child categories
parentscurrent resourceComma-separated parent category IDs
resourcesComma-separated specific product IDs

Sorting

ParameterDefaultDescription
sortbyidSort field
sortdirASCDirection: ASC or DESC
sortbyOptionsSort by product option (see below)
ParameterDefaultDescription
linkLink type ID (from ms3_links table)
masterMaster product ID (output products linked to it)
slaveSlave product ID (output products it is linked to)

Important: parents=0

When using the link parameter for related products, you must set parents => 0 to disable category filtering. Otherwise only related products from the same category are returned.

Filtering

ParameterDefaultDescription
whereJSON with extra conditions
optionFiltersJSON filters by product options
showZeroPricetrueShow zero-price products
showUnpublishedfalseShow unpublished
showDeletedfalseShow deleted
showHiddentrueShow hidden in menu

Extra data

ParameterDefaultDescription
includeContentfalseInclude the content field
includeTVsComma-separated TV list
includeThumbsComma-separated thumbnail sizes
includeVendorFields*Vendor fields (* = all)
includeOptionsComma-separated product options to include
formatPricesfalseFormat prices via $ms3->format->price()
withCurrencyfalseAdd currency symbol (works with formatPrices)
usePackagesComma-separated external packages (see Integration)

Output

ParameterDefaultDescription
returndataFormat: data, json, ids, sql
returnIdsfalseReturn only product IDs
toPlaceholderSave result to a placeholder
toSeparatePlaceholdersPrefix for separate placeholders
outputSeparator\nSeparator between products
tplWrapperWrapper chunk for the full output
wrapIfEmptytrueUse wrapper when result is empty
showLogfalseShow execution log

Table aliases

The msProducts snippet automatically joins related product tables. Fields from the main table (msProduct) are available without a prefix; joined tables require an alias.

Tables and their fields

TableAliasFields
msProduct— (not needed)id, pagetitle, longtitle, alias, uri, parent, createdon, publishedon, template...
msProductDataDataprice, old_price, article, weight, vendor_id, new, popular, favorite, color, size, tags...
msVendorVendorname, country, logo, address, phone, email (with includeVendorFields)

Dynamic aliases

AliasWhen availableDescription
LinkWith link + master/slaveProduct links table
{size}With includeThumbsThumbnails. Alias = size name (small, medium...)
{option}With optionFilters / sortbyOptionsProduct options. Alias = option key (color, size...)

Example

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'where' => [
        'parent' => 15,
        'Data.price:>' => 1000,
        'Data.vendor_id' => 3
    ],
    'sortby' => 'Data.price',
    'sortdir' => 'ASC'
]}

Important

Product fields (price, article, new, popular, etc.) are in the Data table. Without the alias the query fails: use 'Data.price:>' => 1000, not 'price:>' => 1000.

Examples

Basic output

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

Sort by price

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'sortby' => 'Data.price',
    'sortdir' => 'ASC'
]}

New products (sort by date)

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'sortby' => 'createdon',
    'sortdir' => 'DESC',
    'limit' => 8,
    'where' => ['Data.new' => 1]
]}
fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'where' => ['Data.popular' => 1],
    'limit' => 4
]}

Products from a specific vendor

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'where' => ['Data.vendor_id' => 5]
]}

Filter by options

fenom
{* Red products in size M *}
{'msProducts' | snippet : [
    'parents' => 0,
    'optionFilters' => ['color' => 'red', 'size' => 'M']
]}

OR condition in options

fenom
{* Red OR blue products *}
{'msProducts' | snippet : [
    'parents' => 0,
    'optionFilters' => ['color' => 'red', 'OR:color' => 'blue']
]}

Product links let you output accessories, related products, alternatives, and more.

fenom
{* Accessories for the current product *}
{'msProducts' | snippet : [
    'link' => 2,
    'master' => $_modx->resource.id,
    'parents' => 0,
    'limit' => 4,
    'tpl' => 'tpl.msProducts.related'
]}

The master parameter specifies the product for which linked items are searched. The link ID (link) matches the link type in MiniShop3 settings.

fenom
{'msProducts' | snippet : [
    'link' => 2,
    'slave' => $_modx->resource.id,
    'parents' => 0,
    'limit' => 4
]}

MiniShop3 includes these link types by default:

IDName
1Recommended (Related)
2Accessories
3Alternatives

Create new link types under Settings → Link types.

Sort by option

fenom
{* Sort by weight (numeric option) *}
{'msProducts' | snippet : [
    'parents' => 0,
    'sortby' => 'weight',
    'sortbyOptions' => 'weight:number',
    'sortdir' => 'ASC'
]}

Supported types for sortbyOptions:

TypeExampleWhen to use
number / decimalweight:numberDecimals: price, weight, volume
int / integerquantity:intIntegers: quantity, rating, age
date / datetimerelease_date:dateDates: release date, arrival date
(no type)colorText: alphabetical sort

With image thumbnails

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'includeThumbs' => 'small,medium'
]}

In the chunk you get {$small}, {$medium} — URL of the first image for each size.

Multiple product images

The includeThumbs parameter returns only the first image (position = 0). To get 2–3 images for a carousel or gallery, use leftJoin and select:

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'leftJoin' => [
        'Img1' => [
            'class' => 'MiniShop3\\Model\\msProductFile',
            'on' => 'Img1.product_id = msProduct.id AND Img1.position = 0 AND Img1.path LIKE "%/small/%"'
        ],
        'Img2' => [
            'class' => 'MiniShop3\\Model\\msProductFile',
            'on' => 'Img2.product_id = msProduct.id AND Img2.position = 1 AND Img2.path LIKE "%/small/%"'
        ],
        'Img3' => [
            'class' => 'MiniShop3\\Model\\msProductFile',
            'on' => 'Img3.product_id = msProduct.id AND Img3.position = 2 AND Img3.path LIKE "%/small/%"'
        ]
    ],
    'select' => [
        'Img1' => 'Img1.url as img1',
        'Img2' => 'Img2.url as img2',
        'Img3' => 'Img3.url as img3'
    ]
]}

In the chunk you get {$img1}, {$img2}, {$img3} — image URLs in gallery order.

Image position

position = 0 is the first image, position = 1 the second, and so on. Order is defined by the product gallery sort.

Return only IDs

fenom
{set $productIds = 'msProducts' | snippet : [
    'parents' => 5,
    'returnIds' => 1
]}

{* $productIds = "1,2,3,4,5" *}

JSON output (for AJAX)

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'return' => 'json',
    'limit' => 20
]}

With pagination (pdoPage)

fenom
{'pdoPage' | snippet : [
    'element' => 'msProducts',
    'parents' => 0,
    'limit' => 12,
    'tpl' => 'tpl.msProducts.row'
]}

{$_modx->getPlaceholder('page.nav')}

Placeholders in the chunk

In the tpl chunk all product fields are available:

Resource fields

  • {$id} — product ID
  • {$pagetitle} — title
  • {$longtitle} — long title
  • {$description} — description
  • {$introtext} — summary
  • {$content} — content (if includeContent)
  • {$alias} — URL alias
  • {$uri} — full URI
  • {$parent} — parent ID
  • {$template} — template ID
  • {$published} — published
  • {$createdon} — created date
  • {$editedon} — edited date

Product fields (Data)

  • {$article} — SKU
  • {$price} — price
  • {$old_price} — old price
  • {$weight} — weight
  • {$image} — main image
  • {$thumb} — thumbnail
  • {$vendor_id} — vendor ID
  • {$made_in} — country of origin
  • {$new} — "New" flag
  • {$popular} — "Popular" flag
  • {$favorite} — "Favorite" flag
  • {$color} — color (JSON)
  • {$size} — size (JSON)
  • {$tags} — tags (JSON)
  • {$discount} — discount percent (computed automatically)

Formatted placeholders

Placeholders with the _formatted suffix include the currency symbol or weight unit, formatted per ms3_price_format, ms3_currency_symbol, ms3_currency_position, and ms3_weight_unit:

  • {$price_formatted} — price with currency (e.g. 1 234 ₽)
  • {$old_price_formatted} — old price with currency
  • {$cost_formatted} — cost with currency
  • {$old_cost_formatted} — old cost with currency
  • {$weight_formatted} — weight with unit (e.g. 500 g)
  • {$discount_price_formatted} — unit discount with currency
  • {$discount_cost_formatted} — line discount with currency

Breaking change (v1.7.0)

cost_formatted now includes the currency symbol. Custom chunks that append currency manually to cost_formatted will show the symbol twice.

Vendor fields (Vendor)

With includeVendorFields:

  • {$vendor_position} — position
  • {$vendor_name} — name
  • {$vendor_resource_id} — resource ID
  • {$vendor_country} — country
  • {$vendor_logo} — logo
  • {$vendor_address} — address
  • {$vendor_phone} — phone
  • {$vendor_email} — email
  • {$vendor_description} — description
  • {$vendor_properties} — properties

Other

  • {$idx} — index in the result set

Example chunk

fenom
{* tpl.msProducts.row *}
<div class="product-card">
    <a href="{$uri}">
        {if $thumb?}
            <img src="{$thumb}" alt="{$pagetitle}" loading="lazy">
        {/if}

        <h3>{$pagetitle}</h3>

        {if $old_price > $price}
            <span class="old-price">{$old_price} руб.</span>
        {/if}

        <span class="price">{$price} руб.</span>

        {if $new}
            <span class="badge badge-new">New</span>
        {/if}
    </a>

    <button type="button"
            data-ms-action="cart/add"
            data-id="{$id}">
        Add to cart
    </button>
</div>

Integration with external packages

The msProducts snippet integrates with external packages (ms3Variants, msBrands, etc.) via the event system. This lets you extend product data without modifying MiniShop3 core code.

usePackages parameter

To load data from an external package, pass its name in the usePackages parameter:

fenom
{* Load product variants *}
{'msProducts' | snippet : [
    'parents' => 0,
    'usePackages' => 'ms3Variants'
]}

{* Load variants and brands *}
{'msProducts' | snippet : [
    'parents' => 0,
    'usePackages' => 'ms3Variants,msBrands'
]}

Without usePackages, external package data is not loaded — this saves resources on pages that do not need it.

Available placeholders

Each package adds its own placeholders. For example, ms3Variants adds:

PlaceholderTypeDescription
{$has_variants}boolWhether the product has variants
{$variants_count}intNumber of variants
{$variants_json}stringJSON array for JavaScript
{$variants}arrayArray of variants for Fenom

Example with variants

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'usePackages' => 'ms3Variants',
    'tpl' => 'tpl.msProducts.variants'
]}

Chunk tpl.msProducts.variants:

fenom
<div class="product-card" data-product-id="{$id}">
    <h3>{$pagetitle}</h3>
    <div class="price">{$price} руб.</div>

    {if $has_variants}
        <div class="variants-selector" data-variants='{$variants_json}'>
            {* JavaScript initializes selectors from JSON *}
        </div>
    {/if}

    <form method="post" class="ms-product-form">
        <input type="hidden" name="id" value="{$id}">
        <input type="hidden" name="variant_id" value="">
        <button type="submit">Add to cart</button>
    </form>
</div>

Events for developers

External packages use the msOnProductsLoad and msOnProductPrepare events for integration. See Events.