Migrating legacy plugins to the Kibana Platformedit

In Kibana 7.10, support for legacy-style Kibana plugins was completely removed. Moving forward, all plugins must be built on the new Kibana Platform Plugin API. This guide is intended to assist plugin authors in migrating their legacy plugin to the Kibana Platform Plugin API.

Make no mistake, it is going to take a lot of work to move certain plugins to the Kibana Platform.

The goal of this document is to guide developers through the recommended process of migrating at a high level. Every plugin is different, so developers should tweak this plan based on their unique requirements.

First, we recommend you read Kibana Plugin API to get an overview of how plugins work in the Kibana Platform. Then continue here to follow our generic plan of action that can be applied to any legacy plugin.

Challenges to overcome with legacy pluginsedit

Kibana Platform plugins have an identical architecture in the browser and on the server. Legacy plugins have one architecture that they use in the browser and an entirely different architecture that they use on the server.

This means that there are unique sets of challenges for migrating to the Kibana Platform, depending on whether the legacy plugin code is on the server or in the browser.

Challenges on the serveredit

The general architecture of legacy server-side code is similar to the Kibana Platform architecture in one important way: most legacy server-side plugins define an init function where the bulk of their business logic begins, and they access both core and plugin-provided functionality through the arguments given to init. Rarely does legacy server-side code share stateful services via import statements.

Although not exactly the same, legacy plugin init functions behave similarly today as Kibana Platform setup functions. KbnServer also exposes an afterPluginsInit method, which behaves similarly to start. There is no corresponding legacy concept of stop.

Despite their similarities, server-side plugins pose a formidable challenge: legacy core and plugin functionality is retrieved from either the hapi.js server or request god objects. Worse, these objects are often passed deeply throughout entire plugins, which directly couples business logic with hapi. And the worst of it all is, these objects are mutable at any time.

The key challenge to overcome with legacy server-side plugins will decoupling from hapi.

Challenges in the browseredit

The legacy plugin system in the browser is fundamentally incompatible with the Kibana Platform. There is no client-side plugin definition. There are no services that get passed to plugins at runtime. There really isn’t even a concrete notion of core.

When a legacy browser plugin needs to access functionality from another plugin, say to register a UI section to render within another plugin, it imports a stateful (global singleton) JavaScript module and performs some sort of state mutation. Sometimes this module exists inside the plugin itself, and it gets imported via the plugin/ webpack alias. Sometimes this module exists outside the context of plugins entirely and gets imported via the ui/ webpack alias. Neither of these concepts exists in the Kibana Platform.

Legacy browser plugins rely on the feature known as uiExports/, which integrates directly with our build system to ensure that plugin code is bundled together in such a way to enable that global singleton module state. There is no corresponding feature in the Kibana Platform, and in the fact we intend down the line to build Kibana Platform plugins as immutable bundles that can not share state in this way.

The key challenge to overcome with legacy browser-side plugins will be converting all imports from plugin/, ui/, uiExports, and relative imports from other plugins into a set of services that originate at runtime during plugin initialization and get passed around throughout the business logic of the plugin as function arguments.

Plan of actionedit

To move a legacy plugin to the new plugin system, the challenges on the server and in the browser must be addressed.

The approach and level of effort varies significantly between server and browser plugins, but at a high level, the approach is the same.

First, decouple your plugin’s business logic from the dependencies that are not exposed through the Kibana Platform, hapi.js, and Angular.js. Then introduce plugin definitions that more accurately reflect how plugins are defined in the Kibana Platform. Finally, replace the functionality you consume from the core and other plugins with their Kibana Platform equivalents.

Once those things are finished for any given plugin, it can officially be switched to the new plugin system.

Server-side plan of actionedit

Legacy server-side plugins access functionality from the core and other plugins at runtime via function arguments, which is similar to how they must be architected to use the new plugin system. This greatly simplifies the plan of action for migrating server-side plugins. The main challenge here is to de-couple plugin logic from hapi.js server and request objects.

For migration examples, see Migration Examples.

Browser-side plan of actionedit

It is generally a much greater challenge preparing legacy browser-side code for the Kibana Platform than it is server-side, and as such there are a few more steps. The level of effort here is proportional to the extent to which a plugin is dependent on Angular.js.

To complicate matters further, a significant amount of the business logic in Kibana client-side code exists inside the ui/public directory (aka ui modules), and all of that must be migrated as well.

Because the usage of Angular and ui/public modules varies widely between legacy plugins, there is no one size fits all solution to migrating your browser-side code to the Kibana Platform.

For migration examples, see Migration Examples.

Frequently asked questionsedit

Do plugins need to be converted to TypeScript?edit

No. That said, the migration process will require a lot of refactoring, and TypeScript will make this dramatically easier and less risky.

Although it’s not strictly necessary, we encourage any plugin that exposes an extension point to do so with first-class type support so downstream plugins that are using TypeScript can depend on those types.

How can I avoid passing core services deeply within my UI component tree?edit

Some core services are purely presentational, for example core.overlays.openModal(), where UI code does need access to these deeply within your application. However, passing these services down as props throughout your application leads to lots of boilerplate. To avoid this, you have three options:

  • Use an abstraction layer, like Redux, to decouple your UI code from core (this is the highly preferred option).
  • redux-thunk and redux-saga already have ways to do this.
  • Use React Context to provide these services to large parts of your React tree.
  • Create a high-order-component that injects core into a React component.
  • This would be a stateful module that holds a reference to core, but provides it as props to components with a withCore(MyComponent) interface. This can make testing components simpler. (Note: this module cannot be shared across plugin boundaries, see above).
  • Create a global singleton module that gets imported into each module that needs it. This module cannot be shared across plugin boundaries. Example.

If you find that you need many different core services throughout your application, this might indicate a problem in your code and could lead to pain down the road. For instance, if you need access to an HTTP Client or SavedObjectsClient in many places in your React tree, it’s likely that a data layer abstraction (like Redux) could make developing your plugin much simpler.

Without such an abstraction, you will need to mock out core services throughout your test suite and will couple your UI code very tightly to core. However, if you can contain all of your integration points with core to Redux middleware and reducers, you only need to mock core services once and benefit from being able to change those integrations with core in one place rather than many. This will become incredibly handy when core APIs have breaking changes.

How is the common code shared on both the client and the server?edit

There is no formal notion of common code that can safely be imported from either client-side or server-side code. However, if a plugin author wishes to maintain a set of code in their plugin in a single place and then expose it to both server-side and client-side code, they can do so by exporting the index files for both the server and public directories.

Plugins should not ever import code from deeply inside another plugin (e.g. my_plugin/public/components) or from other top-level directories (e.g. my_plugin/common/constants) as these are not checked for breaking changes and are considered unstable and subject to change at any time. You can have other top-level directories like my_plugin/common, but our tooling will not treat these as a stable API, and linter rules will prevent importing from these directories from outside the plugin.

The benefit of this approach is that the details of where code lives and whether it is accessible in multiple runtimes is an implementation detail of the plugin itself. A plugin consumer that is writing client-side code only ever needs to concern themselves with the client-side contracts being exposed, and the same can be said for server-side contracts on the server.

A plugin author, who decides some set of code should diverge from having a single common definition, can now safely change the implementation details without impacting downstream consumers.

How do I find Kibana Platform services?edit

Most of the utilities you used to build legacy plugins are available in the Kibana Platform or Kibana Platform plugins. To help you find the new home for new services, use the tables below to find where the Kibana Platform equivalent lives.

Client-sideedit
Core servicesedit

In client code, core can be imported in legacy plugins via the ui/new_platform module.

Legacy Platform Kibana Platform

chrome.addBasePath

core.http.basePath.prepend

chrome.breadcrumbs.set

core.chrome.setBreadcrumbs

chrome.getUiSettingsClient

core.uiSettings

chrome.helpExtension.set

core.chrome.setHelpExtension

chrome.setVisible

core.chrome.setIsVisible

chrome.getInjected

Request Data with your plugin REST HTTP API.

chrome.setRootTemplate / chrome.setRootController

Use application mounting via core.application.register

chrome.navLinks.update

core.appbase.updater. Use the updater$ property when registering your application via core.application.register

import { recentlyAccessed } from 'ui/persisted_log'

core.chrome.recentlyAccessed

ui/capabilities

core.application.capabilities

ui/documentation_links

core.docLinks

ui/kfetch

core.http

ui/notify

core.notifications and core.overlays. Toast messages are in notifications, banners are in overlays.

ui/routes

There is no global routing mechanism. Each app configures its own routing.

ui/saved_objects

core.savedObjects

ui/doc_title

core.chrome.docTitle

uiExports/injectedVars / chrome.getInjected

Configuration service. Can only be used to expose configuration properties

See also: Public’s CoreStart API Docs

Plugins for shared application servicesedit

In client code, we have a series of plugins that house shared application services, which are not technically part of core, but are often used in Kibana plugins.

This table maps some of the most commonly used legacy items to their Kibana Platform locations. For the API provided by Kibana Plugins see the plugin list.

Legacy Platform Kibana Platform

import 'ui/apply_filters'

N/A. Replaced by triggering an APPLY_FILTER_TRIGGER trigger. Directive is deprecated.

import 'ui/filter_bar'

import { FilterBar } from 'plugins/data/public'. Directive is deprecated.

import 'ui/query_bar'

import { QueryStringInput } from 'plugins/data/public' QueryStringInput. Directives are deprecated.

import 'ui/search_bar'

import { SearchBar } from 'plugins/data/public' SearchBar. Directive is deprecated.

import 'ui/kbn_top_nav'

import { TopNavMenu } from 'plugins/navigation/public'. Directive was removed.

ui/saved_objects/saved_object_finder

import { SavedObjectFinder } from 'plugins/saved_objects/public'

core_plugins/interpreter

plugins.data.expressions

ui/courier

plugins.data.search

ui/agg_types

plugins.data.search.aggs. Most code is available for static import. Stateful code is part of the search service.

ui/embeddable

plugins.embeddables

ui/filter_manager

import { FilterManager } from 'plugins/data/public' FilterManager

ui/index_patterns

import { IndexPatternsService } from 'plugins/data/public' IndexPatternsService

import 'ui/management'

plugins.management.sections. Management plugin setup contract.

import 'ui/registry/field_format_editors'

plugins.indexPatternManagement.fieldFormatEditors indexPatternManagement plugin setup contract.

ui/registry/field_formats

plugins.data.fieldFormats

ui/registry/feature_catalogue

plugins.home.featureCatalogue.register home plugin setup contract

ui/registry/vis_types

plugins.visualizations

ui/vis

plugins.visualizations

ui/share

plugins.share. share plugin start contract. showShareContextMenu is now called toggleShareContextMenu, ShareContextMenuExtensionsRegistryProvider is now called register

ui/vis/vis_factory

plugins.visualizations

ui/vis/vis_filters

plugins.visualizations.filters

ui/utils/parse_es_interval

import { search: { aggs: { parseEsInterval } } } from 'plugins/data/public'. parseEsInterval, ParsedInterval, InvalidEsCalendarIntervalError, InvalidEsIntervalFormatError items were moved to the Data Plugin as a static code

Server-sideedit
Core servicesedit

In server code, core can be accessed from either server.newPlatform or kbnServer.newPlatform:

Legacy Platform Kibana Platform

server.config()

initializerContext.config.create(). Must also define schema. See Configuration

server.route

core.http.createRouter. See HTTP routes migration.

server.renderApp()

response.renderCoreApp(). See Render HTML migration.

server.renderAppWithDefaultConfig()

response.renderAnonymousCoreApp(). See Render HTML migration.

request.getBasePath()

core.http.basePath.get

server.plugins.elasticsearch.getCluster('data')

context.core.elasticsearch.client

server.plugins.elasticsearch.getCluster('admin')

context.core.elasticsearch.client

server.plugins.elasticsearch.createCluster(...)

core.elasticsearch.createClient

server.savedObjects.setScopedSavedObjectsClientFactory

core.savedObjects.setClientFactoryProvider

server.savedObjects.addScopedSavedObjectsClientWrapperFactory

core.savedObjects.addClientWrapper

server.savedObjects.getSavedObjectsRepository

core.savedObjects.createInternalRepository core.savedObjects.createScopedRepository

server.savedObjects.getScopedSavedObjectsClient

core.savedObjects.getScopedClient

request.getSavedObjectsClient

context.core.savedObjects.client

request.getUiSettingsService

context.core.uiSettings.client

kibana.Plugin.deprecations

Handle plugin configuration deprecations and PluginConfigDescriptor.deprecations. Deprecations from Kibana Platform are not applied to legacy configuration

kibana.Plugin.savedObjectSchemas

core.savedObjects.registerType

kibana.Plugin.mappings

core.savedObjects.registerType. Learn more in SavedObjects migration.

kibana.Plugin.migrations

core.savedObjects.registerType. Learn more in SavedObjects migration.

kibana.Plugin.savedObjectsManagement

core.savedObjects.registerType. Learn more in SavedObjects migration.

See also: Server’s CoreSetup API Docs

Plugin servicesedit
Legacy Platform Kibana Platform

xpack_main.registerFeature

plugins.features.registerKibanaFeature

xpack_main.feature(pluginID).registerLicenseCheckResultsGenerator

x-pack licensing plugin

UI Exportsedit

The legacy platform used a set of uiExports to inject modules from one plugin into other plugins. This mechanism is not necessary for the Kibana Platform because all plugins are executed on the page at once, though only one application is rendered at a time.

This table shows where these uiExports have moved to in the Kibana Platform.

Legacy Platform Kibana Platform

aliases

N/A.

app

core.application.register

canvas

Canvas plugin API

chromeNavControls

core.chrome.navControls.register{Left,Right}

docViews

discover.docViews.addDocView

embeddableActions

embeddable plugin

embeddableFactories

embeddable plugin, embeddable.registerEmbeddableFactory

fieldFormatEditors, fieldFormats

data.fieldFormats

hacks

N/A. Just run the code in your plugin’s start method.

home

home plugin home.featureCatalogue.register

indexManagement

index management plugin

injectDefaultVars

N/A. Plugins will only be able to allow config values for the frontend. SeeConfiguration service

inspectorViews

inspector plugin

interpreter

plugins.data.expressions

links

core.application.register

managementSections

plugins.management.sections.register

mappings

core.savedObjects.registerType

migrations

core.savedObjects.registerType

navbarExtensions

N/A. Deprecated.

savedObjectSchemas

core.savedObjects.registerType

savedObjectsManagement

core.savedObjects.registerType

savedObjectTypes

core.savedObjects.registerType

search

data.search

shareContextMenuExtensions

plugins.share

taskDefinitions

taskManager plugin

uiCapabilities

core.application.register

uiSettingDefaults

core.uiSettings.register

validations

core.savedObjects.registerType

visEditorTypes

visualizations plugin

visTypeEnhancers

visualizations plugin

visTypes

visualizations plugin

visualize

visualize plugin

Plugin Specedit
Legacy Platform Kibana Platform

id

manifest.id

require

manifest.requiredPlugins

version

manifest.version

kibanaVersion

manifest.kibanaVersion

configPrefix

manifest.configPath

config

Configuration service

deprecations

Configuration service

uiExports

N/A. Use platform & plugin public contracts

publicDir

N/A. Kibana Platform serves static assets from /public/assets folder under /plugins/{id}/assets/{path*} URL.

preInit, init, postInit

N/A. Use Kibana Platform plugin-lifecycles

See alsoedit

For examples on how to migrate from specific legacy APIs, see Migration Examples.