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.setup – web/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.start – web/src/components/vendor/data/public/plugin.ts |
| Stop | Cleans up resources (timers, listeners, subscriptions). | DataServerPlugin.stop – web/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:
- Explicit contracts:
MySetupandMyStartdefine the API surface for other plugins. - Dependency injection:
MySetupDepsdeclares thedataplugin dependency, injected duringsetup. - 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
Plugininterface enforcessetup,start, andstopmethods, 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 aplugin()factory, and following the lifecycle pattern demonstrated inweb/src/components/vendor/data/public/plugin.tsand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →