Skip to content
  1. Компоненты
  2. MigxPageConfigurator
  3. Разметка вёрстки

Разметка вёрстки

Разметка — основа всей работы с компонентом. Вы берёте готовую вёрстку и навешиваете на нужные элементы атрибуты data-mpc-*: помечаете, где секция, где редактируемое поле, где список, где вызов сниппета. Переписывать вёрстку не нужно — только пометить. По этой разметке нарезка раскладывает шаблон на файлы-секции и чанки, на месте полей ставит плейсхолдеры Fenom, а контент переносит в админку.

Подсказка

Скопируйте в проект файл assets/components/migxpageconfigurator/css/mpc.css — тогда IDE будет подсказывать доступные атрибуты прямо во время разметки.

Осторожно

У всех атрибутов префикс data-mpc-.

Прежде чем размечать секции, готовят каркас, общий для всех страниц шаблона: индексный файл (он отдаёт готовую страницу на фронт) и секцию-обёртку (wrapper), внутрь которой выводятся все остальные секции. Порядок такой:

  1. индексный файл шаблона — pages/index.tpl;
  2. базовая секция-обёртка — wrapper;
  3. заголовок шаблона;
  4. разметка секций.

Индексный файл шаблона

Каждый MODX-шаблон, который создаёт компонент, в теле (поле «Содержимое шаблона») просто подключает один общий файл:

fenom
{include 'file:pages/index.tpl'}

Этот файл — pages/index.tpl — единый «движок вывода» для всех страниц. Готовый образец поставляется в пакете: core/components/migxpageconfigurator/examples/pages/index.tpl. Его копируют в проект — в папку pages/ (путь резолвится pdoTools как @FILE относительно pdotools_elements_path). Образец минимальный:

fenom
{set $path = '!getParsedConfigPath' | snippet:[]}
{if $path}
    {include $path}
{/if}

Как это работает:

  • сниппет getParsedConfigPath возвращает путь к запечённой странице — файлу parsed/<id ресурса>.tpl. В нём все секции собраны в один файл, но запечена прежде всего структура: статичные значения подставлены, а динамика остаётся «живой» и вычисляется уже при отдаче страницы — переводимые значения ({'…' | lexicon}), статические секции (через getStaticSection), некэшируемые сниппеты и Fenom-логика. Поэтому, например, переключение языка не требует перенарезки. Если такого файла ещё нет, страница рендерится на лету и сохраняется (см. Рендеринг);
  • если путь получен — страница подключается из готового файла, без парсинга конфига на каждый запрос;
  • при желании в файл добавляют ветку {else} с выводом {$_modx->resource.content} — запасной вариант для ресурсов, к которым компонент не применён (так сделано в демо-проекте).

Подсказка

Образец почти не зависит от проекта — обычно его копируют один раз и больше не трогают. Главное помнить: тело каждого шаблона = {include 'file:pages/index.tpl'}, а всю реальную вёрстку задают обёртка и секции.

Базовая секция-обёртка (wrapper)

Wrapper — это «рама» страницы: <!DOCTYPE>, <head> с подключением стилей и скриптов, шапка, подвал — всё, что общее для всех страниц. Внутри неё есть зона контента, куда по очереди выводятся остальные секции конкретной страницы.

Помечается как обычная секция, но с именем из системной настройки mpc_wrapper_name (по умолчанию wrapper):

html
<div data-mpc-section="wrapper" class="wrapper" data-mpc-unwrap="1">
  <!-- ...шапка, общие блоки... -->

  <main class="wrap">
    <!--CONTENT-->
    {if $sections}
        {foreach $sections as $section}
            {$section}
        {/foreach}
    {else}
        {$content}
    {/if}
    <!--CONTENT-->
  </main>

  <!-- ...подвал... -->
</div>

Что здесь важно:

  • data-mpc-section="wrapper" — имя должно совпадать с mpc_wrapper_name. В эту секцию «заворачиваются» все остальные: каждый шаблон ссылается на файл обёртки (@FILE sections/wrapper.tpl), а уже она внутри выводит секции страницы.
  • data-mpc-unwrap="1"опционально. Если поставить, сам тег-обёртка (<div class="wrapper">) в результат не попадёт — останется только его содержимое. Удобно, когда лишняя обёртка в разметке не нужна.
  • Вывод секций делает Fenom-цикл {foreach $sections as $section}: если у страницы есть секции ($sections — массив, собранный компонентом), они выводятся по порядку; если секций нет — показывается обычное поле content ресурса (запасной вариант). Именно этот цикл и выполняет всю работу.
  • Парные <!--CONTENT--> — это обычные HTML-комментарии: код их не обрабатывает и от них ничего не зависит. Они стоят лишь как визуальная пометка контентной зоны для того, кто читает шаблон, — можно убрать без последствий.

Этот блок цикла пишется один раз в обёртке и дальше не меняется — отдельные секции его не касаются.

Что доступно в обёртке

Обёртка обрабатывается как Fenom-чанк, в который передаются все данные ресурса. Поэтому в ней доступны плейсхолдеры:

  • $sections — массив секций страницы (что это за массив — см. врезку ниже);
  • поля ресурса$id, $pagetitle, $longtitle, $description, $introtext, $content, $template и прочие. Контентные поля наследуются от «типа страницы»: если у ресурса поле пустое, подставляется значение типа. Какие поля участвуют — задаёт системная настройка mpc_editable_resource_fields (по умолчанию longtitle,description,introtext,content,menutitle); структурные и защищённые поля (id, alias, uri, template, parent, pagetitle…) каскадом не затрагиваются и всегда берутся у самого ресурса;
  • $tvs — TV ресурса. Каскад от «типа страницы» работает так же: TV типа подставляется там, где у ресурса собственного значения нет;
  • $contacts — данные контактов;
  • $sbp_id — id ресурса со статичными блоками, $cp_id — id ресурса с контактами;
  • $gallery — галерея товара, если установлен miniShop2 и у ресурса есть файлы.

Как добавить условие в рендер

Здесь важный нюанс: $sections — это массив уже готовых HTML-строк секций, отсортированных по позиции. В цикле {$section} — это отрендеренная строка, у неё нет метаданных (имени, типа). Поэтому условие «обернуть конкретную секцию по её имени» прямо в обёртке написать нельзя — по имени там не за что зацепиться (доступен только числовой индекс, а он меняется при перестановке секций).

Правильные способы добавить условную вёрстку:

  1. В разметке самой секции — если обёртка/условие относятся к конкретной секции, их задают прямо в её вёрстке (атрибутом data-mpc-if на элементе, обычным тегом-обёрткой). Это основной путь: вёрстка остаётся при секции и переживает любой порядок.
  2. Через событие mpcOnGetSectionHtml — когда нужно вмешаться в готовый HTML конкретной секции по её имени (например, обернуть только секцию features). Событие срабатывает на каждую секцию до сборки страницы и даёт её имя, данные и HTML.

Пример плагина, который оборачивает одну секцию по имени:

php
// Плагин на событие mpcOnGetSectionHtml.
// Параметры события ($section, $html) доступны в плагине напрямую.
if (($section['section_name'] ?? '') === 'features') {
    // вернуть изменённый HTML обратно в рендер
    $modx->event->returnedValues['html'] = '<div class="features-wrap">' . $html . '</div>';
}

Доступны и более ранние события — mpcOnBeforeParseConfig (вмешаться в набор секций до рендера) и mpcOnBeforeRender (поправить весь resourceData). Полный список с параметрами — в разделе Системные события.

Подсказка

Если условие касается одной секции и его можно выразить версткой — делайте это в секции (data-mpc-if или тег-обёртка). К событиям прибегайте, когда логику нельзя зашить в шаблон: она зависит от данных ресурса, внешних условий и т.п.

Заголовок шаблона

Файл шаблона должен начинаться со специального комментария — из него компонент создаёт MODX-шаблон и ресурс-«тип страницы»:

html
<!--##{"templatename":"Главная","wrapper":"wrapper","icon":"icon-home","include":"file:pages/index.tpl"}##-->

Доступные ключи (содержимое — валидный JSON):

КлючОбяз.НазначениеПо умолчанию
templatenameдаимя создаваемого MODX-шаблона. Без него заголовок игнорируется
wrapperнетимя секции-обёртки; в «Описание» шаблона пишется ссылка @FILE sections/<wrapper>.tplwrapper
iconнетCSS-класс иконки шаблонаicon-gears
includeнетпуть к файлу, подключаемому в теле шаблона ({include '…'})file:pages/index.tpl
template_var_idsнетid TV через запятую для привязки к шаблону (в дополнение к настройке mpc_tmplvar_ids)

Тело шаблона (content) и «Описание» (description) компонент формирует сам — из include и wrapper соответственно; задавать их в заголовке не нужно. Имя ресурса-типа собирается как «Шаблон <templatename>».

Не очищайте поле «Описание» у MPC-шаблонов

В «Описании» сгенерированного шаблона лежит служебная ссылка на файл обёртки (@FILE sections/<wrapper>.tpl) — по ней рендер находит wrapper. Это не свободный текст. Если очистить или изменить поле, рендер не найдёт обёртку и соберёт страницу из «голых» секций встык — без <head>, подключения CSS/JS, шапки и подвала (а если секций нет — выведет поле content ресурса). Причём этот сломанный результат закэшируется в parsed/. Чинится возвратом значения @FILE sections/<wrapper>.tpl или повторной нарезкой.

Осторожно

Формат <!--## … ##--> менять нельзя — он распознаётся регулярным выражением, а содержимое внутри должно быть валидным JSON. Значения include и wrapper подставляются в шаблон, поэтому компонент их санирует (защита от инъекции Fenom и path traversal) — не используйте в них кавычки, .. и спецсимволы.

Секция

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

html
<section data-mpc-section="hero" data-mpc-name="Главный экран" id="{$id}">
  ...
</section>
  • data-mpc-section — машинное имя секции (ключ конфигурации). По нему секции матчатся при переиспользовании — наследование от «типа страницы», копирование, мерж при сохранении. Важное следствие: если на одной странице окажутся две секции с одинаковым data-mpc-section, при сохранении вторая перепишет первую (они схлопнутся в одну). Чтобы вывести одну и ту же секцию дважды с разным контентом, пометьте вторую data-mpc-copy и задайте ей свой data-mpc-lexicon (см. Копирование секций);
  • data-mpc-name — человекочитаемое имя, которое увидит менеджер в админке;
  • id="{$id}" — плейсхолдер идентификатора секции. Ставьте его, если секция может встретиться на странице несколько раз, чтобы у блоков были уникальные id.

У секции есть несколько вариантов разметки: обычная, статическая (data-mpc-static — одинаковая на всех страницах, см. Статические секции) и копия (data-mpc-copy — та же вёрстка со своим отдельным контентом, см. Копирование секций). Полный разбор всех вариантов с примерами «вход → выход» — в этих разделах.

Дополнительные атрибуты секции

Кроме базовых, на сам элемент секции можно навесить:

  • data-mpc-lexicon — префикс лексикона секции: под ним группируются переводы её полей. По умолчанию равен имени секции. Задают его явно прежде всего при копировании (data-mpc-copy): у оригинала и копии одинаковое имя секции, поэтому без разных префиксов их переводы слились бы в один набор. Указав копии свой data-mpc-lexicon, вы разводите переводы оригинала и копии (см. Лексиконы):
    html
    <section data-mpc-section="features" data-mpc-lexicon="features" ...>            <!-- оригинал -->
    <section data-mpc-section="features" data-mpc-copy="..." data-mpc-lexicon="features_demo" ...>  <!-- копия -->
  • data-mpc-field="bg_img"фоновое изображение. bg_img — зарезервированное имя поля: компонент берёт URL из background[-image] в style элемента, скачивает и обрезает картинку, делает поле редактируемым и подключает ленивую загрузку фона (на рендере — data-lazy="bg:…"). Это работает на любом элементе с таким полем (секция, div и т.д.), а не только на секции:
    html
    <section data-mpc-section="howto" data-mpc-field="bg_img"
             style="background-image: url('img/hero-bg.jpg')">
    Важно: фон срабатывает именно от имени bg_img. Под любым другим именем элемент обработается как обычное поле (возьмётся его внутреннее содержимое), а background в style будет проигнорирован.
  • data-mpc-if — условный вывод всей секции. В значении — условие на Fenom (пустое значение = «когда поле непустое»). Секция целиком обернётся в {if …}…{/if}:
    html
    <section data-mpc-section="promo" data-mpc-if="$show_promo">…</section>
  • data-mpc-unwrap — вывести только содержимое секции, без её тега-обёртки. Так, например, размечают базовую обёртку, когда лишний <div class="wrapper"> в результате не нужен.

Поля конфигурации

Поле — это редактируемое значение внутри секции: заголовок, абзац, ссылка, картинка, список карточек. Все поля секции хранятся в одном TV mpc_config (JSON, грид MIGX в админке), а в файле секции на месте поля встаёт плейсхолдер Fenom. Значение из вёрстки становится значением по умолчанию — тем, что менеджер увидит и сможет поменять.

Ниже — по нарастанию сложности: простые поля (одно значение), медиа-поля (картинки/видео), списки (повторяющиеся блоки).

Примеры приведены для режима без лексиконов

Все примеры «выход» ниже показывают вывод при выключенных лексиконах (mpc_use_lexicons = Нет) — на месте поля встаёт простой плейсхолдер {$title}.

Если режим лексиконов включён, переводимые значения выносятся в lexicon-файлы, а в файле секции встаёт отложенный плейсхолдер с ##:

##'{$title}' | lexicon}

## здесь не опечатка: он откладывает перевод на финальный проход рендера, когда лексикон ресурса уже загружен. На этом проходе ## превращается в {, а {$title} — в конкретный ключ, и в запечённой странице (parsed/) получается нормальный Fenom-тег {'<префикс>_title' | lexicon} (префикс — имя секции или data-mpc-lexicon). Менять ## вручную нельзя. Логика разметки и имена полей при этом те же — отличается только форма плейсхолдера. Подробнее — в разделе Лексиконы и мультиязычность.

Простые поля

Самый частый случай — одно значение. Помечается атрибутом data-mpc-field="<имя>". Откуда взять значение, компонент определяет по элементу:

  • ссылка или любой элемент с атрибутом href → берётся href;
  • элемент с дочерними тегами → берётся их HTML;
  • иначе → внутренний текст.

В файле секции на месте поля встаёт {$<имя>}:

html
<!-- вход -->
<h1 data-mpc-field="title">Заголовок страницы</h1>
<a  data-mpc-field="link" href="/contacts">Связаться</a>
html
<!-- выход -->
<h1>{$title}</h1>
<a href="{$link}">Связаться</a>

Значения («Заголовок страницы», /contacts) уезжают в mpc_config как дефолты полей title и link.

Редактируемый текст ссылки

У ссылки из примера выше редактируется только URL, а текст «Связаться» остаётся в вёрстке. Ссылка — особый случай: на <a data-mpc-field> значением берётся href, поэтому для текста нужно отдельное поле. Вложите внутрь ещё одно поле с data-mpc-unwrap: внешний <a> заберёт href, вложенный элемент отдаст свой текст отдельным полем, а unwrap уберёт лишний тег-обёртку:

html
<!-- вход -->
<a data-mpc-field="link" href="/contacts">
  <span data-mpc-field="link_text" data-mpc-unwrap="1">Связаться</span>
</a>
html
<!-- выход -->
<a href="{$link}">{$link_text}</a>

Теперь в админке два поля — link (URL) и link_text (подпись).

Внимание

Это приём именно для ссылки (href + текст). Так нельзя собирать произвольную вложенность полей: повторяющиеся блоки (карточки, списки) размечаются не вложением data-mpc-field в data-mpc-field, а через прокладку data-mpc-item — см. Списки.

Тип поля

data-mpc-field создаёт поле, а data-mpc-ftype задаёт, как оно редактируется в админке (если тип не указать — будет обычный текст). На вывод тип почти не влияет: плейсхолдер всё равно {$<имя>} — меняется только виджет ввода в гриде MIGX. Доступные типы и примеры разметки:

ftypeПоле в админкеПример разметки (вход)
text (по умолчанию)однострочный текст<span data-mpc-field="name">Имя</span>
textareaмногострочный текст<p data-mpc-field="note" data-mpc-ftype="textarea">…</p>
richtextвизуальный редактор<div data-mpc-field="body" data-mpc-ftype="richtext">…</div>
numberчисло<span data-mpc-field="qty" data-mpc-ftype="number">12</span>
dateдатапикер<span data-mpc-field="day" data-mpc-ftype="date">2026-06-09</span>
emaile-mail<span data-mpc-field="mail" data-mpc-ftype="email">a@b.ru</span>
urlURL<span data-mpc-field="site" data-mpc-ftype="url">https://…</span>
fileфайловый браузер источника<span data-mpc-field="doc" data-mpc-ftype="file">/files/x.pdf</span>
tag / autotagтеги<span data-mpc-field="tags" data-mpc-ftype="tag">a,b,c</span>
optionрадио-кнопки (один из)+ data-mpc-values (см. ниже)
checkboxфлажки (несколько)+ data-mpc-values (см. ниже)
listboxвыпадающий список (один)+ data-mpc-values (см. ниже)
listbox-multipleсписок с мультивыбором+ data-mpc-values (см. ниже)

У всех текстовых типов выход одинаковый — отличается только редактор в админке. Например для richtext:

html
<!-- вход -->
<div data-mpc-field="body" data-mpc-ftype="richtext">Текст статьи</div>
<!-- выход -->
<div>{$body}</div>

Подсказка

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

Формат не проверяется

ftype задаёт только виджет ввода (для date — датапикер, для file — файловый браузер источника). Валидации формата нет: если вписать значение руками, в date/email/url/file сохранится любой текст, и на фронт он выйдет как есть. Нужна проверка — настраивайте validation в определении поля (MIGX).

Поля с выбором: data-mpc-values

Типы listbox, option, checkbox, listbox-multiple показывают набор вариантов. Список вариантов задаёт data-mpc-values. Хранится в поле ключ выбранного варианта, а подпись уходит в лексикон. Три формата записи:

1. Подпись и ключ через == (когда значение в БД должно отличаться от подписи):

html
<!-- вход -->
<span data-mpc-field="theme" data-mpc-ftype="listbox"
      data-mpc-values="Светлая==light||Тёмная==dark">light</span>
<!-- выход -->
{$theme}

В админке — выпадайка «Светлая / Тёмная», в конфиг пишется ключ light.

2. Просто список (ключ = нормализованное значение, транслит + нижний регистр):

html
<!-- вход -->
<span data-mpc-field="size" data-mpc-ftype="listbox"
      data-mpc-values="Маленький||Средний||Большой">Средний</span>
<!-- выход -->
{$size}

3. Динамические опции из БД — @SELECT (значение, начинающееся с @, идёт в MIGX как есть):

html
<!-- вход -->
<span data-mpc-field="rel" data-mpc-ftype="listbox"
      data-mpc-values="@SELECT `pagetitle`, `id` FROM modx_site_content WHERE published=1">5</span>
<!-- выход -->
{$rel}

Мультивыбор (listbox-multiple, checkbox) — выбранные ключи хранятся одной строкой через ||. На рендере их перебирают:

html
<!-- вход -->
<span data-mpc-field="tags" data-mpc-ftype="listbox-multiple"
      data-mpc-values="Новинка==new||Хит==hot||Скидка==sale">new||hot</span>
<!-- выход (пример обхода значений) -->
{foreach $tags | split:'||' as $t}{$t} {/foreach}

Внимание

Опции берутся из data-mpc-values при нарезке. Поменяли варианты в вёрстке — перенарежьте секцию, иначе в админке останется старый набор. Это часть структуры конфига, поэтому опции обновляются при любой нарезке — ключ обновления контента (--upd) для этого не нужен (он влияет только на перезапись значений полей).

Откуда берутся типы — mpc_base

Готовые прототипы полей (определения типов с их настройками) хранятся в служебной MIGX-конфигурации mpc_base (её имя задаёт настройка mpc_base_section_name) — это и есть «список типов». data-mpc-ftype ссылается на прототип по имени, и компонент клонирует его под ваше поле. Полный перечень типов с описанием вложенных полей каждого — в Справочнике типов полей.

Тип поля определяется по приоритету:

  1. Имя поля совпадает с полем в mpc_base → берётся его определение как есть (data-mpc-ftype при этом игнорируется — имя приоритетнее). Так подключаются предопределённые поля.
  2. Иначе — явный data-mpc-ftype: если в mpc_base есть прототип с таким именем, клонируется он; если нет — компонент создаёт определение простого поля «на лету», подставив указанный тип ввода (text/textarea/number/listbox…) в стандартный каркас MIGX. «Простое» — это одиночное значение без вложенных полей (в отличие от медиа и списков).
  3. Иначе — автоопределение по тегу: <img>/<picture> → изображение, <video>/<audio> → медиа, всё остальное → text.

То есть если тип не указать, обычный элемент станет полем text, а медиа-тег — медиа-полем. Ничего не ломается — это разумный дефолт.

Подпись и описание поля в гриде задают data-mpc-fcap (подпись) и data-mpc-fdesc (описание):

html
<span data-mpc-field="qty" data-mpc-ftype="number"
      data-mpc-fcap="Количество" data-mpc-fdesc="Сколько штук на складе">10</span>

Полный разбор типов и их настроек — в разделе Создание и обновление элементов.

Другие атрибуты поля

На data-mpc-field навешиваются дополнительные атрибуты-модификаторы. Каждый — со своим эффектом.

Условный вывод — data-mpc-if. Поле попадёт в результат только при выполнении условия. Пустое значение if = «когда поле непустое»:

html
<!-- вход -->
<a data-mpc-field="link" data-mpc-if="$link" href="/contacts">Связаться</a>
<!-- выход -->
{if $link}<a href="{$link}">Связаться</a>{/if}

Условие можно задать и явно: data-mpc-if="$price > 0". Полезно для необязательных кнопок/баннеров — не заполнил менеджер, блок не выводится. Тот же атрибут работает на секциях и вызовах сниппетов.

Условие и лексиконы — момент вычисления

По умолчанию условие вычисляется на раннем проходе (при нарезке/сохранении). Простое data-mpc-if="$title" (проверка непустоты) так и работает. Но если условие зависит от переведённого значения, на раннем проходе лексикон ещё не загружен — нужно отложить условие на фронт атрибутом data-mpc-symbol="##". Плюс сам перевод в условии оборачивается как ('{$поле}' | lexicon) — кавычки вокруг {$поле} нужны, чтобы на раннем проходе подставился ключ, а перевод применился уже на фронте.

Рабочий пример — вывести заголовок, только если перевод начинается с «Вёрстка»:

html
<h1 data-mpc-field="title" data-mpc-symbol="##"
    data-mpc-if="('{$title}' | lexicon) | match: 'Вёрстка*'">Вёрстка → нарезка → редактируемая страница</h1>

Без data-mpc-symbol="##" такое условие посчиталось бы до загрузки лексикона и сработало бы неверно.

Служебное значение — data-mpc-remove. Значение собирается в поле, но сам элемент из вёрстки убирается. Классика — блок инлайновых стилей секции:

html
<!-- вход -->
<div data-mpc-field="inline_styles" data-mpc-remove>padding: 80px 0; --color:#fff;</div>
<!-- выход: значение ушло в поле inline_styles, а сам <div> в результат не попал -->

Удобно, когда значение надо собрать и использовать в другом месте, а носитель в разметке не нужен. Собранное inline_styles обычно подставляют в <style> секции, привязав к её id ({$id} — плейсхолдер идентификатора секции):

html
<section data-mpc-section="hero" id="{$id}">
  <style>
    #{$id} { {$inline_styles} }
  </style>

</section>

Так менеджер правит отступы/цвета секции прямо в админке (поле inline_styles), а стили применяются точечно к нужному блоку по его id.

Несколько тегов в одно поле — data-mpc-unwrap. В значение попадает внутренний HTML без тега-обёртки:

html
<!-- вход -->
<div data-mpc-field="advantages" data-mpc-unwrap>
  <h3>Почему мы</h3>
  <ul><li>Быстро</li><li>Надёжно</li></ul>
</div>
<!-- выход -->
{$advantages}

В поле advantages окажутся оба тега (<h3> и <ul>), а временный <div> в вёрстку не войдёт. data-mpc-unwrap также вспомогательно используется в редактируемом тексте ссылки — там он снимает обёртку у вложенного поля. Работает data-mpc-unwrap и с вызовом сниппета/чанком.

Медиа-поля

Если поле — медиа-элемент, компонент собирает все его атрибуты в один объект и обращается к ним через индекс [0]:

html
<!-- вход -->
<img data-mpc-field="img" src="assets/img/hero.png" width="1200" height="500" alt="Главный экран">
html
<!-- выход -->
<img src="{$img[0].src}" width="{$img[0].width}" height="{$img[0].height}" alt="{$img[0].alt}">

Тип медиа определяется по тегу:

  • <img>src, alt, title, width, height;
  • <picture><img> (основное изображение) + набор <source> (со srcset, media, размерами). Внутри <picture> ставьте <img> первым — нарезка берёт основное изображение именно из первого <img> (полный разбор — в Справочнике типов полей);
  • <video> / <audio>src, poster и пр.

Если URL в src внешний (http…), файл скачивается в источник; при заданных width/height подключается обрезка (если задан сниппет-обрезчик) и ленивая загрузка. Медийные модификаторы (data-mpc-nothumb — без обрезки, data-mpc-nolazy — без ленивой загрузки, data-mpc-thumb — свои параметры обрезки) и фон (bg_img) подробно разобраны в разделе Работа с медиа.

Несколько картинок

Если нужно несколько изображений, не заводите специальных «медиа-списков» — проще дать полям обычные имена image_1, image_2 и т.д. (фиксированный набор) или использовать список через data-mpc-item (динамическое число элементов).

Списки

Когда блок повторяется (карточки, слайды, пункты меню) — это список. В отличие от простого поля, число элементов не фиксировано: менеджер добавляет строки в админке. В вёрстке это размечается так: контейнер помечается полем data-mpc-field, каждый повтор — атрибутом data-mpc-item, а поля внутри повтора получают индекс уровня — data-mpc-field-1.

Простой список

html
<!-- вход -->
<div class="cards" data-mpc-field="cards">
  <div class="card" data-mpc-item>
    <img data-mpc-field-1="img" src="assets/img/1.png" alt="Первая карточка">
    <h3 data-mpc-field-1="title">Первая карточка</h3>
    <p  data-mpc-field-1="text">Текст первой карточки</p>
  </div>
  <div class="card" data-mpc-item>
    <img data-mpc-field-1="img" src="assets/img/2.png" alt="Вторая карточка">
    <h3 data-mpc-field-1="title">Вторая карточка</h3>
    <p  data-mpc-field-1="text">Текст второй карточки</p>
  </div>
</div>
html
<!-- выход -->
{foreach $cards as $item1 index=$i1 last=$l1}
  <div class="card">
    <img src="{$item1.img[0].src}" alt="{$item1.img[0].alt}">
    <h3>{$item1.title}</h3>
    <p>{$item1.text}</p>
  </div>
{/foreach}

Что здесь важно:

  • вёрстка берётся только из первого data-mpc-item, а контент — из всех (в админку попадут обе карточки);
  • поля внутри списка адресуются через переменную элемента — $item1 для первого уровня ({$item1.title}, {$item1.text});
  • компонент сам оборачивает блок в {foreach} — цикл писать руками не нужно;
  • index=$i1 last=$l1 компонент добавляет сам: это номер текущей итерации и флаг последней (нужны для условий по позиции — см. data-mpc-cond);
  • в админке контейнер превращается в MIGX-грид — одна строка = одна карточка.

Список в списке (двухуровневый)

Список можно вложить в список: внутри элемента (data-mpc-item) размечаем ещё один контейнер-поле, его повторы помечаем data-mpc-item-1, а поля в них — data-mpc-field-2. Индекс везде растёт на единицу с уровнем вложенности:

html
<!-- вход -->
<div data-mpc-field="blocks">
  <div data-mpc-item>
    <h3 data-mpc-field-1="title">Блок</h3>
    <ul data-mpc-field-1="items">
      <li data-mpc-item-1>
        <span data-mpc-field-2="label">Пункт</span>
      </li>
    </ul>
  </div>
</div>
html
<!-- выход -->
{foreach $blocks as $item1 index=$i1 last=$l1}
  <h3>{$item1.title}</h3>
  <ul>
    {foreach $item1.items as $item2 index=$i2 last=$l2}
      <li><span>{$item2.label}</span></li>
    {/foreach}
  </ul>
{/foreach}

Поле верхнего уровня — {$item1.title}, вложенный список — {$item1.items}, его элементы — $item2 ({$item2.label}). В админке это вложенные MIGX-гриды: грид блоков, внутри каждого блока — грид пунктов.

Типы и атрибуты внутри списка

Внутри списка работает всё то же, что и у простых полей — типы (data-mpc-ftype), значения (data-mpc-values), медиа, модификаторы (if/remove/unwrap). Разница одна: к полю добавляется префикс элемента $itemN. Например медиа-поле в списке остаётся объектом с индексом [0]:

html
<!-- вход -->
<img data-mpc-field-1="photo" data-mpc-ftype="image" src="a.jpg" alt="Фото">
<!-- выход -->
<img src="{$item1.photo[0].src}" alt="{$item1.photo[0].alt}">

Внимание

alt берётся из самой картинки ({$item1.photo[0].alt}), а не из соседнего поля — не подставляйте в alt чужой плейсхолдер вроде {$item1.title}.

Несколько атрибутов относятся именно к спискам.

  • data-mpc-max — ставится на контейнер списка и ограничивает число строк, которые можно добавить в гриде (MIGX maxRecords):
    html
    <div data-mpc-field="slides" data-mpc-max="5">
      <div data-mpc-item>…</div>
    </div>
    В админке нельзя будет добавить больше 5 слайдов.
  • data-mpc-lim и data-mpc-off — ставятся на контейнер списка и задают параметры вывода {foreach}: lim — сколько элементов показать (limit), off — сколько пропустить с начала (offset). Компонент подставит подходящий вариант цикла:
    html
    <div data-mpc-field="news" data-mpc-lim="3" data-mpc-off="1">
      <div data-mpc-item>…</div>
    </div>
    Здесь на странице выведутся 3 элемента, начиная со второго (первый пропущен). В отличие от data-mpc-max (лимит в админке), lim/off ограничивают вывод на фронте.
  • data-mpc-cond — выводить элемент списка по условию итерации. В значении — условие с переменными $i (номер итерации) и $l (номер последней); знаки сравнения кодируются urlencode() (ограничение парсера). Ставится на data-mpc-item:
    html
    <div data-mpc-item data-mpc-cond="$i %3C 3">…</div>  <!-- %3C = «<» : первые две итерации -->

Глубина вложенности

Технически вложенность не ограничена (список в списке в списке…): индекс растёт — data-mpc-item-2, data-mpc-field-3, переменные $item2, $item3. Но на практике дальше второго уровня мы не проверяли и не рекомендуем: и поддерживать такую разметку тяжело, и есть риск необкатанных краевых случаев. Если данные просятся глубже двух уровней — обычно стоит пересмотреть структуру секции.

Сниппеты и чанки

Это самый «продвинутый» уровень разметки — динамика и переиспользуемые куски вёрстки.

Вызов сниппетаdata-mpc-snippet="ИмяСниппета|пресет". Элемент заменяется вызовом сниппета, параметры берутся из пресета по ключу (см. Работа со сниппетами):

html
<ul data-mpc-snippet="pdoMenu|main_menu">...</ul>

После нарезки на месте элемента будет вызов сниппета с параметрами из пресета main_menu.

К вызову сниппета применимы те же модификаторы, что и к полям:

  • data-mpc-if — вызывать сниппет только при условии (вызов обернётся в {if …}…{/if}):
    html
    <ul data-mpc-snippet="pdoMenu|main_menu" data-mpc-if="$show_menu">...</ul>
  • data-mpc-unwrap — подставить результат сниппета без тега-обёртки (в вывод уйдёт только сам вызов, без оборачивающего элемента).

Вырезание в чанкdata-mpc-chunk="путь/к/chunk.tpl". Содержимое элемента вырезается в отдельный чанк. Работает и само по себе, и внутри вызова сниппета (так размечают шаблон строки или обёртки списка):

html
<li data-mpc-chunk="chunks/menuitem.tpl">
  <a href="{$uri}">{$menutitle}</a>
</li>

Как подключить вырезанный чанк обратно, задают парные атрибуты:

  • data-mpc-include{include "file:chunks/promo.tpl"}:
    html
    <div data-mpc-chunk="chunks/promo.tpl" data-mpc-include>
      <h2>Промо информация</h2>
    </div>
  • data-mpc-parse → вызов parseChunk с параметрами из значения атрибута:
    html
    <div data-mpc-chunk="chunks/promo_2.tpl" data-mpc-parse="[$id => 1]">
       <h2>Промо информация из ресурса ID={$id}</h2>
    </div>
    превратится в {$_modx->parseChunk("@FILE chunks/promo.tpl", [$id => 1])}.

Повторное использование чанка

Один и тот же чанк нередко подключают в нескольких местах (шапка меню, карточка товара). Чтобы не нарезать его файл повторно, помечайте такие места data-mpc-copyфайл чанка тогда не пишется, только ставится подключение (include/parse). Файл нарезается лишь из «оригинала» — элемента без data-mpc-copy.

html
<!-- оригинал: и нарезается в файл, и подключается -->
<div data-mpc-chunk="promo.tpl" data-mpc-include>
  <h2>Промо</h2>
</div>

<!-- копия: только подключает promo.tpl, файл не трогает -->
<div data-mpc-chunk="promo.tpl" data-mpc-copy data-mpc-include></div>

В значении data-mpc-copy можно (необязательно) указать имя файла оригинала — как подсказку, где он лежит; на подключение это не влияет. Если значение не указано — элемент всё равно считается копией, файл не нарезается.

Внимание

data-mpc-copy без data-mpc-include или data-mpc-parse — бессмысленен: файл не нарезается и подключение не ставится, элемент просто остаётся в вёрстке. Это, скорее всего, ошибка разметки.

Есть и неявный механизм: если на нескольких элементах указан один и тот же data-mpc-chunk без data-mpc-copy, файл нарезается из первого вхождения, остальные одноимённые пропускаются (в пределах одного запуска нарезки). Он работает, но зависит от порядка/режима нарезки — поэтому для повторного использования лучше явно помечать копии data-mpc-copy.

Момент выполнения сниппета/парса регулирует data-mpc-symbol ({ — выполнить при нарезке/сохранении, ## — отложить на фронт; по умолчанию ##). Некэшируемые сниппеты (AjaxForm, mFilter2) и плейсхолдеры, доступные только на фронте, оставляйте с ##. Подробно — в разделе Работа с чанками.

TV-поля

data-mpc-tv — записать значение в TV (Template Variable) ресурса. Если такого TV ещё нет, компонент создаст и синхронизирует его по разметке. На рендере значение берётся из TV ресурса:

html
<!-- вход -->
<span data-mpc-tv="subtitle">Подзаголовок</span>
<!-- выход -->
<span>{$resource.tvs.subtitle}</span>

В отличие от полей конфигурации (которые лежат в общем mpc_config), TV — это отдельная нативная переменная ресурса. Используйте data-mpc-tv, когда значение нужно как самостоятельный TV (например для выборок pdoTools по нему).

Комбинации с TV

data-mpc-tv поддерживает те же модификаторы типа и значений, что и поля конфигурации, — они задают, каким будет создаваемый TV:

  • data-mpc-ftype — тип создаваемого TV. Поддерживаются нативные типы MODX TV: text, textarea, richtext, number, date, email, url, listbox, listbox-multiple, option, checkbox, file, tag/autotag, image. Если не указать — тип определяется по тегу (<img>/<picture>image, иначе text).

    html
    <span data-mpc-tv="published_at" data-mpc-ftype="date">2026-06-09</span>

    Обратите внимание: у TV нет составных медиа-типов (picture/video/audio) — любое медиа становится одной картинкой (image). Это отличие от полей конфигурации, где медиа собирает все атрибуты в объект.

  • data-mpc-ftype + data-mpc-values — TV-список с опциями (опции уходят в elements TV):

    html
    <span data-mpc-tv="badge" data-mpc-ftype="listbox"
          data-mpc-values="Новинка==new||Хит==hot">new</span>
  • data-mpc-if — условный вывод TV (оборачивается в {if …}), как и у обычных полей:

    html
    <span data-mpc-tv="subtitle" data-mpc-if="$resource.tvs.subtitle">…</span>
  • data-mpc-fcap / data-mpc-fdesc — подпись (caption) и описание (description) создаваемого TV:

    html
    <span data-mpc-tv="published_at" data-mpc-ftype="date"
          data-mpc-fcap="Дата публикации" data-mpc-fdesc="Когда показать на сайте">2026-06-09</span>

    Если data-mpc-fcap не указан, подписью становится имя TV. У уже настроенного TV подпись/описание перезаписываются, только когда атрибуты заданы в разметке — пустые значения существующие настройки не затирают.

Вывод TV другого ресурса

Маркеры внутри блока с data-mpc-res относятся к чужому ресурсу (по его id) — такой TV компонент не создаёт и не синхронизирует, только выводит. Это используется, когда на странице нужно показать значение TV другого ресурса.

Внимание

TV синхронизируется из шаблона (тип/опции), только если он в категории пакета mpc. Чужой TV (созданный вне mpc) компонент не трогает по структуре — лишь привязывает к шаблону и пишет значение. Так правки чужих TV из разметки не ломают их настройки.

Как компонент создаёт и обновляет TV из разметки — в разделе Создание и обновление элементов.

Поля ресурса

data-mpc-rfield — записать значение в нативное поле ресурса (longtitle, description, introtext, content, menutitle). На рендере — {$resource.<поле>}:

html
<!-- вход -->
<h2 data-mpc-rfield="longtitle">Расширенный заголовок</h2>
<!-- выход -->
<h2>{$resource.longtitle}</h2>

Какие поля разрешено писать, задаёт системная настройка mpc_editable_resource_fields (по умолчанию longtitle,description,introtext,content,menutitle); структурные и защищённые поля (id, alias, uri, template, parent, pagetitle и др.) от записи защищены (mpc_protected_resource_fields). Удобно, когда расширенный заголовок/описание/контент страницы редактируются прямо в вёрстке, но должны лечь в стандартные поля ресурса, а не в конфиг.

Подсказка

pagetitle по умолчанию защищёнdata-mpc-rfield="pagetitle" запись не выполнит. Если заголовок страницы нужно писать из вёрстки, перенесите pagetitle из mpc_protected_resource_fields в mpc_editable_resource_fields.

Дополнительные атрибуты

  • data-mpc-if — условный вывод поля (как у обычных полей): оборачивает в {if …}.
    html
    <div data-mpc-rfield="introtext" data-mpc-if="$resource.introtext">…</div>

Как поле ресурса хранится при включённых лексиконах

Если режим лексиконов включён, поле ресурса переводимое (его content-type в mpc_translated_content, обычно text) и не исключено — значение не пишется в колонку как есть. Вместо этого:

  • значение уходит в словарь ресурса под ключом mpc_resource_<поле> (для текущего языка);
  • в саму колонку ресурса пишется этот ключ (mpc_resource_<поле>);
  • на рендере значение резолвится через | lexicon — поэтому показывается перевод для активного языка.

При первом сохранении нового значения ключ синхронизируется во все языки (в остальных языках появляется плейсхолдер) и попадает в список непереведённых (.pending). Ввод перевода на другом языке снимает ключ из этого списка. Это та же логика, что работает на нарезке (общий механизм синхронизации) — поэтому правки из редактора и перенарезка дают согласованный результат.

Без лексиконов (или для непереводимого/исключённого поля) значение пишется в колонку как обычно.

Редактирование полей другого ресурса (cross-resource)

Иногда на странице выводятся данные другого ресурса — например в листинге (карточка статьи, превью), который собирает сниппет вроде pdoResources. Чтобы такие поля можно было править прямо на странице, блок оборачивают атрибутом data-mpc-res="<id>" (id целевого ресурса):

html
<!-- чанк строки листинга: $id, $longtitle, $uri — поля ресурса из выборки -->
{set $lexicons = $id | lexiconsarr: $template}
<li data-mpc-res="{$id}">
  <a href="{$uri}">
    <span data-mpc-rfield="longtitle">{$lexicons[$longtitle] ?: $longtitle}</span>
  </a>
</li>

Здесь lexiconsarr возвращает массив лексиконов конкретного ресурса (по его id и шаблону). Так как при лексиконах в колонке поля лежит ключ, $longtitle — это и есть ключ; перевод берём из массива $lexicons[$longtitle], а ?: $longtitle — запасной вариант, если перевода нет.

Подсказка

Для перевода полей чужого ресурса в листинге используйте lexiconsarr (массив лексиконов нужного ресурса) + обращение по ключу — это надёжнее, чем | lexicon: модификатор lexicon резолвит из общего контекста, и в цикле по разным ресурсам их переводы перетёрли бы друг друга.

Что здесь важно:

  • render-выражение внутри пишет автор — нарезка контент в data-mpc-res не трогает (только сохраняет маркеры). Автор сам выводит поле чужого ресурса (как в примере: lexiconsarr + обращение по ключу);
  • редактор (mpcVisualEditor) по ближайшему data-mpc-res определяет, в какой ресурс писать правку: значение поля и/или лексикон-ключ уходят в тот ресурс (id из data-mpc-res), а не в текущую страницу;
  • то есть один и тот же набор полей листинга редактируется «по месту», но сохраняется каждый в свой ресурс.

Нужен mpcVisualEditor

Редактирование полей прямо на странице (в том числе cross-resource через data-mpc-res) выполняет пакет mpcVisualEditor — без него правки с фронта недоступны. Сама нарезка и вывод значений (data-mpc-rfield) работают и без редактора; data-mpc-res без mpcVE — просто пометка, на рендер не влияет.

Контакты

Контакты (телефоны, почты, адреса, соцсети) хранятся отдельно от секций — в едином ресурсе-хранилище — и выводятся сниппетом на любой странице. Блок контакта помечается data-mpc-contact, поля внутри — data-mpc-cfield:

html
<div data-mpc-contact="phone|header" data-mpc-key="mainphone">
  <a data-mpc-cfield="value" href="tel:+79000000000">+7 900 000-00-00</a>
  <span data-mpc-cfield="caption">Телефон</span>
</div>

data-mpc-contact — тип и плейсмент

Значение — <тип>|<плейсмент> (плейсмент необязателен, по умолчанию default):

  • типphone, email, address, имя соцсети и т.п. Влияет на обработку: для phone значение нормализуется (только цифры), а форматированный вид (fvalue) генерируется по настройкам mpc_phone_regexp / mpc_phone_format.
  • плейсмент — где контакт выводится (header, footer…). Один и тот же контакт можно показать в нескольких местах с разным оформлением. Поэтому подпись и атрибуты привязаны к плейсменту, а само значение — общее (см. перевод ниже).

data-mpc-cfield — роли полей

cfieldЧто это
valueсамо значение — телефон/e-mail/URL. У ссылки берётся href, иначе содержимое
captionподпись (зависит от плейсмента: «Телефон», «Звоните» и т.п.)
attributesдоп. данные/иконка (берётся src или содержимое)
fvalueформатированное значение. Указывать не нужно — для phone генерируется само из value; задают вручную лишь в особых случаях

data-mpc-key — ключ контакта

data-mpc-key задаёт устойчивый идентификатор контакта (mainphone). Один и тот же key + тип — это один контакт, переиспользуемый в разных плейсментах.

Задавайте ключ явно

Если data-mpc-key не указан, ключ вычисляется как md5 от значения. Это хрупко: поменяете телефон — ключ изменится, и контакт «потеряется» (перевод/привязки не подхватятся). Поэтому для контактов, которые выводятся и переводятся, всегда задавайте data-mpc-key.

Перевод контактов

Какие под-поля переводятся, задаёт настройка mpc_contact_lexicon_fields (по умолчанию caption); на конкретном блоке её переопределяет data-mpc-translate (через запятую, например data-mpc-translate="caption,value"). Логика ключей:

  • value / fvalue — данные самого контакта → один перевод на все плейсменты (ключ без плейсмента);
  • caption / attributes — оформление → перевод на каждый плейсмент свой.

Полный разбор (типы контактов, форматирование телефонов, вывод сниппетом) — в разделе Работа с контактами.

Системные настройки

data-mpc-info — записать значение в системную или контекстную настройку MODX. Значение из вёрстки уходит в настройку, а на рендере на месте элемента подставляется её текущее значение {$_modx->config['<ключ>']}:

html
<!-- вход -->
<span data-mpc-info="site_name">Мой сайт</span>
<!-- выход -->
<span>{$_modx->config['site_name']}</span>

Удобно, когда «глобальные» значения (название сайта, телефон в шапке, реквизиты) правятся прямо в вёрстке, но должны жить в настройках MODX, а не в конфиге секции.

Откуда берётся значение

Как и у полей, источник определяется по элементу:

  • ссылка (href) → берётся href;
  • картинка (src) → берётся src;
  • иначе → текст элемента (теги Fenom { и MODX [[ экранируются, чтобы значение из контента не исполнялось при выводе настройки).

Системная или контекстная — data-mpc-ctx

  • без data-mpc-ctx → ищется системная настройка (modSystemSetting), а если её нет — настройка ClientConfig;
  • с data-mpc-ctx → ищется контекстная настройка (modContextSetting) указанного контекста, затем системная, затем ClientConfig (для контекста). Значение атрибута — ключ контекста (web); пустой атрибут = текущий контекст рендера:
    html
    <span data-mpc-info="site_name" data-mpc-ctx="web">Мой сайт</span>

Поддерживаются и нативные настройки MODX, и ClientConfig — в обоих случаях значение пишется в уже существующую настройку (ClientConfig подключается как запасной вариант, если нативной с таким ключом нет).

Настройка должна существовать до нарезки

data-mpc-info обновляет существующую настройку, но не создаёт новую: если настройки с таким ключом нет (ни нативной, ни ClientConfig), значение просто не запишется. Создайте нужные настройки заранее — например консольной командой settings apply (см. Консольные команды (CLI)), а уже потом размечайте и нарезайте шаблон.

Условный вывод

Поддерживается data-mpc-if — вывести значение настройки только по условию (оборачивается в {if …}).

Защита критичных настроек

Запись через контент заблокирована для настроек безопасности — ключи, содержащие allow_manager, manager_, session, password, forgot, filemanager, proxy, http_host, mail_, cache_, register_enabled и т.п., проигнорируются (защита от подмены критичных параметров через вёрстку). Для них data-mpc-info работать не будет — это намеренно.


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