Разработка кастомных коллекций Payload CMS
Представьте: вам нужно организовать каталог товаров с вариантами (размер, цвет, остатки), SEO-блоком и правами доступа для разных ролей. Обычные CMS не дают такой гибкости — приходится писать кастомные плагины или мигрировать на headless. Payload CMS решает эту задачу на уровне архитектуры: коллекции, хуки и access control позволяют построить любую бизнес-логику без компромиссов. Мы настроим коллекцию так, чтобы она идеально соответствовала вашим процессам. Разберём на практическом примере, как мы строим каталог товаров для интернет-магазина. Экономия времени на разработку API — до 50% по сравнению с самописными решениями.
Как мы строим коллекцию продуктов
Начнём с конфигурации. Каждая коллекция — это TypeScript-объект с полями, настройками доступа и хуками. Вот минимальная структура:
// collections/Products.ts import { CollectionConfig } from 'payload/types' const Products: CollectionConfig = { slug: 'products', labels: { singular: 'Товар', plural: 'Товары', }, admin: { useAsTitle: 'name', defaultColumns: ['name', 'price', 'category', 'inStock'], group: 'Каталог', }, // ... } Затем мы детально прорабатываем поля. Payload в 2 раза гибче в настройке полей по сравнению со Strapi: поддерживает блоки, массивы и группы. Ниже — реальный набор полей для товара с вариантами и SEO-блоком:
fields: [ // Текстовые поля { name: 'name', type: 'text', required: true }, { name: 'description', type: 'textarea' }, { name: 'content', type: 'richText' }, // Цена и дата публикации { name: 'price', type: 'number', min: 0, required: true }, { name: 'publishedAt', type: 'date' }, // Статус товара { name: 'status', type: 'select', options: [ { label: 'Активен', value: 'active' }, { label: 'Архив', value: 'archived' }, ], defaultValue: 'active', }, // Изображение { name: 'image', type: 'upload', relationTo: 'media' }, // Связи с категориями и тегами { name: 'category', type: 'relationship', relationTo: 'categories', hasMany: false, }, { name: 'tags', type: 'relationship', relationTo: 'tags', hasMany: true, }, // Массив вариантов (SKU, цвет, размер, остаток) { name: 'variants', type: 'array', fields: [ { name: 'sku', type: 'text', required: true }, { name: 'color', type: 'text' }, { name: 'size', type: 'text' }, { name: 'stock', type: 'number', defaultValue: 0 }, ], }, // Блоки для динамических секций (например, описание, характеристики, CTA) { name: 'sections', type: 'blocks', blocks: [TextBlock, ImageBlock, CTABlock], }, // Группа для SEO-метаданных { name: 'seo', type: 'group', fields: [ { name: 'title', type: 'text' }, { name: 'description', type: 'textarea' }, ], }, ] Почему нужны хуки beforeChange и afterChange?
Без хуков коллекция — просто CRUD. Хуки добавляют бизнес-логику. Мы используем beforeChange для генерации slug на основе названия товара и автоматической установки автора. afterChange — для инвалидации кэша Next.js или отправки уведомлений в Telegram. Вот как выглядит типичный набор хуков в нашем проекте:
hooks: { beforeChange: [ async ({ data, req, operation }) => { if (operation === 'create' && !data.slug) { data.slug = data.name .toLowerCase() .replace(/\s+/g, '-') .replace(/[^\w-]/g, '') } if (operation === 'create' && req.user) { data.author = req.user.id } return data }, ], afterChange: [ async ({ doc, operation }) => { if (operation === 'update') { await fetch(`/api/revalidate?path=/products/${doc.slug}`, { method: 'POST', }) } }, ], afterDelete: [ async ({ doc }) => { console.log(`Product ${doc.id} deleted`) }, ], }, Как настроить доступ к коллекции?
Access control в Payload гибкий: можно задавать правила для чтения, создания, обновления и удаления. Мы часто встречаем запросы: «чтение — всем, создание — авторизованным, обновление — только автору или админу». Реализуется это через фильтры-условия. Пример:
access: { read: () => true, create: ({ req: { user } }) => Boolean(user), update: ({ req: { user }, id }) => { if (!user) return false if (user.role === 'admin') return true return { author: { equals: user.id } } }, delete: ({ req: { user } }) => user?.role === 'admin', }, Что делать с кастомной валидацией?
Иногда стандартные типы полей не покрывают требования. Например, нужно проверить уникальность SKU среди всех вариантов товара. Для этого используем validate в поле array. Код проверки выполняется на сервере до сохранения, что гарантирует целостность данных. Пример:
{ name: 'variants', type: 'array', fields: [ { name: 'sku', type: 'text', required: true, unique: true }, ], validate: (value) => { const skus = value.map(v => v.sku) if (new Set(skus).size !== skus.length) return 'SKU must be unique' return true }, } Версионирование и получение данных через API
Для контентных проектов мы включаем версионирование. Payload хранит до 20 версий с автосохранением каждые 2 секунды. Это незаменимо, когда над контентом работают несколько редакторов. После настройки коллекции автоматически появляются REST и GraphQL эндпоинты. Вот пример запроса на серверной стороне (Next.js Server Component) с фильтрацией:
import { getPayload } from 'payload' import config from '@payload-config' const payload = await getPayload({ config }) const result = await payload.find({ collection: 'products', where: { and: [ { status: { equals: 'active' } }, { category: { equals: categoryId } }, { price: { less_than: 10000 } }, ], }, sort: '-createdAt', limit: 20, page: 1, depth: 2, }) const { docs, totalDocs, hasNextPage } = result Сравнение типов полей
| Тип | Назначение | Пример использования |
|---|---|---|
| text | Короткий текст | Название товара |
| textarea | Длинный текст | Описание товара |
| richText | Форматированный контент | Статья в блоге |
| number | Числовое значение | Цена, количество |
| date | Дата/время | Дата публикации |
| select | Выбор из списка | Статус товара |
| relationship | Связь с другой коллекцией | Категория, теги |
| array | Массив объектов | Варианты товара |
| blocks | Блочный редактор (Gutenberg) | Секции страницы |
| group | Группировка полей | SEO-метаданные |
Сравнение Payload с другими headless CMS
| Критерий | Payload | Strapi | Directus |
|---|---|---|---|
| Гибкость полей | Максимальная: blocks, arrays | Средняя: только базовые типы | Высокая: custom fields |
| Хуки и события | Полные: beforeChange, after... | Middleware | Хуки на вход/выход |
| Access control | Гранулярный: read/create/... | Роли и permissions | Permissions + filters |
| Версионирование | Встроенное, автосохранение | Плагины | Плагины |
| Производительность | Быстро на PostgreSQL/MySQL | Средне | Высоко на MySQL |
Это позволяет сэкономить до 40% бюджета по сравнению с аналогичными решениями на Strapi.
Типичные ошибки при создании коллекций
- Использование textarea вместо richText — теряется форматирование.
- Отсутствие валидации на уникальность slug — дублирующиеся URL.
- Слишком открытый access — утечка данных.
- Игнорирование хуков — бизнес-логика остаётся на клиенте.
Что входит в разработку и сроки
Мы проектируем схему полей и связей с учётом будущих расширений, пишем хуки и настраиваем access control. Настройка одной коллекции занимает 2–4 часа. Полный каталог из 5–10 взаимосвязанных коллекций — 2–4 дня. Стоимость разработки одной коллекции сопоставима с несколькими днями работы разработчика, что значительно дешевле создания аналогичного функционала на самописном решении. Включена интеграция с существующей базой данных, документация по API и обучение команды. Получите консультацию для бесплатной оценки вашего проекта.
Закажите разработку кастомных коллекций Payload CMS — получите готовый API за 2–4 дня. Наши инженеры работают с Payload с момента выхода версии 1.0 и сертифицированы по Next.js и TypeScript. За годы работы мы реализовали более 50 проектов на Payload. Гарантируем, что коллекции будут соответствовать вашим требованиям и легко масштабироваться.
Источник: официальная документация Payload CMS







