src/platform/packages/shared/kbn-connector-specs/README.md
A TypeScript-based specification framework for defining Kibana connectors using a single-file approach. This package enables developers to define complete connector specifications that are automatically discovered and registered to the actions registry.
The @kbn/connector-specs package provides a simplified, declarative way to define connectors for Kibana's Stack Connectors system. Instead of manually registering connectors across multiple files, you define a connector spec in src/specs/<connector>/<connector>.ts, and it will be automatically picked up and registered once exported from src/all_specs.ts.
This package includes Agent Skills that automate the full connector development lifecycle.
The /build-connector skill orchestrates creating a connector spec, workflows, tests, documentation, activation in Kibana, and end-to-end testing via Agent Builder.
Agent Skills must be loaded. If you do not see them already available, it's usually sufficient to prompt:
> Load all **/SKILL.md skills
or:
> Look for connector and agent builder SKILL.md files, and load those skills
In your AI Assistant of choice, run:
/build-connector <service-name>
For example: /build-connector Figma
This will walk through 12 tasks: generating code, reviewing it, activating the connector in a running Kibana, creating a test agent, chatting with it, iterating on quality, and delivering a final result.
| Skill | Description |
|---|---|
/create-connector <name> | Generate just the connector spec, workflows, tests, and docs (no activation or testing) |
/activate-connector <type> | Create a connector instance in a running Kibana via the Actions API |
/review-connector | Review connector spec changes against a quality checklist |
Use the generator to create the folder structure and wire everything up:
node scripts/generate connector my_connector --id ".myConnector" --owner "@elastic/<team-name>"
This will:
src/specs/my_connector/my_connector.ts (spec scaffold)src/specs/my_connector/icon/index.tsx (placeholder icon component)src/all_specs.tssrc/connector_icons_map.ts.A-Z, a-z, 0-9, ., _, -This package still follows a 1.5 files approach for defining connectors:
src/specs/my_connector/) containing my_connector.tssrc/all_specs.ts:
export * from './specs/my_connector/my_connector';
That's it! Once exported from all_specs.ts, the connector spec is automatically discovered and registered to the actions registry during plugin initialization.
The registration happens automatically in both server and public plugin setup:
actions.registerSubActionConnectorType()The createConnectorTypeFromSpec() function converts the SingleFileConnectorDefinition into the format expected by the actions registry.
src/specs/<connector>/<connector>.ts:// src/specs/my_connector/my_connector.ts
import { z } from '@kbn/zod/v4';
import type { ConnectorSpec } from '../../connector_spec';
export const MyConnector: ConnectorSpec = {
metadata: {
id: '.my_connector',
displayName: 'My Connector',
description: 'A custom connector example',
minimumLicense: 'gold',
supportedFeatureIds: ['workflows'],
},
auth: {
types: ['bearer'],
headers: {
'Content-Type': 'application/json',
}
},
schema: z.object({
url: z.string().url().describe('API URL'),
}),
actions: {
execute: {
isTool: true,
input: z.object({
message: z.string().describe('Message to send'),
}),
handler: async (ctx, input) => {
const response = await ctx.client.post(`${ctx.config.url}/api/endpoint`, {
data: input,
});
return response.data;
},
},
},
test: {
handler: async (ctx) => {
await ctx.client.get(`${ctx.config.url}/health`);
return { ok: true, message: 'Connection successful' };
},
},
};
all_specs.ts:// src/all_specs.ts
export * from './specs/my_connector/my_connector';
connector_spec.tsThe main specification file that defines:
SingleFileConnectorDefinition - The main interface for connector definitionsconnector_spec_ui.tsUI metadata extension system that enables:
UISchemas.secret(), UISchemas.textarea(), etc.)specs/Directory containing actual connector implementations
specs/<connector>/icon/Directory containing per-connector icon components (and optional assets)
meta.sensitiveA SingleFileConnectorDefinition consists of:
metadata: {
id: '.connector_id', // Unique connector identifier
displayName: 'Connector Name', // User-facing name
description: 'Description', // Connector description
icon?: string, // EUI icon name (e.g., 'addDataApp', 'globe') - only for built-in EUI icons
docsUrl?: string, // Link to documentation
minimumLicense: 'basic' | 'gold' | 'platinum' | 'enterprise',
supportedFeatureIds: ['workflows', 'alerting', 'cases', ...],
}
Specify which standard auth schemas (if any) are supported by this connector. If none are specified, defaults to no authentication. Can also specify a custom schema for the authType which will be used in place of the default
auth: {
types: [
// use basic auth type with the default schema
'basic',
// use api_key_header auth type with a custom header field
{
type: 'api_key_header',
defaults: {
headerField: 'custom-api-key-field'
}
}
],
// optionally add headers that will be added to all requests
headers: {
'Content-Type': 'application/json',
}
}
A single Zod schema containing all connector config fields and any secrets fields outside of the standard auth schemas
schema: z.object({
// Config fields
url: z.string().url().describe('API URL'),
// Secret fields (marked with meta.sensitive)
apiKey: UISchemas.secret().describe('API Key'),
})
Actions define what the connector can do:
actions: {
actionName: {
isTool?: boolean, // Whether this action is a tool (for AI workflows)
input: z.ZodSchema, // Input validation schema
output?: z.ZodSchema, // Output validation schema (optional)
handler: async (ctx, input) => {
// Action implementation
return result;
},
description?: string,
actionGroup?: string,
supportsStreaming?: boolean,
},
}
Optional connection test:
test: {
handler: async (ctx) => {
// Test connection logic
return { ok: boolean, message?: string };
},
description?: string,
}
Configure connector behavior:
policies: {
rateLimit?: RateLimitPolicy,
pagination?: PaginationPolicy,
retry?: RetryPolicy,
error?: ErrorPolicy,
streaming?: StreamingPolicy,
}
import { HeaderAuthSchema } from '../connector_spec';
schema: z.discriminatedUnion('method', [
z.object({
method: z.literal('headers'),
headers: z.object({
'X-API-Key': UISchemas.secret().describe('API Key'),
}),
}),
])
import { BearerAuthSchema } from '../connector_spec';
schema: z.object({
url: z.string().url(),
...BearerAuthSchema.shape,
})
import { BasicAuthSchema } from '../connector_spec';
schema: z.object({
url: z.string().url(),
...BasicAuthSchema.shape,
})
Use the withUIMeta helper or UISchemas to add UI hints:
import { withUIMeta, UISchemas } from '../connector_spec_ui';
// Sensitive field (password/token)
apiKey: UISchemas.secret('sk-...').describe('API Key')
// Multi-line text
message: UISchemas.textarea({ rows: 5 }).describe('Message')
// JSON editor
config: UISchemas.json().describe('Configuration')
// With custom metadata
field: withUIMeta(z.string(), {
sensitive: true,
section: 'Authentication',
order: 1,
placeholder: 'Enter value...',
helpText: 'Additional help text',
}).describe('Field Label')
Load dropdown options from another action:
channel: z.string().meta({
optionsFrom: {
action: 'getChannels',
map: (result) => result.channels.map(c => ({
value: c.id,
label: c.name
})),
cacheDuration: 300,
},
}).describe('Channel')
Show/hide fields based on other field values:
customHost: z.string().meta({
when: {
field: 'serviceType',
is: 'custom',
then: 'show',
},
}).describe('Custom Host')
Enable streaming responses:
policies: {
streaming: {
enabled: true,
mechanism: 'sse', // or 'chunked', 'websocket'
parser: 'ndjson', // or 'json', 'text', 'custom'
},
}
Configure rate limit handling:
policies: {
rateLimit: {
strategy: 'header',
remainingHeader: 'X-RateLimit-Remaining',
resetHeader: 'X-RateLimit-Reset',
},
}
Icons can be provided in two ways: built-in EUI icons (for simple cases) or lazy-loaded React components (for custom icons).
For connectors that don't need custom branding, you can use any built-in EUI icon by setting the icon name in metadata:
metadata: {
id: '.my_connector',
displayName: 'My Connector',
icon: 'addDataApp', // Any EUI icon name (e.g., 'globe', 'link', 'cloud', etc.)
}
Pros: Simple, no additional files needed, no bundle size impact
Cons: Limited to EUI's built-in icon set
Note: The icon field in metadata should only be used for EUI icon names. For custom icons, use the lazy-loaded approach below.
For custom icons (logos, branded images, etc.), use lazy-loaded React components. This approach keeps custom icon assets out of the main bundle and loads them on-demand. To use lazy icons:
src/specs/{connector_folder}/icon/:Option A: Using EuiIcon with an image file
// src/specs/my_connector/icon/index.tsx
import React from 'react';
import { EuiIcon } from '@elastic/eui';
import type { ConnectorIconProps } from '../../../types';
import myIcon from './my_connector.png'; // or .jpg, .svg
export default (props: ConnectorIconProps) => {
return <EuiIcon type={myIcon} {...props} />;
};
Option B: Using a custom SVG component
// src/icons/my_connector.tsx
import React from 'react';
import type { ConnectorIconProps } from '../types';
export default (props: ConnectorIconProps) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="#00A6C1" {...props}>
<path d="M88.5,20.3c0-3.5-3.8-6.7-7.8-6.7H46V30h42.5L88.5,20.3z" />
</svg>
);
};
src/connector_icons_map.ts:// src/connector_icons_map.ts
import { lazy } from 'react';
import type { ConnectorIconProps } from './types';
export const ConnectorIconsMap: Map<
string,
React.LazyExoticComponent<React.ComponentType<ConnectorIconProps>>
> = new Map([
// ... existing icons ...
[
'.my_connector',
lazy(() => import(/* webpackChunkName: "connectorIconMyConnector" */ './specs/my_connector/icon')),
],
]);
// src/specs/my_connector.ts
export const MyConnector: SingleFileConnectorDefinition = {
metadata: {
id: '.my_connector', // Must match the key in ConnectorIconsMap
displayName: 'My Connector',
// icon is optional - will be resolved from ConnectorIconsMap
},
// ... rest of the definition
};
Pros: Better performance, code splitting, icons loaded on-demand, supports custom branding
Cons: Requires additional files and registration
The system resolves icons in the following order:
metadata.icon is set (EUI icon name), it's used directlymetadata.icon is not set, the system looks up the connector ID in ConnectorIconsMap'globe' iconThe package provides full TypeScript support:
import type {
ConnectorSpec,
ActionDefinition,
ConnectorMetadata,
} from '@kbn/connector-specs';
When adding a new connector:
src/specs/<connector>/ with <connector>.tssrc/all_specs.tsmetadata.icon to an EUI icon name (e.g., 'globe', 'addDataApp')src/specs/<connector>/icon/ and register it in src/connector_icons_map.tssrc/specs/{connector}.test.ts) to validate your action handlers. See existing connector tests for examples.docs/reference/connectors-kibana/{connector-name}-action-type.md. See existing connector docs for the expected structure. Don't forget to update the TOC and relevant _snippets/ files!When creating a new connector with a lazy icon:
src/specs/{connector_folder}/icon/index.tsxsrc/specs/{connector_folder}/icon/index.tsx and asset filessrc/connector_icons_map.ts:
[
'.your_connector_id',
lazy(() => import(/* webpackChunkName: "connectorIconYourConnector" */ './specs/your_connector_folder/icon')),
],
metadata.id in your spec matches the key in the map (including the leading dot)Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0