Розробка кастомних розширень 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 днів.