πŸ”Œ Plugin UI System

Getting Started

This guide walks you through creating your first UI plugin for Ever Gauzy and registering it in the application's plugin-ui.config.ts.

Prerequisites

  • Ever Gauzy monorepo cloned and dependencies installed
  • Basic familiarity with Angular (components, modules, routing)

Understanding plugin-ui.config.ts

All UI plugins are registered in a single configuration file: apps/gauzy/src/plugin-ui.config.ts. This is the entry point that tells the application which plugins to load and how they are organized.

Here is the real production config:

import { LanguagesEnum, WeekDaysEnum } from '@gauzy/contracts';
import { JobEmployeePlugin } from '@gauzy/plugin-job-employee-ui';
import { JobMatchingPlugin } from '@gauzy/plugin-job-matching-ui';
import { JobProposalPlugin, JobProposalTemplatePlugin } from '@gauzy/plugin-job-proposal-ui';
import { JobSearchPlugin } from '@gauzy/plugin-job-search-ui';
import { JobsPlugin } from '@gauzy/plugin-jobs-ui';
import { IntegrationUpworkPlugin } from '@gauzy/plugin-integration-upwork-ui';
import { DashboardTimeTrackReactUiPlugin } from '@gauzy/plugin-dashboard-time-track-react-ui';
import { DayOfWeek, PluginUiConfig } from '@gauzy/plugin-ui';
import { dayOfWeekAsString } from '@gauzy/ui-core/shared';
import { environment } from '@gauzy/ui-config';

export const uiPluginConfig: PluginUiConfig = {
  // ── Internationalization ───────────────────────────────
  defaultLanguage: LanguagesEnum.ENGLISH,
  defaultLocale: 'en-US',
  fallbackLocale: LanguagesEnum.ENGLISH,
  availableLanguages: [
    LanguagesEnum.ENGLISH, LanguagesEnum.FRENCH, LanguagesEnum.SPANISH,
    LanguagesEnum.GERMAN,  LanguagesEnum.RUSSIAN, LanguagesEnum.CHINESE,
    // ... more languages
  ],
  availableLocales: ['en-US', 'fr-FR', 'es-ES', 'de-DE', 'ru-RU', 'zh-CN', /* ... */],
  startWeekOn: dayOfWeekAsString(WeekDaysEnum.MONDAY) as DayOfWeek,

  // ── Plugins ────────────────────────────────────────────
  plugins: [
    // Flat plugin β€” registers directly
    IntegrationUpworkPlugin,

    // Plugin group β€” parent with child plugins
    JobsPlugin.init({
      plugins: [
        JobProposalPlugin,
        JobEmployeePlugin,
        JobSearchPlugin,
        JobMatchingPlugin,
        JobProposalTemplatePlugin
      ]
    }),

    // Conditional plugin β€” only loaded in demo mode
    ...(environment.DEMO ? [DashboardTimeTrackReactUiPlugin] : [])
  ]
};

Key patterns in the config

PatternExampleWhen to use
Flat pluginIntegrationUpworkPluginStandalone plugin, no children
Plugin group with .init()JobsPlugin.init({ plugins: [...] })Parent plugin with customizable child plugins
Conditional loading...(condition ? [Plugin] : [])Feature-flagged or environment-gated plugins

Step 1: Create the Plugin Directory

mkdir -p packages/plugins/my-plugin-ui/src/lib

Standard directory structure:

packages/plugins/my-plugin-ui/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ my-plugin.ts           # Plugin definition
β”‚   β”‚   β”œβ”€β”€ my-plugin.module.ts    # Angular module (if module-based)
β”‚   β”‚   β”œβ”€β”€ my-page.component.ts   # Page components
β”‚   β”‚   └── my-page-route.ts       # Route configuration
β”‚   └── index.ts                   # Public API exports
β”œβ”€β”€ package.json
└── tsconfig.json

Step 2: Choose Your Plugin Type

Option A: Module-Based Plugin (most common)

This is the pattern used by most existing plugins (Upwork, Jobs, Job Search, etc.):

// packages/plugins/my-plugin-ui/src/lib/my-plugin.ts
import { PluginUiDefinition } from '@gauzy/plugin-ui';
import { PermissionsEnum } from '@gauzy/contracts';
import { MyPluginModule } from './my-plugin.module';
import { MY_PLUGIN_PAGE_ROUTE } from './my-page-route';

export const MyPlugin: PluginUiDefinition = {
  id: 'my-plugin',
  version: '1.0.0',
  location: 'pages',
  module: MyPluginModule,
  permissionKeys: [PermissionsEnum.ADMIN_DASHBOARD_VIEW],
  routes: [MY_PLUGIN_PAGE_ROUTE as any]
};

The Angular module:

// packages/plugins/my-plugin-ui/src/lib/my-plugin.module.ts
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MyPageComponent } from './my-page.component';

@NgModule({
  declarations: [MyPageComponent],
  imports: [
    RouterModule.forChild([
      { path: '', component: MyPageComponent }
    ])
  ]
})
export class MyPluginModule {}

The route configuration:

// packages/plugins/my-plugin-ui/src/lib/my-page-route.ts
export const MY_PLUGIN_PAGE_ROUTE = {
  location: 'pages',
  path: 'my-feature',
  loadChildren: () => import('./my-plugin.module').then(m => m.MyPluginModule),
  data: {
    selectors: { project: false, employee: false, organization: true }
  }
};

Option B: Declarative Plugin (no Angular module needed)

Used by the React dashboard plugin and simpler plugins:

// packages/plugins/my-plugin-ui/src/lib/my-plugin.ts
import { defineDeclarativePlugin } from '@gauzy/plugin-ui';
import { PermissionsEnum } from '@gauzy/contracts';

export const MyPlugin = defineDeclarativePlugin('my-plugin', {
  version: '1.0.0',
  location: 'page-sections',
  routes: [MY_PLUGIN_ROUTE as any],

  // Translations with namespace isolation
  translationNamespace: 'MY_PLUGIN',
  translations: {
    en: { MY_PLUGIN: { TITLE: 'My Feature', TAB_LABEL: 'My Tab' } }
  },

  // Plugin settings (auto-generates settings UI)
  settings: {
    title: 'My Plugin Settings',
    description: 'Configure my plugin behavior.',
    category: 'general',
    fields: [
      { key: 'enabled', type: 'boolean', label: 'Enable feature', defaultValue: true, order: 1 },
      { key: 'maxItems', type: 'number', label: 'Max items', defaultValue: 10, order: 2 }
    ]
  },

  // Dashboard tab
  tabs: [
    {
      tabsetId: 'dashboard-page',
      tabId: 'my-tab',
      tabsetType: 'route',
      path: '/pages/dashboard/my-feature',
      tabTitle: (_i18n) => _i18n.getTranslation('MY_PLUGIN.TAB_LABEL'),
      tabIcon: 'star-outline',
      responsive: true,
      order: 5,
      permissions: [PermissionsEnum.ADMIN_DASHBOARD_VIEW]
    }
  ]
});

Step 3: Export from the Package

// packages/plugins/my-plugin-ui/src/index.ts
export { MyPlugin } from './lib/my-plugin';

Step 4: Add the Path Alias

In the root tsconfig.json, add your package path:

{
  "compilerOptions": {
    "paths": {
      "@gauzy/plugin-my-plugin-ui": ["packages/plugins/my-plugin-ui/src/index.ts"]
    }
  }
}

Step 5: Register in plugin-ui.config.ts

// apps/gauzy/src/plugin-ui.config.ts
import { MyPlugin } from '@gauzy/plugin-my-plugin-ui';

export const uiPluginConfig: PluginUiConfig = {
  // ... i18n config stays the same

  plugins: [
    IntegrationUpworkPlugin,
    JobsPlugin.init({ plugins: [/* ... */] }),

    // Add your plugin here
    MyPlugin
  ]
};

Conditional Registration

To load your plugin only under certain conditions:

plugins: [
  // Only in demo environments
  ...(environment.DEMO ? [MyPlugin] : []),

  // Only when a feature flag is set
  ...(environment.FEATURE_MY_PLUGIN ? [MyPlugin] : []),
]

Step 6: Run the Application

yarn start:gauzy

Real-World Examples

// Minimal pattern: id + version + location + module + permissions + routes
export const JobSearchPlugin: PluginUiDefinition = {
  id: 'job-search',
  version: '1.0.0',
  location: 'jobs-sections',
  module: JobSearchModule,
  permissionKeys: [PermissionsEnum.ORG_JOB_SEARCH],
  routes: [JOB_SEARCH_PAGE_ROUTE as any]
};

Plugin Group with Children (like Jobs)

// Parent-child pattern with customizable child plugins
export interface JobsPluginDefinition extends PluginUiDefinition {
  init(opts: { plugins: PluginUiDefinition[] }): PluginUiDefinition;
}

export const JobsPlugin: JobsPluginDefinition = {
  id: 'jobs',
  version: '1.0.0',
  location: 'jobs-sections',
  module: JobsModule,
  routes: [JOBS_PAGE_ROUTE as any],
  navMenu: [{
    type: 'config',
    config: {
      id: 'jobs',
      title: 'Jobs',
      icon: 'fas fa-briefcase',
      link: '/pages/jobs',
      data: { translationKey: 'MENU.JOBS', featureKey: FeatureEnum.FEATURE_JOB },
      items: []
    },
    before: 'employees'
  }],
  featureKey: FeatureEnum.FEATURE_JOB,
  plugins: [],

  // .init() lets consumers choose which child plugins to include
  init(opts: { plugins: PluginUiDefinition[] }): PluginUiDefinition {
    return { ...JobsPlugin, plugins: opts.plugins };
  }
};

Declarative React Plugin (like Dashboard Time Track)

// Full declarative plugin with translations, settings, and tabs
export const DashboardTimeTrackReactUiPlugin = defineDeclarativePlugin(
  'dashboard-time-track-react-ui', {
    version: '1.0.0',
    location: 'page-sections',
    routes: [DASHBOARD_TIME_TRACK_ROUTE as any],
    translationNamespace: 'REACT_UI',
    translations: { en },
    settings: {
      title: 'React Dashboard Widgets',
      description: 'Configure widget visibility.',
      category: 'dashboard',
      fields: [
        { key: 'showMembersWorked', type: 'boolean', label: 'Show Members Worked', defaultValue: true, order: 1 },
        { key: 'showProjectsWorked', type: 'boolean', label: 'Show Projects Worked', defaultValue: true, order: 2 },
        { key: 'refreshInterval', type: 'number', label: 'Auto-refresh (seconds)',
          defaultValue: 300, validation: { min: 0, max: 3600 }, order: 7 }
      ]
    },
    tabs: [{
      tabsetId: 'dashboard-page',
      tabId: 'react-time-tracking',
      tabsetType: 'route',
      path: '/pages/dashboard/react-time-tracking',
      tabTitle: (_i18n) => _i18n.getTranslation('REACT_UI.DASHBOARD_PAGE.TABS.REACT_TIME_TRACKING'),
      tabIcon: 'code-outline',
      responsive: true,
      order: 4,
      permissions: [PermissionsEnum.ADMIN_DASHBOARD_VIEW, PermissionsEnum.TIME_TRACKING_DASHBOARD]
    }]
  }
);

Next Steps

Edit this page

Copyright Β© 2023-Present Ever Co. LTD.Docs by Sourcey