Документация

Данные товара

Поля карточки в списке и страницы товара: цена, наличие, характеристики, варианты покупки, упаковки и услуги.

На этой странице

Товар приходит в шаблон одним набором полей. Один и тот же набор — в категории, поиске, у бренда, по метке, в подборках главной, в избранном, в сравнении, в рекомендациях и в аксессуарах. На странице товара к нему добавляется то, что нужно только карточке: описание, галерея, варианты покупки, упаковки, услуги.

Карточка в списке

Поле Что это
id, slug, name, url товар и адрес его страницы
sku код показанного варианта
sku_id номер варианта, который карточка показывает сейчас — именно он уходит в корзину
price цена в валюте витрины
compare_price зачёркнутая цена или пусто
discount_percent целый процент для метки «−N%» или пусто
unit единица продажи подписью: «шт», «м²»
pack_only товар продаётся только упаковками
rating_avg, rating_count оценка и число отзывов
summary краткое описание — для вида «списком»
in_stock есть на складе
is_available можно купить
preorder нет в наличии, но купить можно
count сколько осталось всего
count_by_warehouse остаток по складам, которые видны покупателю
sku_count остаток показанного варианта
image, image2, gallery главное фото, второе (для наведения) и до пяти снимков
matched_sku вариант, подобранный фильтром
features, features_by_code характеристики
variants, variant_skus, variants_json варианты покупки, если тема их показывает

Минимальная карточка

<article class="card" data-in-cart="{$product.id}">
    <a href="{$product.url}">
        {if $product.image}
            <img src="{thumb src=$product.image size=320}" alt="{$product.name}">
        {else}
            <img src="{$theme.assets}/img/no-photo.svg" alt="">
        {/if}
        <h3>{$product.name}</h3>
    </a>

    <div class="card__price">
        {money amount=$product.price currency=$storefront.currency}
        {if $product.compare_price}
            <s>{money amount=$product.compare_price currency=$storefront.currency}</s>
            <span class="badge">−{$product.discount_percent}%</span>
        {/if}
    </div>

    {if $product.preorder}
        <button data-add="{$product.id}" data-sku="{$product.sku_id}">Предзаказ</button>
    {elseif $product.is_available}
        <button data-add="{$product.id}" data-sku="{$product.sku_id}">В корзину</button>
    {else}
        <span class="card__out">Нет в наличии</span>
    {/if}
</article>

Товар без фото — обычное дело, ветку с заглушкой пишите сразу: иначе сетка разъедется на первом же магазине, куда фотографии ещё не загрузили.

Наличие: три поля, и путать их нельзя

Поле Что означает
in_stock товар физически есть на складе
is_available покупатель может нажать «Купить»
preorder купить можно, но товара нет — это предзаказ

Правильная разметка кнопки — сначала предзаказ, потом обычная покупка:

{if $product.preorder}Предзаказ
{elseif $product.is_available}В корзину
{else}Нет в наличии{/if}

Когда товар снова появится

Владелец может поставить у товара дату поступления. Магазин отдаёт её теме готовой — и на плитке в подборке, и на странице товара:

Поле Что это
restock_text дата словами: «05 сентября 2026»
restock_at та же дата числом, «2026-09-05» — если нужен свой формат

Поля заполнены, только пока товара нет в наличии, и только если дата ещё не прошла: проверять это в теме не нужно, достаточно {if $product.restock_text}. Товар появился — поля пусты, и строка исчезает сама.

Строку ставьте отдельно от ветки «нет в наличии»: при разрешённом предзаказе кнопка покупки остаётся, эта ветка не рисуется, а дата покупателю нужна тем более.

{if $product.restock_text}
    <div class="card__restock">Ожидается: {$product.restock_text}</div>
{/if}

Остатки

count и sku_count бывают пустыми, и это не ноль: пусто означает, что остаток у товара не считают вовсе — услуги, товары под заказ. Написать «осталось 0 шт.» в этом случае нельзя.

{if $product.count !== null && $product.count > 0}
    Осталось {qty value=$product.count} {$product.unit}
{/if}

Отрицательных чисел не бывает: перепроданный остаток приходит нулём.

Разбивка по складам:

{foreach $product.count_by_warehouse as $w}
    {$w.name}{if $w.city}, {$w.city}{/if}: {qty value=$w.count}
{/foreach}

Поля склада: id, name, city, count, priority. Склады, которые владелец не пометил как видимые покупателю, в разбивку не попадают.

Характеристики

{foreach $product.features as $f}
    <dt>{$f.name}</dt>
    <dd>
        {if $f.link}<a href="{$f.link}">{$f.value}</a>{else}{$f.value}{/if}
        {if $f.unit} {$f.unit}{/if}
    </dd>
{/foreach}

Поля: id, code, name, value, unit, link (адрес посадочной страницы, если дополнение его дало), sku_id, values.

Одна строка — одна характеристика. У «Цвета» или «Интернета» значений бывает несколько; магазин сводит их в value через запятую, а не выдаёт три строки «Цвет» подряд. Так же ведут себя списки товаров, поэтому карточка и плитка в подборке показывают товар одинаково.

Разобранные значения лежат рядом, в values: у каждого id, label, color (цвет из справочника, если задан) и link (своя посадочная страница). Перебор values даёт то, чего строка через запятую не умеет, — кружки цветов и ссылку на то значение, по которому покупатель кликнул:

{foreach $f.values as $v}{if !$v@first}, {/if}
    {if $v.color}<i class="spec-dot" style="background: {$v.color|escape}"></i>{/if}
    {if $v.link}<a href="{$v.link}">{$v.label}</a>{else}{$v.label}{/if}
{/foreach}

Ссылка у строки (link) заполняется, только когда значение одно: у характеристики с тремя значениями непонятно, куда вела бы одна ссылка на всю строку.

У товара без характеристик это пустой список, а не отсутствующее поле{if $product.features} безопасно.

Доступ по коду — когда нужна конкретная характеристика:

{if $product.features_by_code.color}
    Цвет: {$product.features_by_code.color.value}
{/if}

Страница товара

Сверх полей карточки приходит:

Поле Что это
summary, description, type описания и тип товара
qty_step, qty_min шаг и минимум количества
gallery снимки: thumb, preview, full, sku_id
variants группы опций покупки
packagings упаковки
services доп-услуги
round_services округлять ли стоимость услуг до целых
accessories группы аксессуаров
short_features первые пять характеристик
default_sku_id вариант, выбранный по умолчанию
tags, rating метки и оценка
json карта вариантов для браузера

Плюс своё: breadcrumbs, rec_cross и rec_up (рекомендации), reviews, questions.

Дробные количества

qty_step и qty_min — шаг и минимум. Если шаг не задан, оба равны единице. Товар продаётся метрами или килограммами — счётчик обязан их соблюдать:

<input type="number" data-qty
       value="{$product.qty_min}"
       min="{$product.qty_min}"
       step="{$product.qty_step}">

Варианты покупки

{foreach $product.variants as $group}
    <div class="variant" data-variant-group="{$group.feature_id}">
        <span>{$group.name}</span>
        {foreach $group.values as $v}
            <button data-variant-value="{$v.id}"
                    {if $group.type == 'color'}style="background: {$v.color}"{/if}>
                {$v.label}
            </button>
        {/foreach}
    </div>
{/foreach}

Группа: feature_id, name, type (text, color, number), values (id, label, color), selected.

Если у вариантов нет опций, но самих вариантов больше одного, магазин делает переключатель сам: по числовой характеристике, если она есть у каждого варианта и всюду разная, иначе по названиям вариантов.

Карта вариантов для браузера

<script type="application/json" data-product-skus>{$product.json nofilter}</script>

Внутри — только данные: skus, default, currency, locale, preorder. Подписи («в наличии», «в корзину») туда не кладутся — они принадлежат теме и передаются отдельно, атрибутами data-l-*.

Вариант в карте: id, sku, price, compare, available, preorder, sellable, stock, gallery, stocks, options, packagings.

sellable со значением «нет» означает «снят с продажи», а не «закончился» — подписку на поступление в этом случае предлагать не надо. У склада в stocks есть level (ok, low, critical); пороги задаёт владелец в карточке склада, не задал — всегда ok.

Не забудьте nofilter: без него в разметку уедет экранированный текст, и скрипты не разберут карту.

Упаковки

{foreach $product.packagings as $p}
    <label>
        <input type="radio" name="packaging" value="{$p.id}" {if $p.is_default}checked{/if}>
        {$p.name} — {$p.factor} {$product.unit},
        {money amount=$p.price currency=$storefront.currency}
    </label>
{/foreach}

Поля: id, name, factor (сколько единиц в упаковке), is_default, price.

Своей цены у упаковки нет — она считается как цена единицы, умноженная на коэффициент. Поэтому разойтись с корзиной ей неоткуда, и своя арифметика в шаблоне тут не нужна.

У варианта покупки бывает своя упаковка, и тогда общие не показываются.

Счётчик пачек с подписью «6 пачек — это 8,4 м²» магазин делает сам, тема даёт разметку — см. Атрибуты data-*.

Доп-услуги

{foreach $product.services as $s}
    <fieldset>
        <legend>{$s.name}{if $s.required} *{/if}</legend>
        {if $s.description}<p>{$s.description}</p>{/if}
        {foreach $s.variants as $v}
            <label>
                <input type="radio" name="service[{$s.id}]" value="{$v.id}"
                       {if $v.is_default}checked{/if}>
                {$v.name} — {money amount=$v.price currency=$storefront.currency}
            </label>
        {/foreach}
    </fieldset>
{/foreach}

Услуга: id, name, description, required, default_variant, variants. Вариант: id, name, price_type (fixed или percent), value, price, is_default. Услуга без вариантов не приходит.

price уже посчитан — для процентных услуг это сумма, а не процент. Если round_services включён, стоимость услуг округляется до целых; клиентская разметка обязана округлять так же, как сервер, иначе покупатель увидит одну сумму, а заплатит другую.

Цена и скидка

Цену, по которой товар продаётся, магазин считает в одном месте и отдаёт готовой:

  • price — цена продажи: уже со всеми каталожными акциями и уже в валюте витрины;
  • compare_price — зачёркнутая: наибольшее из «цены до акции» и «старой цены, заданной владельцем». Получилось не больше текущей — приходит пусто;
  • discount_percent — считается до перевода валюты, поэтому цифра одинакова на любой витрине.

Один товар стоит одинаково в списке, на своей странице, в карте вариантов и в корзине — это правило магазин держит проверкой, в том числе для случаев, когда у варианта своя валюта, а витрина торгует в третьей.

Подробнее — на странице Цены, количества и валюта.

Обновлено 24 августа 2026