Отметим: когда данные в Directus требуют мгновенной реакции — проверка корректности заказа, отправка уведомления о новой заявке или синхронизация с внешней системой — стандартных средств CMS часто не хватает. Разработка кастомных хуков (Hook Extensions) позволяет встроить бизнес-логику прямо в ядро: вы подписываетесь на события CRUD, аутентификации или загрузки файлов и выполняете произвольный код. Это снижает задержки на 30–50% по сравнению с внешними вызовами API, а также упрощает архитектуру — не нужно поднимать отдельный микросервис для обработки данных. Наша команда имеет 5-летний опыт и более 30 проектов на Directus, что обеспечивает надёжное и производительное решение. Свяжитесь с нами, чтобы обсудить вашу задачу — мы подготовим оценку за 1 день.
Проблемы, решаемые кастомными хуками
Кастомные хуки устраняют необходимость во внешних вызовах API для обработки изменений — они работают в том же контексте, что и CMS, что сокращает задержку на 30-50% по сравнению с внешней логикой. Типовые сложности:
- Валидация сложных данных — проверка остатков на складе, корректности связей, уникальности полей. Например, при создании заказа хук проверяет, доступен ли товар в нужном количестве.
- Побочные эффекты — отправка уведомлений в Slack/Telegram, запись аудит-логов, инвалидация кэша. Хуки обрабатывают события в 3–5 раз быстрее, чем внешние API-вызовы.
- Интеграции — синхронизация с ERP, CRM (например, 1С), платёжными шлюзами.
- Автоматизация — генерация slug, обновление остатков, формирование отчётов по расписанию с помощью cron.
Почему кастомные хуки лучше внешней логики?
Внешние сервисы добавляют задержки на сетевые вызовы и требуют отдельного деплоя. Хуки выполняются в том же процессе Directus, что снижает TTFB и упрощает мониторинг. Например, при обработке заказа хук проверяет остатки за 5 мс, тогда как внешний API-вызов занимает 50–100 мс. Это особенно важно для high-load проектов, где каждая миллисекунда на счету.
Как сделать первый хук валидации?
- Создайте файл
extensions/hooks/custom/index.ts. - Экспортируйте функцию, принимающую
HookExtensionContext. - Вызовите
filter('items.create', async (payload, meta, context) => { ... }). - Для отмены операции выбросьте ошибку через
throw new Error('Сообщение'). - Перезапустите Directus — хук подхватится автоматически.
Пример валидации: проверка, что email уникален в коллекции users. Если занят — хук выбрасывает ошибку, и запись не создаётся.
Типы событий
| Тип | Описание | Применение |
|---|---|---|
action |
После выполнения операции | Уведомления, кэш, интеграции |
filter |
До выполнения операции (можно изменить данные) | Валидация, модификация payload |
init |
При старте сервера | Загрузка конфигураций, инициализация подключений |
schedule |
По расписанию cron | Ежедневные отчёты, очистка данных |
Сравнение action и filter хуков
| Критерий | Action-хуки | Filter-хуки |
|---|---|---|
| Когда выполняется | После записи в БД | До записи в БД |
| Может изменить данные | Нет | Да |
| Блокирует ответ | Нет (асинхронные) | Да (синхронные) |
| Типичные сценарии | Уведомления, кэш | Валидация, присвоение значений |
Полный пример Hook Extension
// extensions/hooks/business-logic/index.ts
import type { HookExtensionContext } from '@directus/types'
import type { EventContext } from '@directus/types'
export default ({ action, filter, schedule }: HookExtensionContext) => {
// ===== АВТОМАТИЧЕСКАЯ ГЕНЕРАЦИЯ SLUG =====
filter('items.create', (payload, meta) => {
if (meta.collection === 'articles' && payload.title && !payload.slug) {
payload.slug = generateSlug(payload.title as string)
}
if (meta.collection === 'products' && payload.name && !payload.slug) {
payload.slug = generateSlug(payload.name as string)
}
return payload
})
// ===== ВАЛИДАЦИЯ =====
filter('items.create', async (payload, meta, context) => {
if (meta.collection !== 'orders') return payload
const { database } = context as EventContext & { database: any }
// Проверить наличие товара
if (payload.items && Array.isArray(payload.items)) {
for (const item of payload.items) {
const product = await database('products')
.where({ id: item.product_id })
.first()
if (!product) {
throw new Error(`Product ${item.product_id} not found`)
}
if (product.stock < item.quantity) {
throw new Error(`Insufficient stock for "${product.name}"`)
}
}
}
return payload
})
// ===== ИНВАЛИДАЦИЯ КЭША =====
action('items.update', async ({ collection, keys, payload }) => {
const collectionsToRevalidate = ['articles', 'pages', 'products', 'settings']
if (!collectionsToRevalidate.includes(collection)) return
try {
await fetch(`${process.env.NEXTJS_URL}/api/revalidate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-revalidate-secret': process.env.REVALIDATE_SECRET!,
},
body: JSON.stringify({ collection, keys }),
})
} catch (err) {
console.error('Failed to revalidate cache:', err)
}
})
// ===== УВЕДОМЛЕНИЯ =====
action('items.create', async ({ collection, key, payload }, context) => {
if (collection !== 'contact_submissions') return
// Уведомить команду в Slack
await fetch(process.env.SLACK_WEBHOOK!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `📬 Новая заявка от ${payload.name} <${payload.email}>\n${payload.message}`,
}),
})
})
// ===== АУДИТ ЛОГ =====
action('items.update', async ({ collection, keys, payload, accountability }) => {
if (!accountability?.user) return
// Логировать изменения в audit_logs коллекцию
const { getSchema } = context as any
const schema = await getSchema()
// ... записать в audit_logs
})
// ===== СИНХРОНИЗАЦИЯ ОСТАТКОВ =====
action('items.update', async ({ collection, keys, payload }, context) => {
if (collection !== 'orders') return
if (payload.status !== 'paid') return
const { database } = context as EventContext & { database: any }
const order = await database('orders')
.where({ id: keys[0] })
.first()
if (order?.items) {
const items = JSON.parse(order.items)
for (const item of items) {
await database('products')
.where({ id: item.product_id })
.decrement('stock', item.quantity)
}
}
})
// ===== CRON — ежедневный отчёт =====
schedule('0 9 * * 1-5', async () => {
const response = await fetch(`${process.env.API_URL}/custom/reports/sales`)
const stats = await response.json()
await fetch(process.env.SLACK_WEBHOOK!, {
body: JSON.stringify({
text: `📊 Отчёт за вчера: заказов ${stats.count}, выручка ${stats.revenue.toLocaleString()} ₽`,
}),
})
})
}
function generateSlug(text: string): string {
const translitMap: Record<string, string> = {
а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo',
ж: 'zh', з: 'z', и: 'i', й: 'y', к: 'k', л: 'l', м: 'm',
н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u',
ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch',
ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya',
}
return text
.toLowerCase()
.replace(/[а-яё]/g, char => translitMap[char] || char)
.replace(/\s+/g, '-')
.replace(/[^\w-]/g, '')
.replace(/-+/g, '-')
.slice(0, 100)
}
Как получить доступ к данным из хуков?
Через context.database (Knex instance) можно выполнять прямые SQL-запросы или использовать ItemsService для работы с коллекциями — это даёт гибкость и контроль. Directus Documentation рекомендует использовать ItemsService для единообразия, но прямой доступ через Knex оправдан при сложных join-запросах или массовых операциях.
Реальный кейс: интеграция с 1С
Для крупного интернет-магазина мы реализовали автоматическую синхронизацию заказов и остатков через хуки. После оплаты заказа хук отправляет данные в 1С через REST API, а при изменении остатков во внешней системе — обновляет Directus. Время синхронизации сократилось с 15 минут до 5 секунд. Нагрузка на сервер снизилась на 40% за счёт исключения лишних API-вызовов. Экономия на разработке интеграции составила 50% времени по сравнению с традиционным подходом. Закажите разработку кастомных хуков Directus и автоматизируйте бизнес-процессы без лишних затрат.
Что входит в работу
- Исходный код с комментариями на TypeScript
- Документация по установке и настройке (README)
- Доступ к приватному репозиторию Git
- Обучение команды (1 час онлайн)
- Поддержка в течение 2 недель после сдачи
Ориентировочные сроки
Разработка типового набора из 3–5 хуков (валидация, уведомления, кэш) занимает от 2 до 5 рабочих дней в зависимости от сложности интеграций. Сроки уточняются после анализа вашей бизнес-логики.
Гарантии и поддержка
Мы — команда с 5-летним опытом разработки на Directus, выполнившая более 30 проектов по кастомизации, включая интеграции с 1С, CRM и платёжными системами. Гарантируем качество кода, соблюдение сроков и полную поддержку. Получите консультацию инженера — мы ответим на любые вопросы.







