Данные товара
Поля карточки в списке и страницы товара: цена, наличие, характеристики, варианты покупки, упаковки и услуги.
На этой странице
Товар приходит в шаблон одним набором полей. Один и тот же набор — в категории, поиске, у бренда, по метке, в подборках главной, в избранном, в сравнении, в рекомендациях и в аксессуарах. На странице товара к нему добавляется то, что нужно только карточке: описание, галерея, варианты покупки, упаковки, услуги.
Карточка в списке
| Поле | Что это |
|---|---|
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— считается до перевода валюты, поэтому цифра одинакова на любой витрине.
Один товар стоит одинаково в списке, на своей странице, в карте вариантов и в корзине — это правило магазин держит проверкой, в том числе для случаев, когда у варианта своя валюта, а витрина торгует в третьей.
Подробнее — на странице Цены, количества и валюта.