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

Теги шаблонов: справочник

Деньги, количества, ссылки, переводы, эскизы фотографий, подключение стилей и скриптов — все теги с примерами.

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

Кроме обычных подстановок, условий и циклов Smarty, теме доступны теги самого магазина. Ниже — все они.

Тег Для чего
{money} сумма с валютой
{qty} дробное количество
{url} адрес раздела витрины
{trans} надпись темы, в том числе со склонением
{thumb} эскиз фотографии нужного размера
{posts} записи блогов: на главной, в подвале, где угодно
{products} выбранные товары карточками
{content_block} блок, который владелец пишет один раз для многих страниц
{crm_form} форма на сайте из раздела «CRM → Формы»
{csrf_field} {csrf_token} защита формы
{captcha} проверка «я не робот»
{asset_css} {asset_js} подключить свой стиль или скрипт
{assets_head} {assets_footer} вывести собранный список файлов
{hook} место для дополнений
{dump} {dumpc} посмотреть, что пришло в шаблон

Деньги

{money amount=$product.price currency=$storefront.currency}
{money amount=$order.total currency=$order.currency}
{money amount=$shop->cartTotal() currency=$storefront.currency precision=0}
Параметр По умолчанию Что задаёт
amount 0 сумма
currency USD валюта
locale текущий язык как разделять разряды и дробную часть
precision как в валюте сколько знаков после запятой

⚠️ Валюту передавайте всегда. По умолчанию тег форматирует сумму долларами, и на тестовом магазине с валютой по умолчанию ошибку не видно — а в гривневом магазине цена уедет с долларовым знаком.

precision=0 пригодится там, где копейки ломают вёрстку: сумма корзины в шапке, подпись на карточке.

Тег только форматирует число. Считать в шаблоне не нужно и нельзя — почему, написано на странице Цены, количества и валюта.

Количества

{qty value=$item.qty} {$item.unit}     →  30,8 м²

Товары бывают дробными: метры, килограммы, площадь. Тег выводит число по-местному («30,8», а не «30.8»), отбрасывает хвостовые нули («2», а не «2,000») и не ставит разделитель разрядов — это же число подставляется в поле ввода количества, а «1 200» браузер прочитал бы как «1».

Адреса разделов

<a href="{url path='/search'}">Поиск</a>
<a href="{url path='/'}">На главную</a>
<a href="{url path='/cart' locale='uk'}">Кошик</a>
Параметр По умолчанию Что задаёт
path / путь раздела
locale текущий язык язык, на котором открыть раздел
absolute нет полный адрес с доменом вместо пути

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

Адреса товаров, категорий, брендов, меток и страниц уже приходят готовыми в данных ($product.url, $cat.url). Склеивать их руками нельзя: схему адресов выбирает владелец магазина. Подробности — на странице Адреса разделов и языки.

Надписи темы

{trans key="shop.cart"}
{trans key="shop.products_count" count=$total}
{trans key="shop.hello" name=$customer.first_name}

Строка берётся из файла темы locale/<язык>.json, а если ключа там нет — из надписей самого магазина. Не нашлось нигде — выведется сам ключ: страница не падает, но в вёрстке будет висеть shop.cart.

Всё, кроме key, считается подстановкой: {trans key="shop.hello" name="Иван"} подставит имя вместо :name в строке.

Параметр count включает выбор формы по числу — «1 товар», «2 товара», «5 товаров». Как записать такую строку, написано на странице Тексты темы и языки.

Эскизы фотографий

<img src="{thumb src=$product.image size=320}" alt="{$product.name}">
<img src="{thumb src=$product.image size=120 type='crop'}" alt="">
type Что делает
max (по умолчанию) вписывает картинку целиком в квадрат со стороной size
crop обрезает до квадрата size × size
width задаёт ширину, высота считается сама
height задаёт высоту, ширина считается сама

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

Записи блогов

Тег {posts} ставит статьи блога в любое место оформления: на главную, в подвал, на страницу товара, в текст информационной страницы. Работает он одним из двух способов, и способ выбирает параметр assign. Перепутать их — самая частая ошибка с этим тегом, поэтому сначала о них.

Способ первый: тег рисует сам. Без assign тег сразу выводит карточки статей там, где стоит:

{posts limit=3}

Каждую запись тег рисует шаблоном partials/blog-card.html — тем же, которым тема рисует ленту блога, — и оборачивает список в <div class="posts-embed">. Если такого шаблона в вашей теме нет, карточка берётся из базовой темы default. Переменную тег при этом не создаёт, и перебирать после него в {foreach} нечего.

Способ второй: тег отдаёт данные. С assign тег ничего не выводит, а кладёт список записей в переменную с указанным именем. Разметку вы пишете сами:

{posts limit=3 assign=freshPosts}

{foreach $freshPosts as $post}
    <a href="{$post.url}">{$post.title}</a>
{/foreach}

Тот же список отдаёт помощник $shop->posts():

{assign var=freshPosts value=$shop->posts(3)}
{assign var=freshPosts value=$shop->posts(3, 'news')}
{assign var=freshPosts value=$shop->posts(['blog' => 'news', 'sort' => 'random', 'limit' => 3])}

Помощнику аргументы передаются по порядку: первым — сколько записей нужно, вторым — адрес блога. Остальные условия передаются набором в квадратных скобках, с теми же именами, что у тега. Именованных параметров вида $shop->posts(limit=3) Smarty в вызове метода не понимает и сообщает об ошибке в шаблоне. Если записи нужны только в этом же шаблоне, проще поставить тег с assign.

Условия подборки у тега и помощника одни и те же:

{posts limit=3}                                 три свежие записи карточками
{posts blog="news" limit=4}                     только из блога «Новости»
{posts category="guides" sort="random" limit=3} вперемешку из рубрики
{posts limit=4 assign=fresh}                    отдать записи в переменную
Параметр По умолчанию Что задаёт
blog все блоги витрины блог: номер или адрес; несколько — через запятую
category все рубрики рубрика: номер или адрес; несколько — через запятую
with_children да брать ли записи вложенных рубрик
full нет отдать рубрики записей с полным описанием
tag все метки метка: номер или адрес; несколько — через запятую
ids — номера записей; при sort по умолчанию показываются в этом же порядке
exclude — номера записей, которые показывать не нужно
limit 4 сколько записей показать
offset 0 сколько первых записей пропустить
sort feed в каком порядке (ниже)
pinned — true — только закреплённые записи
assign — имя переменной: тег отдаёт записи, а не разметку
template partials/blog-card.html свой шаблон карточки; запись приходит в него переменной $post
class posts-embed класс обёртки
sort Порядок
feed как в ленте блога: закреплённые сверху, дальше от свежих к старым
new самые свежие
old самые старые
popular которые чаще читают
random вперемешку: при каждом заходе свой набор
title по алфавиту

Магазин показывает только то, что видит покупатель: черновики, записи из архива, записи с будущей датой публикации и записи выключенного блога в подборку не попадают. Берутся записи блогов этой витрины — блог соседней витрины не покажется, даже если написать его адрес. Выключенная рубрика в условии category не найдётся, а если выключена главная рубрика самой записи, запись в подборку попадёт, но без рубрики: поле category у неё будет пустым.

Записей по условиям не нашлось — тег без assign не выводит ничего, и {if} вокруг него не нужен. С assign в переменную попадёт пустой список, поэтому свою разметку оборачивайте в {if $freshPosts}, чтобы на странице не остался пустой заголовок блока.

Блог, рубрику и метку называют как удобнее — адресом или номером:

{posts blog="news" limit=3}   адрес виден в ссылке: /news/
{posts blog=3 limit=3}        номер написан в панели, рядом с готовым кодом

Адрес нагляднее в шаблоне, номер надёжнее: ссылку на блог владелец может поменять, а номер останется прежним. Номер блога написан на его странице в разделе «Статьи», номер рубрики — на странице рубрики, там же стоит и готовая строка с кнопкой «Копировать».

Если на витрине нет ни одного включённого блога, тег без assign ничего не выводит, а с assign отдаёт пустой список.

Блок «Новости» на главной

Так устроен блок свежих статей в базовой теме, файл blocks/news.html. Блок берёт три последние записи из блогов витрины, ставит рядом ссылку на все новости и не показывается вовсе, пока статей нет. Его можно взять за основу своего блока: классы и разметку замените своими, а обращения к записям оставьте как есть.

{posts limit=3 assign=freshPosts}
{assign var=blogs value=$shop->blogs()}

{if $freshPosts}
<section class="news">
    <div class="section-head">
        <h2 class="section-title">{trans key="storefront.news"}</h2>
        {if $blogs}
            <a class="section-more" href="{$blogs[0].url}">
                {trans key="storefront.all_news"}
                <i class="fa-solid fa-arrow-right"></i>
            </a>
        {/if}
    </div>

    <div class="news__grid">
        {foreach $freshPosts as $p}
            <article class="news-card">
                <a class="news-card__cover" href="{$p.url}">
                    {if $p.category}<span class="news-card__cat">{$p.category.name}</span>{/if}
                    {if $p.cover_thumb}
                        <img class="news-card__img" src="{$p.cover_thumb}" alt="{$p.title|escape}" loading="lazy">
                    {else}
                        <i class="fa-regular fa-newspaper news-card__ico"></i>
                    {/if}
                </a>
                <div class="news-card__body">
                    {if $p.date_label}
                        <span class="news-card__date"><i class="fa-regular fa-calendar"></i> {$p.date_label}</span>
                    {/if}
                    <a class="news-card__title" href="{$p.url}">{$p.title}</a>
                    {if $p.summary}<p class="news-card__excerpt">{$p.summary}</p>{/if}
                    <a class="news-card__more" href="{$p.url}">
                        {trans key="storefront.read_more"}
                        <i class="fa-solid fa-arrow-right"></i>
                    </a>
                </div>
            </article>
        {/foreach}
    </div>
</section>
{/if}

$shop->blogs() отдаёт блоги витрины в том порядке, в котором они стоят в панели, поэтому $blogs[0].url — адрес первого блога. Если блогов несколько и ссылка нужна на определённый, найдите его по адресу:

{foreach $shop->blogs() as $b}
    {if $b.slug == 'news'}<a href="{$b.url}">{trans key="storefront.all_news"}</a>{/if}
{/foreach}

Надписи storefront.news, storefront.all_news и storefront.read_more взяты из файлов надписей базовой темы. В своей теме заведите такие же или свои — как это делается, написано выше, в разделе «Надписи темы».

Что есть у записи

Каждая запись из {posts … assign=…} и $shop->posts() — это карточка того же вида, что получает лента блога и шаблон partials/blog-card.html:

Поле Что внутри
url адрес статьи на витрине — его ставят в href
title заголовок
summary анонс, короткий текст для карточки; пусто, если владелец его не написал
cover обложка в полном размере; пусто, если не загружена
cover_thumb эскиз обложки для карточки; пусто, если обложки нет
category.name название главной рубрики записи
category.url адрес страницы этой рубрики
date_label дата подписью на языке витрины: «17 сентября 2026»
date_iso полная дата со временем — для <time datetime="…">
date дата в виде 2026-09-17 — если нужен свой формат
author автор; пусто, если не указан
reading_time сколько минут займёт чтение
views сколько раз статью прочитали
is_pinned запись закреплена вверху ленты
id, slug номер и адрес записи

Поле category целиком пустое, если у записи нет рубрики или рубрика выключена. Поэтому {$p.category.name} всегда оборачивают в {if $p.category}, как в примере выше: без проверки запись без рубрики сломает страницу. Даты считаются в часовом поясе магазина. Остальные данные страниц блога описаны на странице Данные страниц.

Частые ошибки

Тег без assign и {foreach} по своей переменной. Это самая частая причина жалобы «limit не работает». Шаблон написан так:

{$posts = [[ 'title' => '…', 'date' => '07 липня', 'cat' => 'Огляди' ]]}

{posts limit=3}
<div class="news__grid">
    {foreach $posts as $p}
        <a href="#">{$p.title}</a> {$p.cat} {$p.date}
    {/foreach}
</div>

Тег {posts limit=3} без assign честно нарисовал три карточки — шаблоном partials/blog-card.html, над сеткой новостей, а если стили темы этих карточек не знают, их легко не заметить. В переменную тег ничего не положил, поэтому {foreach} перебирает массив, написанный строкой выше, и сколько бы вы ни меняли limit, на странице ничего не меняется. Правильно — попросить данные и перебирать именно их:

{posts limit=3 assign=freshPosts}

{if $freshPosts}
<div class="news__grid">
    {foreach $freshPosts as $p}
        <a href="{$p.url}">{$p.title}</a>
        {if $p.category}{$p.category.name}{/if}
        <time datetime="{$p.date_iso}">{$p.date_label}</time>
    {/foreach}
</div>
{/if}

Заглушка из макета осталась в шаблоне. Массив вида {$posts = [[ 'title' => … ]]} или {assign var=posts value=[…]} нужен только верстальщику, пока у магазина нет статей. В готовой теме его нужно удалить: если он останется, покупатели будут видеть выдуманные новости, а настоящие статьи не появятся, сколько бы их ни написали.

Переменная $posts. На страницах ленты блога, рубрики и метки (blog.html, blog-category.html, blog-tag.html) в $posts уже лежат записи текущей страницы ленты. Если блок новостей стоит в шапке или подвале и перебирает $posts, на страницах блога он покажет ленту, а не свою подборку. А {posts … assign=posts}, поставленный прямо в шаблон страницы блога, подменит саму ленту. Берите для подборки своё имя, например freshPosts.

Имена полей из макета. В макете поля обычно называются по-своему, а у записи магазина имена такие:

В макете У записи магазина
cat category.name, проверять через {if $p.category}
date с готовой подписью вроде «07 липня» date_label; в date лежит дата вида 2026-07-07
href="#" href="{$p.url}"
img, image cover_thumb для карточки, cover в полном размере
text, excerpt summary

Поле, которого у записи нет, — это ошибка в шаблоне, и страница с таким блоком не откроется. Если после перехода на настоящие статьи главная перестала открываться, первым делом сверьте имена полей с таблицей: чаще всего в разметке остался {$p.cat} или {$p.category.name} без проверки {if $p.category}.

Записей меньше, чем limit. limit — это верхняя граница, а не обещание. Если в блогах витрины опубликовано две статьи, при limit=3 их будет две. Черновики, записи из архива, записи с датой публикации в будущем и записи блога другой витрины в подборку не попадают. Проверьте, что статья опубликована и дата её публикации уже наступила.

Товары карточками

{products ids="12,7,3"}
{products ids="12,7,3" template="partials/product-card-small.html"}

Тег показывает выбранные товары карточками темы. Чаще всего его ставит не верстальщик, а владелец магазина: в редакторе текста он отмечает товары, и в описание категории или в статью попадает такая вставка. Товары выводятся в том порядке, в котором их выбрали.

Параметр По умолчанию Что задаёт
ids — номера товаров через запятую
limit 24 сколько товаров показать
template partials/product-card.html свой шаблон карточки
class products-embed класс обёртки

Подборки товаров для главной берутся иначе — у помощника $shop->products(), чтобы владелец менял их состав в панели, не трогая шаблон. Об этом написано на странице Помощник $shop.

Блоки

Блок — кусок текста или вёрстки, который владелец пишет один раз и показывает на многих страницах: преимущества магазина, условия доставки, баннер акции. Заводится он в панели, в разделе «Витрины и контент → Блоки», у каждого блока есть код. Правка блока сразу меняется везде, где он стоит.

{content_block code="ship-info"}
{content_block code="custom_products" list="viagra" limit=12}
{content_block code="category-`$category.slug`-info"}

В шаблон темы блок ставится тегом. В текст страницы, описание товара или категории владелец вставляет его кнопкой «Блок» в редакторе — параметры, если нужны, пишутся там же, под «Дополнительно».

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

Параметры. Всё, кроме code, становится переменными внутри блока: при list="viagra" limit=12 в тексте блока доступны {$list} и {$limit}, а все параметры вместе — {$params}. Так один блок показывает на разных страницах разные данные. Пример блока «Товары списка» (текст блока — вставка Smarty):

{$found = $shop->productList($list)}
{if $found}
    <ul>
    {foreach $found.products as $p}
        {if $p@iteration > $limit|default:12}{break}{/if}
        <li><a href="{$p.url}">{$p.name|escape}</a></li>
    {/foreach}
    </ul>
{/if}

Параметры видны только самому блоку: на страницу и во вложенные блоки они не попадают. Имена params, shop, storefront, theme, seo, customer, contacts, cookies, get, request, action и smarty заняты — такой параметр пропускается.

Внутри блока работают вставки Smarty: теги дополнений, {products}, {posts}, помощник $shop. Когда блок вызван из шаблона темы, ему видны переменные этой страницы и работает {include} шаблонов темы. Блок не может вставить сам себя — такая вставка просто пропускается. Смена темы блоки не трогает: они хранятся в магазине, а не в теме.

Формы из CRM

Формы «Задать вопрос», «Обратная связь» и другие владелец собирает в панели, в разделе «CRM → Формы»: поля, тексты на каждом языке и что делать с заявкой. Как это устроено для владельца, написано на странице Формы на сайте. В тему форма ставится тегом:

{crm_form code="question"}
{crm_form code="question" class="form-wide"}
{crm_form code="wholesale" template="partials/crm-form-compact.html"}
Параметр По умолчанию Что задаёт
code — код формы, его видно на странице формы под кнопкой «Код для темы»
class пусто свой класс, приходит в шаблон как $form.class
template partials/crm-form.html свой шаблон формы в теме

В текст страницы, описание товара или блок владелец вставляет форму кнопкой «Форма» в редакторе, и туда уходит тот же тег. Тег ничего не выводит, если формы с таким кодом нет, она выключена или не работает на этой витрине, а также если путь в template не похож на путь к шаблону: допустимы латинские буквы, цифры, _, - и /, в конце .html. Если такого шаблона нет ни в теме, ни в стандартной теме, тег тоже ничего не выводит. Сбой формы страницу не роняет.

Отправку без перезагрузки, ошибки у полей и «спасибо» даёт сам магазин: вместе с формой он сам подключает /storefront/crm-form.js и /storefront/crm-form.css. Поэтому форма работает в любой теме, а тема отвечает только за разметку и оформление.

Шаблон partials/crm-form.html

Форму рисует шаблон темы partials/crm-form.html или тот, что задан в template. Если в теме такого шаблона нет, магазин берёт его из стандартной темы default: форма появится и в теме, написанной до форм. Кроме $form, шаблону видны $storefront, $theme, $auth, $min_order, $below_min, $free_shipping_from, $free_shipping_left, $cookies, $get, $request и переменные, которые добавляют дополнения. Переменных страницы ($shop, $customer, $product и других) в шаблоне формы нет. Все ключи $form есть всегда.

Ключ Что в нём
$form.code код формы
$form.id id для разметки: crm-form-question; вторая копия той же формы на странице — crm-form-question--2
$form.class класс из параметра class
$form.title «Заголовок над формой», может быть пустым
$form.submit надпись на кнопке
$form.action адрес отправки, уже с языком страницы
$form.fields поля списком (скрытых полей в нём нет)
$form.consent галочка согласия на обработку данных: name, id, text, checked, error; null, если форма согласие не спрашивает
$form.sent форму только что отправили без скрипта, пора показать «спасибо»
$form.again_url эта же страница с пустой формой — для ссылки «отправить ещё одно» без скрипта
$form.success текст «спасибо»
$form.error общая ошибка после отправки без скрипта, иначе null
$form.protection служебные поля формы одной строкой
$form.i18n надписи на языке страницы: required, sending, failed, choose, again

Каждое поле в $form.fields:

Ключ Что в нём
key ключ поля, постоянный
name имя для <input name>: fields[ключ], у флажков fields[ключ][]
id id поля: id формы, дефис и ключ
type text, textarea, phone, email, number, date, select, radio, checkboxes или consent
input вид <input type>: tel, email, number, date или text
autocomplete name, tel, email или off — по тому, куда поле записывается в карточке
label, placeholder подпись и подсказка на языке страницы
required обязательное ли поле
width full или half
maxlength предел длины, null у видов без предела
value значение: после неудачной отправки без скрипта — то, что ввёл покупатель, а вошедшему покупателю — его имя, телефон и почта
checked отмечен ли флажок-согласие
options варианты списка, переключателей и флажков: value, label, selected
error ошибка поля после отправки без скрипта, иначе null

Что обязательно в разметке. Оформление меняйте свободно, но оставьте:

  • {$form.protection nofilter} внутри <form>. Там ключ защиты формы, метка времени, адрес страницы, невидимая ловушка для роботов и капча, если она включена. Без этой строки отправка не пройдёт или уйдёт в спам. Печатать её нужно с nofilter, иначе вместо полей на странице окажется их текст.
  • Имена полей ввода — ровно {$f.name}, у галочки согласия — {$form.consent.name} со значением 1.
  • Опоры data-crm-*, по которым скрипт и стили магазина находят части формы:
Опора Где стоит
data-crm-form="{$form.code}" на самой <form>; там же data-sending (надпись кнопки на время отправки) и data-failed (текст на случай сбоя отправки)
data-crm-body обёртка всего, что прячется после успешной отправки
data-crm-field="{$f.key}" обёртка поля, получает класс is-invalid; у согласия ключ consent
data-crm-error="{$f.key}" место для текста ошибки поля
data-crm-message место для общей ошибки формы
data-crm-success сообщение «спасибо»
data-crm-submit кнопка отправки
data-crm-again «отправить ещё одно», необязательно
data-crm-success-text, data-crm-submit-label необязательно: куда внутри «спасибо» и кнопки ставить текст, если там есть значок
data-crm-grid необязательно, только для стилей: сетка полей, где поле с data-width="half" занимает полстроки
data-crm-choice необязательно, только для стилей: подпись вокруг флажка или переключателя

Пока форма отправляется, на ней стоит класс is-sending, а кнопка выключена. Подсказки браузера об ошибках скрипт отключает: ошибки приходят от магазина на языке витрины и встают у своих полей.

Стили магазина в crm-form.css отвечают за раскладку и запасной вид полей, кнопки и сообщений. Они написаны через :where(), и правило темы их перебивает. Исключений два: элемент с атрибутом hidden внутри формы скрыт всегда, а ловушку для роботов .crm-form__trap тема не переопределяет. Цвет акцента (рамка поля в фокусе, кнопка, флажки) тема задаёт переменной --crm-form-accent.

Без скрипта форма уходит обычной отправкой и возвращает покупателя на ту же страницу, к форме. Результат приходит в тот же $form: sent, error, ошибки полей и введённые значения. Поэтому шаблон должен показывать их сам, как в стандартной теме, где data-crm-success скрыт атрибутом hidden, пока $form.sent ложно. Такая страница в общий кеш не попадает.

Событие после отправки. Когда магазин принял отправку, на форме срабатывает событие cartix:form-sent. Оно всплывает до document, в detail.code — код формы. При ошибке события нет. Отправку робота магазин тоже «принимает», чтобы не подсказывать ему, поэтому событие считает отправки, дошедшие до магазина, а не настоящие заявки. Его удобно отдавать счётчику целей:

document.addEventListener('cartix:form-sent', (event) => {
    window.dataLayer?.push({ event: 'form_sent', form: event.detail.code });
});

Если форма появляется на странице позже, например в окне, которое тема подгружает по клику, оживите её вызовом CartiX.initForms(контейнер), где контейнер — элемент, внутри которого лежит форма (не сама <form>). Форму, которая уже работает, вызов не трогает. Функция есть, только когда на странице уже загружен /storefront/crm-form.js, то есть когда на ней с самого начала стоит хотя бы одна форма: скрипты внутри подгруженного куска браузер при вставке не выполняет.

Свой шаблон проще всего начать с копии themes/default/templates/partials/crm-form.html: перенесите её в свою тему и меняйте оформление, не трогая опоры.

Формы

<form method="post" action="/reviews/submit">
    {csrf_field}
    …
    {captcha form="reviews"}
    <button type="submit">Отправить</button>
</form>

{csrf_field} ставит скрытое поле — без него форма не пройдёт. {csrf_token} отдаёт тот же ключ строкой; в каркас его вставлять не надо — мета-тег csrf-token печатает магазин, а скриптам ключ приходит в CartiX.csrf.

{captcha form="…"} сам спрашивает, включена ли проверка «я не робот» для этой формы, и рисует нужный виджет. Проверка выключена — тег не выводит ничего, и ветку {if} вокруг него писать не надо.

Подробнее — на странице Формы витрины.

Печатные формы заказа

{assign var="invoice" value={order_document order=$order code="order.invoice"}}
{if $invoice}
    <a href="{$invoice}" target="_blank" rel="noopener">Счёт на оплату</a>
{/if}

Тег отдаёт ссылку на печатную форму заказа: счёт, накладную, гарантийный талон — любой бланк из «Настройки» → «Печатные формы». По ссылке открывается готовая к печати страница, та же, что печатает менеджер: номер и дата документа выдаются один раз, поэтому покупатель и бухгалтерия видят одну и ту же бумагу.

order — заказ кабинета ($order на странице account/order.html), его номер или заказ, пришедший в шаблон. code — код бланка: он написан в самой форме, в «Настройки» → «Печатные формы», строкой «Код бланка» рядом с названием, и копируется кнопкой. pdf=true — та же бумага файлом:

{assign var="pdf" value={order_document order=$order code="order.delivery_note" pdf=true}}
{if $pdf}<a href="{$pdf}">Скачать накладную</a>{/if}

Печать по клику

Обычная ссылка открывает бумагу и оставляет покупателя один на один с вопросом, чем её напечатать: на телефоне печать спрятана в меню «Поделиться». Скажите print=true — страница откроет окно печати сама, как только загрузится:

{assign var="doc" value={order_document order=$order code="order.invoice" print=true}}
{if $doc}
    <a href="{$doc}" target="_blank" rel="noopener">Распечатать счёт</a>
{/if}

Печать вызывается после полной загрузки, а не сразу: иначе лист уходит в принтер без логотипа и печати, которые не успели загрузиться. С pdf=true параметр не нужен и не действует — там окно печати открывает программа просмотра файла.

Если не хочется уводить покупателя на другую вкладку, печатайте в скрытой рамке — это те же несколько строк, библиотека для этого не нужна:

<a class="js-print" href="{order_document order=$order code="order.invoice"}">Распечатать счёт</a>

{literal}
<script>
document.addEventListener('click', function (e) {
    var link = e.target.closest('.js-print');
    if (!link) return;

    e.preventDefault();

    var frame = document.createElement('iframe');
    frame.style.cssText = 'position:fixed;right:0;bottom:0;width:0;height:0;border:0';
    frame.src = link.getAttribute('href');
    frame.onload = function () { frame.contentWindow.focus(); frame.contentWindow.print(); };
    document.body.appendChild(frame);
});
</script>
{/literal}

Рамку не убирайте сразу после print() — пока открыто окно печати, документ обязан оставаться на странице. Один и тот же приём работает для любого бланка: достаточно поменять код формы в ссылке.

Документ на другом языке

По умолчанию бумага выходит на языке заказа: покупатель оформлял по-украински — и счёт будет украинский, даже если сейчас он смотрит магазин по-русски. Нужен другой язык — скажите об этом прямо:

{assign var="en" value={order_document order=$order code="order.invoice" locale="en"}}
{if $en}<a href="{$en}">Invoice in English</a>{/if}

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

{assign var="doc" value={order_document order=$order code="order.invoice" locale=$storefront.locale}}
{if $doc}<a href="{$doc}">Счёт на оплату</a>{/if}

Вместе с pdf это тоже работает:

{order_document order=$order code="order.invoice" locale="en" pdf=true}

Просить можно только язык, на котором работает сам магазин («Основные настройки» → «Региональные настройки»). Незнакомый код тег молча пропускает мимо ушей — бумага выйдет на языке заказа, а не пустой. Если бланк на нужном языке не написан, магазин возьмёт его на своём основном: счёт на чужом языке лучше пустого листа.

Где показывать документы и какие — решает тема: в кабинете, на странице «Заказ оформлен», в письме. Магазин отвечает за две вещи, в которых тема ошибиться не может:

  • ссылку получает только тот, чей это заказ — вошедший покупатель со своим заказом либо гость, оформивший его в этой же сессии;
  • выключенный или пустой бланк ссылки не даёт вовсе, иначе кнопка вела бы на «страница не найдена».

В обоих случаях тег возвращает пустую строку — поэтому ссылку прячут обычным {if}, как в примерах выше.

Адрес построен на личном ключе заказа — том же, по которому покупатель попадает на оплату. Своего секрета у документа нет, и такую ссылку можно положить в письмо. Помните: кто ссылку получил, тот документ и откроет.

Стили и скрипты

{block name="page_assets"}
    {asset_css file="css/product.css"}
    {asset_js file="js/product.js" defer=true}
{/block}
Параметр Что делает
file путь внутри assets/ вашей темы
inline вставить содержимое прямо в страницу, а не ссылкой
key имя, по которому файл не подключится дважды
head / footer куда выводить
остальные уезжают атрибутами в <link> или <script>

{assets_head} и {assets_footer} выводят собранный список — последними, когда страница собрана целиком, поэтому файл можно объявить в любом месте шаблона. Соседние файлы магазин склеивает в один сжатый. Что попадает в склейку и как это проверить — на странице Стили, скрипты и картинки темы.

Место для дополнений

{hook name="product.buybox.end" product=$product}

Сюда дополнения выводят свою разметку. Подписчиков нет — тег не выводит ничего. Каждое дополнение вызывается отдельно и в защите от ошибок: упавшее не выводит ничего, страница остаётся живой.

Всё, что передано параметрами, уезжает дополнению — поэтому полезно передавать контекст: товар, позицию корзины. Полный список мест — на странице Точки для дополнений.

Посмотреть, что пришло в шаблон

{dump}                    всё, что доступно этому шаблону
{dump var=$product}       одно значение
{dumpc var=$product}      то же самое в консоль браузера
{$product|dump nofilter}  привычной записью

Видит только сотрудник магазина. Покупателю блок не покажется, даже если тег забыли в теме. Пароли, ключи и подписи в выводе заменяются звёздочками, глубина ограничена шестью уровнями, длинные списки и строки обрезаются.

Теги дополнений

Дополнение может завести собственный тег — он появится во всех темах сразу. Установленные сейчас:

Тег Дополнение
{slider id="home-hero"} Слайдеры и баннеры
{slider_data id="home-hero" assign=hero} то же, отдаёт данные в переменную
{oneclick_button product=$product} Купить в один клик
{back_in_stock product=$product} Сообщить о поступлении
{callback_button} Обратный звонок
{shorturl url=$product.url} Короткие ссылки

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

Что приносит установленное дополнение, видно двумя способами: в его описании в разделе «Дополнения» и по папке templates/plugins/<дополнение>/ в вашей теме — дополнение с собственной разметкой кладёт шаблоны прямо туда.

Обработчики значений

В Smarty 5 в шаблон нельзя писать любую функцию языка. Магазин возвращает безопасный набор:

Текст. ucfirst, lcfirst, ucwords, trim, ltrim, rtrim, str_pad, str_replace, str_contains, str_starts_with, str_ends_with, sprintf, strrev, mb_strlen, mb_substr, mb_strtolower, mb_strtoupper, urlencode, rawurlencode, htmlspecialchars_decode.

Числа. abs, ceil, floor, intval, floatval, max, min, intdiv.

Списки. array_keys, array_values, array_slice, array_reverse, array_sum, array_unique, array_flip.

Оформление. tel превращает телефон из настроек в ссылку: <a href="tel:{$phone|tel}">. color_tone отвечает light или dark для цвета из настроек — по нему тема выбирает светлый или тёмный логотип (подробнее).

Отладка. dump, dumpc.

Сверх этого работает всё, что Smarty умеет сам: count, round, implode, substr, json_encode, date_format, escape, default, upper, nl2br, truncate, strip_tags.

{$product.name|mb_strtoupper}
{$product.summary|truncate:120:'…'}
{$product.price|default:0}
{$order.created_at|date_format:'%d.%m.%Y'}

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

Экранирование

Всё, что выводится, экранируется само. {$product.name} безопасен всегда, даже если в названии товара есть < или кавычки.

Там, где данные содержат готовую разметку, нужен nofilter:

{$page.content nofilter}       содержимое информационной страницы
{$product.description nofilter} описание товара
{$product.json nofilter}       карта вариантов покупки для браузера

Забыли nofilter — покупатель увидит текст с &lt;p&gt; вместо абзацев.

Чего в теме нет

Теги {sum}, {in_words}, {number} и {profile} относятся к печатным бланкам — счетам, накладным, чекам, — а не к витрине. В шаблоне темы их нет. Про них написано в разделе Печатные бланки.

Обновлено 13 октября 2026