Розробка кастомних хуків (Hooks) Payload CMS

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомних хуків (Hooks) Payload CMS
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

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

Етапи розробки

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

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Кастомні hooks Payload CMS

Hooks в Payload — основний механізм розширення бізнес-логіки. Вони виконуються на певних етапах життєвого циклу документа: до/після читання, створення, зміни, видалення. Hooks — async-функції TypeScript з повною типізацією.

Типи hooks колекцій

// collections/Orders.ts
const Orders: CollectionConfig = {
  slug: 'orders',
  hooks: {
    beforeOperation: [/* валідація до операції */],
    beforeValidate: [/* зміна даних до валідації */],
    beforeChange: [/* трансформація даних */],
    afterChange: [/* side effects після збереження */],
    beforeRead: [/* фільтрація даних при читанні */],
    afterRead: [/* збагачення даних після читання */],
    beforeDelete: [/* перевірки перед видаленням */],
    afterDelete: [/* cleanup після видалення */],
  },
}

Hooks beforeChange: трансформація даних

import type { CollectionBeforeChangeHook } from 'payload/types'

const generateOrderNumber: CollectionBeforeChangeHook = async ({
  data,
  req,
  operation,
}) => {
  if (operation === 'create') {
    // Генерація номера замовлення
    const count = await req.payload.count({ collection: 'orders' })
    data.orderNumber = `ORD-${String(count + 1).padStart(6, '0')}`

    // Встановити автора
    if (req.user) {
      data.createdBy = req.user.id
    }

    // Timestamp
    data.createdAt = new Date().toISOString()
  }
  return data
}

const validateStock: CollectionBeforeChangeHook = async ({ data, req }) => {
  // Перевірити наявність товарів перед створенням замовлення
  for (const item of data.items || []) {
    const product = await req.payload.findByID({
      collection: 'products',
      id: item.product,
    })
    if (product.stock < item.quantity) {
      throw new Error(`Товар "${product.name}": недостатньо на складі`)
    }
  }
  return data
}

Hooks afterChange: side effects

import type { CollectionAfterChangeHook } from 'payload/types'

const sendOrderConfirmation: CollectionAfterChangeHook = async ({
  doc,
  operation,
  req,
}) => {
  if (operation === 'create') {
    // Відправити email через сервіс
    await emailService.send({
      to: doc.customerEmail,
      subject: `Замовлення #${doc.orderNumber} прийнято`,
      template: 'order-confirmation',
      data: { order: doc },
    })
  }

  if (operation === 'update' && doc.status === 'shipped') {
    await emailService.send({
      to: doc.customerEmail,
      subject: `Замовлення #${doc.orderNumber} відправлено`,
      template: 'order-shipped',
      data: { order: doc, trackingNumber: doc.trackingNumber },
    })
  }
}

const revalidateCache: CollectionAfterChangeHook = async ({ doc }) => {
  // Інвалідація Next.js ISR кеша
  const paths = [`/products/${doc.slug}`, '/products']
  await Promise.all(
    paths.map(path =>
      fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/revalidate`, {
        method: 'POST',
        headers: { 'x-revalidate-secret': process.env.REVALIDATE_SECRET! },
        body: JSON.stringify({ path }),
      })
    )
  )
}

const syncWithCRM: CollectionAfterChangeHook = async ({ doc, operation }) => {
  if (operation === 'create') {
    // Створити сделку в CRM
    await crmClient.deals.create({
      title: `Замовлення #${doc.orderNumber}`,
      amount: doc.total,
      contactEmail: doc.customerEmail,
    })
  }
}

Hooks afterRead: збагачення даних

import type { CollectionAfterReadHook } from 'payload/types'

const addComputedFields: CollectionAfterReadHook = async ({ doc }) => {
  // Вичислити підсумкові поля, які не зберігаються в БД
  if (doc.items) {
    doc.subtotal = doc.items.reduce(
      (sum: number, item: any) => sum + item.price * item.quantity,
      0
    )
    doc.totalItems = doc.items.length
  }
  return doc
}

Hooks beforeDelete: захист від видалення

import type { CollectionBeforeDeleteHook } from 'payload/types'

const preventDeleteWithOrders: CollectionBeforeDeleteHook = async ({
  id,
  req,
}) => {
  // Запретити видалення клієнта з активними замовленнями
  const orders = await req.payload.find({
    collection: 'orders',
    where: {
      and: [
        { customer: { equals: id } },
        { status: { not_in: ['completed', 'cancelled'] } },
      ],
    },
    limit: 1,
  })

  if (orders.totalDocs > 0) {
    throw new Error('Неможливо видалити клієнта з активними замовленнями')
  }
}

Hooks Global

const Settings: GlobalConfig = {
  slug: 'settings',
  hooks: {
    afterChange: [
      async ({ doc }) => {
        // При зміні налаштувань — інвалідувати весь сайт
        await fetch('/api/revalidate?path=/', { method: 'POST' })
      },
    ],
  },
}

Повторне використання hooks

// hooks/shared/timestamps.ts
import type { CollectionBeforeChangeHook } from 'payload/types'

export const setTimestamps: CollectionBeforeChangeHook = ({ data, operation }) => {
  if (operation === 'create') {
    data.createdAt = new Date().toISOString()
  }
  data.updatedAt = new Date().toISOString()
  return data
}

// У колекціях:
hooks: {
  beforeChange: [setTimestamps, ...otherHooks],
}

Обробка помилок у hooks

const validateHook: CollectionBeforeChangeHook = async ({ data }) => {
  try {
    await externalValidationService.validate(data)
  } catch (error) {
    if (error instanceof ValidationError) {
      // Payload покаже помилку в admin UI
      throw new Error(`Validation failed: ${error.message}`)
    }
    // Логування неочікуваних помилок без блокування збереження
    console.error('External validation error:', error)
  }
  return data
}

Часові рамки

Набір hooks для бізнес-логіки однієї колекції (email-сповіщення, CRM-синхронізація, валідація) — 1–2 дні.