Разработка кастомных коллекций 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







