Кастомні колекції Payload CMS
Колекція (Collection) в Payload — тип контенту з REST API, GraphQL схемою і інтерфейсом в admin panel, генерованими автоматично з TypeScript-конфігурації. Кожна колекція зберігається в окремій таблиці PostgreSQL або колекції MongoDB.
Структура колекції
// collections/Products.ts
import { CollectionConfig } from 'payload/types'
const Products: CollectionConfig = {
slug: 'products', // URL сегмент: /api/products
labels: {
singular: 'Товар',
plural: 'Товари',
},
admin: {
useAsTitle: 'name',
defaultColumns: ['name', 'price', 'category', 'inStock'],
group: 'Каталог',
},
// ...
}
Типи полів
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,
},
// Масив об'єктів
{
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 },
],
},
// Блоки (як Gutenberg)
{
name: 'sections',
type: 'blocks',
blocks: [TextBlock, ImageBlock, CTABlock],
},
// Група полів
{
name: 'seo',
type: 'group',
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'textarea' },
],
},
]
Контроль доступу (Access Control)
access: {
// Читання — публічне
read: () => true,
// Створення — тільки авторизовані
create: ({ req: { user } }) => Boolean(user),
// Оновлення — тільки власник або admin
update: ({ req: { user }, id }) => {
if (!user) return false
if (user.role === 'admin') return true
return { author: { equals: user.id } } // фільтр-умова
},
// Видалення — тільки admin
delete: ({ req: { user } }) => user?.role === 'admin',
},
Hooks колекції
hooks: {
beforeChange: [
async ({ data, req, operation }) => {
// Автоматичне створення slug
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 }) => {
// Інвалідація Next.js кеша
if (operation === 'update') {
await fetch(`/api/revalidate?path=/products/${doc.slug}`, {
method: 'POST',
})
}
},
],
afterDelete: [
async ({ doc }) => {
// Очищення пов'язаних даних
console.log(`Product ${doc.id} deleted`)
},
],
},
Версіонування
versions: {
maxPerDoc: 20,
drafts: {
autosave: {
interval: 2000, // автозбереження кожні 2 секунди
},
},
},
Запити до колекції
// На сервері (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, // глибина populate зв'язків
})
const { docs, totalDocs, hasNextPage } = result
REST API (автогенерований)
# Список
GET /api/products?where[status][equals]=active&limit=20
# Один документ
GET /api/products/:id
# Створити (потрібна авторизація)
POST /api/products
Authorization: Bearer <token>
Content-Type: application/json
# Оновити
PATCH /api/products/:id
# Видалити
DELETE /api/products/:id
Часові рамки
Налаштування однієї колекції з полями, доступом і hooks — 2–4 години. Повний каталог (5–10 взаємопов'язаних колекцій) — 2–4 дні.







