Skip to main content

workflows-custom-steps

Register and implement custom workflow steps from an external Kibana plugin using `@kbn/workflows-extensions`. Use when adding or modifying a step type with `registerStepDefinition`, designing input/output/config Zod schemas, implementing `createServerStepDefinition` / `createPublicStepDefinition`, choosing `StepCategory`, building `editorHandlers` (selection / dynamicSchema), wiring `callKibanaApi` / `onCancel`, deciding sync vs async loader registration, managing per-step approval files under `approved_step_definitions/`, or reviewing PRs that touch any of these.

소스 정보

저장소
elastic/kibana
최근 소스 활동
2026년 7월 2일 07:55
감지된 SKILL.md 언어
영어
스타
21,236
포크
8,623

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
2 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
workflows-custom-steps
description
Register and implement custom workflow steps from an external Kibana plugin using `@kbn/workflows-extensions`. Use when adding or modifying a step type with `registerStepDefinition`, designing input/output/config Zod schemas, implementing `createServerStepDefinition` / `createPublicStepDefinition`, choosing `StepCategory`, building `editorHandlers` (selection / dynamicSchema), wiring `callKibanaApi` / `onCancel`, deciding sync vs async loader registration, managing per-step approval files under `approved_step_definitions/`, or reviewing PRs that touch any of these.
# Workflows — Custom Step Registration > Custom steps run inside the Kibana workflow engine and become part of the YAML the user writes. A misconfigured step can break workflows on every restart, pull heavy modules into the main bundle, leak resources on cancellation, or silently fail the approval gate. The defaults are not always right — verify each field below explicitly. ## Canonical guide — read this for implementation steps Follow the end-to-end steps, code templates, naming conventions, and approval workflow in: **[dev_docs/STEPS.md](../../dev_docs/STEPS.md)** Sections to use while implementing: | Task | STEPS.md section | |---|---| | Internal vs external boundary | [Important: Internal vs External Steps](../../dev_docs/STEPS.md#important-internal-vs-external-steps) | | Define common step (`id`, schemas, `label`, `category`, `documentation`) | [Step 1](../../dev_docs/STEPS.md#step-1-define-common-step-definition) | | Server handler (`createServerStepDefinition`) | [Step 2](../../dev_docs/STEPS.md#step-2-implement-server-side-handler) | | Public definition (icon, editor handlers) | [Step 3](../../dev_docs/STEPS.md#step-3-implement-public-side-definition) | | Custom property selection / dynamic schema | [Custom Property Selection](../../dev_docs/STEPS.md#custom-property-selection) | | Plugin setup registration (sync vs async loader) | [Step 4](../../dev_docs/STEPS.md#step-4-register-in-plugin-setup) | | YAML naming conventions (type / config / input) | [Workflow YAML Naming Conventions](../../dev_docs/STEPS.md#workflow-yaml-naming-conventions) | | `config` vs `input` mental model | [Config vs Inputs: Mental Model](../../dev_docs/STEPS.md#config-vs-inputs-mental-model) | | Error handling and `ExecutionError` | [Error Handling](../../dev_docs/STEPS.md#error-handling) | | `callKibanaApi` usage | [Calling Kibana APIs](../../dev_docs/STEPS.md#calling-kibana-apis-callkibanaapi) | | Cancellation cleanup | [Cancellation Cleanup (`onCancel`)](../../dev_docs/STEPS.md#cancellation-cleanup-oncancel) | | Approval gate (per-step file workflow) | [Step Definition Approval Process](../../dev_docs/STEPS.md#step-definition-approval-process) | ## Overview A custom workflow step is owned and registered by a **plugin other than `workflows_extensions`**. The workflows-team plugin only hosts internal steps; everything external must live in the owning plugin. A step lives in three layers: - **Common** — `id`, `label`, `description`, `category`, `inputSchema`, `outputSchema`, optional `configSchema` / `documentation` / `stability` / `deprecation`. Imported by both server and public to keep them in sync. - **Server** — wraps the common definition with a `handler` (and optional `onCancel`) via `createServerStepDefinition`. - **Public** — wraps the common definition with an `icon` and optional `editorHandlers` via `createPublicStepDefinition`. Both sides register through the `workflowsExtensions` setup contract: `registerStepDefinition(definition | () => Promise<definition | undefined>)`. **Additional references:** - Extended code templates (full scaffold, selection handlers, dynamic schema): [reference.md](reference.md) - Worked example: `examples/workflows_extensions_example/` - Common base type: `src/platform/packages/shared/kbn-workflows/spec/step_definition_types.ts` (`BaseStepDefinition`, `StepCategory`, `StabilityLevel`, `StepDocumentation`) - Editor handler types: `src/platform/packages/shared/kbn-workflows/types/v1.ts` (`EditorHandlers`, `StepPropertyHandler`, `PropertySelectionHandler`, `ConnectorIdSelectionHandler`, `DynamicSchema`) - Reusable index selection handler: `src/platform/packages/shared/kbn-workflows-ui/src/lib/steps/editor_handlers/index_selection_handler.ts` - Server step types: `src/platform/plugins/shared/workflows_extensions/server/step_registry/types.ts` (`ServerStepDefinition`, `StepHandler`, `StepHandlerContext`, `ContextManager`, `CallKibanaApiParams`, `OnCancelHandler`) - Public step types: `src/platform/plugins/shared/workflows_extensions/public/step_registry/types.ts` (`PublicStepDefinition`) - `ExecutionError`: `src/platform/packages/shared/kbn-workflows/server/errors/execution_error.ts` - Approval fixtures (one file per step): `src/platform/plugins/shared/workflows_extensions/test/scout/api/fixtures/approved_step_definitions/` ## 0. Locate the owning plugin (do this first) **Before creating or editing any files**, ask the user for their **plugin id** (`plugin.id` from `kibana.jsonc`, camelCase — e.g. `cases`, `agentBuilder`, `workflowsExtensionsExample`). Do not guess or assume a plugin. If the user already named their plugin in the request, confirm it matches `plugin.id` before proceeding. ### Resolve the plugin root ```bash rg '"id": "<pluginId>"' --glob '**/kibana.jsonc' ``` The directory containing that `kibana.jsonc` is the **plugin root**. Read it to confirm `plugin.server` / `plugin.browser` and whether `workflowsExtensions` is already in `requiredPlugins`. ### Choose file locations inside the plugin Inspect the plugin root for existing step or workflow extension layout. **Follow conventions already used in that plugin** rather than inventing new paths. | If the plugin already has… | Add files there | |---|---| | `common/workflows/steps/` (e.g. `cases`) | `common/workflows/steps/<step_name>.ts` | | `common/step_types/` (e.g. example plugin) | `common/step_types/<step_name>.ts` | | No step files yet | Use `common/step_types/<step_name>.ts` and mirror under `server/step_types/` and `public/step_types/` | Also check for existing registration hooks: - `server/**/step_types/index.ts` or `server/plugin.ts` — server registration - `public/**/step_types/` or `public/plugin.ts` — public registration Wire new steps into those existing index/setup files when present; only create new index files when the plugin has no step layout yet. ### Derive the step namespace Step ids use `<namespace>.<action>` (kebab-case namespace, camelCase action). Prefer the namespace already used by that plugin's steps. If none exist, derive kebab-case from `plugin.id` (e.g. `agentBuilder` → `agent-builder`) and confirm with the user if ambiguous. Reserved internal prefixes (`ai`, `data`, `flowControl`, `external`, `elasticsearch`, `kibana`, `kibana.cases`) must not be used outside `workflows_extensions`. ## File layout Mirror the layout used by `examples/workflows_extensions_example/` so reviewers and the workflows team can find things: ``` your-plugin/ ├── common/step_types/<step_name>.ts # common definition (id + schemas + label + category) ├── server/step_types/<step_name>.ts # createServerStepDefinition + handler ├── server/step_types/index.ts # registerStepDefinitions(setup) ├── public/step_types/<step_name>.ts # createPublicStepDefinition + icon + editorHandlers └── public/step_types/index.ts # registerStepDefinitions(setup, deps) ``` Keep `id`, `inputSchema`, `outputSchema`, `configSchema` in the **common** file only. Re-importing them on both sides is how server/public stay locked together. ## Agent-specific rules (beyond STEPS.md) These are easy to miss during implementation or review — they are not always spelled out in the contributing doc: | Concern | Rule | |---|---| | `id` reserved prefixes | `ai`, `data`, `flowControl`, `external`, `elasticsearch`, `kibana`, `kibana.cases` are reserved for internal/categorized steps. The `elasticsearch.` prefix is also special-cased by the auto-generated step path. Use a fresh kebab-case namespace per plugin | | `category` field name | The live enum is `StepCategory` from `@kbn/workflows` (values: `Elasticsearch`, `External`, `Ai`, `Kibana`, `KibanaCases`, `Data`, `FlowControl`). Some older docs still say `actionsMenuCatalog` / `StepMenuCatalog` — those names are stale | | i18n template syntax | Strings in `documentation.details` / `documentation.examples[]` that contain `{{ ... }}` MUST be passed through i18n `values:` so the i18n linter does not interpret them as variables | | Reserved config keys | `if`, `foreach`, `on-failure`, `timeout` are reserved by the engine — never redeclare them in `configSchema` | | Real plugin clients beat `callKibanaApi` | When the target plugin exposes a request-scoped client (`alerting.getRulesClientWithRequest`, `cases.getCasesClientWithRequest`, etc.), pass `context.contextManager.getFakeRequest()` to it. `callKibanaApi` is the fallback when no client exists | | `callKibanaApi` hard limits | No multipart / `form_data`, no streaming/SSE, no custom TLS or fetcher options. Caller-supplied `Authorization`, `Content-Type`, `kbn-xsrf`, `x-elastic-internal-origin`, and event-chain headers are dropped (engine owns them). Non-2xx (except 304) throws `Error('HTTP <status>: <body>')`. For unsupported transports use the `kibana.request` YAML step | | `onCancel` semantics | Invoked **after** `abortSignal` fires AND `run()` resolves — never in parallel. Steps that complete normally skip it. MUST be idempotent; thrown errors are logged but never disrupt cancellation. An empty `onCancel` "just to be safe" is an anti-pattern | | `ExecutionError` type discipline | Pick **specific** `type` values (`ValidationError`, `PermissionError`, `NetworkError`) — never `'Error'`. Plain `throw new Error(...)` is auto-converted; only reach for `ExecutionError` when you need a custom `type` or structured `details` | | Public icon | **Must be a React component** via `React.lazy` from `@elastic/eui/es/components/icon/assets/*`. EUI icon name strings (`'star'`) are not supported — the build will not fail, the icon will simply be missing | | `connectorIdSelection` placement | Only recognised on **`config['connector-id']`** (exact key, under `config`). Renaming to `connectorId`, `connector_id`, or `my-connector-id`, or moving under `input`, silently disables the picker. Verified in `workflows_management/public/shared/lib/connectors_utils.ts` | | `getIndexSelectionHandler` wiring | Requires `dataViews` (`DataViewsContract`) and `application` (`ApplicationStart`) services — must be injected via a public-side factory. Attaches in the **`selection`** slot of any field whose value is an index pattern (not field-name restricted) | | `selection.dependsOnValues` | List every sibling field (`config.foo` / `input.x` dot path) your `search` / `resolve` / `getDetails` read from `context.values`. Missing entries cause stale cache hits when the user edits the sibling | | `selection.getDetails` | Avoid network calls when `option` is present — use `option.label` / `option.value` / `context.values`. Only fetch when `option === null`. The combined `resolve` + `getDetails` outcome is cached for ~30s per logical field | | Dynamic output schema | Express via `editorHandlers.dynamicSchema.getOutputSchema({ input, config })` for autocomplete; **server still validates against the static `outputSchema`** in the common definition, so keep that schema as the union of all possible shapes | | Public async loader | Prefer async import to keep zod + step module out of the plugin's main bundle. Loaders that reject (or throw inside the registry) are caught and logged; one broken loader does NOT prevent other steps from registering — verify the log when a step is silently missing | | Conditional registration | Loaders returning `undefined` are skipped silently (unlike triggers, which do not support this). Use for feature flags | | Registration timing | All `registerStepDefinition` calls happen in `setup()`, never `start()`. Engine and UI both `await workflowsExtensions.isReady()` before reading the registry | For the YAML naming conventions (the single most common mistake — only the step type action is camelCase, config/input keys we own never are), follow [STEPS.md → Workflow YAML Naming Conventions](../../dev_docs/STEPS.md#workflow-yaml-naming-conventions). ## Quick rule reference | Concern | Rule | Default if omitted | When wrong | |---|---|---|---| | `id` | `<kebab-namespace>.<camelAction>`; stable for the life of the workflow | n/a | Renames break user YAML; reserved prefixes collide with internals | | Common file | Holds `id`, schemas, `label`, `description`, `category`, `documentation` | n/a | Drift between server and public side | | `category` | Pick the `StepCategory` matching the actions-menu group users expect | n/a | Step hides in the wrong section | | `configSchema` vs `inputSchema` | Config controls behavior; input carries payload | n/a | Awkward YAML; bad selection UX | | Key casing (keys we own) | Config: kebab-case; input (`with:`): kebab-case or snake_case — never camelCase | n/a | camelCase args drift from the conventions; inherited OpenAPI/connector shapes excepted | | `createServerStepDefinition` | Use it (don't hand-annotate types) | n/a | Loss of input/output type inference, drift | | `abortSignal` | Pass to ES, HTTP, loops | n/a | Step runs past cancellation; blocks shutdown | | `onCancel` | Implement only when the step holds resources beyond the signal | none | Leaks; or empty stub gives false confidence | | `ExecutionError` | Use when you need a custom `type` or `details` | auto-conversion of raw errors | Useless `'Error'` type; lost debugging context | | `callKibanaApi` | Use only when no request-scoped client exists | n/a | Re-invents auth; brittle on transport change | | Icon | `React.lazy` from `@elastic/eui/es/components/icon/assets/*` | none | Missing icon in editor; bundles all EUI icons | | Connector picker | `connectorIdSelection` on `config['connector-id']` | none | Hand-rolled selection misses "create connector" link + type filtering | | Index picker | `getIndexSelectionHandler({ dataViews, application }, options)` in `selection` slot | none | Hand-rolled handler misses wildcard/alias/data-stream semantics | | `selection.dependsOnValues` | List every sibling field your handlers read | `{ config: {}, input: {} }` | Stale cache hits on sibling edits | | `selection.getDetails` | Avoid network calls when `option` is present | n/a | Slow hovers; redundant fetches | | Public registration | Async loader to keep modules out of main bundle | sync inline | Step module + zod inflate plugin bundle | | Conditional registration | Loader returns `undefined` to skip | always registers | Cannot feature-flag | | Approval gate | Create/update `approved_step_definitions/<step.id>.txt` with the new `definitionHash` (one file per step) | test fails | CI blocks merge until updated | ## Author checklist When adding a new step: 1. **Plugin location** - [ ] User's `plugin.id` confirmed; plugin root resolved from `kibana.jsonc` - [ ] File paths follow the plugin's existing step/workflow layout - [ ] `workflowsExtensions` is in `requiredPlugins` in `kibana.jsonc`
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기