Разделы документации
ОбзорБыстрый стартРедакции и возможностиМодели и поляРедактор контентаМедиатекаЛокализацияПубликация и работа командыAPI, SDK и генерация типовРасширения и инструментыРабочие проектыЗадания, вебхуки и наблюдаемостьАудит и управление даннымиСоветники и доверие к плагинамКорпоративный входПространства, квоты и масштабированиеCommerce и PortalПрава и безопасностьРазвёртывание и обновленияЛицензии и установка пакетовТекущие ограниченияПомощь и диагностикаДанные в кабинетеCore CMSDeveloper PlatformProduction UseWorkflowOperationsComplianceAI AssistantsPlugin TrustEnterprise IdentityEnterprise ScaleEnterprise DeploymentCommerce BundlePortal BundleNotty CMS DocumentationAuth & SecurityContent ModelingDeploymentEcosystem & Packaging ConventionsEditions and First-party ModulesExtensibilityGetting StartedMedia ManagementModule Extraction PathDraft & PublishUpgrade GuideWebhooks & IntegrationsCookbook: Blog with Next.jsCookbook: Custom PluginCookbook: Multilingual SiteOperations DocsBackup AutomationDeployment BlueprintsRunbook — Восстановление БД из бэкапаRunbook — Плановый деплойRunbook — Реакция на инцидентRunbook — Откат релизаRunbook — Горизонтальное масштабированиеRunbook — Ротация секретовRunbook — Major upgradeSecrets ManagementNotty CMS — Capability MapNotty Configuration ModelGenerated App ContractComponents and Dynamic ZonesMiddleware SystemPerformance & Scaling ToolkitDisaster Recovery PlaybookDistribution Model
Документация / Технический справочник

Extensibility

Техническое руководство из исходного проекта Notty. Примеры, параметры и эксплуатационные ограничения.

Все редакцииОбновлено 2026-09-30

Notty CMS supports three extension mechanisms: plugins, middleware, and themes.

Status note: @notty/plugin-api and @notty/theme-api are already usable, but they are still early-stage and distributed through the restricted @notty npm scope. For the least-friction path, start from create-notty --plugin/--theme or pnpm dlx @notty/cli scaffold plugin|theme.

Plugins

Plugins are the primary way to extend Notty. A plugin can add routes, hooks, dashboard widgets, navigation items, settings, and custom policies.

Plugin Structure

my-plugin/
  src/index.ts     # Plugin definition
  package.json     # Dependencies + notty manifest
  tsconfig.json

Define a Plugin

// src/index.ts
import { definePlugin } from '@notty/plugin-api';

export default definePlugin({
  manifest: {
    name: 'my-analytics-plugin',
    displayName: 'Analytics',
    description: 'Track content views and engagement',
    version: '1.0.0',
    kind: 'plugin',
    engines: { notty: '>=0.14.0' },

    // Auto-generate a settings form in admin
    configSchema: {
      trackingId: {
        type: 'string',
        label: 'Tracking ID',
        description: 'Your analytics provider tracking ID',
        default: '',
      },
      enabledContentTypes: {
        type: 'string',
        label: 'Content types to track',
        description: 'Comma-separated list',
        default: '*',
      },
    },
  },

  register(ctx) {
    // Everything below runs once at startup
  },

  async init(ctx) {
    // Async init: database setup, external connections
  },

  async boot(ctx) {
    // Final setup after all plugins initialized
  },

  async destroy(ctx) {
    // Cleanup on shutdown
  },
});

Plugin Lifecycle

Phase Sync/Async Purpose
register sync Register routes, hooks, policies, admin extensions
init async Database migrations, external service connections
boot async Final setup, safe to use other plugins' services
destroy async Cleanup on server shutdown

Register Custom Routes

Routes are mounted at /api/x/<plugin-name>/:

register(ctx) {
  // GET /api/x/my-analytics-plugin/stats
  ctx.routes.get('/stats', async (req) => {
    const contentType = req.query.contentType as string;
    return { views: 1234, contentType };
  });

  // POST /api/x/my-analytics-plugin/track
  ctx.routes.post('/track', async (req) => {
    const { contentType, entryId } = req.body;
    // Record the view...
    return { tracked: true };
  }, {
    policies: ['isAuthenticated'],
    description: 'Track a content view',
  });
}

Register Content Hooks

React to content lifecycle events:

register(ctx) {
  ctx.hooks.on('content:afterCreate', (payload) => {
    ctx.log.info(`Created: ${payload.contentType} #${payload.entry}`);
  });

  ctx.hooks.on('content:afterUpdate', (payload) => {
    ctx.log.info(`Updated: ${payload.contentType} #${payload.entry}`);
  });

  ctx.hooks.on('content:afterDelete', (payload) => {
    ctx.log.info(`Deleted: ${payload.contentType} #${payload.entry}`);
  });
}

Add Dashboard Widgets

Widgets appear on the admin dashboard:

register(ctx) {
  // Stat card
  ctx.admin.addDashboardWidget({
    id: 'analytics:total-views',
    title: 'Total Views',
    icon: 'BarChart',
    size: 'small',
    type: 'stat',
    statValue: '12,345',
    statLabel: 'Last 30 days',
    statColor: '#6366f1',
  });

  // Link list
  ctx.admin.addDashboardWidget({
    id: 'analytics:links',
    title: 'Quick Links',
    icon: 'ExternalLink',
    size: 'medium',
    type: 'link-list',
    links: [
      {
        label: 'View Dashboard',
        href: '/plugins/analytics',
        icon: 'BarChart',
        description: 'Full analytics dashboard',
      },
    ],
  });
}

Add Navigation Items

Add entries to the admin sidebar:

register(ctx) {
  ctx.admin.addNavigationItem({
    id: 'analytics:main',
    label: 'Analytics',
    icon: 'BarChart',
    path: '/plugins/analytics',
    section: 'plugins',
  });
}

Add Settings Section

Use configSchema from the manifest to auto-generate settings:

register(ctx) {
  ctx.admin.addSettingsSection({
    id: 'analytics:settings',
    label: 'Analytics',
    description: 'Configure analytics tracking',
    icon: 'BarChart',
    path: 'analytics',
    type: 'config-schema',
  });
}

Register Custom Policies

Policies are reusable authorization checks:

register(ctx) {
  ctx.policies.register(
    'analytics:hasAccess',
    (policyCtx) => {
      return policyCtx.adminUser?.role === 'super-admin';
    },
    'Only super-admins can access analytics'
  );
}

Install a Plugin

Register plugins in notty.config.ts:

import { defineConfig } from '@notty/core/config';
import analyticsPlugin from 'my-analytics-plugin';

export default defineConfig({
  plugins: [{ source: analyticsPlugin, config: { trackingId: 'UA-123456' } }],
});

Middleware

Middleware intercepts requests and events at three levels: admin API, content API, and system events.

Admin Middleware

Intercept admin API requests (user management, schema operations, settings):

// src/middlewares/index.ts
import { defineAdminMiddleware } from '@notty/core';

export default defineAdminMiddleware({
  name: 'audit-admin-actions',
  routes: 'admin:*', // Glob pattern: all admin routes
  methods: ['POST', 'PUT', 'DELETE'],
  order: 10, // Lower = runs earlier

  async handler(ctx, input, next) {
    console.log(`[Audit] ${ctx.adminUser?.username} → ${ctx.routeName}`);
    const result = await next();
    console.log(`[Audit] ${ctx.routeName} completed`);
    return result;
  },
});

Route patterns:

Pattern Matches
admin:* All admin routes
admin:users:* User management routes
admin:roles:* Role management routes
admin:schemas:* Schema management routes

Content Middleware

Intercept content CRUD operations:

import { defineContentMiddleware } from '@notty/core';

export default defineContentMiddleware({
  name: 'auto-slug',
  contentTypes: '*', // All content types, or ['article', 'page']
  operations: ['create', 'update'],
  order: 20,

  async handler(ctx, input, next) {
    const data = input as Record<string, unknown>;

    // Auto-generate slug from title
    if (data.title && !data.slug) {
      data.slug = String(data.title)
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/(^-|-$)/g, '');
    }

    return next();
  },
});

System Middleware

React to system events (app lifecycle, content events):

import { defineSystemMiddleware } from '@notty/core';

export default defineSystemMiddleware({
  name: 'log-system-events',
  events: ['app:start', 'content:afterCreate'],
  order: 10,

  async handler(ctx) {
    console.log(`[System] ${ctx.event}`, ctx.payload);
  },
});

Register Middleware

In a generated app, middleware is registered via src/plugins/app.ts:

// src/plugins/app.ts
import { registerAppMiddlewares } from '../middlewares';

export default {
  register(ctx) {
    registerAppMiddlewares(ctx);
  },
};
// src/middlewares/index.ts
import auditMiddleware from './admin/audit';
import autoSlugMiddleware from './content/auto-slug';
import logEvents from './system/log-events';

export function registerAppMiddlewares(ctx) {
  ctx.middleware.addAdmin(auditMiddleware);
  ctx.middleware.addContent(autoSlugMiddleware);
  ctx.middleware.addSystem(logEvents);
}

Themes

Themes customize the admin panel appearance with design tokens, branding, and layout.

Define a Theme

import { defineTheme } from '@notty/theme-api';

export default defineTheme({
  manifest: {
    name: 'my-brand-theme',
    displayName: 'My Brand',
    version: '1.0.0',
    kind: 'theme',
    engines: { notty: '>=0.14.0' },

    tokens: {
      colors: {
        brand: {
          primary: { value: '#10b981', description: 'Green primary' },
          secondary: { value: '#34d399' },
          accent: { value: '#a78bfa' },
        },
        surface: {
          background: { value: '#ffffff' },
          foreground: { value: '#1f2937' },
          card: { value: '#f9fafb' },
          sidebar: { value: '#f3f4f6' },
        },
      },
      typography: {
        heading: { fontFamily: '"Poppins", sans-serif', fontWeight: 600 },
        body: { fontFamily: '"Inter", sans-serif', fontSize: '14px' },
      },
      radii: {
        sm: { value: '4px' },
        md: { value: '8px' },
        lg: { value: '16px' },
      },
    },

    branding: {
      title: 'My Company CMS',
      footerText: 'Powered by Notty',
      hidePoweredBy: false,
    },

    layout: {
      sidebarPosition: 'left',
      sidebarCollapsed: false,
      headerVisible: true,
    },
  },

  register(ctx) {
    ctx.log.info('Brand theme loaded');
  },
});

Install a Theme

import { defineConfig } from '@notty/core/config';
import brandTheme from 'my-brand-theme';

export default defineConfig({
  plugins: [{ source: brandTheme }],
});

Override Theme Tokens

Override specific tokens without forking the theme:

{
  source: brandTheme,
  config: {
    overrides: {
      colors: {
        brand: {
          primary: { value: '#3b82f6' },  // Switch to blue
        },
      },
    },
  },
}

Custom Routes

Add custom API routes to your project without a full plugin:

// src/routes/index.ts
export function registerAppRoutes(ctx) {
  // GET /api/x/app/health
  ctx.routes.get('/health', () => {
    return { status: 'ok', uptime: process.uptime() };
  });

  // POST /api/x/app/contact
  ctx.routes.post('/contact', async (req) => {
    const { name, email, message } = req.body;
    // Send email, store in database, etc.
    return { sent: true };
  });
}

Extension Point Reference

What How Where
HTTP routes ctx.routes.get/post/put/delete() Plugins, app routes
Content hooks ctx.hooks.on('content:*') Plugins
Dashboard widgets ctx.admin.addDashboardWidget() Plugins
Sidebar navigation ctx.admin.addNavigationItem() Plugins
Settings section ctx.admin.addSettingsSection() Plugins
Custom policies ctx.policies.register() Plugins
Admin middleware ctx.middleware.addAdmin() Middleware module
Content middleware ctx.middleware.addContent() Middleware module
System events ctx.middleware.addSystem() Middleware module
Design tokens manifest.tokens Themes
Branding manifest.branding Themes
Layout manifest.layout Themes

Further Reading

Источник: docs/guide/extensibility.md. Снимок документации исходного проекта. Технический справочник сохраняет язык оригинала.