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







