[[kibana-platform-api]] == {kib} Core API experimental[] {kib} Core provides a set of low-level API's required to run all {kib} plugins. These API's are injected into your plugin's lifecycle methods and may be invoked during that lifecycle only: [source,typescript] ---- import type { PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; export class MyPlugin { constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup) { // called when plugin is setting up during Kibana's startup sequence } public start(core: CoreStart) { // called after all plugins are set up } public stop() { // called when plugin is torn down during Kibana's shutdown sequence } } ---- === Server-side [[configuration-service]] ==== Configuration service {kib} provides `ConfigService` if a plugin developer may want to support adjustable runtime behavior for their plugins. Plugins can only read their own configuration values, it is not possible to access the configuration values from {kib} Core or other plugins directly. [source,js] ---- // in Legacy platform const basePath = config.get('server.basePath'); // in Kibana Platform 'basePath' belongs to the http service const basePath = core.http.basePath.get(request); ---- To have access to your plugin config, you _should_: * Declare plugin-specific `configPath` (will fallback to plugin `id` if not specified) in {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`kibana.json`] manifest file. * Export schema validation for the config from plugin's main file. Schema is mandatory. If a plugin reads from the config without schema declaration, `ConfigService` will throw an error. *my_plugin/server/index.ts* [source,typescript] ---- import { schema, TypeOf } from '@kbn/config-schema'; export const plugin = … export const config = { schema: schema.object(…), }; export type MyPluginConfigType = TypeOf; ---- * Read config value exposed via `PluginInitializerContext`. *my_plugin/server/index.ts* [source,typescript] ---- import type { PluginInitializerContext } from 'kibana/server'; export class MyPlugin { constructor(initializerContext: PluginInitializerContext) { this.config$ = initializerContext.config.create(); // or if config is optional: this.config$ = initializerContext.config.createIfExists(); } ---- If your plugin also has a client-side part, you can also expose configuration properties to it using the configuration `exposeToBrowser` allow-list property. *my_plugin/server/index.ts* [source,typescript] ---- import { schema, TypeOf } from '@kbn/config-schema'; import type { PluginConfigDescriptor } from 'kibana/server'; const configSchema = schema.object({ secret: schema.string({ defaultValue: 'Only on server' }), uiProp: schema.string({ defaultValue: 'Accessible from client' }), }); type ConfigType = TypeOf; export const config: PluginConfigDescriptor = { exposeToBrowser: { uiProp: true, }, schema: configSchema, }; ---- Configuration containing only the exposed properties will be then available on the client-side using the plugin's `initializerContext`: *my_plugin/public/index.ts* [source,typescript] ---- interface ClientConfigType { uiProp: string; } export class MyPlugin implements Plugin { constructor(private readonly initializerContext: PluginInitializerContext) {} public async setup(core: CoreSetup, deps: {}) { const config = this.initializerContext.config.get(); } ---- All plugins are considered enabled by default. If you want to disable your plugin, you could declare the `enabled` flag in the plugin config. This is a special {kib} Platform key. {kib} reads its value and won’t create a plugin instance if `enabled: false`. [source,js] ---- export const config = { schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), }; ---- [[handle-plugin-configuration-deprecations]] ===== Handle plugin configuration deprecations If your plugin has deprecated configuration keys, you can describe them using the `deprecations` config descriptor field. Deprecations are managed on a per-plugin basis, meaning you don’t need to specify the whole property path, but use the relative path from your plugin’s configuration root. *my_plugin/server/index.ts* [source,typescript] ---- import { schema, TypeOf } from '@kbn/config-schema'; import type { PluginConfigDescriptor } from 'kibana/server'; const configSchema = schema.object({ newProperty: schema.string({ defaultValue: 'Some string' }), }); type ConfigType = TypeOf; export const config: PluginConfigDescriptor = { schema: configSchema, deprecations: ({ rename, unused }) => [ rename('oldProperty', 'newProperty'), unused('someUnusedProperty'), ], }; ---- In some cases, accessing the whole configuration for deprecations is necessary. For these edge cases, `renameFromRoot` and `unusedFromRoot` are also accessible when declaring deprecations. *my_plugin/server/index.ts* [source,typescript] ---- export const config: PluginConfigDescriptor = { schema: configSchema, deprecations: ({ renameFromRoot, unusedFromRoot }) => [ renameFromRoot('oldplugin.property', 'myplugin.property'), unusedFromRoot('oldplugin.deprecated'), ], }; ---- ==== Logging service Allows a plugin to provide status and diagnostic information. For detailed instructions see the {kib-repo}blob/{branch}/src/core/server/logging/README.md[logging service documentation]. [source,typescript] ---- import type { PluginInitializerContext, CoreSetup, Plugin, Logger } from 'kibana/server'; export class MyPlugin implements Plugin { private readonly logger: Logger; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); } public setup(core: CoreSetup) { try { this.logger.debug('doing something...'); // … } catch (e) { this.logger.error('failed doing something...'); } } } ---- ==== Elasticsearch service `Elasticsearch service` provides `elasticsearch.client` program API to communicate with Elasticsearch server REST API. `elasticsearch.client` interacts with Elasticsearch service on behalf of: - `kibana_system` user via `elasticsearch.client.asInternalUser.*` methods. - a current end-user via `elasticsearch.client.asCurrentUser.*` methods. In this case Elasticsearch client should be given the current user credentials. See <> and <>. {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md[Elasticsearch service API docs] [source,typescript] ---- import { CoreStart, Plugin } from 'kibana/public'; export class MyPlugin implements Plugin { public start(core: CoreStart) { async function asyncTask() { const result = await core.elasticsearch.client.asInternalUser.ping(…); } asyncTask(); } } ---- For advanced use-cases, such as a search, use {kib-repo}blob/{branch}/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md[Data plugin] include::saved-objects-service.asciidoc[leveloffset=+1] ==== HTTP service Allows plugins: * to extend the {kib} server with custom REST API. * to execute custom logic on an incoming request or server response. * implement custom authentication and authorization strategy. See {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md[HTTP service API docs] [source,typescript] ---- import { schema } from '@kbn/config-schema'; import type { CoreSetup, Plugin } from 'kibana/server'; export class MyPlugin implements Plugin { public setup(core: CoreSetup) { const router = core.http.createRouter(); const validate = { params: schema.object({ id: schema.string(), }), }; router.get({ path: 'my_plugin/{id}', validate }, async (context, request, response) => { const data = await findObject(request.params.id); if (!data) return response.notFound(); return response.ok({ body: data, headers: { 'content-type': 'application/json' } }); }); } } ---- ==== UI settings service The program interface to <>. It makes it possible for Kibana plugins to extend Kibana UI Settings Management with custom settings. See: - {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.register.md[UI settings service Setup API docs] - {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicestart.register.md[UI settings service Start API docs] [source,typescript] ---- import { schema } from '@kbn/config-schema'; import type { CoreSetup,Plugin } from 'kibana/server'; export class MyPlugin implements Plugin { public setup(core: CoreSetup) { core.uiSettings.register({ custom: { value: '42', schema: schema.string(), }, }); const router = core.http.createRouter(); router.get({ path: 'my_plugin/{id}', validate: …, }, async (context, request, response) => { const customSetting = await context.uiSettings.client.get('custom'); … }); } } ---- === Client-side ==== Application service Kibana has migrated to be a Single Page Application. Plugins should use `Application service` API to instruct Kibana what an application should be loaded & rendered in the UI in response to user interactions. [source,typescript] ---- import { AppMountParameters, CoreSetup, Plugin, DEFAULT_APP_CATEGORIES } from 'kibana/public'; export class MyPlugin implements Plugin { public setup(core: CoreSetup) { core.application.register({ // <1> category: DEFAULT_APP_CATEGORIES.kibana, id: 'my-plugin', title: 'my plugin title', euiIconType: '/path/to/some.svg', order: 100, appRoute: '/app/my_plugin', // <2> async mount(params: AppMountParameters) { // <3> // Load application bundle const { renderApp } = await import('./application'); // Get start services const [coreStart, depsStart] = await core.getStartServices(); // <4> // Render the application return renderApp(coreStart, depsStart, params); // <5> }, }); } } ---- <1> See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[application.register interface] <2> Application specific URL. <3> `mount` callback is invoked when a user navigates to the application-specific URL. <4> `core.getStartServices` method provides API available during `start` lifecycle. <5> `mount` method must return a function that will be called to unmount the application. NOTE:: you are free to use any UI library to render a plugin application in DOM. However, we recommend using React and https://elastic.github.io/eui[EUI] for all your basic UI components to create a consistent UI experience. ==== HTTP service Provides API to communicate with the {kib} server. Feel free to use another HTTP client library to request 3rd party services. [source,typescript] ---- import { CoreStart } from 'kibana/public'; interface ResponseType {…}; async function fetchData(core: CoreStart) { return await core.http.get<>( '/api/my_plugin/', { query: … }, ); } ---- See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.httpsetup.md[for all available API]. ==== Elasticsearch service Not available in the browser. Use {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md[Data plugin] instead. == Patterns [[scoped-services]] === Scoped services 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. In the legacy platform, Kibana requires binding elasticsearch related API with an incoming request to access elasticsearch service on behalf of a user. [source,js] ---- async function handler(req, res) { const dataCluster = server.plugins.elasticsearch.getCluster('data'); const data = await dataCluster.callWithRequest(req, 'ping'); } ---- The Kibana Platform introduced a handler interface on the server-side to perform that association internally. Core services, that require impersonation with an incoming request, are exposed via `context` argument of {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandler.md[the request handler interface.] The above example looks in the Kibana Platform as [source,js] ---- async function handler(context, req, res) { const data = await context.core.elasticsearch.client.asCurrentUser('ping'); } ---- The {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md[request handler context] exposed the next scoped *core* services: [width="100%",cols="30%,70%",options="header",] |=== |Legacy Platform |Kibana Platform |`request.getSavedObjectsClient` |{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md[`context.savedObjects.client`] |`server.plugins.elasticsearch.getCluster('admin')` |{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`] |`server.plugins.elasticsearch.getCluster('data')` |{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`] |`request.getUiSettingsService` |{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md[`context.uiSettings.client`] |=== ==== Declare a custom scoped service Plugins can extend the handler context with a custom API that will be available to the plugin itself and all dependent plugins. For example, the plugin creates a custom elasticsearch client and wants to use it via the request handler context: [source,typescript] ---- import type { CoreSetup, IScopedClusterClient } from 'kibana/server'; export interface MyPluginContext { client: IScopedClusterClient; } // extend RequestHandlerContext when a dependent plugin imports MyPluginContext from the file declare module 'kibana/server' { interface RequestHandlerContext { myPlugin?: MyPluginContext; } } class MyPlugin { setup(core: CoreSetup) { const client = core.elasticsearch.createClient('myClient'); core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => { return { client: client.asScoped(req) }; }); const router = core.http.createRouter(); router.get( { path: '/api/my-plugin/', validate: … }, async (context, req, res) => { const data = await context.myPlugin.client.asCurrentUser('endpoint'); } ); } ----