Типы контента и поля
На странице Первый тип контента — базовый тур: как создать тип, какие бывают поля, как устроены ссылки и локализация. Здесь — справочник: наследование, интерфейсы, полиморфные блоки, разрешения и то, что под капотом схемы.
Наследование (Extends)
Тип может наследовать поля другого типа. В редакторе типа выберите
Родительский тип контента (раздел Наследование) — наследник получит все
поля родителя и добавит свои. Пример: blog-post наследует page —
появляются title, slug, status, seo, blocks, а к ним добавляются
excerpt, published_at, author, tags.
Правила наследования:
- Одинарное и цепочечное — тип наследует одного родителя, но родитель
сам может наследовать:
landing-page → page → …. Глубина не ограничена. - Потомок перекрывает родителя — если в потомке объявлено поле с тем же именем, оно заменяет родительское на том же месте (порядок сохраняется).
- Порядок полей стабилен — сначала поля родителя (в их порядке), затем поля потомка.
- Циклы запрещены —
A extends B extends Aотклоняется с ошибкойCIRCULAR_INHERITANCE. - Родитель должен существовать — указать несуществующий slug нельзя,
сервер вернёт
PARENT_NOT_FOUND. - Extends нельзя менять после создания типа — родитель фиксируется один раз.
- Наследник может не иметь своих полей —
landing-pageнаследуетpageи не добавляет ни одного своего поля: вся страница собирается из блоков.
Наследованные поля попадают не только в GraphQL-тип, но и в фильтры и
сортировку. blogPosts(where: { status: { eq: "published" } }) работает
именно потому, что status унаследован от page.
Интерфейсы (Node, Timestamped)
Каждый тип автоматически реализует два GraphQL-интерфейса:
| Интерфейс | Поля |
|---|---|
Node |
id: ID! |
Timestamped |
created_at: DateTime, updated_at: DateTime |
Эти поля добавляются сервером — их не нужно объявлять в схеме типа. id
генерируется автоматически при создании записи, created_at и
updated_at проставляются при записи.
Существует третий интерфейс — Entity (id, slug, name), но он не
добавляется типам автоматически и нужен для внутренней типизации.
Полиморфные блоки
Поле типа «Тип контента» + Сделать массивом без указания целевого типа —
это полиморфный список блоков. Так устроено поле blocks у типа page:
в одну страницу можно сложить блоки любых типов (Hero, Text, Gallery, CTA и
т. д.), и порядок элементов сохраняется.
В GraphQL такое поле получает тип [Block], где Block — это union всех
типов в схеме. Запрос:
query {
pages {
title
blocks {
... on HeroSection { title subtitle }
... on TextBlock { body }
... on Gallery { images { url } }
}
}
}
Ограничения полиморфных блоков:
- Нельзя фильтровать и сортировать — поле
blocksне появляется в<Type>Whereи<Type>OrderByField. - Нельзя локализовать — сервер отклонит (
LOCALIZED_POLYMORPHIC_BLOCKS_UNSUPPORTED). Список блоков один на все языки. - Имена полей должны быть совместимы — все типы в схеме входят в union
Block, поэтому одноимённые поля в разных типах должны совпадать по GraphQL-типу. Иначе —BLOCK_UNION_FIELD_TYPE_CONFLICT.
Union живой: он содержит все типы схемы, поэтому созданный позже тип блока сразу доступен через blocks (дайте асинхронной пересборке схемы секунду-другую). Перечислять фрагменты руками тоже не обязательно: голая выборка blocks { } разворачивается сервером в выборки скалярных полей каждого типа с __typename. А если два УЖЕ СУЩЕСТВУЮЩИХ типа объявили одноимённое поле с разными скалярными типами (например, Int и Float), запросы не ломаются — сервер проставит алиас конфликтующему полю для каждого члена union. Проверка при создании выше страхует только НОВЫЕ определения; старые коллизии терпятся на этапе запросов, а не ломают сайт.
Если нужен список ссылок на один конкретный тип (например, теги), укажите целевой тип — тогда поле становится обычным массивом ссылок, который можно фильтровать и локализовать.
Поле типа Список (select)
Список (select) — это enum-поле: значение выбирается из
предопределённого списка вариантов. В GraphQL оно представлено как String,
но сервер принуждает значение к списку разрешённых — сохранить
произвольную строку нельзя.
Варианты настраиваются прямо в редакторе поля. Когда выбран тип Список, появляется редактор Вариантов: каждая строка содержит:
- Значение (сохраняется) — что записывается в БД (
draft,published). - Подпись (отображается) — что видит редактор в админке (
Черновик,Опубликовано).
Также выбирается Значение по умолчанию — оно подставляется в новую запись.
Пример: поле status у page хранит draft или published.
Попробовать записать archive — сервер вернёт
SELECT_VALUE_NOT_ALLOWED.
Если у Списка не задано ни одного варианта, проверка отключается (поле ведёт себя как свободный текст). Это нужно для обратной совместимости со старыми полями, созданными до этого механизма.
Зарезервированные имена
GraphQL-имя типа — это PascalCase от slug (blog-post → BlogPost). Имена,
которые заняты самой схемой, нельзя использовать:
Node, Entity, Timestamped, EntityDefinition, PropertyDefinition,
Condition, ValidationRule, APIKey,
APIKeyMutation, Query, Mutation, CreateEntityInput,
UpdateEntityInput, PropertyInput, ConditionInput,
ValidationRuleInput, AuthPayload.
Если PascalCase от slug совпадёт с одним из них, сервер отклонит создание типа.
Разрешения на тип
У каждого типа есть матрица разрешений — кто из ролей может читать, создавать, обновлять и удалять записи. По умолчанию:
| Роль | Чтение | Создание | Изменение | Удаление |
|---|---|---|---|---|
| viewer | да | — | — | — |
| editor | да | да | да | — |
| admin / owner | да | да | да | да |
Разрешения хранятся в поле permissions типа и редактируются на странице
«Разрешения» в админке. Подробнее — на странице
Роли и доступ.
Что под капотом
Записи хранятся как JSONB в таблице entity_data. Схема типа (определение
полей) живёт отдельно, в entity_definitions. Когда вы меняете схему:
- Сервер валидирует тип (зарезервированные имена, циклы наследования, конфликты Block-union).
- Сохраняет новую версию в
entity_definitionsи фиксирует версию схемы. SchemaManagerпересобирает GraphQL-схему из актуальных определений.- Новая схема вступает в силу атомарно — идущие запросы дорабатывают на старой, новые идут на новой.
Добавить поле — и оно в API. Удалить — и его нет. Мигрировать данные не нужно: JSON хранит форму, а определение описывает её.
Что дальше
- GraphQL-запросы — singular и plural, переменные, локаль.
- Фильтрация и сортировка — Where, операторы, OrderBy.
- Page Builder — соберите страницу из блоков.
---