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