INFINI Console Plugin System Architecture: A Deep Dive into Extensibility

INFINI Console implements a Kibana-based plugin architecture where TypeScript classes implement a generic Plugin interface with lifecycle methods (setup, start, stop) and dependency injection to enable modular, decoupled extensibility.

INFINI Console, part of the infinilabs/console repository, extends the Kibana platform to provide a comprehensive management interface for INFINI Labs products. The INFINI Console plugin system architecture treats plugins as first-class modules, allowing developers to extend functionality without modifying the core codebase through a well-defined lifecycle and contract-based dependency injection.

Core Plugin Architecture and Lifecycle

At the heart of the system lies the generic Plugin interface that every extension must implement. This interface enforces a strict lifecycle through three primary methods: setup, start, and stop.

The Generic Plugin Interface

Every plugin is a TypeScript class implementing Plugin<TSetup, TStart, TSetupDeps, TStartDeps>. The generic parameters define the contracts exposed to other plugins and the dependencies required at each phase:

export class MyPlugin implements Plugin<TSetup, TStart, TSetupDeps, TStartDeps> {
  constructor(initializerContext: PluginInitializerContext) { … }
  public setup(core: CoreSetup<TStartDeps, TStart>, deps: TSetupDeps): TSetup { … }
  public start(core: CoreStart, deps: TStartDeps): TStart { … }
  public stop() { … }
}

The initializerContext provides configuration and logging capabilities during instantiation.

Lifecycle Phases: Constructor, Setup, Start, and Stop

The platform orchestrates plugins through four distinct phases:

Phase What happens Example source
Constructor Plugin instance is created, usually stores the initializerContext. DataServerPlugin constructor – web/src/components/vendor/data/server/plugin.ts
Setup Receives CoreSetup and declared setup dependencies. Registers UI elements, routes, services, and returns a setup contract. IndexPatternManagementPlugin.setupweb/src/components/vendor/index_pattern_management/public/plugin.ts
Start Receives CoreStart and start dependencies. Starts long-running services and returns a start contract. DataPublicPlugin.startweb/src/components/vendor/data/public/plugin.ts
Stop Cleans up resources (timers, listeners, subscriptions). DataServerPlugin.stopweb/src/components/vendor/data/server/plugin.ts

During setup, plugins receive CoreSetup containing HTTP configuration, UI settings, and notification services. During start, they receive CoreStart with runtime services like authenticated HTTP clients and saved objects.

Dependency Injection and Plugin Contracts

The architecture employs explicit dependency injection through TypeScript interfaces, enabling type-safe communication between plugins without tight coupling.

Declaring Setup and Start Dependencies

Plugins declare their dependencies using separate interfaces for setup and start phases:

export interface IndexPatternManagementSetupDependencies {
  management: ManagementSetup;          // from the management plugin
  urlForwarding: UrlForwardingSetup;    // from the url_forwarding plugin
}

During setup, the platform passes the concrete objects:

setup(core, { management, urlForwarding })

(See full implementation in web/src/components/vendor/index_pattern_management/public/plugin.ts.)

Contract-Based Communication Between Plugins

The generic TSetup and TStart parameters define the contracts that plugins expose. When a plugin returns an object from setup() or start(), other plugins can consume these methods through their dependency declarations.

For example, the DataPublicPlugin in web/src/components/vendor/data/public/plugin.ts exposes search, field formats, and query services through its start contract, allowing dependent plugins to execute searches without knowing the implementation details.

Extensibility Points in INFINI Console

The plugin system provides specific hooks for extending the UI, services, and data models.

Application Registration and URL Generation

Plugins register new applications using core.application.register() during setup. The DiscoverPlugin in web/src/components/vendor/discover/public/plugin.ts demonstrates this by registering the Discover app and providing deep links through URL generators:

plugins.share?.urlGenerators.registerUrlGenerator(/* ... */);

Service Registration and Saved Object Types

Server-side plugins register domain-specific services and persistence models. The DataServerPlugin in web/src/components/vendor/data/server/plugin.ts registers search services and index pattern saved object types, making them available to the client through the start contract.

UI Actions, Embeddables, and State Containers

The public data plugin in web/src/components/vendor/data/public/plugin.ts registers UI actions and triggers using uiActions.registerAction(), allowing other plugins to hook into user interactions like filter creation or query execution.

State containers and sync utilities under vendor/utils/public/state_sync/ enable plugins to share UI state across the application boundary without direct coupling.

Building a Custom Plugin: Minimal Example

To create a new plugin, implement the Plugin interface with explicit contracts and dependency declarations:

// src/plugins/my_plugin/public/plugin.ts
import {
  PluginInitializerContext,
  CoreSetup,
  CoreStart,
  Plugin,
} from 'src/core/public';

export interface MySetup {
  hello(): void;
}
export interface MyStart {
  greet(name: string): string;
}
export interface MySetupDeps {
  // e.g. depend on the data plugin's public contract
  data: DataPublicPluginSetup;
}
export interface MyStartDeps {}

export class MyPlugin
  implements Plugin<MySetup, MyStart, MySetupDeps, MyStartDeps>
{
  constructor(context: PluginInitializerContext) {}

  public setup(core: CoreSetup<MyStartDeps, MyStart>, { data }: MySetupDeps): MySetup {
    core.application.register({
      id: 'myPlugin',
      title: 'My Plugin',
      async mount(params) {
        // mount UI here
        return () => {}; // unmount
      },
    });

    return {
      hello() {
        console.log('Hello from MyPlugin!');
      },
    };
  }

  public start(core: CoreStart, _deps: MyStartDeps): MyStart {
    return {
      greet(name) {
        return `Hello, ${name}!`;
      },
    };
  }

  public stop() {}
}

/** Entry point required by the Kibana platform */
export function plugin(initializerContext: PluginInitializerContext) {
  return new MyPlugin(initializerContext);
}

Key implementation details:

  1. Explicit contracts: MySetup and MyStart define the API surface for other plugins.
  2. Dependency injection: MySetupDeps declares the data plugin dependency, injected during setup.
  3. Lifecycle compliance: The plugin() factory function instantiates the class, satisfying the platform's entry point requirement.

Key Source Files and Implementation References

The following files demonstrate the plugin architecture in the infinilabs/console repository:

File Role Link
web/src/components/vendor/index_pattern_management/public/plugin.ts Example of a public UI plugin that registers a management app and forwards legacy URLs. IndexPatternManagementPlugin
web/src/components/vendor/discover/public/plugin.ts Shows a feature‑rich plugin (Discover) that registers an app, URL generator, doc‑view extensions, and state sync. DiscoverPlugin
web/src/components/vendor/data/server/plugin.ts Core server‑side plugin providing data services (search, field formats, index patterns). DataServerPlugin
web/src/components/vendor/data/public/plugin.ts Companion public data plugin exposing services to the UI and other public plugins. DataPublicPlugin
src/core/public/plugin.ts (in the Kibana core – not duplicated here) Defines the generic Plugin interface used throughout. (core library)
src/core/server/plugin.ts Server‑side counterpart of the core Plugin interface. (core library)

These files together illustrate the full lifecycle, dependency injection, and extensibility hooks that make INFINI Console a pluggable platform. By following the same pattern—implementing Plugin, declaring contracts, registering UI components or services, and exporting a plugin() factory—developers can extend the console without modifying the core codebase.

Summary

  • INFINI Console extends the Kibana platform, treating plugins as first-class modules with a strict lifecycle.
  • The generic Plugin interface enforces setup, start, and stop methods, with TypeScript generics defining contracts (TSetup, TStart) and dependencies (TSetupDeps, TStartDeps).
  • Dependency injection occurs through explicit interface declarations, allowing type-safe access to other plugins' contracts without tight coupling.
  • Extensibility points include application registration, URL generators, service exposure, saved object types, UI actions, and state containers.
  • Implementation requires extending Plugin, exporting a plugin() factory, and following the lifecycle pattern demonstrated in web/src/components/vendor/data/public/plugin.ts and related files.

Frequently Asked Questions

What interface must a plugin implement in INFINI Console?

Every plugin must implement the generic Plugin<TSetup, TStart, TSetupDeps, TStartDeps> interface defined in the Kibana core (src/core/public/plugin.ts or src/core/server/plugin.ts). This interface requires three methods: setup(core, deps) for bootstrap registration, start(core, deps) for runtime initialization, and stop() for cleanup. The generic parameters define the contracts the plugin exposes and the dependencies it requires from other plugins.

How does dependency injection work between plugins?

Dependency injection operates through explicit TypeScript interface declarations. Plugins define TSetupDeps and TStartDeps interfaces listing required dependencies (e.g., management: ManagementSetup). The platform automatically resolves these by matching dependency names to exported contracts from other plugins, injecting them as the second parameter to setup() and start() methods. This ensures type-safe, decoupled communication where plugins interact only through declared contracts.

What is the difference between the setup and start phases?

The setup phase executes while the platform is booting, before the application becomes fully operational. During setup, plugins register routes, UI applications, saved object types, and return setup contracts. The start phase executes after all setup phases complete, when the platform is fully initialized. During start, plugins launch long-running services, establish runtime connections, and return start contracts. Setup focuses on static registration; start focuses on dynamic service activation.

Can plugins register new applications in the INFINI Console UI?

Yes, plugins can register entirely new applications using core.application.register() during the setup phase. This method accepts an application descriptor including a unique ID, title, and async mount function that renders the UI. For example, the Discover plugin in web/src/components/vendor/discover/public/plugin.ts registers the Discover application and provides deep linking capabilities through URL generators, demonstrating how plugins can add full-featured UI modules to the console.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →