Перейти к содержимому
Версия 1.0 ещё не выпущена; проект находится на стадии pre-MVP. См. возможности и ограничения версии 1.0.

Справочник моделей и полей

Модель определяет структуру контента сайта. key поля — идентификатор для API, label — отображаемое имя. При изменении определения сохраняйте существующие ID полей: изменение ID поля не является безопасным переименованием. Смена label никогда не переименовывает key. Новое поле берёт key из label, пока вы не введёте свой. Существующий key меняется только через Ключ поля: ID поля сохраняется, а переименование проверяется перед сохранением. Применяйте изменения модели только после проверки совместимости.

Если существующая модель не загрузилась, нажмите Повторить. Редактор станет доступен после загрузки определения. Во время проверки совместимости и сохранения редактирование недоступно.

Тип Какие настройки проверить
text Одна или несколько строк, с числом строк, подсказкой и значением по умолчанию. Поддерживает проверку длины и шаблона.
rich Редактор форматированного текста с настраиваемой панелью. Хранит один форматированный документ как канонический HTML, до 256 КиБ: текстовые блоки, таблицы, галереи изображений, блоки аудио, видео и файлов, а также встраивания по ссылкам YouTube, Vimeo или разрешённого https-источника (источники встраиваний). Сохранение, в котором есть что-то другое, отклоняется с указанием поля, языка и элемента. Публичный API отдаёт проверенный HTML; отрисовывайте его функцией renderRichDocument из контракта страницы, как это делают собственные рендереры Dee Wan для постов блога и кратких описаний в Последние записи (Сайты с блогом).
number Шаг, значение по умолчанию и формат отображения: integer, decimal, currency или percentage. Поддерживает числовые границы.
boolean Необязательное логическое значение по умолчанию.
select Обязательный список вариантов: строки или {value, label}. Поддерживает одно или несколько значений и значение по умолчанию. Произвольные значения доступны только при одиночном выборе.
date Формат date, datetime или time. Поддерживает значение по умолчанию и границы даты. Отдельного типа поля datetime нет.
point Широта и долгота хранятся вместе. См. даты с часовым поясом и координаты.
media Виды файлов (image, document, audio, video, file; изображения, если ничего не выбрано), максимальный размер в байтах, один или несколько файлов и правила обрезки (соотношение сторон, минимальный размер). Сохранение, которое помещает файл другого вида, отклоняется (media_kind_not_accepted), как и файл больше максимального размера (media_too_large_for_field). Кадрируются только изображения: обрезка или точка фокуса у другого файла отклоняется (media_framing_not_image). В медиаполе верхнего уровня редактор задаёт точку фокуса для каждого изображения. При правиле соотношения сторон обрезка следует за ней. Кадрирование хранится рядом со ссылкой в долях изображения и никогда не меняет файл. Сохранённые байты ничто не масштабирует и не обрезает; сгенерированный сайт может отдавать масштабированные и обрезанные копии (см. предпросмотр и клиент). У изображения может быть точка фокуса по умолчанию (Медиацентр → Редактировать изображение). Она задаёт только начало нового использования изображения; существующие использования сохраняют своё кадрирование. Копия сайта переносит её. Внутри компонента кадрирование хранится в самом значении: кадрированное использование — это { media_id, crop?, focal? }, некадрированное остаётся просто id. Изображение секции страницы кадрируется для каждого слота в инспекторе страницы (контракт страницы версии 4). Какие виды и форматы файлов вообще принимает медиатека — настройка сайта (Настройки → Сайт → Медиафайлы). Изображение можно обрезать и повернуть в новую копию (Медиацентр → Редактировать → Обрезать и повернуть); использования оригинала не меняются.
json Принимает любое значение JSON, массив или объект, с необязательным текстом JSON Schema. Документ страницы slides дополнительно подчиняется контракту страницы.
relation Слаг целевой модели (несколько моделей: см. Связь с несколькими моделями), отображаемое поле и кратность: один-к-одному, многие-к-одному, один-ко-многим или многие-ко-многим. В связях один-к-одному и один-ко-многим каждая связанная запись принадлежит только одной записи; другая запись, указывающая на неё в том же поле, отклоняется с relation_target_taken. Каждая связанная запись показывает записи, которые на неё указывают, в блоке Ссылаются сюда по модели и полю источника, для всех четырёх видов. Редактор, которому можно читать запись и менять источник, добавляет запись к источнику или убирает её. Изменение сохраняется в исходной записи как новая черновая версия, действует для всех её языков и появляется на сайте после публикации этой записи. Когда источник один-к-одному или многие-к-одному уже указывает на другую запись, редактор сначала подтверждает замену, в которой названы обе записи. Изменить существующее поле на связь один-к-одному или один-ко-многим нельзя, пока две записи ссылаются на одну и ту же цель (relation_targets_shared). Поля компонентов не предлагают один-ко-многим. Удаление любой из записей удаляет связь и сохраняет другую запись. Обычные связи остаются в пределах сайта. Общие ссылки доступны только для полей связей моделей; поля компонентов включать их не могут (shared_reference_component_unavailable). Неиерархическое поле многие-к-одному или многие-ко-многим может принимать явные общие ссылки (sharedReferences: true) по разрешению администраторов обоих сайтов. Они читают опубликованные публичные записи и не дают права изменять исходный сайт. Контекстные условия выбора для таких полей недоступны (shared_reference_choice_unavailable). Связь один-к-одному или многие-к-одному с собственной моделью можно отметить как Иерархия (родитель/потомок) ("hierarchical": true). Тогда сохранение или восстановление, которое делает запись собственным родителем или помещает её под один из её дочерних элементов, отклоняется с relation_hierarchy_cycle. Включить отметку нельзя, пока сохранённые связи уже образуют цикл; сохранение модели перечисляет такие записи. Сгенерированный API отклоняет те же циклы с тем же кодом, в том числе непосредственно при записи в базу данных (см. запись связей и переводов).
component Допустимые компоненты (components: один слаг или несколько для списка смешанных блоков), повторяемость, сворачивание и настройки отображения для конкретного использования. Определения сайта могут явно переопределять общие. См. смешанные поля компонентов.

В поле форматированного текста записи выделите текст и выберите Фрагменты → Сохранить выделенное как фрагмент, чтобы сохранить его под именем. Фрагменты → Вставить фрагмент ищет фрагменты сайта и вставляет выбранный в позицию курсора. Вставка создаёт копию: если позже переименовать, изменить или удалить фрагмент, уже вставленный текст не меняется.

Фрагменты в полях форматированного текста модели может использовать любой, кто может создавать или изменять записи этой модели. Свои сохранённые фрагменты можно переименовать, заменить или удалить; люди с site:settings управляют всеми. На сайте не больше 200 фрагментов, каждый до 64 КиБ. Имя содержит от 1 до 80 символов и уникально на сайте без учёта регистра. Фрагмент содержит то, что принимает поле форматированного текста; сырой HTML, стили и скрипты отклоняются. Фрагменты приватны: они никогда не появляются в публичном API или на сайте.

Копия сайта (формат 1.12) переносит фрагменты и изображения, которые они показывают. Отмена её импорта удаляет только добавленные импортом фрагменты, которые с тех пор никто не менял; изменённый фрагмент остаётся и попадает в список.

Осознанно выбирайте обязательность и переводимость. Валидация поддерживает длину и шаблон текста, числовые границы, количество выбранных значений и повторяемых компонентов, а также границы дат. Минимум выше максимума и шаблон, который не компилируется, отклоняются при сохранении модели. Сервер проверяет сохранение контента по тем же правилам, что и редактор. Настройки интерфейса включают скрытие, группы, порядок и подсказки. Скрытие поля не является политикой доступа. В шаблонах поддерживаются свойства Unicode: например, \p{L} для букв и \p{N} для цифр. Пользовательский шаблон только с латинскими буквами по-прежнему ограничивает ввод.

Сгенерированный автономный API применяет заданные для текста правила minLength, maxLength и pattern, включая свойства Unicode и намеренное ограничение символами ASCII. После изменения этих правил заново сгенерируйте и разверните бэкенд. Ошибки генерации описаны в справочнике API.

Поле модели или компонента может зависеть от соседнего логического поля, списка, текста, числа или даты. В разделе Интерфейс включите Показывать, только когда…, выберите поле, сравнение и значение. Объединяйте до четырёх сравнений через Все условия (AND) или Любое условие (OR), до трёх уровней вложенности. Старый формат { "field": "featured", "in": [true] } сохраняется; типизированное сравнение — { "field": "price", "op": "gte", "values": ["100"] }, группы — { "all": [...] } и { "any": [...] }. Текст поддерживает равенство и поиск подстроки, числа и календарные даты — равенство и диапазоны; проверки пустоты не принимают значений. Неизвестные поля, неподходящие значения и операции, недоступные варианты и циклы отклоняются при сохранении структуры. Скрытое значение сохраняется; обязательность и остальные правила действуют только пока поле показано. Это настройка отображения, а не доступа. Редактор, API, инструменты управления и проверки импорта используют одну проверку; копии сайта переносят условие.

Сайт задаёт языки, основной язык и строгую политику либо fallback. При fallback каждый язык может перечислить резервные языки по порядку в разделе Настройки → Языки; посетитель видит первый из них, на котором есть запись, затем основной язык. Редактор записи показывает, какой язык видят посетители, пока перевод не опубликован. Проверяйте локаль возвращённой записи, а не предполагайте её совпадение с запрошенной. Приватная модель требует соответствующего доступа к API. Перенос контента в публичную модель открывает его публичным читателям.

Модели генерируемой страницы нужна документированная структура title и slides. См. контракт страницы. Остальные модели остаются данными API, а не автоматическими страницами. Слаги общих компонентов стабильны. Ответвление для сайта заменяет общую версию только в ссылках этого сайта.

При изменении списка полей компонента прежняя структура сохраняется автоматически. Существующие записи сохраняют значения и версию структуры; изменение не создаёт версию контента и ничего не публикует. Новые экземпляры используют новую структуру. Чтобы перенести существующий экземпляр, нажмите Обновить компонент, когда будете готовы. Во время изменения параллельное сохранение контента может отклоняться с component_migration_in_progress; повторите его после завершения изменения.

В редакторе компонента Устаревший тип запрещает добавление и дублирование этого типа. Существующие компоненты остаются доступными для просмотра и редактирования. Данные не удаляются. Убрать тип из списка допустимых типов смешанного поля можно после переноса или удаления всех его экземпляров.

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

Допускается до 64 сохранённых определений общим размером 2 МиБ. Текущие поля, прежние определения и метаданные вместе должны помещаться в строку хранилища: не более 1 900 000 байт UTF-8 с учётом служебного запаса. Превышение любого ограничения отклоняет сохранение или импорт с component_retention_limit до записи компонента. На время изменения запись затронутого контента блокируется тем же механизмом, что и удаление поля. Для общих компонентов в развёртываниях с отдельными БД сайтов сохраняется существующий запрет на такую общую операцию. Копии сайта и сгенерированные приложения переносят версии, признак устаревшего типа и привязки экземпляров. Сохранение компонента не удаляет прежние определения.

Поле компонента, которое допускает несколько компонентов, — это список смешанных блоков, как динамическая зона. В разделе Настройки поля выберите каждый допустимый компонент; порядок выбора — это порядок, в котором их предлагают редакторам. Два и более компонента делают поле списком, поэтому оно всегда повторяемое (иначе mixed_component_not_list). Каждый компонент можно разрешить один раз.

  • Конфигурация: { "type": "component", "components": ["hero", "quote"], "repeatable": true }. Поле с одним компонентом использует тот же ключ: "components": ["seo"].
  • Сохранённое значение — упорядоченный список. Каждый блок несёт _id (его идентичность), _component (его компонент), _version (версию компонента, с которой блок последний раз сохранялся), enabled (false скрывает блок) и поля своего компонента. При сохранении каждый блок проверяется по своей версии структуры компонента. Сохранённая прежняя версия меняется только при явном обновлении компонента.
  • Версия компонента начинается с 1 и увеличивается на единицу при каждом изменении списка его полей. Существующие экземпляры сохраняют свою структуру и _version до явного обновления.
  • В редакторе Добавить блок предлагает допустимые компоненты. Меню ⋯ каждого блока содержит Выше, Ниже, Дублировать, Скрыть или Показать и Удалить. Чтобы изменить порядок, перетащите маркер или сфокусируйтесь на нём и нажимайте клавиши со стрелками.
  • Скрытый блок остаётся в сохранённом списке с enabled: false для редакторов, истории и копий. Публичное чтение, управляемый рендерер, сгенерированный сайт и ответы генерируемого бэкенда анонимным читателям и токенам чтения его не включают. enabled варианта A/B в поле с одним компонентом это не затрагивает.
  • Добавить компонент в список допустимых всегда безопасно. Удаление компонента отклоняется, пока сохранённые записи содержат его блоки (component_type_removed).
  • Разрешение второго компонента в поле с одним компонентом преобразует его сохранённые значения в блоки (_id block-1, block-2, …, компонент, его текущая версия, показан). Возврат к единственному оставшемуся компоненту преобразует блоки в экземпляры. Каждая запись получает новую текущую версию с пометкой «Component field changed shape»; старые версии сохраняют прежнюю форму. Возврат к полю с одним экземпляром отклоняется, пока запись содержит больше одного блока (storage_shape_changed). Преобразование отклоняется, если оно переписало бы больше 200 сохранённых значений (component_migration_too_large).
  • История, восстановление, экспорт, копия сайта, импорт и генерируемые бэкенды сохраняют список в сохранённом виде. В CSV/XLSX блоки записываются в ячейки как JSON; их можно импортировать через «Файлы коллекции». Медиа внутри блоков отслеживаются по компоненту, поэтому ключ блока другого типа никогда не читается как медиа.
  • Связь внутри значения компонента (любого компонента, на любой глубине, в любом блоке) называет свои цели по id. Публичное чтение и опубликованный сгенерированный сайт отбрасывают цель, которой больше нет, которая в корзине, снята с публикации или недоступна этому читателю: одиночная ссылка становится null, список сохраняет остальные цели по порядку. Сохранённая версия не меняется.
  • Предпросмотр показывает последние доступные редактору сохранённые черновики связанных записей, даже если они ещё не публиковались. Для обычных связей и связей внутри компонентов действуют одинаковые права на поля и язык и правила скрытия приватных полей. Новые ссылки предпросмотра также проверяют текущие права их создателя. См. предпросмотр черновиков.
  • Копия сайта переносит компоненты, которыми владеет этот сайт и которые используют его модели (components.json, с версией каждого), и записывает их до моделей, повторно используя компонент назначения с теми же полями и отклоняя другой компонент под тем же слагом (component_slug_taken, component_shadows_shared, component_name_taken). Каждая связь внутри значения компонента сопоставляется с записью, созданной копией; связь, которую копия не несёт, блокирует проверку (unresolved_relation). Отмена импорта удаляет записанные им компоненты, как только их не использует ни одно поле.
  • У смешанного поля нет генерации ИИ и вариантов A/B (mixed_component_context). Варианты A/B — это экземпляры одного повторяемого поля с одним компонентом, из которых случайно показывается один; для отдельных блоков они не задаются.

У каждого языка записи свой адрес — его slug: публичный API отдаёт французскую версию по /v1/<model>/<slug>?locale=fr под её французским slug, и каждая языковая версия доступна только по своему slug. Чтение одной записи перечисляет в alternates все опубликованные языки и их slug. У модели может быть и текстовое поле с ключом slug. Если поле переводимое, значение каждого языка — адрес этого языка. Если общее, оно хранит адрес основного языка. Редактор, API контента, API управления, плагины, импорт таблиц, импорт пакета и импорт Strapi следуют одним правилам. Проверка таблицы сообщает slug_invalid, slug_cleared и slug_reserved до применения, в том числе когда две строки одного файла называют один адрес на одном языке. Проверка импорта Strapi сообщает slug_invalid, slug_conflict и slug_taken до запуска.

Сохранение Откуда адрес языка Отказ
Создание slugs[lang], иначе поле slug этого языка, иначе его собственный заголовок, иначе (для перевода) адрес основного языка. Названный slug без букв и цифр (slug_invalid). Названный slug, который другая запись держит на этом языке (slug_reserved, slug_taken). slugs[lang] и поле slug называют разные адреса (slug_conflict).
Обновление slugs[lang] или значение поля slug этого языка, отличное от сохранённого. Всё остальное сохраняет адрес. Те же отказы. Очистка адреса или заполненного поля slug (slug_cleared).
  • Каждый отказ называет поле и язык: { reason, field: "slug", locale, slug, held_by?, version? }. Редактор показывает его под адресом этого языка, а не во всплывающем сообщении.
  • Названный slug нормализуется: буквы, цифры и комбинируемые знаки Unicode приводятся к форме NFKC и нижнему регистру, слова разделяются одиночными дефисами. Harbour Wall становится harbour-wall. Нормализованный адрес записывается в поле slug этого языка, а ответ на сохранение возвращает адреса всех языков как slugs.
  • Заголовок не является адресом, поэтому никогда не приводит к отказу. Занятый slug из заголовка получает -2, -3 и так далее, только на своём языке. Slug занят, если на том же языке его держит черновик, текущая или опубликованная версия другой записи. Заголовки на русском, тайском, японском и других языках дают адрес на том же языке. Заголовок без букв и цифр не даёт адреса; перевод тогда берёт адрес основного языка. При создании slugs[lang]: null означает отсутствие адреса на этом языке. Адрес из заголовка обрезается до 180 символов до добавления суффикса -2.
  • Язык, добавленный без slug, получает адрес основного языка, с суффиксом, если на этом языке он уже занят.
  • Старая запись, чьё поле slug уже отличается от адреса, сохраняет оба значения при несвязанных сохранениях. Правка поля это исправляет.
  • Переименование черновика не переносит опубликованный адрес, пока этот язык не опубликован.
  • Каждый язык сохраняет адрес своей опубликованной версии. Публикация одного языка под новым адресом переносит и перенаправляет только этот язык.
  • Публикация закрепляет адрес каждого публикуемого языка. Язык страницы без адреса опубликовать нельзя (page_address_required с его locale). Адрес, который другая запись уже держит на этом языке, отклоняется (slug_reserved), и ничего не публикуется.
  • Управляемый маршрут без {locale} даёт адрес только языковой версии, возвращаемой при чтении без параметра локали: на языке по умолчанию, иначе на одном из резервных языков сайта по порядку, иначе на основном.
  • Восстановление возвращает адрес каждого языка, записанный в версии. Импорт пакета, копия сайта и перенос истории переносят адрес каждого языка в его версиях.

Для поля связи переводимой модели можно включить Разные связи для каждого языка: в разделе Настройки поля включите переключатель. Настройка хранится как config.localizedMembership: true. Без неё все языки показывают один общий список.

  • Каждый язык хранит свой список и порядок. Редактор показывает и сохраняет список языка, который вы редактируете, на любом языке, включая переводы.
  • API сохранения принимает эти списки в locale_relations (язык → поле → список, { set } или { connect, disconnect, move }, где элементы connect могут нести позицию). Отправка такого поля в relations отклоняется с relation_localized_field; отправка общего поля по языкам отклоняется с relation_not_localized. Язык, которого у записи нет, отклоняется с relation_locale_missing.
  • Каждое правило применяется к списку одного языка за раз: кратность «к одному», лимиты записей, один владелец цели в связях один-к-одному и один-ко-многим (relation_target_taken только когда другая запись держит цель на том же языке) и циклы Иерархия (родитель/потомок).
  • Версия хранит эти списки в locale_relations. Публикация, предпросмотр, восстановление, корзина, импорт пакета, копия сайта и перенос истории сохраняют список каждого языка. Окончательное удаление языка удаляет его списки. Новый язык начинается с пустых списков.
  • Ссылаются сюда меняет список открытого языка. Его представление объединяет языки записи в одну строку (основной язык, когда он ссылается на запись); удаление там меняет только список языка этой строки.
  • Массовое действие Категории и теги меняет список каждого выбранного языка. Несколько выбранных языков одной записи меняются одним сохранением с проверкой одной общей версии; остальные строки отвечают covered.
  • Включение опции копирует текущий список каждой записи во все языки. Выключение сохраняет список основного языка каждой записи и удаляет остальные; проверка модели сначала предупреждает об этом. Выключение в поле один-к-одному или один-ко-многим блокируется, если после выключения списки основного языка двух записей указывали бы на одну цель (relation_targets_shared), а в поле Иерархия (родитель/потомок) — если эти списки помещали бы запись под саму себя (hierarchy_cycles_shared). Восстановленная версия, созданная до изменения, следует тому же правилу.
  • Опция отклоняется в модели без переводов и в компоненте (localized_membership_unsupported, localized_membership_not_in_component).
  • Генерируемый бэкенд хранит такое поле, включая связь «к одному», в связующей таблице, строки которой несут locale; каждое правило уникальности действует в пределах языка. Миграция загружает список каждого опубликованного языка из версии, которая его отдаёт. PUT /api/<owner>/relations/<field> принимает locale и записывает список этого языка с теми же лимитами записей, правилами одного владельца и циклов родителя (unknown_locale для отсутствующего или неизвестного языка). Маршрут связующей таблицы только для чтения отдаёт список одного языка с where[locale][equals]=<lang>; язык без списка читается как пустой и никогда как список другого языка. Общий список сохраняет связующую таблицу без locale, потому что SQLite считает значения NULL различными в уникальном индексе.

Поле-связь модели может принимать записи нескольких моделей. В настройках поля оставьте Целевая модель и отметьте модели в блоке Также принимать. Это хранится в config.allowedModels — списке, который начинается с целевой модели. Без него поле принимает только свою целевую модель.

  • Список может смешивать модели в любом порядке. Выбор ищет сразу по всем принимаемым моделям, сначала новые, и каждая строка называет свою модель. Сузьте поиск до одной модели кнопками над списком. Создать новую спрашивает, запись какой модели создать, если поле принимает несколько.
  • Ссылка содержит модель целевой записи. API сохранения принимает либо id экземпляра, либо { "model": "<slug>", "id": "<instance id>" } везде, где в записи связи ожидается id (relations, locale_relations, в списке, set, connect, disconnect и move). Если model указана, она должна быть моделью самой цели.
  • Сохранение отклоняется с relation_target_not_found, если цели нет на этом сайте, она принадлежит модели, которую поле не принимает, указана другая модель, чем у неё, или цель нельзя читать тому, кто сохраняет. Ответ эти случаи не различает. Повторное указание одной цели отклоняется с relation_duplicate_target, а вторая цель в поле «к одному» — с relation_cardinality.
  • Версия, опубликованная версия и восстановление хранят модель каждой цели рядом с её id. Восстановление проверяется по моделям, которые поле принимает сейчас: версия со связью на модель, которую поле больше не принимает, отклоняется с relation_target_not_found.
  • Публикация, снятие с публикации и корзина обращаются с записью одинаково, какая бы модель на неё ни ссылалась: при снятии с публикации записи, на которую ссылаются опубликованные записи через поле, они перечисляются, а Ссылаются сюда показывает запись из каждой модели, которую принимает поле.
  • Публичное чтение отдаёт каждую цель как { instance_id, model, slug, display }; см. Public read API. Цель модели, которую поле больше не принимает, не отдаётся. relation, tag и expand по полю, принимающему несколько моделей, отклоняются.
  • Копия сайта и импорт пакета сохраняют порядок списка и модель каждой цели. Запись, ссылка в которой называет не ту модель или модель, которую поле назначения не принимает, не импортируется.
  • Убрать принимаемую модель, пока на неё ссылаются записи, нельзя: проверка модели блокирует это (relation_model_removed) и называет записи. Добавление модели или смена порядка списка проверки не требует. Смена значения Целевая модель на другую модель подчиняется тому же правилу.
  • Связь Иерархия (родитель/потомок) по-прежнему принимает одну модель (mixed_relation_hierarchical). Подпись цели — Поле отображения поля; у цели, чья модель не имеет такого поля, показывается короткая форма её id.

Поле модели типа text или number может иметь признак Уникальное: включите Уникальное в разделе Свойства поля. Переводимое поле также выбирает Уникально в каждом языке (locale: у двух записей не может быть одного значения на одном языке) или Уникально во всех языках (model: значение встречается в модели один раз, включая разные языки одной записи). Общее поле уникально среди всех записей. Настройка хранится как config.unique. Поля компонентов, другие типы и область по языку у общего поля отклоняются (unique_unsupported).

  • Значения сравниваются как сохранены: текст чувствителен к регистру и не обрезается, числа сравниваются по значению. Пустое значение ни с чем не конфликтует. Уникальный текст — не длиннее 255 символов.
  • И текущая, и опубликованная версии записи резервируют значения, как и адреса. Версии одной записи друг с другом не конфликтуют. Запись в корзине держит свои значения, пока её не удалят окончательно.
  • Сохранение, восстановление, импорт, применение таблицы или публикация старой версии, повторяющие значение, отклоняются с field_value_taken (409). Редактор показывает отказ под полем и сохраняет правку. Другая запись называется только тому, кто может её читать. Проверка таблицы сообщает об этом до применения. Проверка импорта сообщает до запуска, включая записи одного импорта с одинаковым значением, а запуск блокирует такую запись строкой проблемы.
  • Включение Unique или смена области сначала проверяется. Проверка перечисляет записи с общим значением (unique_duplicates, с примерами значений) и записи со значением длиннее 255 символов (unique_value_too_long). Применение отклоняется с model_change_blocked (409), где названо каждое из них, пока всё не исправлено. Изменение во время применения отклоняется (model_change_stale).
  • Генерируемый бэкенд получает уникальный столбец: @unique на столбце поля или @@unique([field, lang]) для области по языку. Столбец связи один-к-одному тоже уникален. Готовность к генерации и сборка отклоняют опубликованные записи с общим уникальным значением (unique_value_shared) или общей целью связи один-к-одному (one_to_one_target_shared) до генерации. Пустой уникальный текст записывается как null.

Числа вводятся в десятичной записи, в том числе с десятичной экспонентой; строки в шестнадцатеричной, двоичной и восьмеричной записи отклоняются.

В настройках модели раздел Уникальные сочетания позволяет задать до восьми именованных групп из двух–четырёх текстовых или числовых полей. Выберите Среди всех языков или В каждом языке. Все поля одной группы должны быть либо переводимыми, либо общими; область отдельного языка доступна только переводимым полям. Формат: settings.unique_groups: [{ "name": "catalog_sku", "fields": ["catalog", "sku"], "scope": "model" }].

Сочетание участвует в проверке, только когда заполнены все поля. Текст сравнивается точно, числа — по числовому значению; текст ограничен 255 символами. Текущая и опубликованная версии резервируют сочетание. Сохранение, восстановление, импорт и публикация используют одну атомарную проверку. При включении ограничения проверяются существующие повторы. Генерация создаёт составной уникальный индекс; при области отдельного языка в него входит lang. Повторы в опубликованных данных блокируют генерацию.

Модель — это коллекция (любое число записей) или одиночная модель (одна запись, например главная страница или настройки сайта). Включите Одна запись в общих настройках модели. Вид хранится как content_model.kind и передаётся в теле применения модели как model.kind — через конструктор моделей, API управления (REST, CLI и MCP) и копию сайта. Если при обновлении не указать kind, сохранённый вид остаётся.

  • Одиночная модель содержит одну запись, сколько бы у неё ни было языков. Запись в корзине тоже занимает место. Восстановите её или удалите окончательно, чтобы создать другую.
  • Второе создание отклоняется с single_entry_exists (409) с указанием существующей записи и того, находится ли она в корзине. Сохранения, импорт, копии сайта, применение таблиц, плагины и API управления создают записи одним и тем же путём. Две одновременные операции создания не могут обе завершиться успешно: проверка повторяется внутри записи в базу.
  • Копия сайта, которая несёт больше одной записи для одиночной модели, блокируется до запуска (single_entry_conflict). Как и копия, чья одиночная модель уже содержит запись на этом сайте, в том числе в корзине (single_entry_taken), если только эта запись не пришла из той же исходной записи, как при повторном импорте.
  • Коллекция становится одиночной, только пока содержит не больше одной записи с учётом корзины. Иначе изменение отклоняется с model_change_blocked с причиной single_entry_conflict. Одиночная модель всегда может стать коллекцией; ничего не теряется.
  • В Контент выбор одиночной модели открывает её запись. Когда записи нет, список предлагает одно действие Создать запись. Когда запись в корзине, он предлагает Восстановить.
  • Публичный API не меняется: GET /v1/<model> отвечает списком не более чем из одного элемента, а GET /v1/<model>/<slug> отдаёт его по адресу. См. публичный API.
  • Генерируемый бэкенд ограничивает таблицу одиночной модели одной строкой. Уникальный индекс отклоняет вторую строку, кто бы её ни записывал, а маршрут создания отвечает single_entry_exists (409).

Модель можно положить в папку в списке Контент, чтобы сайт со многими моделями было легко просматривать. Задайте её в общих настройках модели в поле Папка. Длина — от 1 до 40 символов после обрезки пробелов, Blog и blog — одна папка. Очистите поле, чтобы убрать модель из папки.

  • Папка существует, пока её использует модель. Списка папок для управления нет, вложенных папок нет.
  • Модели без папки идут первыми. Далее папки по имени; каждая открывается и закрывается, браузер помнит закрытые. Папка с открытой моделью открывается сама.
  • Папка сохраняется сама, когда поле теряет фокус, а не по Сохранить. Нужно право применения изменений модели (model:apply).
  • Это только организация для редакторов. Ревизию модели она не меняет, поэтому никогда не делает открытый редактор устаревшим и не вызывает проверку критичных изменений. Она не входит во входные данные генерации, в генерируемый бэкенд и в любой публичный ответ.
  • Копия сайта переносит папку вместе с каждой создаваемой моделью. Модель, которая уже есть в месте назначения, сохраняет свою папку. API управления задаёт её через setModelFolder (PUT /models/{id}/folder, schema:write) и отдаёт как folder у каждой модели.

Для любого поля модели или компонента можно включить Скрытое в разделе Свойства поля. Настройка хранится как custom_field.private или как private: true в списке полей компонента.

  • Редакторы видят и меняют приватные значения. История, восстановление, экспорт, копия сайта, импорт и авторизованное чтение через API управления их сохраняют.
  • Каждое публичное чтение их исключает: элементы GET /v1, развёрнутые связи, связанные страницы, лента блога, поиск по сайту, сгенерированный публичный клиент TypeScript, сгенерированные сайты и ответы генерируемого бэкенда анонимным читателям и токенам чтения. Приватное поле связи или медиа исключается также из relations и media. Внутри значения компонента каждое приватное поле компонента удаляется везде, где отдаётся значение, включая сгенерированные сайты.
  • Публичный запрос, который называет приватное поле в фильтре, sort, fields, relation, expand или курсоре связанных страниц, отклоняется точно так же, как если бы поля не существовало.
  • Переименование или удаление приватного поля никогда не раскрывает его значение. Опубликованные версии хранят старый ключ, но публичные чтения, поиск по сайту и сгенерированные сайты отдают только поля, которые модель объявляет сейчас.
  • Поле модели не может занять ключ, под которым сохранённые версии хранят значение прежнего поля. Правило охватывает новые поля модели, переименование и изменения, которые иначе трактуют поле компонента модели. Сохранение возвращает 409 model_change_blocked с retained_key_reuse. Выберите другой ключ, пока хотя бы одна хранимая версия содержит прежнее значение. При изменении полей самого компонента сохраняются прежняя структура и привязки экземпляров: старые значения не переосмысливаются. Для каждой прежней структуры действуют её собственные настройки приватности и приватные поля текущей структуры. Другие исторические версии на неё не влияют. Поля сопоставляются по постоянному идентификатору, в том числе после переименования, а при его отсутствии — по ключу. Изменение новой структуры не делает старое приватное значение публичным. Импорт сопоставляет значения с записанными определениями источника и проверенного назначения (unmapped_source_key, source_private_value, import_definition_changed). См. Импорт.
  • Компонент, созданный или скопированный под slug, который сайт уже читает, — это новое определение для сохранённых значений сайта. Его сравнивают с определением, которое этот slug читал раньше. Ключ, которого в том определении не было, считается новым полем. Так же считается ключ, который там был приватным, а новое определение стало бы его отдавать. Отказ — 409 retained_key_reuse, и ничего не создаётся. Подтверждение переопределения (acknowledge_shadow) его не снимает. Форк копирует общее определение точно, поэтому ничего не читает по-другому. Если позже сделать поле собственного компонента сайта публичным, это обычное изменение приватности.
  • В установке с отдельной БД каждого сайта изменение списка полей или статуса устаревшего типа общего компонента отклоняется с 409 split_topology_migration_unsupported. Название менять можно. Перед правкой полей создайте копию компонента для сайта. Создание нового общего определения или замещение общего компонента по-прежнему не может открыть ранее сохранённые значения под чужим ключом (retained_key_reuse или split_topology_reinterpretation_unsupported); недоступная БД сайта возвращает 503 site_database_unavailable. На время изменения собственного компонента запись затронутого контента блокируется. Параллельные сохранение, восстановление, перенос истории и восстановление языка возвращают component_migration_in_progress; ранее подготовленная запись — model_contract_changed. Повторите операцию после изменения структуры. Если изменилось определение, записанное при проверке импорта, выполните проверку заново.
  • Переименование, удаление или смена типа поля также прерывает любое сохранение, запланированное по старому определению (model_contract_changed); повторите его. Версия с этим ключом, сохранённая во время проверки, отклоняет изменение (model_change_stale или component_changed для компонента); повторите попытку.
  • Перевод поля в приватные или обратно в публичные переиндексирует поиск по сайту для модели и очищает её кешированные публичные чтения. Ключ кеша чтений также учитывает приватные поля модели, поэтому после перевода поля в приватные прежний кешированный ответ не используется. Изменение также прерывает любое сохранение, запланированное по старому определению (model_contract_changed); повторите его.
  • Генерируемый бэкенд сохраняет приватные столбцы для редакторов. Анонимное чтение, токены чтения и каждый ответ на запись выбирают только публичные столбцы. Столбец компонента, в котором где-либо ниже есть приватное поле, исключается из этих ответов целиком: сгенерированные маршруты возвращают собственную выборку guard, а частичная проекция JSON потребовала бы второго переписывания ответа на каждом маршруте чтения и записи. Связующая таблица приватной списковой связи читателям не отдаётся.
  • Сохранённый запрос, привязанный к доверенному маршруту, операция которого отдаётся анонимно (public в политике бэкенда), не может выбирать, фильтровать или сортировать по приватному полю. Привязка такого запроса или правка привязанного так, чтобы он называл такое поле, отклоняется (private_field_on_public_route, 422), а генерация отклоняет набор (saved_query_private_field), если с тех пор изменилась политика или поле. Поле компонента, содержащее приватное поле на любой глубине, здесь тоже считается приватным, как и для генерируемого читателя; если определения компонентов прочитать нельзя, приватным считается каждое поле компонента.

Любая модель может принимать комментарии посетителей к опубликованным записям. Включите Комментарии посетителей в общих настройках модели. Настройка — собственный ключ модели comments в её настройках ({"comments": true}); больше нигде она не хранится. Модель постов блога — одна из таких моделей: Записи → Комментарии → Принимать новые комментарии меняет тот же ключ. Модель, которая не публична, никогда не показывает комментарии.

  • Комментарий принадлежит модели, записи и одному языку, но не адресу. Переименование записи сохраняет её комментарии. У каждого языка своя ветка. Ответ можно оставить на одобренный комментарий верхнего уровня той же записи и того же языка; допускается один уровень ответов.
  • Комментарии показываются только у записи, которая опубликована сейчас. Снятие с публикации или корзина их скрывают. Выключение настройки скрывает все комментарии и отклоняет новые комментарии и новые реакции; ничего не удаляется. Удалить навсегда в корзине удаляет комментарии записи и их реакции.
  • Новые комментарии ожидают модерации; возможные состояния: Ждут, Одобрены, Спам или Отклонены. Модерируйте их в Контент → Комментарии, где собраны все модели и есть фильтр по модели, или в блоге в Записи → Комментарии. Правила те же, что у блога: см. комментарии блога.
Действие Право
Чтение и модерация content:publish
Окончательное удаление content:publish и удаление в модели комментария
Включение или выключение комментариев модели site:settings (Записи → Комментарии, PUT /api/comments/settings) или model:apply (переключатель Комментарии посетителей модели, сохраняется вместе с моделью в разделе Конструктор контента)
Отметить комментарий как Helpful от своего имени чтение в модели комментария

Комментарий принимает один вид реакции: Helpful.

  • Одна реакция на посетителя или человека, комментарий и вид. Повторная отправка засчитывается один раз. Реакцию можно отозвать. Реакцию принимает только одобренный комментарий.
  • Посетители различаются в пределах комментария по дайджесту сайта, комментария и адреса клиента с ключом FORM_INTAKE_HMAC_KEY Worker приёма. Setup генерирует этот ключ один раз и никогда его не заменяет. Адрес никогда не хранится, и реакции одного посетителя на два комментария нельзя связать. Посетители с одного общего адреса считаются одним.
  • Worker приёма без ключа отклоняет реакции посетителей (not_configured, 503) и сообщает Reactions are not set up on this site yet. Комментарии продолжают работать. Запустите setup ещё раз, чтобы добавить ключ.
  • Вошедший человек отмечает комментарий как Helpful в списке модерации от своего имени.
  • Счётчик показывается у комментария. Dee Wan не хранит статистику реакций.
  • Один клиент может отправить 60 реакций каждые 10 минут на сайт; они считаются отдельно от комментариев и форм.

Посетители выбирают Oldest (по умолчанию), Newest или Most helpful (?comments_order=), а публичное чтение принимает order=oldest|newest|best.

  • best: сначала больше реакций Helpful; при равном числе — сначала старые; затем по id комментария. Порядок зависит только от сохранённых реакций и времени комментариев, а не от текущего времени, поэтому для одних и тех же данных он всегда одинаков.
  • Ответы под своим комментарием всегда идут от старых к новым.
  • Управляемый рендерер рисует комментарии под каждым постом блога и каждой страницей записи, которую отдаёт маршрут. Страница, модель которой не принимает комментарии, их не рисует.
  • Экспортированный сайт рисует их в постах блога и на страницах записей каждой модели, которая принимает комментарии. Эти страницы отдаются во время запроса, как посты блога, и читают комментарии вживую; остальные страницы остаются предварительно отрисованными. Какие модели принимают комментарии, фиксируется при генерации сайта: после включения или выключения комментариев модели сгенерируйте сайт заново.
  • Рендереры читают GET /v1/_comments?entry=&order=; посетители отправляют на /v1/interactions/comment.submit и /v1/interactions/comment.react на хосте сайта. Оба работают без JavaScript. См. публичный API.
  • При включённом публичном пограничном кеше реакция сразу обновляет кешированные комментарии, только если у Worker приёма есть настройки очистки (PUBLIC_CACHE_MAX_AGE, CF_PURGE_ZONE_ID, CF_PURGE_API_TOKEN). Иначе счётчики обновляются в течение пяти минут. Модерация и настройка модели очищают кеш из админки.
  • Копия сайта и перенос истории сайта несут каждый комментарий каждой экспортированной записи с его состоянием, приватным email и счётчиками реакций. Кто реагировал, не переносится: счётчики приходят как импортированные реакции комментария и складываются с новыми, поэтому скопированный комментарий сохраняет своё место в best.
  • API управления выводит список комментариев, читает, модерирует и удаляет их, а также включает или выключает комментарии модели — одинаково через REST, CLI и MCP. См. API управления.

Установленный плагин может проверять контент перед сохранением или публикацией (Настройки → Плагины, под плагином). Собственные правила Dee Wan выполняются первыми; проверка их никогда не заменяет.

  • Проверка, которая отказывает, останавливает сохранение, восстановление или публикацию. Редактор называет плагин и проверку, показывает её сообщение у названных полей и сохраняет вашу правку. Ничего не записывается.
  • Проверка, которая предупреждает, пропускает изменение и показывает предупреждение. Сохранять второй раз не нужно.
  • Когда проверка не отвечает, решает настройка плагина. По умолчанию изменение проходит с заметкой. Если включено Блокировать изменения без ответа, ничего не меняется, и редактор говорит, что плагин не ответил.
  • Проверяются сохранения, восстановления, публикации, массовые действия, применение таблиц, импорт, API управления и операции записи других плагинов. Выключенный плагин и плагин без доступа к типу контента к проверке не привлекаются.

Перед применением изучите предварительный результат изменения модели. Перед разрушительными или несовместимыми изменениями сохраните резервную копию. Применение изменений модели не создаёт резервную копию. После изменения моделей обновите публичный TypeScript-клиент и пересоберите использующий его сайт.

Каноническая схема конфигурации полей — frontend/src/lib/eav/field-config-schema.ts. Типы, поддерживаемые генерируемым бэкендом, перечислены в backend/src/lib/graduate/input.ts. Справочник API автономного бэкенда и детали реализации описывают экспортированные API и БД, а не внутреннее хранение CMS.

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

Подписи зависят от языка интерфейса пользователя, а не от языка записи. Порядок подстановки: точная локаль интерфейса, её базовый язык, язык сайта по умолчанию, затем исходная подпись. Пустой перевод пропускается. Ключи API, слаги моделей, ключи групп и значения вариантов не меняются. Определения моделей, API управления и копии сайта сохраняют переводы; значения публичного контента не меняются.

{
"model": {
"settings": {
"translations": {
"ru": { "name": "Статьи", "groups": { "seo": { "label": "Поиск" } } }
}
}
},
"field": {
"ui": {
"group": "seo",
"translations": {
"ru": { "label": "Заголовок", "placeholder": "Название статьи" }
}
},
"config": {
"type": "select",
"options": [
{
"value": "gold",
"label": "Gold",
"translations": { "ru": "Золотой" }
}
]
}
}
}

По умолчанию Проверка черновиков разрешает незаполненные поля. Можно сохранить отсутствующие обязательные значения, в том числе внутри компонентов и групп. Указанные значения всё равно должны соответствовать типу, диапазону и структуре. При публикации проверяются все обязательные поля. Выберите Требовать заполнения всех полей, чтобы проверять их и при сохранении черновика. Настройка model.settings.validation_mode принимает draft или strict.

Для поля date с форматом datetime включите Сохранять часовой пояс (withTimezone: true). Редактор сохраняет местные дату и время вместе с выбранным часовым поясом IANA. Несуществующее время при переходе на летнее время отклоняется; для повторяющегося местного времени нужно явно выбрать смещение. При смене часового пояса введённое местное время сохраняется, а соответствующий момент пересчитывается. Для повторяющегося времени выбор смещения остаётся доступным; секунды и миллисекунды сохраняются. Потребители получают оба значения:

{ "datetime": "2026-10-18T05:30:00.000Z", "timezone": "Asia/Bangkok" }

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

Поле point хранит конечные числа: широту от −90 до 90 и долготу от −180 до 180. Ноль — допустимая координата. Два поля ввода сохраняют одно значение:

{ "latitude": 13.7563, "longitude": 100.5018 }

Фильтры координат поддерживают радиус в метрах и прямоугольную область, включая пересечение меридиана 180°. Сортировка по близости использует точное расстояние по большой окружности; при равном расстоянии порядок определяется ID записи. Подсчёт, страницы и выбор записей по фильтру используют тот же запрос. Координаты и даты с часовым поясом сохраняют объектную форму в истории, импорте, публичных ответах и сгенерированных бэкендах.

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

Настройка Также принимать доступна для полей-связей в повторно используемых компонентах, включая вложенные и повторяемые компоненты и списки смешанных блоков. Каждый выбранный элемент сохраняет пару { model, id } и своё место в списке. При изменении связей сервер проверяет фактическую модель цели, сайт и доступ на чтение. Правила кратности и валидации поля продолжают действовать.

История и восстановление сохраняют эти значения. При копировании сайта и импорте пакета ID и модель заменяются вместе на значения целевой записи. Неразрешённая ссылка блокирует импорт. Публичное чтение и генерируемый сайт исключают неопубликованные, удалённые и недоступные цели, не меняя сохранённую версию.

Публичное иерархическое поле отдаёт предков из опубликованных версий на нужном языке в ancestors[field]. У каждого элемента есть ID, язык фактически отданной версии, её текущая опубликованная подпись и реальный адрес на сайте. В цепочку входят не более 32 ближайших предков, от самого дальнего из включённых к родителю. Ответ содержит не более восьми публичных иерархических полей в порядке их ключей. Более длинная цепочка обрывается на этом пределе. Недоступный предок, приватное поле подписи или отсутствие публичного адреса останавливают цепочку. Система не придумывает адреса и подписи.

Общий SEO-рендерер формирует BreadcrumbList, когда цепочка, подпись и адрес текущей записи доступны. Изменение черновика не меняет опубликованные хлебные крошки. Публикация переименованного или перемещённого предка обновляет следующий ответ и сбрасывает зависимый кеш модели. Генерируемый сайт получает цепочку из опубликованных входных данных; после их изменения выполните генерацию повторно.


Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: fe23db481c422b40e317b92fc98116b4c0c26a87 (с локальными изменениями)
Дата сборки (UTC): 2026-10-18T09:22:34.630Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску