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







