Схема полей: полный справочник
Схема полей (schema_content) — JSON, который описывает форму настроек блока.
Из неё редактор собирает форму, а имена полей становятся переменными шаблона.
Это самая проверяемая часть блока: она валидируется при каждом сохранении.
Мета-схема, по которой идёт проверка, опубликована по адресу
/block-schema.json
с разрешающим CORS — её можно скачать и валидировать свои схемы локально,
до отправки на сервер.
Корень схемы
Заголовок раздела «Корень схемы»{ "type": "object", "label": "Карточка товара", "fields": {}}| Свойство | Обязательно | Значение |
|---|---|---|
type |
да | всегда строка "object" |
fields |
да | карта «ключ поля → определение поля»; может быть пустой {} |
label |
нет | заголовок формы в редакторе |
examples |
нет | наборы примерных данных, см. Пресеты |
bindingSlots |
нет | слоты живых данных; наличие делает блок SSR-блоком |
slots |
нет | слоты композиции: области под вложенные блоки, см. Блоки-контейнеры |
client |
нет | клиентское поведение SSR-блока (refreshOn) |
Других свойств в корне быть не может — любое лишнее отклоняется.
Имена полей
Заголовок раздела «Имена полей»Ключ поля становится переменной Liquid: title → {{ props.title }}. Правила:
- ключ произвольный, но осмысленный — им пользуется автор шаблона;
- имена
size,first,lastзапрещены на любом уровне вложенности. Это зарезервированные аксессоры Liquid:{{ props.size }}вернёт количество элементов, а не ваше значение, и фильтр| defaultне спасёт. Используйтеbox_size,first_item,last_item.
Общие свойства поля
Заголовок раздела «Общие свойства поля»Эти свойства принимает большинство типов:
| Свойство | Тип | Значение |
|---|---|---|
type |
строка | тип поля; единственное обязательное свойство |
label |
строка | подпись поля в форме |
description |
строка | пояснение под контролом; добавляйте, только если label недостаточно |
required |
boolean | поле обязательно к заполнению |
default |
по типу поля | значение, подставляемое в пустую форму |
tab |
строка | имя вкладки формы, под которой показать поле |
required и default не принимают контейнерные типы object и array —
у контейнера нет собственного значения. tab принимают все типы.
Вкладки
Заголовок раздела «Вкладки»Как только tab появился хотя бы у одного поля, форма переключается в режим вкладок.
Поля без tab попадают на вкладку «Общие», она идёт первой и открыта по умолчанию.
Остальные вкладки идут в порядке первого появления полей в объекте fields —
отдельной сортировки нет, порядком управляет порядок ключей в JSON. Если вкладка
«Общие» не нужна, проставьте tab всем полям: пустая вкладка не показывается.
Типы полей
Заголовок раздела «Типы полей»Всего двенадцать типов.
Текстовые: text, textarea, url, email
Заголовок раздела «Текстовые: text, textarea, url, email»Различаются только контролом и подсказкой ввода; хранят строку.
| Параметр | Значение |
|---|---|
default |
строка |
{ "email": { "type": "email", "label": "Почта", "default": "hi@example.com" }, "about": { "type": "textarea", "label": "Описание", "tab": "Контент" }}В шаблоне: {{ props.about }}.
Кнопка загрузки картинки. Хранит публичный URL, а не файл. Требует настроенного в аккаунте S3-хранилища; можно вписать и внешний URL с CDN.
| Параметр | Значение |
|---|---|
default |
строка — URL картинки |
<img src="{{ props.cover }}" alt="" />Поиск иконки через Iconify. Хранит готовую SVG-разметку, а не идентификатор иконки — поэтому в шаблоне поле выводится как есть и рисует инлайновый SVG.
| Параметр | Значение |
|---|---|
default |
id иконки Iconify, например "mdi:home"; при инициализации формы превращается в SVG |
<span class="icon">{{ props.icon }}</span>select и radio
Заголовок раздела «select и radio»Выбор одного значения из списка. Отличаются только видом контрола: выпадающий список против группы переключателей.
| Параметр | Обязательно | Значение |
|---|---|---|
options |
да | массив { "label": "…", "value": "…" }, минимум один элемент |
default |
нет | одно из options[].value |
{ "theme": { "type": "select", "label": "Тема", "options": [ { "label": "Светлая", "value": "light" }, { "label": "Тёмная", "value": "dark" } ], "default": "light" }}Значение, которого нет в options, отклоняется при сохранении данных.
Переключатель. Хранит boolean.
| Параметр | Значение |
|---|---|
default |
true или false (именно boolean, не строка "true") |
{% if props.is_wide %}<div class="wide">{% endif %}Палитра. Хранит строку с hex-цветом вида #ff0000.
| Параметр | Значение |
|---|---|
default |
строка, например "#ffffff" |
date, time, datetime
Заголовок раздела «date, time, datetime»Выбор даты и времени. Хранят строку фиксированного формата:
| Тип | Формат | Пример |
|---|---|---|
date |
YYYY-MM-DD |
2026-04-06 |
time |
HH:MM |
14:30 |
datetime |
YYYY-MM-DDTHH:MM |
2026-04-06T14:30 |
default задаётся в том же формате.
object — вложенная группа
Заголовок раздела «object — вложенная группа»Группирует поля в один объект.
| Параметр | Обязательно | Значение |
|---|---|---|
fields |
да | карта вложенных полей, тот же формат, что и в корне |
Вложенность не ограничена: внутри object может лежать другой object или array.
Запрет на имена size/first/last действует и здесь.
{ "button": { "type": "object", "label": "Кнопка", "fields": { "text": { "type": "text", "label": "Надпись", "default": "Купить" }, "href": { "type": "url", "label": "Ссылка" } } }}В шаблоне: {{ props.button.text }}.
array — повторяющаяся группа
Заголовок раздела «array — повторяющаяся группа»Список однотипных элементов: слайды, пункты списка, карточки.
| Параметр | Обязательно | Значение |
|---|---|---|
item |
да | определение элемента; должно быть полем типа object |
min |
нет | минимум элементов; он же задаёт стартовое количество |
max |
нет | максимум элементов |
itemLabel |
нет | шаблон подписи элемента в редакторе, {{fieldKey}} подставляется |
{ "slides": { "type": "array", "label": "Слайды", "min": 1, "max": 6, "itemLabel": "{{caption}}", "item": { "type": "object", "fields": { "image": { "type": "image", "label": "Картинка" }, "caption": { "type": "text", "label": "Подпись" } } } }}В шаблоне разворачивается циклом:
{% for slide in props.slides %} <figure><img src="{{ slide.image }}"><figcaption>{{ slide.caption }}</figcaption></figure>{% endfor %}Значения по умолчанию
Заголовок раздела «Значения по умолчанию»Пустая форма и блок без сохранённых значений заполняются по одним и тем же правилам — одинаково на сервере при публикации и в браузере при предпросмотре:
| Тип | Значение, если default не задан |
|---|---|
текстовые, image, icon, color, date, time, datetime |
"" (пустая строка) |
select, radio |
первое значение из options |
switch |
false |
object |
объект, собранный по тем же правилам из вложенных полей |
array |
min элементов (по умолчанию один), каждый собран по правилам item.fields |
Отсюда практическое следствие: массив без min в пустой форме приходит с одним
пустым элементом, а не пустым списком.
Полный пример схемы
Заголовок раздела «Полный пример схемы»{ "type": "object", "label": "Карточка тарифа", "fields": { "title": { "type": "text", "label": "Название", "required": true, "default": "Базовый" }, "price": { "type": "text", "label": "Цена", "tab": "Контент" }, "accent": { "type": "color", "label": "Акцентный цвет", "default": "#2563eb", "tab": "Оформление" }, "is_featured": { "type": "switch", "label": "Выделить", "default": false, "tab": "Оформление" }, "size": { "type": "select", "label": "Размер", "options": [ { "label": "Обычный", "value": "md" }, { "label": "Крупный", "value": "lg" } ], "default": "md", "tab": "Оформление" }, "cta": { "type": "object", "label": "Кнопка", "fields": { "text": { "type": "text", "label": "Надпись", "default": "Выбрать" }, "href": { "type": "url", "label": "Ссылка" } } }, "features": { "type": "array", "label": "Что входит", "min": 1, "max": 10, "itemLabel": "{{text}}", "item": { "type": "object", "fields": { "text": { "type": "text", "label": "Пункт" }, "icon": { "type": "icon", "label": "Иконка", "default": "mdi:check" } } } } }, "examples": [ { "title": "Тариф Pro", "data": { "title": "Pro", "price": "990 ₽" } } ]}Блоки-контейнеры (slots)
Заголовок раздела «Блоки-контейнеры (slots)»Непустой slots в корне делает блок контейнером: редактор сможет вкладывать
в объявленные области другие блоки, а шаблон выводит собранный HTML детей.
{ "type": "object", "fields": {}, "slots": { "left": { "label": "Левая колонка" }, "right": { "label": "Правая колонка", "max": 1 } }}<div class="cols"> <div class="cols__left">{{ slots.left }}</div> <div class="cols__right">{{ slots.right }}</div></div>| Свойство | Обязательно | Значение |
|---|---|---|
label |
нет | название области в редакторе; без него показывается имя ключа |
max |
нет | максимум блоков в области; без него — без ограничения |
Правила:
- имя слота — латиница, цифры и подчёркивание, не с цифры;
- имена
slotsиbindingSlotsне должны пересекаться: в шаблоне это{{ slots.x }}и{{ data.x }}, одинаковое имя отклоняется валидатором; - вложенность — один уровень: блок, лежащий в слоте, своих детей иметь не может.
Число лежит в мета-схеме под ключом
x-maxSlotDepth; - пустая область даёт пустую строку, а не ошибку;
- контейнер может быть и SSR-блоком: признаки независимы. Слоты в таком блоке PageCraft подставляет сам, остальной шаблон уезжает на витрину как есть;
- вложенные блоки — полноценные экземпляры со своими значениями полей, привязками и закреплённой версией. Порядок считается внутри слота, а не по странице.
Как складывать блоки в слоты через API — Сборка страницы.
Валидация схемы при сохранении блока
Заголовок раздела «Валидация схемы при сохранении блока»Схема проверяется в два слоя, и второй не запускается, если провалился первый — иначе поверх структурных ошибок пришёл бы шум производных претензий.
Слой 1 — структура. Схема проверяется мета-схемой /block-schema.json.
Сюда попадают: неизвестный тип поля, лишние свойства, отсутствие options у select,
неверный тип default, запрещённые имена полей.
Слой 2 — семантика. То, что JSON Schema выразить не может: ключи examples[].data
должны существовать в fields, источники в bindingSlots — в реестре источников,
а имена slots не должны совпадать с именами bindingSlots.
Ошибки возвращаются кодом 422 одной строкой:
{ "error": "Ошибка схемы: schema_content.fields: имя поля \"size\" недопустимо — зарезервированное имя Liquid (нельзя size/first/last)" }Тексты ошибок структуры
Заголовок раздела «Тексты ошибок структуры»| Что не так | Сообщение |
|---|---|
Нет fields в корне |
schema_content: The required properties (["fields"]) are missing |
Имя поля size / first / last |
schema_content.fields: имя поля "size" недопустимо — зарезервированное имя Liquid (нельзя size/first/last) |
Неизвестный тип ("type": "string") |
schema_content.fields.title.type: The data should match one item from enum |
Лишнее свойство поля (placeholder) |
schema_content.fields.title: Additional object properties are not allowed: ["placeholder"] |
select без options |
schema_content.fields.mode: The required properties (["options"]) are missing |
switch с "default": "true" |
schema_content.fields.flag.default: The data (string) must match the type: boolean |
Ключ data примера не найден в fields |
schema_content.examples[0].data: поле "titel" не существует в схеме |
| Имя слота начинается с цифры | schema_content.slots: имя слота "1bad" недопустимо — только латиница, цифры и подчёркивание, не с цифры |
Имя слота занято bindingSlots |
slots.x: имя занято bindingSlots — слот композиции и SSR-слот не могут называться одинаково |
Валидация значений при работе со страницей
Заголовок раздела «Валидация значений при работе со страницей»Схема описывает форму, значения приходят отдельно — в placeholder_values
экземпляра блока на странице. Они проверяются против схемы того блока, который
ставится на страницу.
Ошибки возвращаются кодом 400 и, в отличие от ошибок схемы, разложены по путям — клиент может исправить их точечно:
{ "error": "Ошибка валидации блоков", "errors": [ { "path": "blocks[0].placeholder_values.title", "message": "ожидалась строка, получено int" }, { "path": "blocks[0].placeholder_values.items[1].name", "message": "поле обязательно — передайте значение либо задайте default в схеме блока" } ]}Что именно проверяется:
| Ситуация | Сообщение |
|---|---|
Обязательное поле не передано и default в схеме нет |
поле обязательно — передайте значение либо задайте default в схеме блока |
| Не тот тип у строкового поля | ожидалась строка, получено int |
Не тот тип у switch |
ожидалось boolean, получено string |
Значение вне options |
значение "c" не входит в options[].value (a, b) |
В object пришёл массив |
ожидался объект |
В array пришёл объект |
ожидался массив |
В _markdown пришла не строка |
ожидалась строка (Markdown-контент) |
Проверка нестрогая к лишнему: ключи, которых нет в схеме, ошибкой не считаются —
шаблон Liquid вправе использовать переменные, не объявленные в fields.
Пример: валидный и невалидный payload
Заголовок раздела «Пример: валидный и невалидный payload»Схема:
{ "type": "object", "fields": { "title": { "type": "text", "required": true }, "is_wide": { "type": "switch" }, "mode": { "type": "select", "options": [ { "label": "A", "value": "a" }, { "label": "B", "value": "b" } ]}, "items": { "type": "array", "item": { "type": "object", "fields": { "name": { "type": "text", "required": true } } } } }}Валидные значения:
{ "title": "Привет", "is_wide": true, "mode": "b", "items": [{ "name": "Раз" }, { "name": "Два" }], "utm": "лишний ключ — не ошибка"}Невалидные и почему:
{ "is_wide": "yes", "mode": "c", "items": { "name": "Раз" }}title: поле обязательно — передайте значение либо задайте default в схеме блокаis_wide: ожидалось boolean, получено stringmode: значение "c" не входит в options[].value (a, b)items: ожидался массивРасхождение: required в схеме и в валидаторе
Заголовок раздела «Расхождение: required в схеме и в валидаторе»Ещё две тонкости про required, которые видно только на практике:
- Пустая строка проходит. Сервер проверяет наличие ключа и тип значения,
а не «непустоту»:
{"title": ""}для обязательного поля будет принято. Форма редактора такое значение не пропустит, API — пропустит. requiredуswitchбессмыслен. Булево значение всегда определено; форма это правило игнорирует, и сервер тоже.
Рекомендация автору блока: если поле помечено required, задайте ему default.
Тогда блок останется работоспособным независимо от того, каким путём в него пришли
значения.
Как проверить схему, не отправляя её на сервер
Заголовок раздела «Как проверить схему, не отправляя её на сервер»- Скачайте мета-схему:
curl -O https://page-craft.4partners.io/block-schema.json. - Прогоните свою схему любым валидатором JSON Schema Draft-07 (ajv, opis, python-jsonschema).
- Для конкретного блока дополнительно возьмите
GET /api/v1/blocks/{id}/placeholder-schema— там готовый контракт дляplaceholder_values, минимальный валидныйexampleи список SSR-слотов, так что структуру значений не придётся выводить изschema_contentруками.
Источники: backend/public/block-schema.json,
backend/src/Service/BlockSchemaValidator.php,
backend/src/Service/Block/BlockPlaceholderValidator.php,
backend/src/Service/Block/BlockPlaceholderSchemaBuilder.php,
backend/src/Publishing/BlockRenderer.php (buildDefaultData),
backend/src/Service/Page/PageBlockService.php,
backend/src/EventListener/ApiExceptionListener.php,
front/src/lib/schema.ts, front/src/lib/schemaValidation.ts.