Разработка кастомных расширений Directus

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомных расширений Directus
Сложная
~3-5 рабочих дней
Часто задаваемые вопросы

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

Этапы разработки

Последние работы

  • 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

Разработка кастомных расширений Directus

Directus расширяется через Extensions — TypeScript/JavaScript модули в директории extensions/. Типы расширений: Hooks (события), Endpoints (маршруты), Operations (шаги для Flows), Interfaces (поля admin UI), Displays, Panels, Modules, Layouts, Bundles.

Структура Extensions

extensions/
├── hooks/
│   └── sync-to-crm/
│       ├── index.ts
│       └── package.json
├── endpoints/
│   └── custom-api/
│       ├── index.ts
│       └── package.json
├── operations/
│   └── send-sms/
│       ├── index.ts         # серверная часть
│       ├── app.ts           # UI для Flow builder
│       └── package.json
└── interfaces/
    └── color-picker/
        ├── index.ts         # Vue компонент
        └── package.json

Hook Extension

// extensions/hooks/lifecycle-events/index.ts
import type { HookExtensionContext } from '@directus/types'

export default ({ action, filter, schedule, init }: HookExtensionContext) => {
  // Событие при создании записи
  action('items.create', async ({ collection, item, accountability }) => {
    if (collection === 'orders') {
      console.log(`New order created: ${item.id}`)

      // Отправить в CRM
      await fetch(process.env.CRM_WEBHOOK!, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          event: 'order_created',
          orderId: item.id,
          customer: item.customer_email,
          total: item.total,
        }),
      })
    }
  })

  // Событие при обновлении
  action('items.update', async ({ collection, keys, payload }) => {
    if (collection === 'articles' && payload.status === 'published') {
      for (const id of keys) {
        // Инвалидировать кэш Next.js
        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({ tag: 'articles', id }),
        })
      }
    }
  })

  // Filter — изменить данные перед сохранением
  filter('items.create', (payload, { collection }) => {
    if (collection === 'articles' && !payload.slug) {
      payload.slug = (payload.title as string)
        ?.toLowerCase()
        .replace(/\s+/g, '-')
        .replace(/[^\w-]/g, '')
    }
    return payload
  })

  // Cron job
  schedule('0 8 * * *', async () => {
    console.log('Daily digest job running...')
    // Отправить дайджест подписчикам
  })

  // Инициализация — выполняется при старте
  init('app.before', async ({ app }) => {
    console.log('Directus starting up...')
  })
}

Endpoint Extension

// extensions/endpoints/reports/index.ts
import type { EndpointExtensionContext } from '@directus/types'
import { Router } from 'express'

export default (router: Router, { services, getSchema }: EndpointExtensionContext) => {
  // GET /reports/sales
  router.get('/sales', async (req, res) => {
    const schema = await getSchema()
    const { ItemsService } = services

    // Проверка аутентификации
    if (!req.accountability?.role) {
      return res.status(403).json({ error: 'Unauthorized' })
    }

    const ordersService = new ItemsService('orders', { schema, accountability: req.accountability })

    const orders = await ordersService.readByQuery({
      filter: { status: { _eq: 'completed' } },
      fields: ['id', 'total', 'date_created', 'customer'],
      limit: -1,
    })

    const totalRevenue = orders.reduce((sum: number, o: any) => sum + (o.total || 0), 0)
    const avgOrder = orders.length > 0 ? totalRevenue / orders.length : 0

    return res.json({
      totalOrders: orders.length,
      totalRevenue,
      avgOrder: Math.round(avgOrder * 100) / 100,
    })
  })

  // POST /reports/export
  router.post('/export', async (req, res) => {
    const { collection, format = 'csv', filters } = req.body
    const schema = await getSchema()
    const { ItemsService } = services

    const service = new ItemsService(collection, { schema, accountability: req.accountability })
    const items = await service.readByQuery({ filter: filters, limit: -1 })

    if (format === 'csv') {
      const headers = Object.keys(items[0] || {})
      const csv = [
        headers.join(','),
        ...items.map((item: any) => headers.map(h => JSON.stringify(item[h] ?? '')).join(','))
      ].join('\n')

      res.setHeader('Content-Type', 'text/csv')
      res.setHeader('Content-Disposition', `attachment; filename="${collection}.csv"`)
      return res.send(csv)
    }

    return res.json({ data: items })
  })
}

Operation Extension (для Flows)

// extensions/operations/send-sms/index.ts (серверная часть)
import type { OperationExtensionContext } from '@directus/types'

export default {
  id: 'send-sms',
  handler: async ({ phone, message }: { phone: string; message: string }, context: OperationExtensionContext) => {
    if (!phone || !message) {
      throw new Error('Phone and message are required')
    }

    const response = await fetch('https://api.sms-provider.com/send', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.SMS_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ to: phone, text: message }),
    })

    const result = await response.json()

    if (!result.success) {
      throw new Error(`SMS failed: ${result.error}`)
    }

    return { sent: true, messageId: result.id }
  },
}
// extensions/operations/send-sms/app.ts (UI для Flow builder)
export default {
  id: 'send-sms',
  name: 'Send SMS',
  icon: 'sms',
  description: 'Отправить SMS через провайдера',
  overview: ({ phone, message }: any) => [
    { label: 'Phone', text: phone },
    { label: 'Message', text: message },
  ],
  options: [
    {
      field: 'phone',
      name: 'Phone Number',
      type: 'string',
      meta: { interface: 'input', required: true },
    },
    {
      field: 'message',
      name: 'Message',
      type: 'text',
      meta: { interface: 'input-multiline', required: true },
    },
  ],
}

Bundle — несколько расширений в одном пакете

// extensions/my-bundle/index.ts
import type { BundleExtension } from '@directus/types'

const bundle: BundleExtension = {
  hooks: [lifecycleHook],
  endpoints: [reportsEndpoint],
  operations: [{ api: sendSmsOperation, app: sendSmsApp }],
}

export default bundle

Сборка расширений

// extensions/hooks/sync-to-crm/package.json
{
  "name": "directus-extension-sync-to-crm",
  "version": "1.0.0",
  "directus:extension": { "type": "hook", "path": "dist/index.js", "source": "src/index.ts" },
  "scripts": { "build": "directus-extension build" },
  "devDependencies": { "@directus/extensions-sdk": "^10.0.0" }
}
cd extensions/hooks/sync-to-crm
npm install
npm run build
# Перезапустить Directus

Сроки

Разработка набора расширений (Hook + Endpoint + Operation) — 3–5 дней.