Настройка GraphQL API Craft CMS
Craft CMS 3.3+ включает встроенный GraphQL API. Доступен по умолчанию на /api или кастомному endpoint. Поддерживает запросы элементов (Entry, Asset, Category, Tag, User), но не мутации (изменение данных через GraphQL не поддерживается нативно).
Включение и настройка
// config/general.php
'enableGraphqlApi' => true,
'maxGraphqlComplexity' => 500, // ограничение сложности запроса
'maxGraphqlDepth' => 10, // максимальная глубина вложенности
'maxGraphqlResults' => 100, // максимальное количество результатов
Токены доступа
В CP → GraphQL → Schemas создаём схему с нужными правами:
- Public Schema — без авторизации, только публичный контент
- Private Schema — с токеном, может включать черновики
// Запрос с токеном
fetch('/api', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.CRAFT_GRAPHQL_TOKEN}`,
},
body: JSON.stringify({ query, variables }),
});
Типичные запросы
# Список постов блога
query BlogPosts($limit: Int, $offset: Int) {
entries(
section: "blog"
orderBy: "postDate DESC"
limit: $limit
offset: $offset
status: "live"
) {
id
title
slug
postDate @formatDateTime(format: "d.m.Y")
url
... on blog_article_Entry {
summary
heroImage { url(width: 800) alt width height }
categories { title slug }
author { fullName photo { url(width: 100, height: 100) } }
}
}
entryCount(section: "blog", status: "live")
}
# Один пост по slug
query BlogPost($slug: String!) {
entry(section: "blog", slug: [$slug]) {
... on blog_article_Entry {
title
postDate @formatDateTime(format: "d MMMM Y", locale: "ru")
pageBody {
... on pageBody_richText_BlockType {
typeHandle
content
}
... on pageBody_image_BlockType {
typeHandle
image { url(width: 1200) alt width height }
caption
alignment
}
... on pageBody_codeBlock_BlockType {
typeHandle
language
code
}
}
}
}
}
Next.js клиент с кэшированием
// lib/craftGraphql.ts
const CRAFT_ENDPOINT = process.env.CRAFT_GRAPHQL_URL!;
const CRAFT_TOKEN = process.env.CRAFT_GRAPHQL_TOKEN!;
async function craftQuery<T>(
query: string,
variables?: Record<string, unknown>,
options?: { revalidate?: number }
): Promise<T> {
const res = await fetch(CRAFT_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CRAFT_TOKEN}`,
},
body: JSON.stringify({ query, variables }),
next: { revalidate: options?.revalidate ?? 3600 },
});
const { data, errors } = await res.json();
if (errors?.length) throw new Error(errors[0].message);
return data;
}
Inline Fragments для Entry Types
Каждый Entry Type доступен как отдельный тип в GraphQL через паттерн {sectionHandle}_{typeHandle}_Entry:
entries(section: ["blog", "news"]) {
__typename
id
title
... on blog_article_Entry {
summary
readingTime
}
... on blog_podcast_Entry {
audioFile { url }
duration
}
... on news_pressRelease_Entry {
pdfFile { url }
companyName
}
}
Кастомные GraphQL-запросы через плагин/модуль
Встроенный GraphQL только читает данные. Для мутаций используем кастомный REST endpoint:
// modules/sitecustom/controllers/ApiController.php
public function actionSubmitForm(): Response
{
\Craft::$app->response->headers->add('Content-Type', 'application/json');
$data = \Craft::$app->request->post();
// Валидация, создание элемента через Craft API
$entry = new Entry();
$entry->sectionId = \Craft::$app->sections->getSectionByHandle('submissions')->id;
$entry->setFieldValues(['name' => $data['name'], 'email' => $data['email']]);
if (\Craft::$app->elements->saveElement($entry)) {
return $this->asJson(['success' => true]);
}
return $this->asJson(['errors' => $entry->errors], 422);
}
Настройка GraphQL API с токенами и интеграцией с Next.js — 1–2 дня.







