Разработка кастомных хуков (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

Кастомные хуки Payload CMS

Хуки в Payload — основной механизм расширения бизнес-логики. Они выполняются на определённых этапах жизненного цикла документа: до/после чтения, создания, изменения, удаления. Хуки — async-функции TypeScript с полной типизацией.

Типы хуков коллекций

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

Хуки 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
}

Хуки 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,
    })
  }
}

Хуки 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
}

Хуки 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('Нельзя удалить клиента с активными заказами')
  }
}

Хуки Global

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

Переиспользование хуков

// 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],
}

Обработка ошибок в хуках

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
}

Сроки

Набор хуков для бизнес-логики одной коллекции (email-уведомления, CRM-синхронизация, валидация) — 1–2 дня.