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 |