Розробка плагінів для Payload CMS: архітектура та реалізація

Коли функціональність потрібна одразу в кількох колекціях, дублювання коду та ручні правки перетворюють підтримку на рутину. Ми розробляємо плагіни для Payload CMS, які централізують логіку та усувають дублювання. Наша команда реалізує проєкт під ключ — від архітектури до впровадження, забезпечуючи стабільну роботу та подальшу підтримку.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Розробка плагінів для Payload CMS: архітектура та реалізація
Складний
~3-5 днів
Часті запитання

Розробка плагінів для Payload CMS

Уявіть: у вас 10 колекцій, в кожну потрібно додати однакові SEO-поля — мета-заголовок, опис, зображення та прапорець noIndex. Ручне додавання займає 2-3 години, але при зміні структури поля доведеться правити кожну колекцію — це 20-30 годин на підтримку на рік. Плагіни Payload CMS вирішують цю проблему раз і назавжди: ви описуєте поля один раз у коді плагіна, а потім підключаєте його до потрібних колекцій одним рядком. Ми розробляємо такі плагіни під ключ з гарантією якості та підтримкою. Наш досвід — понад 5 років і 15+ плагінів для різних проєктів. Якщо вам потрібен плагін, зв'яжіться з нами для консультації.

Чому плагіни вигідні?

Основна проблема — дублювання коду. Якщо функціональність потрібна в кількох колекціях, копіювання полів і хуків роздуває базу коду. Друга проблема — складність підтримки: зміна логіки вимагає правки в кожному місці. Третя — відсутність єдиного інтерфейсу для схожих операцій. Плагіни централізують функціональність: SEO, аудит, пошук, кастомні ендпоінти. Наприклад, плагін SEO додає однакові мета-поля у всі вказані колекції, а плагін аудиту протоколює всі зміни в єдину колекцію логів. Порівняння з ручним додаванням: плагін у 3-4 рази швидше в підтримці та менш схильний до помилок. Впровадження скорочує час на 30-40%. На одному з наших проєктів для інтернет-магазину з 30 колекціями ми розробили плагін SEO, який дозволив додати мета-поля одним рядком коду, скоротивши час на 40%.

Як розробити плагін для Payload CMS?

Згідно з офіційною документацією Payload CMS, плагін — це "функція, яка приймає конфігурацію та повертає змінену конфігурацію". Це не магія: плагін просто додає колекції, поля, хуки, ендпоінти та компоненти до існуючої конфігурації перед ініціалізацією CMS. Офіційні плагіни (@payloadcms/seo, @payloadcms/form-builder) слідують цій же моделі.

// Тип плагіна
type Plugin = (incomingConfig: Config) => Config

// Найпростіший плагін
const myPlugin: Plugin = (config) => {
  return {
    ...config,
    collections: [
      ...(config.collections || []), // додати колекцію
    ],
    hooks: {
      ...config.hooks,
      afterInit: [
        ...(config.hooks?.afterInit || []), // додати хук
      ],
    },
  }
}

export default buildConfig({
  plugins: [myPlugin],
})

Приклади плагінів: SEO, аудит, пошук

Плагін SEO додає мета-поля у всі вказані колекції:

// plugins/seo/index.ts
import type { Config, CollectionConfig, GlobalConfig } from 'payload/types'

interface SEOPluginConfig {
  collections?: string[] // slug колекцій, куди додати SEO-поля
  globals?: string[]
  uploadsCollection?: string
  generateTitle?: (doc: any) => string
  generateDescription?: (doc: any) => string
}

export const seoPlugin = (pluginConfig: SEOPluginConfig) => (config: Config): Config => {
  const seoFields = [
    {
      name: 'meta',
      type: 'group' as const,
      label: 'SEO',
      admin: {
        position: 'sidebar' as const,
      },
      fields: [
        {
          name: 'title',
          type: 'text' as const,
          admin: {
            description: ({ doc }: any) => pluginConfig.generateTitle?.(doc) || 'Автозаповнення: заголовок документа',
          },
        },
        {
          name: 'description',
          type: 'textarea' as const,
          maxLength: 160,
        },
        {
          name: 'image',
          type: 'upload' as const,
          relationTo: pluginConfig.uploadsCollection || 'media',
        },
        {
          name: 'noIndex',
          type: 'checkbox' as const,
          defaultValue: false,
        },
      ],
    },
  ]

  return {
    ...config,
    collections: config.collections?.map(collection => {
      if (pluginConfig.collections?.includes(collection.slug)) {
        return {
          ...collection,
          fields: [...(collection.fields || []), ...seoFields],
        }
      }
      return collection
    }),
    globals: config.globals?.map(global => {
      if (pluginConfig.globals?.includes(global.slug)) {
        return {
          ...global,
          fields: [...(global.fields || []), ...seoFields],
        }
      }
      return global
    }),
    hooks: {
      ...config.hooks,
      afterRead: [
        ...(config.hooks?.afterRead || []),
        ({ doc }: any) => {
          if (!doc.meta?.title && pluginConfig.generateTitle) {
            doc.meta = {
              ...doc.meta,
              title: pluginConfig.generateTitle(doc),
            }
          }
          return doc
        },
      ],
    },
  }
}

Плагін аудиту дій логує всі зміни:

// plugins/audit-log/index.ts
import type { Config } from 'payload/types'

interface AuditLogConfig {
  collections: string[]
}

export const auditLogPlugin = ({ collections }: AuditLogConfig) => (config: Config): Config => {
  const auditCollection = {
    slug: 'audit-logs',
    admin: {
      hidden: true,
    },
    access: {
      read: ({ req }: any) => req.user?.role === 'admin',
      create: () => false,
      update: () => false,
      delete: () => false,
    },
    fields: [
      {
        name: 'collection',
        type: 'text' as const,
      },
      {
        name: 'docId',
        type: 'text' as const,
      },
      {
        name: 'operation',
        type: 'text' as const,
      },
      {
        name: 'user',
        type: 'relationship' as const,
        relationTo: 'users' as const,
      },
      {
        name: 'before',
        type: 'json' as const,
      },
      {
        name: 'after',
        type: 'json' as const,
      },
      {
        name: 'timestamp',
        type: 'date' as const,
      },
    ],
  }

  const auditedCollections = config.collections?.map(collection => {
    if (!collections.includes(collection.slug)) return collection
    return {
      ...collection,
      hooks: {
        ...collection.hooks,
        afterChange: [
          ...(collection.hooks?.afterChange || []),
          async ({ doc, previousDoc, operation, req }: any) => {
            if (!req.payload) return
            await req.payload.create({
              collection: 'audit-logs',
              data: {
                collection: collection.slug,
                docId: String(doc.id),
                operation,
                user: req.user?.id,
                before: previousDoc || null,
                after: doc,
                timestamp: new Date().toISOString(),
              },
              disableVerificationEmail: true,
            })
          },
        ],
      },
    }
  })

  return {
    ...config,
    collections: [
      ...(auditedCollections || []),
      auditCollection,
    ],
  }
}

Пошуковий плагін додає кастомний ендпоінт /search:

// plugins/search/index.ts
export const searchPlugin = (config: Config): Config => ({
  ...config,
  endpoints: [
    ...(config.endpoints || []),
    {
      path: '/search',
      method: 'get' as const,
      handler: async (req: any, res: any) => {
        const { q } = req.query
        if (!q) return res.json({ docs: [] })
        const results = await Promise.all([
          req.payload.find({
            collection: 'posts',
            where: {
              or: [
                { title: { like: q } },
                { excerpt: { like: q } }
              ]
            },
            limit: 5,
          }),
          req.payload.find({
            collection: 'products',
            where: {
              name: { like: q }
            },
            limit: 5,
          }),
        ])
        return res.json({
          docs: [
            ...results[0].docs.map(d => ({ ...d, _type: 'post' })),
            ...results[1].docs.map(d => ({ ...d, _type: 'product' })),
          ],
        })
      },
    },
  ],
})

Публікація плагіна як npm-пакета

// package.json плагіна
{
  "name": "@myorg/payload-plugin-seo",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "peerDependencies": {
    "payload": "^2.0.0"
  },
  "scripts": {
    "build": "tsc"
  }
}

Експорт плагіна з src/index.ts: export { seoPlugin } from './plugin'; export type { SEOPluginConfig } from './types';. Після збірки публікуйте пакет в npm. Документація в README обов'язкова.

Порівняння типів плагінів

Тип плагіна Призначення Складність Приклад використання
SEO Додавання мета-полів Низька seoPlugin({ collections: ['posts'] })
Аудит Логування змін Середня auditLogPlugin({ collections: ['posts'] })
Пошук Кастомний ендпоінт Висока searchPlugin()

Типи хуків, що використовуються в плагінах

Хук Призначення Приклад у плагіні
beforeChange Валідація перед збереженням Перевірка унікальності slug
afterChange Логування змін Плагін аудиту
beforeRead Модифікація даних перед читанням Автозаповнення мета-полів
afterRead Постобробка SEO-плагін (додавання мета-заголовка)

Процес розробки плагіна

  1. Аналіз вимог: визначаємо колекції, хуки та ендпоінти. Враховуємо можливі колізії з існуючими полями.
  2. Проектування інтерфейсу: створюємо TypeScript-типи для конфігурації плагіна. Використовуємо строгу типізацію, щоб уникнути помилок на етапі компіляції.
  3. Реалізація: пишемо код плагіна, використовуємо глобальні хуки для централізованих змін. Код покриваємо unit-тестами (Jest) з покриттям не менше 90%.
  4. Тестування: перевіряємо критичні сценарії, включаючи граничні випадки (порожні колекції, відсутність хуків). Також проводимо інтеграційне тестування з реальною Payload CMS.
  5. Публікація та документація: готуємо README з прикладами використання, збираємо TypeScript у dist і публікуємо в npm. Весь процес займає від 3 до 10 днів залежно від складності.

Що входить у роботу

  • Вихідний код плагіна на TypeScript з повною типізацією.
  • Unit-тести (Jest) з покриттям не менше 90%.
  • Документація (README) з прикладами конфігурації та використання.
  • Підтримка після впровадження: виправлення та доопрацювання протягом місяця.

Типові помилки при розробці плагінів

  • Не перевіряти, що колекції та хуки можуть бути undefined або порожніми — викликають помилки при запуску.
  • Використовувати хук afterInit для додавання полів замість модифікації колекцій безпосередньо — після ініціалізації поля вже не застосувати.
  • Забувати експортувати типи конфігурації плагіна — користувачі не зможуть отримати автодоповнення в IDE.

Коли замовляють розробку плагіна?

Кожен наш плагін тестується та документується. Ми знаходимо оптимальні архітектурні рішення, враховуючи специфіку вашого проєкту. Плагін окупається вже через кілька місяців використання — економія часу на додаванні однотипних полів сягає 40%. Якщо ви хочете отримати готове рішення з гарантією якості, замовте розробку плагіна — зв'яжіться з нами для обговорення завдання.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060