{"payload":{"allShortcutsEnabled":false,"fileTree":{"src/core":{"items":[{"name":"public","path":"src/core/public","contentType":"directory"},{"name":"server","path":"src/core/server","contentType":"directory"},{"name":"test_helpers","path":"src/core/test_helpers","contentType":"directory"},{"name":"types","path":"src/core/types","contentType":"directory"},{"name":"utils","path":"src/core/utils","contentType":"directory"},{"name":"CONVENTIONS.md","path":"src/core/CONVENTIONS.md","contentType":"file"},{"name":"MIGRATION.md","path":"src/core/MIGRATION.md","contentType":"file"},{"name":"MIGRATION_EXAMPLES.md","path":"src/core/MIGRATION_EXAMPLES.md","contentType":"file"},{"name":"PRINCIPLES.md","path":"src/core/PRINCIPLES.md","contentType":"file"},{"name":"README.md","path":"src/core/README.md","contentType":"file"},{"name":"TESTING.md","path":"src/core/TESTING.md","contentType":"file"},{"name":"index.ts","path":"src/core/index.ts","contentType":"file"}],"totalCount":12},"src":{"items":[{"name":"cli","path":"src/cli","contentType":"directory"},{"name":"cli_keystore","path":"src/cli_keystore","contentType":"directory"},{"name":"cli_plugin","path":"src/cli_plugin","contentType":"directory"},{"name":"core","path":"src/core","contentType":"directory"},{"name":"dev","path":"src/dev","contentType":"directory"},{"name":"docs","path":"src/docs","contentType":"directory"},{"name":"fixtures","path":"src/fixtures","contentType":"directory"},{"name":"legacy","path":"src/legacy","contentType":"directory"},{"name":"optimize","path":"src/optimize","contentType":"directory"},{"name":"plugins","path":"src/plugins","contentType":"directory"},{"name":"setup_node_env","path":"src/setup_node_env","contentType":"directory"},{"name":"test_utils","path":"src/test_utils","contentType":"directory"},{"name":"type_definitions","path":"src/type_definitions","contentType":"directory"},{"name":"apm.js","path":"src/apm.js","contentType":"file"}],"totalCount":14},"":{"items":[{"name":".ci","path":".ci","contentType":"directory"},{"name":".github","path":".github","contentType":"directory"},{"name":"common","path":"common","contentType":"directory"},{"name":"config","path":"config","contentType":"directory"},{"name":"data","path":"data","contentType":"directory"},{"name":"docs","path":"docs","contentType":"directory"},{"name":"examples","path":"examples","contentType":"directory"},{"name":"licenses","path":"licenses","contentType":"directory"},{"name":"packages","path":"packages","contentType":"directory"},{"name":"rfcs","path":"rfcs","contentType":"directory"},{"name":"scripts","path":"scripts","contentType":"directory"},{"name":"src","path":"src","contentType":"directory"},{"name":"tasks","path":"tasks","contentType":"directory"},{"name":"test","path":"test","contentType":"directory"},{"name":"typings","path":"typings","contentType":"directory"},{"name":"utilities","path":"utilities","contentType":"directory"},{"name":"vars","path":"vars","contentType":"directory"},{"name":"webpackShims","path":"webpackShims","contentType":"directory"},{"name":"x-pack","path":"x-pack","contentType":"directory"},{"name":".backportrc.json","path":".backportrc.json","contentType":"file"},{"name":".browserslistrc","path":".browserslistrc","contentType":"file"},{"name":".editorconfig","path":".editorconfig","contentType":"file"},{"name":".eslintignore","path":".eslintignore","contentType":"file"},{"name":".eslintrc.js","path":".eslintrc.js","contentType":"file"},{"name":".fossa.yml","path":".fossa.yml","contentType":"file"},{"name":".gitattributes","path":".gitattributes","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":".i18nrc.json","path":".i18nrc.json","contentType":"file"},{"name":".node-version","path":".node-version","contentType":"file"},{"name":".nvmrc","path":".nvmrc","contentType":"file"},{"name":".prettierrc","path":".prettierrc","contentType":"file"},{"name":".sass-lint.yml","path":".sass-lint.yml","contentType":"file"},{"name":".telemetryrc.json","path":".telemetryrc.json","contentType":"file"},{"name":".yarnrc","path":".yarnrc","contentType":"file"},{"name":"CONTRIBUTING.md","path":"CONTRIBUTING.md","contentType":"file"},{"name":"FAQ.md","path":"FAQ.md","contentType":"file"},{"name":"Gruntfile.js","path":"Gruntfile.js","contentType":"file"},{"name":"Jenkinsfile","path":"Jenkinsfile","contentType":"file"},{"name":"LICENSE.txt","path":"LICENSE.txt","contentType":"file"},{"name":"NOTICE.txt","path":"NOTICE.txt","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"STYLEGUIDE.md","path":"STYLEGUIDE.md","contentType":"file"},{"name":"TYPESCRIPT.md","path":"TYPESCRIPT.md","contentType":"file"},{"name":"api-documenter.json","path":"api-documenter.json","contentType":"file"},{"name":"github_checks_reporter.json","path":"github_checks_reporter.json","contentType":"file"},{"name":"kibana.d.ts","path":"kibana.d.ts","contentType":"file"},{"name":"package.json","path":"package.json","contentType":"file"},{"name":"preinstall_check.js","path":"preinstall_check.js","contentType":"file"},{"name":"tsconfig.browser.json","path":"tsconfig.browser.json","contentType":"file"},{"name":"tsconfig.json","path":"tsconfig.json","contentType":"file"},{"name":"tsconfig.types.json","path":"tsconfig.types.json","contentType":"file"},{"name":"yarn.lock","path":"yarn.lock","contentType":"file"}],"totalCount":52}},"fileTreeProcessingTime":24.664939,"foldersToFetch":[],"repo":{"id":7833168,"defaultBranch":"main","name":"kibana","ownerLogin":"elastic","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2013-01-26T04:00:59.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/6764390?v=4","public":true,"private":false,"isOrgOwned":true},"symbolsExpanded":false,"treeExpanded":true,"refInfo":{"name":"7.9","listCacheKey":"v0:1710822039.0","canEdit":false,"refType":"branch","currentOid":"e000e35ca8bd86ecd866d39eda03a6d81e5a8731"},"path":"src/core/MIGRATION.md","currentUser":null,"blob":{"rawLines":null,"stylingDirectives":null,"colorizedLines":null,"csv":null,"csvError":null,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/elastic/kibana/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"MIGRATION.md","displayUrl":"https://github.com/elastic/kibana/blob/7.9/src/core/MIGRATION.md?raw=true","headerInfo":{"blobSize":"123 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"ec89fd2","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Felastic%2Fkibana%2Fblob%2F7.9%2Fsrc%2Fcore%2FMIGRATION.md","isCSV":false,"isRichtext":true,"toc":[{"level":1,"text":"Migrating legacy plugins to the new platform","anchor":"migrating-legacy-plugins-to-the-new-platform","htmlText":"Migrating legacy plugins to the new platform"},{"level":2,"text":"Overview","anchor":"overview","htmlText":"Overview"},{"level":3,"text":"Architecture","anchor":"architecture","htmlText":"Architecture"},{"level":3,"text":"Services","anchor":"services","htmlText":"Services"},{"level":3,"text":"Integrating with other plugins","anchor":"integrating-with-other-plugins","htmlText":"Integrating with other plugins"},{"level":3,"text":"Challenges to overcome with legacy plugins","anchor":"challenges-to-overcome-with-legacy-plugins","htmlText":"Challenges to overcome with legacy plugins"},{"level":4,"text":"Challenges on the server","anchor":"challenges-on-the-server","htmlText":"Challenges on the server"},{"level":4,"text":"Challenges in the browser","anchor":"challenges-in-the-browser","htmlText":"Challenges in the browser"},{"level":3,"text":"Plan of action","anchor":"plan-of-action","htmlText":"Plan of action"},{"level":2,"text":"Server-side plan of action","anchor":"server-side-plan-of-action","htmlText":"Server-side plan of action"},{"level":3,"text":"De-couple from hapi.js server and request objects","anchor":"de-couple-from-hapijs-server-and-request-objects","htmlText":"De-couple from hapi.js server and request objects"},{"level":3,"text":"Introduce new plugin definition shim","anchor":"introduce-new-plugin-definition-shim","htmlText":"Introduce new plugin definition shim"},{"level":3,"text":"Switch to new platform services","anchor":"switch-to-new-platform-services","htmlText":"Switch to new platform services"},{"level":3,"text":"Migrate to the new plugin system","anchor":"migrate-to-the-new-plugin-system","htmlText":"Migrate to the new plugin system"},{"level":2,"text":"Browser-side plan of action","anchor":"browser-side-plan-of-action","htmlText":"Browser-side plan of action"},{"level":4,"text":"1. Create a plugin definition file","anchor":"1-create-a-plugin-definition-file","htmlText":"1. Create a plugin definition file"},{"level":4,"text":"2. Export all static code and types from public/index.ts","anchor":"2-export-all-static-code-and-types-from-publicindexts","htmlText":"2. Export all static code and types from public/index.ts"},{"level":4,"text":"3. Export your runtime contract","anchor":"3-export-your-runtime-contract","htmlText":"3. Export your runtime contract"},{"level":4,"text":"4. Move \"owned\" UI modules into your plugin and expose them from your public contract","anchor":"4-move-owned-ui-modules-into-your-plugin-and-expose-them-from-your-public-contract","htmlText":"4. Move \"owned\" UI modules into your plugin and expose them from your public contract"},{"level":4,"text":"5. Provide plugin extension points decoupled from angular.js","anchor":"5-provide-plugin-extension-points-decoupled-from-angularjs","htmlText":"5. Provide plugin extension points decoupled from angular.js"},{"level":4,"text":"6. Move all webpack alias imports into uiExport entry files","anchor":"6-move-all-webpack-alias-imports-into-uiexport-entry-files","htmlText":"6. Move all webpack alias imports into uiExport entry files"},{"level":4,"text":"7. Switch to new platform services","anchor":"7-switch-to-new-platform-services","htmlText":"7. Switch to new platform services"},{"level":4,"text":"8. Migrate to the new plugin system","anchor":"8-migrate-to-the-new-plugin-system","htmlText":"8. Migrate to the new plugin system"},{"level":4,"text":"Bonus: Tips for complex migration scenarios","anchor":"bonus-tips-for-complex-migration-scenarios","htmlText":"Bonus: Tips for complex migration scenarios"},{"level":2,"text":"Keep Kibana fast","anchor":"keep-kibana-fast","htmlText":"Keep Kibana fast"},{"level":4,"text":"How to understand how big the bundle size of my plugin is?","anchor":"how-to-understand-how-big-the-bundle-size-of-my-plugin-is","htmlText":"How to understand how big the bundle size of my plugin is?"},{"level":2,"text":"Frequently asked questions","anchor":"frequently-asked-questions","htmlText":"Frequently asked questions"},{"level":3,"text":"Is migrating a plugin an all-or-nothing thing?","anchor":"is-migrating-a-plugin-an-all-or-nothing-thing","htmlText":"Is migrating a plugin an all-or-nothing thing?"},{"level":3,"text":"Do plugins need to be converted to TypeScript?","anchor":"do-plugins-need-to-be-converted-to-typescript","htmlText":"Do plugins need to be converted to TypeScript?"},{"level":3,"text":"Can static code be shared between plugins?","anchor":"can-static-code-be-shared-between-plugins","htmlText":"Can static code be shared between plugins?"},{"level":4,"text":"Background","anchor":"background","htmlText":"Background"},{"level":4,"text":"What goes wrong if I do share modules with state?","anchor":"what-goes-wrong-if-i-do-share-modules-with-state","htmlText":"What goes wrong if I do share modules with state?"},{"level":4,"text":"How to decide what code can be statically imported","anchor":"how-to-decide-what-code-can-be-statically-imported","htmlText":"How to decide what code can be statically imported"},{"level":4,"text":"Concrete Example","anchor":"concrete-example","htmlText":"Concrete Example"},{"level":3,"text":"How can I avoid passing Core services deeply within my UI component tree?","anchor":"how-can-i-avoid-passing-core-services-deeply-within-my-ui-component-tree","htmlText":"How can I avoid passing Core services deeply within my UI component tree?"},{"level":3,"text":"How is \"common\" code shared on both the client and server?","anchor":"how-is-common-code-shared-on-both-the-client-and-server","htmlText":"How is \"common\" code shared on both the client and server?"},{"level":3,"text":"When does code go into a plugin, core, or packages?","anchor":"when-does-code-go-into-a-plugin-core-or-packages","htmlText":"When does code go into a plugin, core, or packages?"},{"level":3,"text":"How do I build my shim for New Platform services?","anchor":"how-do-i-build-my-shim-for-new-platform-services","htmlText":"How do I build my shim for New Platform services?"},{"level":4,"text":"Client-side","anchor":"client-side","htmlText":"Client-side"},{"level":5,"text":"Core services","anchor":"core-services","htmlText":"Core services"},{"level":5,"text":"Plugins for shared application services","anchor":"plugins-for-shared-application-services","htmlText":"Plugins for shared application services"},{"level":4,"text":"Server-side","anchor":"server-side","htmlText":"Server-side"},{"level":5,"text":"Core services","anchor":"core-services-1","htmlText":"Core services"},{"level":5,"text":"Plugin services","anchor":"plugin-services","htmlText":"Plugin services"},{"level":4,"text":"UI Exports","anchor":"ui-exports","htmlText":"UI Exports"},{"level":4,"text":"Plugin Spec","anchor":"plugin-spec","htmlText":"Plugin Spec"},{"level":2,"text":"How to","anchor":"how-to","htmlText":"How to"},{"level":3,"text":"Configure plugin","anchor":"configure-plugin","htmlText":"Configure plugin"},{"level":4,"text":"Handle plugin configuration deprecations","anchor":"handle-plugin-configuration-deprecations","htmlText":"Handle plugin configuration deprecations"},{"level":3,"text":"Use scoped services","anchor":"use-scoped-services","htmlText":"Use scoped services"},{"level":4,"text":"Declare a custom scoped service","anchor":"declare-a-custom-scoped-service","htmlText":"Declare a custom scoped service"},{"level":3,"text":"Mock new platform services in tests","anchor":"mock-new-platform-services-in-tests","htmlText":"Mock new platform services in tests"},{"level":4,"text":"Writing mocks for your plugin","anchor":"writing-mocks-for-your-plugin","htmlText":"Writing mocks for your plugin"},{"level":4,"text":"Using mocks in your tests","anchor":"using-mocks-in-your-tests","htmlText":"Using mocks in your tests"},{"level":4,"text":"What about karma tests?","anchor":"what-about-karma-tests","htmlText":"What about karma tests?"},{"level":3,"text":"Provide Legacy Platform API to the New platform plugin","anchor":"provide-legacy-platform-api-to-the-new-platform-plugin","htmlText":"Provide Legacy Platform API to the New platform plugin"},{"level":4,"text":"On the server side","anchor":"on-the-server-side","htmlText":"On the server side"},{"level":4,"text":"On the client side","anchor":"on-the-client-side","htmlText":"On the client side"},{"level":3,"text":"Updates an application navlink at runtime","anchor":"updates-an-application-navlink-at-runtime","htmlText":"Updates an application navlink at runtime"},{"level":3,"text":"Logging config migration","anchor":"logging-config-migration","htmlText":"Logging config migration"}],"lineInfo":{"truncatedLoc":"1712","truncatedSloc":"1298"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"issueTemplate":null,"discussionTemplate":null,"language":"Markdown","languageID":222,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/elastic/kibana/blob/7.9/src/core/MIGRATION.md","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/elastic/kibana/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/elastic/kibana/raw/7.9/src/core/MIGRATION.md","renderImageOrRaw":false,"richText":"

Migrating legacy plugins to the new platform

\n\n

Make no mistake, it is going to take a lot of work to move certain plugins to the new platform. Our target is to migrate the entire repo over to the new platform throughout 7.x and to remove the legacy plugin system no later than 8.0, and this is only possible if teams start on the effort now.

\n

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

\n

We'll start with an overview of how plugins work in the new platform, and we'll end with a generic plan of action that can be applied to any plugin in the repo today.

\n

Overview

\n

Plugins in the new platform are not especially novel or complicated to describe. Our intention wasn't to build some clever system that magically solved problems through abstractions and layers of obscurity, and we wanted to make sure plugins could continue to use most of the same technologies they use today, at least from a technical perspective.

\n

New platform plugins exist in the src/plugins and x-pack/plugins directories. See all conventions for first-party Elastic plugins.

\n

Architecture

\n

Plugins are defined as classes and exposed to the platform itself through a simple wrapper function. A plugin can have browser side code, server side code, or both. There is no architectural difference between a plugin in the browser and a plugin on the server, which is to say that in both places you describe your plugin similarly, and you interact with core and/or other plugins in the same way.

\n

The basic file structure of a new platform plugin named \"demo\" that had both client-side and server-side code would be:

\n
src/plugins\n  demo\n    kibana.json [1]\n    public\n      index.ts [2]\n      plugin.ts [3]\n    server\n      index.ts [4]\n      plugin.ts [5]\n
\n

[1] kibana.json is a static manifest file that is used to identify the plugin and to determine what kind of code the platform should execute from the plugin:

\n
{\n  \"id\": \"demo\",\n  \"version\": \"kibana\",\n  \"server\": true,\n  \"ui\": true\n}
\n

More details aboutmanifest file format

\n

Note that package.json files are irrelevant to and ignored by the new platform.

\n

[2] public/index.ts is the entry point into the client-side code of this plugin. It must export a function named plugin, which will receive a standard set of core capabilities as an argument (e.g. logger). It should return an instance of its plugin definition for the platform to register at load time.

\n
import { PluginInitializerContext } from 'kibana/server';\nimport { Plugin } from './plugin';\n\nexport function plugin(initializerContext: PluginInitializerContext) {\n  return new Plugin(initializerContext);\n}
\n

[3] public/plugin.ts is the client-side plugin definition itself. Technically speaking it does not need to be a class or even a separate file from the entry point, but all plugins at Elastic should be consistent in this way. See all conventions for first-party Elastic plugins.

\n
import { PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';\n\nexport class Plugin {\n  constructor(initializerContext: PluginInitializerContext) {\n  }\n\n  public setup(core: CoreSetup) {\n    // called when plugin is setting up\n  }\n\n  public start(core: CoreStart) {\n    // called after all plugins are set up\n  }\n\n  public stop() {\n    // called when plugin is torn down, aka window.onbeforeunload\n  }\n}
\n

[4] server/index.ts is the entry-point into the server-side code of this plugin. It is identical in almost every way to the client-side entry-point:

\n
import { PluginInitializerContext } from 'kibana/server';\nimport { Plugin } from './plugin';\n\nexport function plugin(initializerContext: PluginInitializerContext) {\n  return new Plugin(initializerContext);\n}
\n

[5] server/plugin.ts is the server-side plugin definition. The shape of this plugin is the same as it's client-side counter-part:

\n
import { PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';\n\nexport class Plugin {\n  constructor(initializerContext: PluginInitializerContext) {\n  }\n\n  public setup(core: CoreSetup) {\n    // called when plugin is setting up during Kibana's startup sequence\n  }\n\n  public start(core: CoreStart) {\n    // called after all plugins are set up\n  }\n\n  public stop() {\n    // called when plugin is torn down during Kibana's shutdown sequence\n  }\n}
\n

The platform does not impose any technical restrictions on how the internals of the plugin are architected, though there are certain considerations related to how plugins interact with core and how plugins interact with other plugins that may greatly impact how they are built.

\n

Services

\n

The various independent domains that make up core are represented by a series of services, and many of those services expose public interfaces that are provided to all plugins. Services expose different features at different parts of their lifecycle. We describe the lifecycle of core services and plugins with specifically-named functions on the service definition.

\n

In the new platform, there are three lifecycle functions today: setup, start, and stop. The setup functions are invoked sequentially while Kibana is setting up on the server or when it is being loaded in the browser. The start functions are invoked sequentially after setup has completed for all plugins. The stop functions are invoked sequentially while Kibana is gracefully shutting down on the server or when the browser tab or window is being closed.

\n

The table below explains how each lifecycle event relates to the state of Kibana.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
lifecycle eventserverbrowser
setupbootstrapping and configuring routesloading plugin bundles and configuring applications
startserver is now serving trafficbrowser is now showing UI to the user
stopserver has received a request to shutdownuser is navigating away from Kibana
\n

There is no equivalent behavior to start or stop in legacy plugins, so this guide primarily focuses on migrating functionality into setup.

\n

The lifecycle-specific contracts exposed by core services are always passed as the first argument to the equivalent lifecycle function in a plugin. For example, the core UiSettings service exposes a function get to all plugin setup functions. To use this function to retrieve a specific UI setting, a plugin just accesses it off of the first argument:

\n
import { CoreSetup } from 'kibana/server';\n\nexport class Plugin {\n  public setup(core: CoreSetup) {\n    core.uiSettings.get('courier:maxShardsBeforeCryTime');\n  }\n}
\n

Different service interfaces can and will be passed to setup and stop because certain functionality makes sense in the context of a running plugin while other types of functionality may have restrictions or may only make sense in the context of a plugin that is stopping.

\n

For example, the stop function in the browser gets invoked as part of the window.onbeforeunload event, which means you can't necessarily execute asynchronous code here in a reliable way. For that reason, core likely wouldn't provide any asynchronous functions to plugin stop functions in the browser.

\n

Core services that expose functionality to plugins always have their setup function ran before any plugins.

\n

These are the contracts exposed by the core services for each lifecycle event:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
lifecycle eventcontract
contructorPluginInitializerContext
setupCoreSetup
startCoreStart
stop
\n

Integrating with other plugins

\n

Plugins can expose public interfaces for other plugins to consume. Like core, those interfaces are bound to the lifecycle functions setup and/or start.

\n

Anything returned from setup or start will act as the interface, and while not a technical requirement, all first-party Elastic plugins should expose types for that interface as well. 3rd party plugins wishing to allow other plugins to integrate with it are also highly encouraged to expose types for their plugin interfaces.

\n

foobar plugin.ts:

\n
export type FoobarPluginSetup = ReturnType<Plugin['setup']>;\nexport type FoobarPluginStart = ReturnType<Plugin['start']>;\n\nexport class Plugin {\n  public setup() {\n    return {\n      getFoo() {\n        return 'foo';\n      }\n    };\n  }\n\n  public start() {\n    return {\n      getBar() {\n        return 'bar';\n      }\n    }\n  }\n}
\n

Unlike core, capabilities exposed by plugins are not automatically injected into all plugins. Instead, if a plugin wishes to use the public interface provided by another plugin, they must first declare that plugin as a dependency in their kibana.json.

\n

demo kibana.json:

\n
{\n  \"id\": \"demo\",\n  \"requiredPlugins\": [\n    \"foobar\"\n  ],\n  \"server\": true,\n  \"ui\": true\n}
\n

With that specified in the plugin manifest, the appropriate interfaces are then available via the second argument of setup and/or start:

\n

demo plugin.ts:

\n
import { CoreSetup, CoreStart } from 'src/core/server';\nimport { FoobarPluginSetup, FoobarPluginStop } from '../../foobar/server';\n\ninterface DemoSetupPlugins {\n  foobar: FoobarPluginSetup;\n}\n\ninterface DemoStartPlugins {\n  foobar: FoobarPluginStart;\n}\n\nexport class Plugin {\n  public setup(core: CoreSetup, plugins: DemoSetupPlugins) {\n    const { foobar } = plugins;\n    foobar.getFoo(); // 'foo'\n    foobar.getBar(); // throws because getBar does not exist\n  }\n\n  public start(core: CoreStart, plugins: DemoStartPlugins) {\n    const { foobar } = plugins;\n    foobar.getFoo(); // throws because getFoo does not exist\n    foobar.getBar(); // 'bar'\n  }\n\n  public stop() {},\n}
\n

Challenges to overcome with legacy plugins

\n

New platform plugins have 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.

\n

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

\n

Challenges on the server

\n

The general shape/architecture of legacy server-side code is similar to the new 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.

\n

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

\n

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.

\n

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

\n

Challenges in the browser

\n

The legacy plugin system in the browser is fundamentally incompatible with the new 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\".

\n

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 exist in the new platform.

\n

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 new platform, and in fact we intend down the line to build new platform plugins as immutable bundles that can not share state in this way.

\n

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.

\n

Plan of action

\n

In order to move a legacy plugin to the new plugin system, the challenges on the server and in the browser must be addressed. Fortunately, the hardest problems can be solved in legacy plugins today without consuming the new plugin system at all.

\n

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

\n

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

\n

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

\n

Server-side plan of action

\n

Legacy server-side plugins access functionality from 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.

\n

Here is the high-level for migrating a server-side plugin:

\n\n

These steps (except for the last one) do not have to be completed strictly in order, and some can be done in parallel or as part of the same change. In general, we recommend that larger plugins approach this more methodically, doing each step in a separate change. This makes each individual change less risk and more focused. This approach may not make sense for smaller plugins. For instance, it may be simpler to switch to New Platform services when you introduce your Plugin class, rather than shimming it with the legacy service.

\n

De-couple from hapi.js server and request objects

\n

Most integrations with core and other plugins occur through the hapi.js server and request objects, and neither of these things are exposed through the new platform, so tackle this problem first.

\n

Fortunately, decoupling from these objects is relatively straightforward.

\n

The server object is introduced to your plugin in its legacy init function, so in that function you will \"pick\" the functionality you actually use from server and attach it to a new interface, which you will then pass in all the places you had previously been passing server.

\n

The request object is introduced to your plugin in every route handler, so at the root of every route handler, you will create a new interface by \"picking\" the request information (e.g. body, headers) and core and plugin capabilities from the request object that you actually use and pass that in all the places you previously were passing request.

\n

Any calls to mutate either the server or request objects (e.g. server.decorate()) will be moved toward the root of the legacy init function if they aren't already there.

\n

Let's take a look at an example legacy plugin definition that uses both server and request.

\n
// likely imported from another file\nfunction search(server, request) {\n  const { elasticsearch } = server.plugins;\n  return elasticsearch.getCluster('admin').callWithRequest(request, 'search');\n}\n\nexport default (kibana) => {\n  return new kibana.Plugin({\n    id: 'demo_plugin',\n\n    init(server) {\n      server.route({\n        path: '/api/demo_plugin/search',\n        method: 'POST',\n        async handler(request) {\n          search(server, request); // target acquired\n        }\n      });\n\n      server.expose('getDemoBar', () => {\n        return `Demo ${server.plugins.foo.getBar()}`;\n      });\n    }\n  });\n}
\n

This example legacy plugin uses hapi's server object directly inside of its init function, which is something we can address in a later step. What we need to address in this step is when we pass the raw server and request objects into our custom search function.

\n

Our goal in this step is to make sure we're not integrating with other plugins via functions on server.plugins.* or on the request object. You should begin by finding all of the integration points where you make these calls, and put them behind a \"facade\" abstraction that can hide the details of where these APIs come from. This allows you to easily switch out how you access these APIs without having to change all of the code that may use them.

\n

Instead, we identify which functionality we actually need from those objects and craft custom new interfaces for them, taking care not to leak hapi.js implementation details into their design.

\n
import { ElasticsearchPlugin, Request } from '../elasticsearch';\nexport interface ServerFacade {\n  plugins: {\n    elasticsearch: ElasticsearchPlugin\n  }\n}\nexport interface RequestFacade extends Request {\n}\n\n// likely imported from another file\nfunction search(server: ServerFacade, request: RequestFacade) {\n  const { elasticsearch } = server.plugins;\n  return elasticsearch.getCluster('admin').callWithRequest(request, 'search');\n}\n\nexport default (kibana) => {\n  return new kibana.Plugin({\n    id: 'demo_plugin',\n\n    init(server) {\n      const serverFacade: ServerFacade = {\n        plugins: {\n          elasticsearch: server.plugins.elasticsearch\n        }\n      }\n\n      server.route({\n        path: '/api/demo_plugin/search',\n        method: 'POST',\n        async handler(request) {\n          const requestFacade: RequestFacade = {\n            headers: request.headers\n          };\n          search(serverFacade, requestFacade);\n        }\n      });\n\n      server.expose('getDemoBar', () => {\n        return `Demo ${server.plugins.foo.getBar()}`;\n      });\n    }\n  });\n}
\n

This change might seem trivial, but it's important for two reasons.

\n

First, the business logic built into search is now coupled to an object you created manually and have complete control over rather than hapi itself. This will allow us in a future step to replace the dependency on hapi without necessarily having to modify the business logic of the plugin.

\n

Second, it forced you to clearly define the dependencies you have on capabilities provided by core and by other plugins. This will help in a future step when you must replace those capabilities with services provided through the new platform.

\n

Introduce new plugin definition shim

\n

While most plugin logic is now decoupled from hapi, the plugin definition itself still uses hapi to expose functionality for other plugins to consume and access functionality from both core and a different plugin.

\n
// index.ts\n\nexport default (kibana) => {\n  return new kibana.Plugin({\n    id: 'demo_plugin',\n\n    init(server) {\n      const serverFacade: ServerFacade = {\n        plugins: {\n          elasticsearch: server.plugins.elasticsearch\n        }\n      }\n\n      // HTTP functionality from legacy\n      server.route({\n        path: '/api/demo_plugin/search',\n        method: 'POST',\n        async handler(request) {\n          const requestFacade: RequestFacade = {\n            headers: request.headers\n          };\n          search(serverFacade, requestFacade);\n        }\n      });\n\n      // Exposing functionality for other plugins\n      server.expose('getDemoBar', () => {\n        return `Demo ${server.plugins.foo.getBar()}`; // Accessing functionality from another plugin\n      });\n    }\n  });\n}
\n

We now move this logic into a new plugin definition, which is based off of the conventions used in real new platform plugins. While the legacy plugin definition is in the root of the plugin, this new plugin definition will be under the plugin's server/ directory since it is only the server-side plugin definition.

\n
// server/plugin.ts\nimport { CoreSetup, Plugin } from 'src/core/server';\nimport { ElasticsearchPlugin } from '../elasticsearch';\n\ninterface FooSetup {\n  getBar(): string\n}\n\n// We inject the miminal legacy dependencies into our plugin including dependencies on other legacy\n// plugins. Take care to only expose the legacy functionality you need e.g. don't inject the whole\n// `Legacy.Server` if you only depend on `Legacy.Server['route']`.\ninterface LegacySetup {\n  route: Legacy.Server['route']\n  plugins: {\n    elasticsearch: ElasticsearchPlugin, // note: Elasticsearch is in CoreSetup in NP, rather than a plugin\n    foo: FooSetup\n  }\n}\n\n// Define the public API's for our plugins setup and start lifecycle\nexport interface DemoSetup {\n  getDemoBar: () => string;\n}\nexport interface DemoStart {}\n\n// Once we start dependending on NP plugins' setup or start API's we'll add their types here\nexport interface DemoSetupDeps {}\nexport interface DemoStartDeps {}\n\nexport class DemoPlugin implements Plugin<DemoSetup, DemoStart, DemoSetupDeps, DemoStartDeps> {\n  public setup(core: CoreSetup, plugins: PluginsSetup, __LEGACY: LegacySetup): DemoSetup {\n    // We're still using the legacy Elasticsearch and http router here, but we're now accessing\n    // these services in the same way a NP plugin would: injected into the setup function. It's\n    // also obvious that these dependencies needs to be removed by migrating over to the New\n    // Platform services exposed through core.\n    const serverFacade: ServerFacade = {\n      plugins: {\n        elasticsearch: __LEGACY.plugins.elasticsearch\n      }\n    }\n\n    __LEGACY.route({\n      path: '/api/demo_plugin/search',\n      method: 'POST',\n      async handler(request) {\n        const requestFacade: RequestFacade = {\n          headers: request.headers\n        };\n        search(serverFacade, requestFacade);\n      }\n    });\n\n    // Exposing functionality for other plugins\n    return {\n      getDemoBar() {\n        return `Demo ${__LEGACY.plugins.foo.getBar()}`; // Accessing functionality from another legacy plugin\n      }\n    };\n  }\n}
\n

The legacy plugin definition is still the one that is being executed, so we now \"shim\" this new plugin definition into the legacy world by instantiating it and wiring it up inside of the legacy init function.

\n
// index.ts\n\nimport { Plugin, PluginDependencies, LegacySetup } from './server/plugin';\n\nexport default (kibana) => {\n  return new kibana.Plugin({\n    id: 'demo_plugin',\n\n    init(server) {\n      // core setup API's\n      const coreSetup = server.newPlatform.setup.core;\n\n      // For now we don't have any dependencies on NP plugins\n      const pluginsSetup: PluginsSetup = {};\n\n      // legacy dependencies\n      const __LEGACY: LegacySetup = {\n        route: server.route,\n        plugins: {\n          elasticsearch: server.plugins.elasticsearch,\n          foo: server.plugins.foo\n        }\n      };\n\n      const demoSetup = new Plugin().setup(coreSetup, pluginsSetup, __LEGACY);\n\n      // continue to expose functionality to legacy plugins\n      server.expose('getDemoBar', demoSetup.getDemoBar);\n    }\n  });\n}
\n
\n

Note: An equally valid approach is to extend CoreSetup with a __legacy\nproperty instead of introducing a third parameter to your plugins lifecycle\nfunction. The important thing is that you reduce the legacy API surface that\nyou depend on to a minimum by only picking and injecting the methods you\nrequire and that you clearly differentiate legacy dependencies in a namespace.

\n
\n

This introduces a layer between the legacy plugin system with hapi.js and the logic you want to move to the new plugin system. The functionality exposed through that layer is still provided from the legacy world and in some cases is still technically powered directly by hapi, but building this layer forced you to identify the remaining touch points into the legacy world and it provides you with control when you start migrating to new platform-backed services.

\n
\n

Need help constructing your shim? There are some common APIs that are already present in the New Platform. In these cases, it may make more sense to simply use the New Platform service rather than crafting your own shim. Refer to the How do I build my shim for New Platform services? section for a table of legacy to new platform service translations to identify these. Note that while some APIs have simply moved others are completely different. Take care when choosing how much refactoring to do in a single change.

\n
\n

Switch to new platform services

\n

At this point, your legacy server-side plugin is described in the shape and\nconventions of the new plugin system, and all of the touch points with the\nlegacy world and hapi.js have been isolated inside the __LEGACY parameter.

\n

Now the goal is to replace all legacy services with services provided by the new platform instead.

\n

For the first time in this guide, your progress here is limited by the migration efforts within core and other plugins.

\n

As core capabilities are migrated to services in the new platform, they are made available as lifecycle contracts to the legacy init function through server.newPlatform. This allows you to adopt the new platform service APIs directly in your legacy plugin as they get rolled out.

\n

For the most part, care has been taken when migrating services to the new platform to preserve the existing APIs as much as possible, but there will be times when new APIs differ from the legacy equivalents.

\n

If a legacy API differs from its new platform equivalent, some refactoring will be required. The best outcome comes from updating the plugin code to use the new API, but if that's not practical now, you can also create a facade inside your new plugin definition that is shaped like the legacy API but powered by the new API. Once either of these things is done, that override can be removed from the shim.

\n

Eventually, all __LEGACY dependencies will be removed and your Plugin will\nbe powered entirely by Core API's from server.newPlatform.setup.core.

\n
init(server) {\n  // core setup API's\n  const coreSetup = server.newPlatform.setup.core;\n\n  // For now we don't have any dependencies on NP plugins\n  const pluginsSetup: PluginsSetup = {};\n\n  // legacy dependencies, we've removed our dependency on elasticsearch and server.route\n  const __LEGACY: LegacySetup = {\n    plugins: {\n      foo: server.plugins.foo\n    }\n  };\n\n  const demoSetup = new Plugin().setup(coreSetup, pluginsSetup, __LEGACY);\n}
\n

At this point, your legacy server-side plugin logic is no longer coupled to\nthe legacy core.

\n

A similar approach can be taken for your plugin dependencies. To start\nconsuming an API from a New Platform plugin access these from\nserver.newPlatform.setup.plugins and inject it into your plugin's setup\nfunction.

\n
init(server) {\n  // core setup API's\n  const coreSetup = server.newPlatform.setup.core;\n\n  // Depend on the NP plugin 'foo'\n  const pluginsSetup: PluginsSetup = {\n    foo: server.newPlatform.setup.plugins.foo\n  };\n\n  const demoSetup = new Plugin().setup(coreSetup, pluginsSetup);\n}
\n

As the plugins you depend on are migrated to the new platform, their contract\nwill be exposed through server.newPlatform, so the __LEGACY dependencies\nshould be removed. Like in core, plugins should take care to preserve their\nexisting APIs to make this step as seamless as possible.

\n

It is much easier to reliably make breaking changes to plugin APIs in the new\nplatform than it is in the legacy world, so if you're planning a big change,\nconsider doing it after your dependent plugins have migrated rather than as\npart of your own migration.

\n

Eventually, all __LEGACY dependencies will be removed and your plugin will be\nentirely powered by the New Platform and New Platform plugins.

\n
\n

Note: All New Platform plugins are exposed to legacy plugins via\nserver.newPlatform.setup.plugins. Once you move your plugin over to the\nNew Platform you will have to explicitly declare your dependencies on other\nplugins in your kibana.json manifest file.

\n
\n

At this point, your legacy server-side plugin logic is no longer coupled to legacy plugins.

\n

Migrate to the new plugin system

\n

With both shims converted, you are now ready to complete your migration to the new platform.

\n

Many plugins will copy and paste all of their plugin code into a new plugin directory in either src/plugins for OSS or x-pack/plugins for commerical code and then delete their legacy shims. It's at this point that you'll want to make sure to create your kibana.json file if it does not already exist.

\n

With the previous steps resolved, this final step should be easy, but the exact process may vary plugin by plugin, so when you're at this point talk to the platform team to figure out the exact changes you need.

\n

Other plugins may want to move subsystems over individually. For instance, you can move routes over to the New Platform in groups rather than all at once. Other examples that could be broken up:

\n\n

In general, we recommend moving all at once by ensuring you're not depending on any legacy code before you move over.

\n

Browser-side plan of action

\n

It is generally a much greater challenge preparing legacy browser-side code for the new 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.

\n

To complicate matters further, a significant amount of the business logic in Kibana's client-side code exists inside the ui/public directory (aka ui modules), and all of that must be migrated as well. Unlike the server-side code where the order in which you migrated plugins was not particularly important, it's important that UI modules be addressed as soon as possible.

\n

Because 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 new platform. The best place to start is by checking with the platform team to help identify the best migration path for your particular plugin.

\n

That said, we've seen a series of patterns emerge as teams begin migrating browser code. In practice, most migrations will follow a path that looks something like this:

\n

1. Create a plugin definition file

\n

We've found that doing this right away helps you start thinking about your plugin in terms of lifecycle methods and services, which makes the rest of the migration process feel more natural. It also forces you to identify which actions \"kick off\" your plugin, since you'll need to execute those when the setup/start methods are called.

\n

This definition isn't going to do much for us just yet, but as we get further into the process, we will gradually start returning contracts from our setup and start methods, while also injecting dependencies as arguments to these methods.

\n
// public/plugin.ts\nimport { CoreSetup, CoreStart, Plugin } from 'kibana/server';\nimport { FooSetup, FooStart } from '../../../../legacy/core_plugins/foo/public';\n\n/**\n * These are the private interfaces for the services your plugin depends on.\n * @internal\n */\nexport interface DemoSetupDeps {\n  foo: FooSetup;\n}\nexport interface DemoStartDeps {\n  foo: FooStart;\n}\n\n/**\n * These are the interfaces with your public contracts. You should export these\n * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces.\n * @public\n */\nexport type DemoSetup = {}\nexport type DemoStart = {}\n\n/** @internal */\nexport class DemoPlugin implements Plugin<DemoSetup, DemoStart, DemoSetupDeps, DemoStartDeps> {\n  public setup(core: CoreSetup, plugins: DemoSetupDeps): DemoSetup {\n    // kick off your plugin here...\n    return {\n      fetchConfig: () => ({}),\n    };\n  }\n\n  public start(core: CoreStart, plugins: DemoStartDeps): DemoStart {\n    // ...or here\n    return {\n      initDemo: () => ({}),\n    };\n  }\n\n  public stop() {}\n}
\n

2. Export all static code and types from public/index.ts

\n

If your plugin needs to share static code with other plugins, this code must be exported from your top-level public/index.ts. This includes any type interfaces that you wish to make public. For details on the types of code that you can safely share outside of the runtime lifecycle contracts, see Can static code be shared between plugins?

\n
// public/index.ts\nimport { DemoSetup, DemoStart } from './plugin';\n\nconst myPureFn = (x: number): number => x + 1;\nconst MyReactComponent = (props) => {\n  return <h1>Hello, {props.name}</h1>;\n}\n\n// These are your public types & static code\nexport {\n  myPureFn,\n  MyReactComponent,\n  DemoSetup,\n  DemoStart,\n}
\n

While you're at it, you can also add your plugin initializer to this file:

\n
// public/index.ts\nimport { PluginInitializer, PluginInitializerContext } from 'kibana/server';\nimport { DemoSetup, DemoStart, DemoSetupDeps, DemoStartDeps, DemoPlugin } from './plugin';\n\n// Core will be looking for this when loading our plugin in the new platform\nexport const plugin: PluginInitializer<DemoSetup, DemoStart, DemoSetupDeps, DemoStartDeps> = (\n  initializerContext: PluginInitializerContext\n) => {\n  return new DemoPlugin();\n};\n\nconst myPureFn = (x: number): number => x + 1;\nconst MyReactComponent = (props) => {\n  return <h1>Hello, {props.name}</h1>;\n}\n\n/** @public */\nexport {\n  myPureFn,\n  MyReactComponent,\n  DemoSetup,\n  DemoStart,\n}
\n

Great! So you have your plugin definition, and you've moved all of your static exports to the top level of your plugin... now let's move on to the runtime contract your plugin will be exposing.

\n

3. Export your runtime contract

\n

Next, we need a way to expose your runtime dependencies. In the new platform, core will handle this for you. But while we are still in the legacy world, other plugins will need a way to consume your plugin's contract without the help of core.

\n

So we will take a similar approach to what was described above in the server section: actually call the Plugin.setup() and Plugin.start() methods, and export the values those return for other legacy plugins to consume. By convention, we've been placing this in a legacy.ts file, which also serves as our shim where we import our legacy dependencies and reshape them into what we are expecting in the new platform:

\n
// public/legacy.ts\nimport { PluginInitializerContext } from 'kibana/server';\nimport { npSetup, npStart } from 'ui/new_platform';\nimport { plugin } from '.';\n\nimport { setup as fooSetup, start as fooStart } from '../../foo/public/legacy'; // assumes `foo` lives in `legacy/core_plugins`\n\nconst pluginInstance = plugin({} as PluginInitializerContext);\nconst __LEGACYSetup = {\n  bar: {},        // shim for a core service that hasn't migrated yet\n  foo: fooSetup,  // dependency on a legacy plugin\n};\nconst __LEGACYStart = {\n  bar: {},        // shim for a core service that hasn't migrated yet\n  foo: fooStart,  // dependency on a legacy plugin\n};\n\nexport const setup = pluginInstance.setup(npSetup.core, npSetup.plugins, __LEGACYSetup);\nexport const start = pluginInstance.start(npStart.core, npStart.plugins, __LEGACYStart);
\n
\n

As you build your shims, you may be wondering where you will find some legacy services in the new platform. Skip to the tables below for a list of some of the more common legacy services and where we currently expect them to live.

\n
\n

Notice how in the example above, we are importing the setup and start contracts from the legacy shim provided by foo plugin; we could just as easily be importing modules from ui/public here as well.

\n

The point is that, over time, this becomes the one file in our plugin containing stateful imports from the legacy world. And that is where things start to get interesting...

\n

4. Move \"owned\" UI modules into your plugin and expose them from your public contract

\n

Everything inside of the ui/public directory is going to be dealt with in one of the following ways:

\n\n

To rapidly define ownership and determine interdependencies, UI modules should move to the most appropriate plugins to own them. Modules that are considered \"core\" can remain in the ui directory as the platform team works to move them out.

\n

Concerns around ownership or duplication of a given module should be raised and resolved with the appropriate team so that the code is either duplicated to break the interdependency or a team agrees to \"own\" that extension point in one of their plugins and the module moves there.

\n

A great outcome is a module being deleted altogether because it isn't used or it was used so lightly that it was easy to refactor away.

\n

If it is determined that your plugin is going to own any UI modules that other plugins depend on, you'll want to migrate these quickly so that there's time for downstream plugins to update their imports. This will ultimately involve moving the module code into your plugin, and exposing it via your setup/start contracts, or as static code from your plugin/index.ts. We have identified owners for most of the legacy UI modules; if you aren't sure where you should move something that you own, please consult with the platform team.

\n

Depending on the module's level of complexity and the number of other places in Kibana that rely on it, there are a number of strategies you could use for this:

\n\n

5. Provide plugin extension points decoupled from angular.js

\n

There will be no global angular module in the new platform, which means none of the functionality provided by core will be coupled to angular. Since there is no global angular module shared by all applications, plugins providing extension points to be used by other plugins can not couple those extension points to angular either.

\n

All teams that own a plugin are strongly encouraged to remove angular entirely, but if nothing else they must provide non-angular-based extension points for plugins.

\n

One way to address this problem is to go through the code that is currently exposed to plugins and refactor away all of the touch points into angular.js. This might be the easiest option in some cases, but it might be hard in others.

\n

Another way to address this problem is to create an entirely new set of plugin APIs that are not dependent on angular.js, and then update the implementation within the plugin to \"merge\" the angular and non-angular capabilities together. This is a good approach if preserving the existing angular API until we remove the old plugin system entirely is of critical importance. Generally speaking though, the removal of angular and introduction of a new set of public plugin APIs is a good reason to make a breaking change to the existing plugin capabilities. Make sure the PRs are tagged appropriately so we add these changes to our plugin changes blog post for each release.

\n

Please talk with the platform team when formalizing any client-side extension points that you intend to move to the new platform as there are some bundling considerations to consider.

\n

6. Move all webpack alias imports into uiExport entry files

\n

Existing plugins import three things using webpack aliases today: services from ui/public (ui/), services from other plugins (plugins/), and uiExports themselves (uiExports/). These webpack aliases will not exist once we remove the legacy plugin system, so part of our migration effort is addressing all of the places where they are used today.

\n

In the new platform, dependencies from core and other plugins will be passed through lifecycle functions in the plugin definition itself. In a sense, they will be run from the \"root\" of the plugin.

\n

With the legacy plugin system, extensions of core and other plugins are handled through entry files defined as uiExport paths. In other words, when a plugin wants to serve an application (a core-owned thing), it defines a main entry file for the app via the app uiExport, and when a plugin wants to extend visTypes (a plugin-owned thing), they do so by specifying an entry file path for the visType uiExport.

\n

Each uiExport path is an entry file into one specific set of functionality provided by a client-side plugin. All webpack alias-based imports should be moved to these entry files, where they are appropriate. Moving a deeply nested webpack alias-based import in a plugin to one of the uiExport entry files might require some refactoring to ensure the dependency is now passed down to the appropriate place as function arguments instead of via import statements.

\n

For stateful dependencies using the plugins/ and ui/ webpack aliases, you should be able to take advantage of the legacy.ts shim you created earlier. By placing these imports directly in your shim, you can pass the dependencies you need into your Plugin.start and Plugin.setup methods, from which point they can be passed down to the rest of your plugin's entry files.

\n

For items that don't yet have a clear \"home\" in the new platform, it may also be helpful to somehow indicate this in your shim to make it easier to remember that you'll need to change this later. One convention we've found helpful for this is simply using a namespace like __LEGACY:

\n
// public/legacy.ts\nimport { uiThing } from 'ui/thing';\n...\n\nconst pluginInstance = plugin({} as PluginInitializerContext);\nconst __LEGACY = {\n  foo: fooSetup,\n  uiThing, // eventually this will move out of __LEGACY and into a NP plugin\n};\n\n...\nexport const setup = pluginInstance.setup(npSetup.core, npSetup.plugins, __LEGACY);
\n

7. Switch to new platform services

\n

At this point, your plugin has one or more uiExport entry files that together contain all of the webpack alias-based import statements needed to run your plugin. Each one of these import statements is either a service that is or will be provided by core or a service provided by another plugin.

\n

As new non-angular-based APIs are added, update your entry files to import the correct service API. The service APIs provided directly from the new platform can be imported through the ui/new_platform module for the duration of this migration. As new services are added, they will also be exposed there. This includes all core services as well as any APIs provided by real new platform plugins.

\n

Once all of the existing webpack alias-based imports in your plugin switch to ui/new_platform, it no longer depends directly on the legacy \"core\" features or other legacy plugins, so it is ready to officially migrate to the new platform.

\n

8. Migrate to the new plugin system

\n

With all of your services converted, you are now ready to complete your migration to the new platform.

\n

Many plugins at this point will copy over their plugin definition class & the code from their various service/uiExport entry files directly into the new plugin directory. The legacy.ts shim file can then simply be deleted.

\n

With the previous steps resolved, this final step should be easy, but the exact process may vary plugin by plugin, so when you're at this point talk to the platform team to figure out the exact changes you need.

\n

Other plugins may want to move subsystems over individually. Examples of pieces that could be broken up:

\n\n

Bonus: Tips for complex migration scenarios

\n

For a few plugins, some of these steps (such as angular removal) could be a months-long process. In those cases, it may be helpful from an organizational perspective to maintain a clear separation of code that is and isn't \"ready\" for the new platform.

\n

One convention that is useful for this is creating a dedicated public/np_ready directory to house the code that is ready to migrate, and gradually move more and more code into it until the rest of your plugin is essentially empty. At that point, you'll be able to copy your index.ts, plugin.ts, and the contents of ./np_ready over into your plugin in the new platform, leaving your legacy shim behind. This carries the added benefit of providing a way for us to introduce helpful tooling in the future, such as custom eslint rules, which could be run against that specific directory to ensure your code is ready to migrate.

\n

Keep Kibana fast

\n

tl;dr: Load as much code lazily as possible.\nEveryone loves snappy applications with responsive UI and hates spinners. Users deserve the best user experiences regardless of whether they run Kibana locally or in the cloud, regardless of their hardware & environment.\nThere are 2 main aspects of the perceived speed of an application: loading time and responsiveness to user actions.\nNew platform loads and bootstraps all the plugins whenever a user lands on any page. It means that adding every new application affects overall loading performance in the new platform, as plugin code is loaded eagerly to initialize the plugin and provide plugin API to dependent plugins.\nHowever, it's usually not necessary that the whole plugin code should be loaded and initialized at once. The plugin could keep on loading code covering API functionality on Kibana bootstrap but load UI related code lazily on-demand, when an application page or management section is mounted.\nAlways prefer to require UI root components lazily when possible (such as in mount handlers). Even if their size may seem negligible, they are likely using some heavy-weight libraries that will also be removed from the initial plugin bundle, therefore, reducing its size by a significant amount.

\n
import { Plugin, CoreSetup, AppMountParameters } from 'src/core/public';\nexport class MyPlugin implements Plugin<MyPluginSetup> {\n  setup(core: CoreSetup, plugins: SetupDeps){\n   core.application.register({\n     id: 'app',\n     title: 'My app',\n     async mount(params: AppMountParameters) {\n        const { mountApp } = await import('./app/mount_app');\n        return mountApp(await core.getStartServices(), params);\n      },\n    });\n    plugins.management.sections.section.kibana.registerApp({\n      id: 'app',\n      title: 'My app',\n      order: 1,\n      async mount(params) {\n        const { mountManagementSection } = await import('./app/mount_management_section');\n        return mountManagementSection(coreSetup, params);\n      },\n   })\n   return {\n      doSomething(){}\n   }\n }\n}
\n

How to understand how big the bundle size of my plugin is?

\n

New platform plugins are distributed as a pre-built with @kbn/optimizer package artifacts. It allows us to get rid of the shipping of optimizer in the distributable version of Kibana.\nEvery NP plugin artifact contains all plugin dependencies required to run the plugin, except some stateful dependencies shared across plugin bundles via @kbn/ui-shared-deps.\nIt means that NP plugin artifacts tend to have a bigger size than the legacy platform version.\nTo understand the current size of your plugin artifact, run @kbn/optimizer as

\n
node scripts/build_kibana_platform_plugins.js --dist --no-examples
\n

and check the output in the target sub-folder of your plugin folder

\n
ls -lh plugins/my_plugin/target/public/\n# output\n# an async chunk loaded on demand\n... 262K 0.plugin.js\n# eagerly loaded chunk\n... 50K  my_plugin.plugin.js
\n

you might see at least one js bundle - my_plugin.plugin.js. This is the only artifact loaded by the platform during bootstrap in the browser. The rule of thumb is to keep its size as small as possible.\nOther lazily loaded parts of your plugin present in the same folder as separate chunks under {number}.plugin.js names.\nIf you want to investigate what your plugin bundle consists of you need to run @kbn/optimizer with --profile flag to get generated webpack stats file.

\n
node scripts/build_kibana_platform_plugins.js --dist --no-examples --profile
\n

Many OSS tools are allowing you to analyze generated stats file

\n\n

Frequently asked questions

\n

Is migrating a plugin an all-or-nothing thing?

\n

It doesn't have to be. Within the Kibana repo, you can have a new platform plugin with the same name as a legacy plugin.

\n

Technically speaking, you could move all of your server-side code to the new platform and leave the legacy browser-side code where it is. You can even move only a portion of code on your server at a time, like on a route by route basis for example.

\n

For any new plugin APIs being defined as part of this process, it is recommended to create those APIs in new platform plugins, and then core will pass them down into the legacy world to be used there. This leaves one less thing you need to migrate.

\n

Do plugins need to be converted to TypeScript?

\n

No. That said, the migration process will require a lot of refactoring, and TypeScript will make this dramatically easier and less risky. Independent of the new platform effort, our goals are to convert the entire Kibana repo to TypeScript over time, so now is a great time to do it.

\n

At the very least, any plugin exposing an extension point should do so with first-class type support so downstream plugins that are using TypeScript can depend on those types.

\n

Can static code be shared between plugins?

\n

tl;dr Yes, but it should be limited to pure functional code that does not depend on outside state from the platform or a plugin.

\n

Background

\n
\n

Don't care why, just want to know how? Skip to the \"how\" section below.

\n
\n

Legacy Kibana has never run as a single page application. Each plugin has it's own entry point and gets \"ownership\" of every module it imports when it is loaded into the browser. This has allowed stateful modules to work without breaking other plugins because each time the user navigates to a new plugin, the browser reloads with a different entry bundle, clearing the state of the previous plugin.

\n

Because of this \"feature\" many undesirable things developed in the legacy platform:

\n\n

The New Platform's primary goal is to make developing Kibana plugins easier, both for developers at Elastic and in the community. The approach we've chosen is to enable plugins to integrate and communicate at runtime rather than at build time. By wiring services and plugins up at runtime, we can ship stable APIs that do not have to be compiled into every plugin and instead live inside a solid core that each plugin gets connected to when it executes.

\n

This applies to APIs that plugins expose as well. In the new platform, plugins can communicate through an explicit interface rather than importing all the code from one another and having to recompile Webpack bundles when a plugin is disabled or a new plugin is installed.

\n

You've probably noticed that this is not the typical way a JavaScript developer works. We're used to importing code at the top of files (and for some use-cases this is still fine). However, we're not building a typical JavaScript application, we're building an application that is installed into a dynamic system (the Kibana Platform).

\n

What goes wrong if I do share modules with state?

\n

One goal of a stable Kibana core API is to allow Kibana instances to run plugins with varying minor versions, e.g. Kibana 8.4.0 running PluginX 8.0.1 and PluginY 8.2.5. This will be made possible by building each plugin into an “immutable bundle” that can be installed into Kibana. You can think of an immutable bundle as code that doesn't share any imported dependencies with any other bundles, that is all it's dependencies are bundled together.

\n

This method of building and installing plugins comes with side effects which are important to be aware of when developing a plugin.

\n\n

How to decide what code can be statically imported

\n

The general rule of thumb here is: any module that is not purely functional should not be shared statically, and instead should be exposed at runtime via the plugin's setup and/or start contracts.

\n

Ask yourself these questions when deciding to share code through static exports or plugin contracts:

\n\n

If you answered yes to any of the above questions, you probably have an impure module that cannot be shared across plugins. Another way to think about this: if someone literally copied and pasted your exported module into their plugin, would it break if:

\n\n

If your module were to break for either of these reasons, it should not be exported statically. This can be more easily illustrated by examples of what can and cannot be exported statically.

\n

Examples of code that could be shared statically:

\n\n

Examples of code that could not be shared statically and how to fix it:

\n\n

In any case, you will also need to carefully consider backward compatibility (BWC). Whatever you choose to export will need to work for the entire major version cycle (eg. Kibana 8.0-8.9), regardless of which version of the export a plugin has bundled and which minor version of Kibana they're using. Breaking changes to static exports are only allowed in major versions. However, during the 7.x cycle, all of these APIs are considered \"experimental\" and can be broken at any time. We will not consider these APIs stable until 8.0 at the earliest.

\n

Concrete Example

\n

Ok, you've decided you want to export static code from your plugin, how do you do it? The New Platform only considers values exported from my_plugin/public and my_plugin/server to be stable. The linter will only let you import statically from these top-level modules. In the future, our tooling will enforce that these APIs do not break between minor versions. All code shared among plugins should be exported in these modules like so:

\n
// my_plugin/public/index.ts\nexport { MyPureComponent } from './components';\n\n// regular plugin export used by core to initialize your plugin\nexport const plugin = ...;
\n

These can then be imported using relative paths from other plugins:

\n
// my_other_plugin/public/components/my_app.ts\nimport { MyPureComponent } from '../my_plugin/public';
\n

If you have code that should be available to other plugins on both the client and server, you can have a common directory. See How is \"common\" code shared on both the client and server?

\n

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

\n

There are some Core services that are purely presentational, for example core.overlays.openModal() or core.application.createLink() 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:

\n
    \n
  1. Use an abstraction layer, like Redux, to decouple your UI code from core (this is the highly preferred option); or\n\n
  2. \n
  3. Use React Context to provide these services to large parts of your React tree; or
  4. \n
  5. Create a high-order-component that injects core into a React component; or\n
      \n
    • 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).
    • \n
    \n
  6. \n
  7. Create a global singleton module that gets imported into each module that needs it. (Note: this module cannot be shared across plugin boundaries, see above). Example.
  8. \n
\n

If you find that you need many different Core services throughout your application, this may be a code smell 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 (see option 1).

\n

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/or 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.

\n

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

\n

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 in the index files for both the server and public directories.

\n

Plugins should not ever import code from deeply inside another plugin (eg. my_plugin/public/components) or from other top-level directories (eg. 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.

\n

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.

\n

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

\n

See all conventions for first-party Elastic plugins.

\n

When does code go into a plugin, core, or packages?

\n

This is an impossible question to answer definitively for all circumstances. For each time this question is raised, we must carefully consider to what extent we think that code is relevant to almost everyone developing in Kibana, what license the code is shipping under, which teams are most appropriate to \"own\" that code, is the code stateless etc.

\n

As a general rule of thumb, most code in Kibana should exist in plugins. Plugins are the most obvious way that we break Kibana down into sets of specialized domains with controls around interdependency communication and management. It's always possible to move code from a plugin into core if we ever decide to do so, but it's much more disruptive to move code from core to a plugin.

\n

There is essentially no code that can't exist in a plugin. When in doubt, put the code in a plugin.

\n

After plugins, core is where most of the rest of the code in Kibana will exist. Functionality that's critical to the reliable execution of the Kibana process belongs in core. Services that will widely be used by nearly every non-trivial plugin in any Kibana install belong in core. Functionality that is too specialized to specific use cases should not be in core, so while something like generic saved objects is a core concern, index patterns are not.

\n

The packages directory should have the least amount of code in Kibana. Just because some piece of code is not stateful doesn't mean it should go into packages. The packages directory exists to aid us in our quest to centralize as many of our owned dependencies in this single monorepo, so it's the logical place to put things like Kibana specific forks of node modules or vendor dependencies.

\n

How do I build my shim for New Platform services?

\n

Many of the utilities you're using to build your plugins are available in the New Platform or in New Platform plugins. To help you build the shim for these new services, use the tables below to find where the New Platform equivalent lives.

\n

Client-side

\n

TODO: add links to API docs on items in \"New Platform\" column.

\n
Core services
\n

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

\n
import { npStart: { core } } from 'ui/new_platform';
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Legacy PlatformNew PlatformNotes
chrome.addBasePathcore.http.basePath.prepend
chrome.navLinks.updatecore.appbase.updaterUse the updater$ property when registering your application via core.application.register
chrome.breadcrumbs.setcore.chrome.setBreadcrumbs
chrome.getUiSettingsClientcore.uiSettings
chrome.helpExtension.setcore.chrome.setHelpExtension
chrome.setVisiblecore.chrome.setIsVisible
chrome.setRootTemplate / chrome.setRootController--Use application mounting via core.application.register (not available to legacy plugins at this time).
import { recentlyAccessed } from 'ui/persisted_log'core.chrome.recentlyAccessed
ui/capabilitiescore.application.capabilities
ui/documentation_linkscore.docLinks
ui/kfetchcore.httpAPI is nearly identical
ui/notifycore.notifications and core.overlaysToast messages are in notifications, banners are in overlays. May be combined later.
ui/routes--There is no global routing mechanism. Each app configures its own routing.
ui/saved_objectscore.savedObjectsClient API is the same
ui/doc_titlecore.chrome.docTitle
uiExports/injectedVars / chrome.getInjectedConfigure plugin and PluginConfigDescriptor.exposeToBrowserCan only be used to expose configuration properties
\n

See also: Public's CoreStart API Docs

\n
Plugins for shared application services
\n

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

\n

This table maps some of the most commonly used legacy items to their new platform locations.

\n
import { npStart: { plugins } } from 'ui/new_platform';
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Legacy PlatformNew PlatformNotes
import 'ui/apply_filters'N/A. Replaced by triggering an APPLY_FILTER_TRIGGER trigger.Directive is deprecated.
import 'ui/filter_bar'import { FilterBar } from '../data/public'Directive is deprecated.
import 'ui/query_bar'import { QueryStringInput } from '../data/public'Directives are deprecated.
import 'ui/search_bar'import { SearchBar } from '../data/public'Directive is deprecated.
import 'ui/kbn_top_nav'import { TopNavMenu } from '../navigation/public'Directive was moved to src/plugins/kibana_legacy.
ui/saved_objects/components/saved_object_finderimport { SavedObjectFinder } from '../saved_objects/public'
core_plugins/interpreterplugins.data.expressions
ui/courierplugins.data.search
ui/agg_typesplugins.data.search.aggsMost code is available for static import. Stateful code is part of the search service.
ui/embeddableplugins.embeddables
ui/filter_managerplugins.data.filter--
ui/index_patternsplugins.data.indexPatterns
import 'ui/management'plugins.management.sections
import 'ui/registry/field_format_editors'plugins.indexPatternManagement.fieldFormatEditors
ui/registry/field_formatsplugins.data.fieldFormats
ui/registry/feature_catalogueplugins.home.featureCatalogue.registerMust add home as a dependency in your kibana.json.
ui/registry/vis_typesplugins.visualizations--
ui/visplugins.visualizations--
ui/shareplugins.shareshowShareContextMenu is now called toggleShareContextMenu, ShareContextMenuExtensionsRegistryProvider is now called register
ui/vis/vis_factoryplugins.visualizations--
ui/vis/vis_filtersplugins.visualizations.filters--
ui/utils/parse_es_intervalimport { search: { aggs: { parseEsInterval } } } from '../data/public'parseEsInterval, ParsedInterval, InvalidEsCalendarIntervalError, InvalidEsIntervalFormatError items were moved to the Data Plugin as a static code
\n

Server-side

\n
Core services
\n

In server code, core can be accessed from either server.newPlatform or kbnServer.newPlatform. There are not currently very many services available on the server-side:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Legacy PlatformNew PlatformNotes
server.config()initializerContext.config.create()Must also define schema. See how to configure plugin
server.routecore.http.createRouterExamples
server.renderApp()response.renderCoreApp()Examples
server.renderAppWithDefaultConfig()response.renderAnonymousCoreApp()Examples
request.getBasePath()core.http.basePath.get
server.plugins.elasticsearch.getCluster('data')context.core.elasticsearch.dataClient
server.plugins.elasticsearch.getCluster('admin')context.core.elasticsearch.adminClient
server.plugins.elasticsearch.createCluster(...)core.elasticsearch.legacy.createClient
server.savedObjects.setScopedSavedObjectsClientFactorycore.savedObjects.setClientFactoryProvider
server.savedObjects.addScopedSavedObjectsClientWrapperFactorycore.savedObjects.addClientWrapper
server.savedObjects.getSavedObjectsRepositorycore.savedObjects.createInternalRepository core.savedObjects.createScopedRepository
server.savedObjects.getScopedSavedObjectsClientcore.savedObjects.getScopedClient
request.getSavedObjectsClientcontext.core.savedObjects.client
request.getUiSettingsServicecontext.core.uiSettings.client
kibana.Plugin.deprecationsHandle plugin configuration deprecations and PluginConfigDescriptor.deprecationsDeprecations from New Platform are not applied to legacy configuration
kibana.Plugin.savedObjectSchemascore.savedObjects.registerTypeExamples
kibana.Plugin.mappingscore.savedObjects.registerTypeExamples
kibana.Plugin.migrationscore.savedObjects.registerTypeExamples
kibana.Plugin.savedObjectsManagementcore.savedObjects.registerTypeExamples
\n

See also: Server's CoreSetup API Docs

\n
Plugin services
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Legacy PlatformNew PlatformNotes
server.plugins.xpack_main.registerFeatureplugins.features.registerFeature
server.plugins.xpack_main.feature(pluginID).registerLicenseCheckResultsGeneratorx-pack licensing plugin
\n

UI Exports

\n

The legacy platform uses a set of \"uiExports\" to inject modules from one plugin into other plugins. This mechansim is not necessary in the New Platform because all plugins are executed on the page at once (though only one application) is rendered at a time.

\n

This table shows where these uiExports have moved to in the New Platform. In most cases, if a uiExport you need is not yet available in the New Platform, you may leave in your legacy plugin for the time being and continue to migrate the rest of your app to the New Platform.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Legacy PlatformNew PlatformNotes
aliases
appcore.application.register
canvasShould be an API on the canvas plugin.
chromeNavControlscore.chrome.navControls.register{Left,Right}
contextMenuActionsShould be an API on the devTools plugin.
devTools
docViewsplugins.discover.docViews.addDocViewShould be an API on the discover plugin.
embeddableActionsShould be an API on the embeddables plugin.
embeddableFactoriesShould be an API on the embeddables plugin.
fieldFormatEditors
fieldFormatsplugins.data.fieldFormats
hacksn/aJust run the code in your plugin's start method.
homeplugins.home.featureCatalogue.registerMust add home as a dependency in your kibana.json.
indexManagementShould be an API on the indexManagement plugin.
injectDefaultVarsn/aPlugins will only be able to allow config values for the frontend. See #41990
inspectorViewsShould be an API on the data (?) plugin.
interpreterShould be an API on the interpreter plugin.
linksn/aNot necessary, just register your app via core.application.register
managementSectionsplugins.management.sections.register
mappingsPart of SavedObjects, see #33587
migrationsPart of SavedObjects, see #33587
navbarExtensionsn/aDeprecated
savedObjectSchemasPart of SavedObjects, see #33587
savedObjectsManagementPart of SavedObjects, see #33587
savedObjectTypesPart of SavedObjects, see #33587
search
shareContextMenuExtensions
styleSheetPaths
taskDefinitionsShould be an API on the taskManager plugin.
uiCapabilitiescore.application.register
uiSettingDefaultscore.uiSettings.register
validationsPart of SavedObjects, see #33587
visEditorTypes
visTypeEnhancers
visTypesplugins.visualizations.types
visualize
\n

Plugin Spec

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Legacy PlatformNew Platform
idmanifest.id
requiremanifest.requiredPlugins
versionmanifest.version
kibanaVersionmanifest.kibanaVersion
configPrefixmanifest.configPath
configexport config
deprecationsexport config
uiExportsN/A. Use platform & plugin public contracts
publicDirN/A. Platform serves static assets from /public/assets folder under /plugins/{id}/assets/{path*} URL.
preInit, init, postInitN/A. Use NP lifecycle events
\n

How to

\n

Configure plugin

\n

Kibana provides ConfigService if a plugin developer may want to support adjustable runtime behavior for their plugins. Access to Kibana config in New platform has been subject to significant refactoring.

\n

Config service does not provide access to the whole config anymore. New platform plugin cannot read configuration parameters of the core services nor other plugins directly. Use plugin contract to provide data.

\n
// your-plugin.js\n// in Legacy platform\nconst basePath = config.get('server.basePath');\n// in New platform\nconst basePath = core.http.basePath.get(request);
\n

In order to have access to your plugin config, you should:

\n\n
// my_plugin/server/index.ts\nimport { schema, TypeOf } from '@kbn/config-schema';\nexport const plugin = ...\nexport const config = {\n  schema: schema.object(...),\n};\nexport type MyPluginConfigType = TypeOf<typeof config.schema>;
\n\n
class MyPlugin {\n  constructor(initializerContext: PluginInitializerContext) {\n    this.config$ = initializerContext.config.create<MyPluginConfigType>();\n    // or if config is optional:\n    this.config$ = initializerContext.config.createIfExists<MyPluginConfigType>();\n  }
\n

If your plugin also have a client-side part, you can also expose configuration properties to it using the configuration exposeToBrowser allow-list property.

\n
// my_plugin/server/index.ts\nimport { schema, TypeOf } from '@kbn/config-schema';\nimport { PluginConfigDescriptor } from 'kibana/server';\n\nconst configSchema = schema.object({\n  secret: schema.string({ defaultValue: 'Only on server' }),\n  uiProp: schema.string({ defaultValue: 'Accessible from client' }),\n});\n\ntype ConfigType = TypeOf<typeof configSchema>;\n\nexport const config: PluginConfigDescriptor<ConfigType> = {\n  exposeToBrowser: {\n    uiProp: true,\n  },\n  schema: configSchema,\n};
\n

Configuration containing only the exposed properties will be then available on the client-side using the plugin's initializerContext:

\n
// my_plugin/public/index.ts\ninterface ClientConfigType {\n  uiProp: string;\n}\n\nexport class Plugin implements Plugin<PluginSetup, PluginStart> {\n  constructor(private readonly initializerContext: PluginInitializerContext) {}\n\n  public async setup(core: CoreSetup, deps: {}) {\n    const config = this.initializerContext.config.get<ClientConfigType>();\n    // ...\n  }
\n

All plugins are considered enabled by default. If you want to disable your plugin by default, you could declare the enabled flag in plugin config. This is a special Kibana platform key. The platform reads its value and won't create a plugin instance if enabled: false.

\n
export const config = {\n  schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }),\n};
\n

Handle plugin configuration deprecations

\n

If your plugin have deprecated properties, you can describe them using the deprecations config descriptor field.

\n

The system is quite similar to the legacy plugin's deprecation management. The most important difference\nis that deprecations are managed on a per-plugin basis, meaning that you don't need to specify the whole\nproperty path, but use the relative path from your plugin's configuration root.

\n
// my_plugin/server/index.ts\nimport { schema, TypeOf } from '@kbn/config-schema';\nimport { PluginConfigDescriptor } from 'kibana/server';\n\nconst configSchema = schema.object({\n  newProperty: schema.string({ defaultValue: 'Some string' }),\n});\n\ntype ConfigType = TypeOf<typeof configSchema>;\n\nexport const config: PluginConfigDescriptor<ConfigType> = {\n  schema: configSchema,\n  deprecations: ({ rename, unused }) => [\n    rename('oldProperty', 'newProperty'),\n    unused('someUnusedProperty'),\n  ]\n};
\n

In some cases, accessing the whole configuration for deprecations is necessary. For these edge cases,\nrenameFromRoot and unusedFromRoot are also accessible when declaring deprecations.

\n
// my_plugin/server/index.ts\nexport const config: PluginConfigDescriptor<ConfigType> = {\n  schema: configSchema,\n  deprecations: ({ renameFromRoot, unusedFromRoot }) => [\n    renameFromRoot('oldplugin.property', 'myplugin.property'),\n    unusedFromRoot('oldplugin.deprecated'),\n  ]\n};
\n

Note that deprecations registered in new platform's plugins are not applied to the legacy configuration.\nDuring migration, if you still need the deprecations to be effective in the legacy plugin, you need to declare them in\nboth plugin definitions.

\n

Use scoped services

\n

Whenever Kibana needs to get access to data saved in elasticsearch, it should perform a check whether an end-user has access to the data.\nIn the legacy platform, Kibana requires to bind elasticsearch related API with an incoming request to access elasticsearch service on behalf of a user.

\n
  async function handler(req, res) {\n    const dataCluster = server.plugins.elasticsearch.getCluster('data');\n    const data = await dataCluster.callWithRequest(req, 'ping');\n  }
\n

The new platform introduced a handler interface on the server-side to perform that association internally. Core services, that require impersonation with an incoming request, are\nexposed via context argument of the request handler interface.\nThe above example looks in the new platform as

\n
  async function handler(context, req, res) {\n    const data = await context.core.elasticsearch.adminClient.callAsInternalUser('ping')\n  }
\n

The request handler context exposed the next scoped core services:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Legacy PlatformNew Platform
request.getSavedObjectsClientcontext.savedObjects.client
server.plugins.elasticsearch.getCluster('admin')context.elasticsearch.adminClient
server.plugins.elasticsearch.getCluster('data')context.elasticsearch.dataClient
request.getUiSettingsServicecontext.uiSettings.client
\n

Declare a custom scoped service

\n

Plugins can extend the handler context with custom API that will be available to the plugin itself and all dependent plugins.\nFor example, the plugin creates a custom elasticsearch client and want to use it via the request handler context:

\n
import { CoreSetup, IScopedClusterClient } from 'kibana/server';\n\nexport interface MyPluginContext {\n  client: IScopedClusterClient;\n}\n\n// extend RequestHandlerContext when a dependent plugin imports MyPluginContext from the file\ndeclare module 'src/core/server' {\n  interface RequestHandlerContext {\n    myPlugin?: MyPluginContext;\n  }\n}\n\nclass Plugin {\n  setup(core: CoreSetup) {\n    const client = core.elasticsearch.createClient('myClient');\n    core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => {\n      return { client: client.asScoped(req) };\n    });\n\n    router.get(\n      { path: '/api/my-plugin/', validate },\n      async (context, req, res) => {\n        const data = await context.myPlugin.client.callAsCurrentUser('endpoint');\n        ...\n      }\n    );\n  }
\n

Mock new platform services in tests

\n

Writing mocks for your plugin

\n

Core services already provide mocks to simplify testing and make sure plugins always rely on valid public contracts:

\n
// my_plugin/server/plugin.test.ts\nimport { configServiceMock } from 'src/core/server/mocks';\n\nconst configService = configServiceMock.create();\nconfigService.atPath.mockReturnValue(config$);\n\nconst plugin = new MyPlugin({ configService }, );
\n

Or if you need to get the whole core setup or start contracts:

\n
// my_plugin/public/plugin.test.ts\nimport { coreMock } from 'src/core/public/mocks';\n\nconst coreSetup = coreMock.createSetup();\ncoreSetup.uiSettings.get.mockImplementation((key: string) => {\n  \n});\n\nconst plugin = new MyPlugin(coreSetup, ...);
\n

Although it isn't mandatory, we strongly recommended you export your plugin mocks as well, in order for dependent plugins to use them in tests. Your plugin mocks should be exported from the root /server and /public directories in your plugin:

\n
// my_plugin/server/mocks.ts or my_plugin/public/mocks.ts\nconst createSetupContractMock = () => {\n  const startContract: jest.Mocked<MyPluginStartContract>= {\n    isValid: jest.fn();\n  }\n  // here we already type check as TS infers to the correct type declared above\n  startContract.isValid.mockReturnValue(true);\n  return startContract;\n}\n\nexport const myPluginMocks = {\n  createSetup: createSetupContractMock,\n  createStart: \n}
\n

Plugin mocks should consist of mocks for public APIs only: setup/start/stop contracts. Mocks aren't necessary for pure functions as other plugins can call the original implementation in tests.

\n

Using mocks in your tests

\n

During the migration process, it is likely you are preparing your plugin by shimming in new platform-ready dependencies via the legacy ui/new_platform module:

\n
import { npSetup, npStart } from 'ui/new_platform';
\n

If you are using this approach, the easiest way to mock core and new platform-ready plugins in your legacy tests is to mock the ui/new_platform module:

\n
jest.mock('ui/new_platform');
\n

This will automatically mock the services in ui/new_platform thanks to the helpers that have been added to that module.

\n

If others are consuming your plugin's new platform contracts via the ui/new_platform module, you'll want to update the helpers as well to ensure your contracts are properly mocked.

\n
\n

Note: The ui/new_platform mock is only designed for use by old Jest tests. If you are writing new tests, you should structure your code and tests such that you don't need this mock. Instead, you should import the core mock directly and instantiate it.

\n
\n

What about karma tests?

\n

While our plan is to only provide first-class mocks for Jest tests, there are many legacy karma tests that cannot be quickly or easily converted to Jest -- particularly those which are still relying on mocking Angular services via ngMock.

\n

For these tests, we are maintaining a separate set of mocks. Files with a .karma_mock.{js|ts|tsx} extension will be loaded globally before karma tests are run.

\n

It is important to note that this behavior is different from jest.mock('ui/new_platform'), which only mocks tests on an individual basis. If you encounter any failures in karma tests as a result of new platform migration efforts, you may need to add a .karma_mock.js file for the affected services, or add to the existing karma mock we are maintaining in ui/new_platform.

\n

Provide Legacy Platform API to the New platform plugin

\n

On the server side

\n

During migration, you can face a problem that not all API is available in the New platform yet. You can work around this by extending your\nnew platform plugin with Legacy API:

\n\n
class MyPlugin {\n  public async setup(core){\n    return {\n      registerLegacyAPI: (legacyAPI) => (this.legacyAPI = legacyAPI)\n    }\n  }\n}
\n\n
new kibana.Plugin({\n  init(server){\n    const myPlugin = server.newPlatform.setup.plugins.myPlugin;\n    if (!myPlugin) {\n      throw new Error('myPlugin plugin is not available.');\n    }\n    myPlugin.registerLegacyAPI({ ... });\n  }\n})
\n\n
class MyPlugin {\n  private getLegacyAPI(){\n    return this.legacyAPI;\n  }\n  public async setup(core){\n    const routeHandler = (context, req, req) => {\n      const legacyApi = this.getLegacyAPI();\n      // ...\n    }\n    return {\n      registerLegacyAPI: (legacyAPI) => (this.legacyAPI = legacyAPI)\n    }\n  }\n}
\n

On the client side

\n

It's not currently possible to use a similar pattern on the client-side.\nBecause Legacy platform plugins heavily rely on global angular modules, which aren't available on the new platform.\nSo you can utilize the same approach for only stateless Angular components, as long as they are not consumed by a New Platform application. When New Platform applications are on the page, no legacy code is executed, so the registerLegacyAPI function would not be called.

\n

Updates an application navlink at runtime

\n

The application API now provides a way to updates some of a registered application's properties after registration.

\n
// inside your plugin's setup function\nexport class MyPlugin implements Plugin {\n  private appUpdater = new BehaviorSubject<AppUpdater>(() => ({}));\n  setup({ application }) {\n    application.register({\n      id: 'my-app',\n      title: 'My App',\n      updater$: this.appUpdater,\n      async mount(params) {\n        const { renderApp } = await import('./application');\n        return renderApp(params);\n      },\n    });\n  }\n  start() {\n     // later, when the navlink needs to be updated\n     appUpdater.next(() => {\n       navLinkStatus: AppNavLinkStatus.disabled,\n       tooltip: 'Application disabled',\n     })\n  }
\n

Logging config migration

\n

Read

\n
","renderedFileInfo":null,"shortPath":null,"symbolsEnabled":true,"tabSize":2,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":null,"showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null,"symbols":{"timed_out":false,"not_analyzed":false,"symbols":[{"name":"Migrating legacy plugins to the new platform","kind":"section_1","ident_start":2,"ident_end":46,"extent_start":0,"extent_end":126245,"fully_qualified_name":"Migrating legacy plugins to the new platform","ident_utf16":{"start":{"line_number":0,"utf16_col":2},"end":{"line_number":0,"utf16_col":46}},"extent_utf16":{"start":{"line_number":0,"utf16_col":0},"end":{"line_number":1712,"utf16_col":0}}},{"name":"Overview","kind":"section_2","ident_start":5212,"ident_end":5220,"extent_start":5209,"extent_end":19458,"fully_qualified_name":"Overview","ident_utf16":{"start":{"line_number":68,"utf16_col":3},"end":{"line_number":68,"utf16_col":11}},"extent_utf16":{"start":{"line_number":68,"utf16_col":0},"end":{"line_number":334,"utf16_col":0}}},{"name":"Architecture","kind":"section_3","ident_start":5734,"ident_end":5746,"extent_start":5730,"extent_end":9491,"fully_qualified_name":"Architecture","ident_utf16":{"start":{"line_number":74,"utf16_col":4},"end":{"line_number":74,"utf16_col":16}},"extent_utf16":{"start":{"line_number":74,"utf16_col":0},"end":{"line_number":176,"utf16_col":0}}},{"name":"Services","kind":"section_3","ident_start":9495,"ident_end":9503,"extent_start":9491,"extent_end":13300,"fully_qualified_name":"Services","ident_utf16":{"start":{"line_number":176,"utf16_col":4},"end":{"line_number":176,"utf16_col":12}},"extent_utf16":{"start":{"line_number":176,"utf16_col":0},"end":{"line_number":219,"utf16_col":0}}},{"name":"Integrating with other plugins","kind":"section_3","ident_start":13304,"ident_end":13334,"extent_start":13300,"extent_end":15422,"fully_qualified_name":"Integrating with other plugins","ident_utf16":{"start":{"line_number":219,"utf16_col":4},"end":{"line_number":219,"utf16_col":34}},"extent_utf16":{"start":{"line_number":219,"utf16_col":0},"end":{"line_number":298,"utf16_col":0}}},{"name":"Challenges to overcome with legacy plugins","kind":"section_3","ident_start":15426,"ident_end":15468,"extent_start":15422,"extent_end":18581,"fully_qualified_name":"Challenges to overcome with legacy plugins","ident_utf16":{"start":{"line_number":298,"utf16_col":4},"end":{"line_number":298,"utf16_col":46}},"extent_utf16":{"start":{"line_number":298,"utf16_col":0},"end":{"line_number":324,"utf16_col":0}}},{"name":"Challenges on the server","kind":"section_4","ident_start":15860,"ident_end":15884,"extent_start":15855,"extent_end":17036,"fully_qualified_name":"Challenges on the server","ident_utf16":{"start":{"line_number":304,"utf16_col":5},"end":{"line_number":304,"utf16_col":29}},"extent_utf16":{"start":{"line_number":304,"utf16_col":0},"end":{"line_number":314,"utf16_col":0}}},{"name":"Challenges in the browser","kind":"section_4","ident_start":17041,"ident_end":17066,"extent_start":17036,"extent_end":18581,"fully_qualified_name":"Challenges in the browser","ident_utf16":{"start":{"line_number":314,"utf16_col":5},"end":{"line_number":314,"utf16_col":30}},"extent_utf16":{"start":{"line_number":314,"utf16_col":0},"end":{"line_number":324,"utf16_col":0}}},{"name":"Plan of action","kind":"section_3","ident_start":18585,"ident_end":18599,"extent_start":18581,"extent_end":19458,"fully_qualified_name":"Plan of action","ident_utf16":{"start":{"line_number":324,"utf16_col":4},"end":{"line_number":324,"utf16_col":18}},"extent_utf16":{"start":{"line_number":324,"utf16_col":0},"end":{"line_number":334,"utf16_col":0}}},{"name":"Server-side plan of action","kind":"section_2","ident_start":19461,"ident_end":19487,"extent_start":19458,"extent_end":35808,"fully_qualified_name":"Server-side plan of action","ident_utf16":{"start":{"line_number":334,"utf16_col":3},"end":{"line_number":334,"utf16_col":29}},"extent_utf16":{"start":{"line_number":334,"utf16_col":0},"end":{"line_number":693,"utf16_col":0}}},{"name":"De-couple from hapi.js server and request objects","kind":"section_3","ident_start":20554,"ident_end":20603,"extent_start":20550,"extent_end":25030,"fully_qualified_name":"De-couple from hapi.js server and request objects","ident_utf16":{"start":{"line_number":347,"utf16_col":4},"end":{"line_number":347,"utf16_col":53}},"extent_utf16":{"start":{"line_number":347,"utf16_col":0},"end":{"line_number":447,"utf16_col":0}}},{"name":"Introduce new plugin definition shim","kind":"section_3","ident_start":25034,"ident_end":25070,"extent_start":25030,"extent_end":30979,"fully_qualified_name":"Introduce new plugin definition shim","ident_utf16":{"start":{"line_number":447,"utf16_col":4},"end":{"line_number":447,"utf16_col":40}},"extent_utf16":{"start":{"line_number":447,"utf16_col":0},"end":{"line_number":597,"utf16_col":0}}},{"name":"Switch to new platform services","kind":"section_3","ident_start":30983,"ident_end":31014,"extent_start":30979,"extent_end":34634,"fully_qualified_name":"Switch to new platform services","ident_utf16":{"start":{"line_number":597,"utf16_col":4},"end":{"line_number":597,"utf16_col":35}},"extent_utf16":{"start":{"line_number":597,"utf16_col":0},"end":{"line_number":677,"utf16_col":0}}},{"name":"Migrate to the new plugin system","kind":"section_3","ident_start":34638,"ident_end":34670,"extent_start":34634,"extent_end":35808,"fully_qualified_name":"Migrate to the new plugin system","ident_utf16":{"start":{"line_number":677,"utf16_col":4},"end":{"line_number":677,"utf16_col":36}},"extent_utf16":{"start":{"line_number":677,"utf16_col":0},"end":{"line_number":693,"utf16_col":0}}},{"name":"Browser-side plan of action","kind":"section_2","ident_start":35811,"ident_end":35838,"extent_start":35808,"extent_end":52467,"fully_qualified_name":"Browser-side plan of action","ident_utf16":{"start":{"line_number":693,"utf16_col":3},"end":{"line_number":693,"utf16_col":30}},"extent_utf16":{"start":{"line_number":693,"utf16_col":0},"end":{"line_number":936,"utf16_col":0}}},{"name":"1. Create a plugin definition file","kind":"section_4","ident_start":36970,"ident_end":37004,"extent_start":36965,"extent_end":38631,"fully_qualified_name":"1. Create a plugin definition file","ident_utf16":{"start":{"line_number":703,"utf16_col":5},"end":{"line_number":703,"utf16_col":39}},"extent_utf16":{"start":{"line_number":703,"utf16_col":0},"end":{"line_number":753,"utf16_col":0}}},{"name":"2. Export all static code and types from `public/index.ts`","kind":"section_4","ident_start":38636,"ident_end":38694,"extent_start":38631,"extent_end":40344,"fully_qualified_name":"2. Export all static code and types from `public/index.ts`","ident_utf16":{"start":{"line_number":753,"utf16_col":5},"end":{"line_number":753,"utf16_col":63}},"extent_utf16":{"start":{"line_number":753,"utf16_col":0},"end":{"line_number":805,"utf16_col":0}}},{"name":"3. Export your runtime contract","kind":"section_4","ident_start":40349,"ident_end":40380,"extent_start":40344,"extent_end":42556,"fully_qualified_name":"3. Export your runtime contract","ident_utf16":{"start":{"line_number":805,"utf16_col":5},"end":{"line_number":805,"utf16_col":36}},"extent_utf16":{"start":{"line_number":805,"utf16_col":0},"end":{"line_number":839,"utf16_col":0}}},{"name":"4. Move \"owned\" UI modules into your plugin and expose them from your public contract","kind":"section_4","ident_start":42561,"ident_end":42646,"extent_start":42556,"extent_end":45522,"fully_qualified_name":"4. Move \"owned\" UI modules into your plugin and expose them from your public contract","ident_utf16":{"start":{"line_number":839,"utf16_col":5},"end":{"line_number":839,"utf16_col":90}},"extent_utf16":{"start":{"line_number":839,"utf16_col":0},"end":{"line_number":867,"utf16_col":0}}},{"name":"5. Provide plugin extension points decoupled from angular.js","kind":"section_4","ident_start":45527,"ident_end":45587,"extent_start":45522,"extent_end":47184,"fully_qualified_name":"5. Provide plugin extension points decoupled from angular.js","ident_utf16":{"start":{"line_number":867,"utf16_col":5},"end":{"line_number":867,"utf16_col":65}},"extent_utf16":{"start":{"line_number":867,"utf16_col":0},"end":{"line_number":879,"utf16_col":0}}},{"name":"6. Move all webpack alias imports into uiExport entry files","kind":"section_4","ident_start":47189,"ident_end":47248,"extent_start":47184,"extent_end":49682,"fully_qualified_name":"6. Move all webpack alias imports into uiExport entry files","ident_utf16":{"start":{"line_number":879,"utf16_col":5},"end":{"line_number":879,"utf16_col":64}},"extent_utf16":{"start":{"line_number":879,"utf16_col":0},"end":{"line_number":908,"utf16_col":0}}},{"name":"7. Switch to new platform services","kind":"section_4","ident_start":49687,"ident_end":49721,"extent_start":49682,"extent_end":50658,"fully_qualified_name":"7. Switch to new platform services","ident_utf16":{"start":{"line_number":908,"utf16_col":5},"end":{"line_number":908,"utf16_col":39}},"extent_utf16":{"start":{"line_number":908,"utf16_col":0},"end":{"line_number":916,"utf16_col":0}}},{"name":"8. Migrate to the new plugin system","kind":"section_4","ident_start":50663,"ident_end":50698,"extent_start":50658,"extent_end":51482,"fully_qualified_name":"8. Migrate to the new plugin system","ident_utf16":{"start":{"line_number":916,"utf16_col":5},"end":{"line_number":916,"utf16_col":40}},"extent_utf16":{"start":{"line_number":916,"utf16_col":0},"end":{"line_number":930,"utf16_col":0}}},{"name":"Bonus: Tips for complex migration scenarios","kind":"section_4","ident_start":51487,"ident_end":51530,"extent_start":51482,"extent_end":52467,"fully_qualified_name":"Bonus: Tips for complex migration scenarios","ident_utf16":{"start":{"line_number":930,"utf16_col":5},"end":{"line_number":930,"utf16_col":48}},"extent_utf16":{"start":{"line_number":930,"utf16_col":0},"end":{"line_number":936,"utf16_col":0}}},{"name":"Keep Kibana fast","kind":"section_2","ident_start":52470,"ident_end":52486,"extent_start":52467,"extent_end":56320,"fully_qualified_name":"Keep Kibana fast","ident_utf16":{"start":{"line_number":936,"utf16_col":3},"end":{"line_number":936,"utf16_col":19}},"extent_utf16":{"start":{"line_number":936,"utf16_col":0},"end":{"line_number":999,"utf16_col":0}}},{"name":"How to understand how big the bundle size of my plugin is?","kind":"section_4","ident_start":54574,"ident_end":54632,"extent_start":54569,"extent_end":56320,"fully_qualified_name":"How to understand how big the bundle size of my plugin is?","ident_utf16":{"start":{"line_number":972,"utf16_col":5},"end":{"line_number":972,"utf16_col":63}},"extent_utf16":{"start":{"line_number":972,"utf16_col":0},"end":{"line_number":999,"utf16_col":0}}},{"name":"Frequently asked questions","kind":"section_2","ident_start":56323,"ident_end":56349,"extent_start":56320,"extent_end":111593,"fully_qualified_name":"Frequently asked questions","ident_utf16":{"start":{"line_number":999,"utf16_col":3},"end":{"line_number":999,"utf16_col":29}},"extent_utf16":{"start":{"line_number":999,"utf16_col":0},"end":{"line_number":1347,"utf16_col":0}}},{"name":"Is migrating a plugin an all-or-nothing thing?","kind":"section_3","ident_start":56355,"ident_end":56401,"extent_start":56351,"extent_end":57025,"fully_qualified_name":"Is migrating a plugin an all-or-nothing thing?","ident_utf16":{"start":{"line_number":1001,"utf16_col":4},"end":{"line_number":1001,"utf16_col":50}},"extent_utf16":{"start":{"line_number":1001,"utf16_col":0},"end":{"line_number":1009,"utf16_col":0}}},{"name":"Do plugins need to be converted to TypeScript?","kind":"section_3","ident_start":57029,"ident_end":57075,"extent_start":57025,"extent_end":57541,"fully_qualified_name":"Do plugins need to be converted to TypeScript?","ident_utf16":{"start":{"line_number":1009,"utf16_col":4},"end":{"line_number":1009,"utf16_col":50}},"extent_utf16":{"start":{"line_number":1009,"utf16_col":0},"end":{"line_number":1015,"utf16_col":0}}},{"name":"Can static code be shared between plugins?","kind":"section_3","ident_start":57545,"ident_end":57587,"extent_start":57541,"extent_end":67638,"fully_qualified_name":"Can static code be shared between plugins?","ident_utf16":{"start":{"line_number":1015,"utf16_col":4},"end":{"line_number":1015,"utf16_col":46}},"extent_utf16":{"start":{"line_number":1015,"utf16_col":0},"end":{"line_number":1141,"utf16_col":0}}},{"name":"Background","kind":"section_4","ident_start":57728,"ident_end":57738,"extent_start":57723,"extent_end":60279,"fully_qualified_name":"Background","ident_utf16":{"start":{"line_number":1019,"utf16_col":5},"end":{"line_number":1019,"utf16_col":15}},"extent_utf16":{"start":{"line_number":1019,"utf16_col":0},"end":{"line_number":1038,"utf16_col":0}}},{"name":"What goes wrong if I do share modules with state?","kind":"section_4","ident_start":60284,"ident_end":60333,"extent_start":60279,"extent_end":62070,"fully_qualified_name":"What goes wrong if I do share modules with state?","ident_utf16":{"start":{"line_number":1038,"utf16_col":5},"end":{"line_number":1038,"utf16_col":54}},"extent_utf16":{"start":{"line_number":1038,"utf16_col":0},"end":{"line_number":1048,"utf16_col":0}}},{"name":"How to decide what code can be statically imported","kind":"section_4","ident_start":62075,"ident_end":62125,"extent_start":62070,"extent_end":66563,"fully_qualified_name":"How to decide what code can be statically imported","ident_utf16":{"start":{"line_number":1048,"utf16_col":5},"end":{"line_number":1048,"utf16_col":55}},"extent_utf16":{"start":{"line_number":1048,"utf16_col":0},"end":{"line_number":1120,"utf16_col":0}}},{"name":"Concrete Example","kind":"section_4","ident_start":66568,"ident_end":66584,"extent_start":66563,"extent_end":67638,"fully_qualified_name":"Concrete Example","ident_utf16":{"start":{"line_number":1120,"utf16_col":5},"end":{"line_number":1120,"utf16_col":21}},"extent_utf16":{"start":{"line_number":1120,"utf16_col":0},"end":{"line_number":1141,"utf16_col":0}}},{"name":"How can I avoid passing Core services deeply within my UI component tree?","kind":"section_3","ident_start":67642,"ident_end":67715,"extent_start":67638,"extent_end":69898,"fully_qualified_name":"How can I avoid passing Core services deeply within my UI component tree?","ident_utf16":{"start":{"line_number":1141,"utf16_col":4},"end":{"line_number":1141,"utf16_col":77}},"extent_utf16":{"start":{"line_number":1141,"utf16_col":0},"end":{"line_number":1156,"utf16_col":0}}},{"name":"How is \"common\" code shared on both the client and server?","kind":"section_3","ident_start":69902,"ident_end":69960,"extent_start":69898,"extent_end":71465,"fully_qualified_name":"How is \"common\" code shared on both the client and server?","ident_utf16":{"start":{"line_number":1156,"utf16_col":4},"end":{"line_number":1156,"utf16_col":62}},"extent_utf16":{"start":{"line_number":1156,"utf16_col":0},"end":{"line_number":1168,"utf16_col":0}}},{"name":"When does code go into a plugin, core, or packages?","kind":"section_3","ident_start":71469,"ident_end":71520,"extent_start":71465,"extent_end":73211,"fully_qualified_name":"When does code go into a plugin, core, or packages?","ident_utf16":{"start":{"line_number":1168,"utf16_col":4},"end":{"line_number":1168,"utf16_col":55}},"extent_utf16":{"start":{"line_number":1168,"utf16_col":0},"end":{"line_number":1180,"utf16_col":0}}},{"name":"How do I build my shim for New Platform services?","kind":"section_3","ident_start":73215,"ident_end":73264,"extent_start":73211,"extent_end":111593,"fully_qualified_name":"How do I build my shim for New Platform services?","ident_utf16":{"start":{"line_number":1180,"utf16_col":4},"end":{"line_number":1180,"utf16_col":53}},"extent_utf16":{"start":{"line_number":1180,"utf16_col":0},"end":{"line_number":1347,"utf16_col":0}}},{"name":"Client-side","kind":"section_4","ident_start":73512,"ident_end":73523,"extent_start":73507,"extent_end":87014,"fully_qualified_name":"Client-side","ident_utf16":{"start":{"line_number":1184,"utf16_col":5},"end":{"line_number":1184,"utf16_col":16}},"extent_utf16":{"start":{"line_number":1184,"utf16_col":0},"end":{"line_number":1252,"utf16_col":0}}},{"name":"Core services","kind":"section_5","ident_start":73595,"ident_end":73608,"extent_start":73589,"extent_end":81005,"fully_qualified_name":"Core services","ident_utf16":{"start":{"line_number":1188,"utf16_col":6},"end":{"line_number":1188,"utf16_col":19}},"extent_utf16":{"start":{"line_number":1188,"utf16_col":0},"end":{"line_number":1217,"utf16_col":0}}},{"name":"Plugins for shared application services","kind":"section_5","ident_start":81011,"ident_end":81050,"extent_start":81005,"extent_end":87014,"fully_qualified_name":"Plugins for shared application services","ident_utf16":{"start":{"line_number":1217,"utf16_col":6},"end":{"line_number":1217,"utf16_col":45}},"extent_utf16":{"start":{"line_number":1217,"utf16_col":0},"end":{"line_number":1252,"utf16_col":0}}},{"name":"Server-side","kind":"section_4","ident_start":87019,"ident_end":87030,"extent_start":87014,"extent_end":97980,"fully_qualified_name":"Server-side","ident_utf16":{"start":{"line_number":1252,"utf16_col":5},"end":{"line_number":1252,"utf16_col":16}},"extent_utf16":{"start":{"line_number":1252,"utf16_col":0},"end":{"line_number":1288,"utf16_col":0}}},{"name":"Core services","kind":"section_5","ident_start":87038,"ident_end":87051,"extent_start":87032,"extent_end":97253,"fully_qualified_name":"Core services","ident_utf16":{"start":{"line_number":1254,"utf16_col":6},"end":{"line_number":1254,"utf16_col":19}},"extent_utf16":{"start":{"line_number":1254,"utf16_col":0},"end":{"line_number":1282,"utf16_col":0}}},{"name":"Plugin services","kind":"section_5","ident_start":97259,"ident_end":97274,"extent_start":97253,"extent_end":97980,"fully_qualified_name":"Plugin services","ident_utf16":{"start":{"line_number":1282,"utf16_col":6},"end":{"line_number":1282,"utf16_col":21}},"extent_utf16":{"start":{"line_number":1282,"utf16_col":0},"end":{"line_number":1288,"utf16_col":0}}},{"name":"UI Exports","kind":"section_4","ident_start":97985,"ident_end":97995,"extent_start":97980,"extent_end":109847,"fully_qualified_name":"UI Exports","ident_utf16":{"start":{"line_number":1288,"utf16_col":5},"end":{"line_number":1288,"utf16_col":15}},"extent_utf16":{"start":{"line_number":1288,"utf16_col":0},"end":{"line_number":1333,"utf16_col":0}}},{"name":"Plugin Spec","kind":"section_4","ident_start":109852,"ident_end":109863,"extent_start":109847,"extent_end":111593,"fully_qualified_name":"Plugin Spec","ident_utf16":{"start":{"line_number":1333,"utf16_col":5},"end":{"line_number":1333,"utf16_col":16}},"extent_utf16":{"start":{"line_number":1333,"utf16_col":0},"end":{"line_number":1347,"utf16_col":0}}},{"name":"How to","kind":"section_2","ident_start":111596,"ident_end":111602,"extent_start":111593,"extent_end":126245,"fully_qualified_name":"How to","ident_utf16":{"start":{"line_number":1347,"utf16_col":3},"end":{"line_number":1347,"utf16_col":9}},"extent_utf16":{"start":{"line_number":1347,"utf16_col":0},"end":{"line_number":1712,"utf16_col":0}}},{"name":"Configure plugin","kind":"section_3","ident_start":111608,"ident_end":111624,"extent_start":111604,"extent_end":116486,"fully_qualified_name":"Configure plugin","ident_utf16":{"start":{"line_number":1349,"utf16_col":4},"end":{"line_number":1349,"utf16_col":20}},"extent_utf16":{"start":{"line_number":1349,"utf16_col":0},"end":{"line_number":1481,"utf16_col":0}}},{"name":"Handle plugin configuration deprecations","kind":"section_4","ident_start":114772,"ident_end":114812,"extent_start":114767,"extent_end":116486,"fully_qualified_name":"Handle plugin configuration deprecations","ident_utf16":{"start":{"line_number":1435,"utf16_col":5},"end":{"line_number":1435,"utf16_col":45}},"extent_utf16":{"start":{"line_number":1435,"utf16_col":0},"end":{"line_number":1481,"utf16_col":0}}},{"name":"Use scoped services","kind":"section_3","ident_start":116490,"ident_end":116509,"extent_start":116486,"extent_end":119854,"fully_qualified_name":"Use scoped services","ident_utf16":{"start":{"line_number":1481,"utf16_col":4},"end":{"line_number":1481,"utf16_col":23}},"extent_utf16":{"start":{"line_number":1481,"utf16_col":0},"end":{"line_number":1544,"utf16_col":0}}},{"name":"Declare a custom scoped service","kind":"section_4","ident_start":118778,"ident_end":118809,"extent_start":118773,"extent_end":119854,"fully_qualified_name":"Declare a custom scoped service","ident_utf16":{"start":{"line_number":1509,"utf16_col":5},"end":{"line_number":1509,"utf16_col":36}},"extent_utf16":{"start":{"line_number":1509,"utf16_col":0},"end":{"line_number":1544,"utf16_col":0}}},{"name":"Mock new platform services in tests","kind":"section_3","ident_start":119858,"ident_end":119893,"extent_start":119854,"extent_end":123465,"fully_qualified_name":"Mock new platform services in tests","ident_utf16":{"start":{"line_number":1544,"utf16_col":4},"end":{"line_number":1544,"utf16_col":39}},"extent_utf16":{"start":{"line_number":1544,"utf16_col":0},"end":{"line_number":1623,"utf16_col":0}}},{"name":"Writing mocks for your plugin","kind":"section_4","ident_start":119900,"ident_end":119929,"extent_start":119895,"extent_end":121550,"fully_qualified_name":"Writing mocks for your plugin","ident_utf16":{"start":{"line_number":1546,"utf16_col":5},"end":{"line_number":1546,"utf16_col":34}},"extent_utf16":{"start":{"line_number":1546,"utf16_col":0},"end":{"line_number":1595,"utf16_col":0}}},{"name":"Using mocks in your tests","kind":"section_4","ident_start":121555,"ident_end":121580,"extent_start":121550,"extent_end":122649,"fully_qualified_name":"Using mocks in your tests","ident_utf16":{"start":{"line_number":1595,"utf16_col":5},"end":{"line_number":1595,"utf16_col":30}},"extent_utf16":{"start":{"line_number":1595,"utf16_col":0},"end":{"line_number":1615,"utf16_col":0}}},{"name":"What about karma tests?","kind":"section_4","ident_start":122654,"ident_end":122677,"extent_start":122649,"extent_end":123465,"fully_qualified_name":"What about karma tests?","ident_utf16":{"start":{"line_number":1615,"utf16_col":5},"end":{"line_number":1615,"utf16_col":28}},"extent_utf16":{"start":{"line_number":1615,"utf16_col":0},"end":{"line_number":1623,"utf16_col":0}}},{"name":"Provide Legacy Platform API to the New platform plugin","kind":"section_3","ident_start":123469,"ident_end":123523,"extent_start":123465,"extent_end":125357,"fully_qualified_name":"Provide Legacy Platform API to the New platform plugin","ident_utf16":{"start":{"line_number":1623,"utf16_col":4},"end":{"line_number":1623,"utf16_col":58}},"extent_utf16":{"start":{"line_number":1623,"utf16_col":0},"end":{"line_number":1682,"utf16_col":0}}},{"name":"On the server side","kind":"section_4","ident_start":123530,"ident_end":123548,"extent_start":123525,"extent_end":124867,"fully_qualified_name":"On the server side","ident_utf16":{"start":{"line_number":1625,"utf16_col":5},"end":{"line_number":1625,"utf16_col":23}},"extent_utf16":{"start":{"line_number":1625,"utf16_col":0},"end":{"line_number":1676,"utf16_col":0}}},{"name":"On the client side","kind":"section_4","ident_start":124872,"ident_end":124890,"extent_start":124867,"extent_end":125357,"fully_qualified_name":"On the client side","ident_utf16":{"start":{"line_number":1676,"utf16_col":5},"end":{"line_number":1676,"utf16_col":23}},"extent_utf16":{"start":{"line_number":1676,"utf16_col":0},"end":{"line_number":1682,"utf16_col":0}}},{"name":"Updates an application navlink at runtime","kind":"section_3","ident_start":125361,"ident_end":125402,"extent_start":125357,"extent_end":126157,"fully_qualified_name":"Updates an application navlink at runtime","ident_utf16":{"start":{"line_number":1682,"utf16_col":4},"end":{"line_number":1682,"utf16_col":45}},"extent_utf16":{"start":{"line_number":1682,"utf16_col":0},"end":{"line_number":1710,"utf16_col":0}}},{"name":"Logging config migration","kind":"section_3","ident_start":126161,"ident_end":126185,"extent_start":126157,"extent_end":126245,"fully_qualified_name":"Logging config migration","ident_utf16":{"start":{"line_number":1710,"utf16_col":4},"end":{"line_number":1710,"utf16_col":28}},"extent_utf16":{"start":{"line_number":1710,"utf16_col":0},"end":{"line_number":1712,"utf16_col":0}}}]}},"copilotInfo":null,"copilotAccessAllowed":false,"csrf_tokens":{"/elastic/kibana/branches":{"post":"mA7SiLar-bPLnVuOwPTAFVZIBg5WZhStUQtfZ0VMB6XB1F8bc4X-AuUiNPT6OU8oylRzieqJRpTZYENtFN1MYQ"},"/repos/preferences":{"post":"rbbb6BEtMUWeRGXhsZbFh3n2buTozkuDeQaGymGWvkEXwBK-6itNZhdAWyx4NpCZP_ZUCzAlfXHfDwaSX58xUA"}}},"title":"kibana/src/core/MIGRATION.md at 7.9 · elastic/kibana"}