elastic / elastic/docs-content
[Security][TH/Workflows][9.6 & Serverless] Notes Workflow Steps docs
- Dominant language
- No language data
- Stars
- 47
- Forks
- 261
- Avg merge
- 3d 21h
- Merged PRs (30d)
- 141
Description
### Description
For 9.6 we are shipping a set of **named Workflow action steps for document notes** under the existing `security.*` namespace. Until now, analysts had no named steps for creating, reading, updating, or deleting notes on alerts, attacks, or other Elasticsearch documents — those operations were only reachable through the generic `kibana.request` step against `PATCH`/`GET`/`DELETE /api/note`, which requires API knowledge and offers no discoverability or schema validation in the Workflows editor.
These new steps expose explicit input schemas so parameters are validated at save time and are discoverable in the Workflows editor. They close the gap between Kibana's Notes UI ([Investigate notes](https://www.elastic.co/docs/solutions/security/investigate/notes)) and Workflows automation.
Four steps were delivered:
| Step | Operation | Availability |
| ---- | --------- | ------------ |
| `security.createNote` | Create a note on an alert, attack, or document | Technical Preview |
| `security.getNotes` | Retrieve all notes attached to an alert, attack, or document | Technical Preview |
| `security.updateNote` | Update the text of an existing note | Technical Preview |
| `security.deleteNote` | Delete one or more notes by note ID (bulk supported) | Technical Preview |
All four steps are **Technical Preview** in 9.6 (no feature flag — active by default). They live in the `StepCategory.KibanaSecurity` category and are automatically authenticated using the permissions/API key of the identity executing the workflow (same model as the existing `kibana.*`, `cases.*`, and `security.*` steps). They are implemented as custom steps on top of the public Notes API (`/api/note`) via the `callKibanaApi` platform utility.
> [!TIP]
> Notes attach to a document (alert, attack, or any Elasticsearch document) by its `_id`, so a **single step covers all object types**. There is no attack-specific notes API.
> [!NOTE]
> **Timeline notes are out of scope** for these steps. `security.createNote` always creates a document-attached note (`timelineId` is sent as empty). `security.updateNote` preserves an existing Timeline association if one is already present, but these steps are not a way to create or manage Timeline-only notes.
---
## Docs work
There are **four deliverables**, closely modeled on the existing Alert triage / Attack triage / Detection rules family pages that landed from [docs-content#7222](https://github.com/elastic/docs-content/issues/7222) / [docs-content#7449](https://github.com/elastic/docs-content/pull/7449).
The published [Security action steps](https://www.elastic.co/docs/explore-analyze/workflows/steps/security) overview currently lists only three families: **Alert triage**, **Attack triage**, and **Detection rules**. Add a fourth **Notes** family.
### 1. New page: Notes action steps
Create a new reference page under `explore-analyze/workflows/steps/`, modeled on the **Detection rules action steps** page (source: `explore-analyze/workflows/steps/detection-rules.md` — [published](https://www.elastic.co/docs/explore-analyze/workflows/steps/detection-rules)).
**Suggested path:** `explore-analyze/workflows/steps/notes.md`
**Suggested title / navigation_title:** `Notes` (page H1: _Notes action steps_)
Reuse the Detection rules / Alert triage page structure:
- Frontmatter (`navigation_title`, `applies_to`, `description`, `products`). All four steps are Technical Preview in 9.6 — use `applies_to` values consistent with other preview workflow steps (`stack: preview 9.6+`, `serverless: preview`).
- Short intro describing the notes family, that notes attach by Elasticsearch `_id`, and that Timeline notes are out of scope.
- A **Shared conventions** section (see below).
- A **Step catalog** with jump links.
- One subsection per step: purpose sentence, a parameter table (`Parameter | Location | Type | Required | Description`), and a YAML example — identical layout to the Alert triage / Detection rules pages.
- Include the `:::{include} ../_snippets/schema-location-legend.md :::` legend, as the other Security family pages do.
- An **Output** subsection per step (or a shared output section where shapes overlap) — `createNote`/`updateNote`/`getNotes` return structured fields that downstream steps consume. Mirror the Detection rules page's Output section.
- A **Related** section linking to Security action steps, Alert triage, Attack triage, Cases action steps, Alert triggers, and the product [Notes](https://www.elastic.co/docs/solutions/security/investigate/notes) page.
#### Shared conventions to document
- **All parameters live under `with`.** There are no top-level fields specific to this family.
- **Notes attach by Elasticsearch `_id`.** `document_id` is the `_id` of an alert, attack, or any other Elasticsearch document. One step type covers all object types — there is no `alert_id` / `attack_id` split.
- **Markdown is supported** in the `text` field (`security.createNote` and `security.updateNote`). Max length is **60,000 characters**.
- **Timeline notes are out of scope.** These steps manage notes attached to a document. They are not a replacement for Timeline-only notes. Product docs already cover the Notes UI: [Investigate notes](https://www.elastic.co/docs/solutions/security/investigate/notes).
- **Privileges.** The executing identity needs the Kibana **Notes** feature privileges under Security:
- `notes_read` for `security.getNotes`
- `notes_write` for `security.createNote`, `security.updateNote`, and `security.deleteNote`
- **Chaining uses `note_id`, not `noteId`.** Step outputs are snake_case. When composing `getNotes` → `updateNote` / `deleteNote`, use `foreach.item.note_id` (and `steps.create_note.output.note_id`). Do not use the raw Notes API field names (`noteId`, `note`, `eventId`).
- **Single vs bulk.** `security.createNote`, `security.getNotes`, and `security.updateNote` target **one document / one note** per invocation. `security.deleteNote` is the exception: `ids` accepts either a single string or an array of strings (same `z.union` pattern as `security.setAlertStatus` / `security.setAttackStatus`).
- **Product cap.** You can attach up to 100 notes to an alert or event (see the product Notes page). Workflows that create notes in a loop can hit this limit.
- **⚠️ Parameter naming differs across the four steps** — please document exactly as implemented, do not normalize:
- Create and get take **`document_id`** only (create also takes **`text`**).
- Update takes **`note_id` + `document_id` + `text`**. `document_id` is required on update so the handler can locate the note and preserve its Timeline association; it is not optional.
- Delete takes **`ids`** (note `savedObjectId`(s)), **not** `note_id` and **not** `document_id`.
#### Per-step reference (schemas as implemented)
**`security.createNote`** — Create a note on an alert, attack, or document.
Creates a new note attached to the document identified by `document_id`. The returned `note_id` can be used by later `security.updateNote` / `security.deleteNote` steps.
| Parameter | Location | Type | Required | Description |
| --------- | -------- | ---- | -------- | ----------- |
| `text` | `with` | `string` (markdown, 1–60,000 chars) | Yes | The text content of the note. Markdown is supported. |
| `document_id` | `with` | `string` (Elasticsearch `_id`, max 256 chars) | Yes | The `_id` of the alert, attack, or document to attach the note to. |
**Output**
| Field | Type | Description |
| ----- | ---- | ----------- |
| `success` | `boolean` | Whether the note was created. |
| `note_id` | `string` (optional) | The `savedObjectId` of the newly created note. Use this in later update/delete steps. |
| `message` | `string` (optional) | Human-readable result message. |
```yaml
- name: create_alert_note
type: security.createNote
with:
text: "{{ steps.generate_summary.output.summary }}"
document_id: "{{ variables.alert_id }}"
```
```yaml
- name: create_attack_note
type: security.createNote
with:
text: "Triaged automatically by the enrichment workflow."
document_id: "{{ variables.attack_id }}"
```
Example: create a note on the triggering alert, with text entered at run time.
```yaml
name: Test create note
enabled: true
description: Create a note on the selected alert with text entered at run time
triggers:
- type: alert
- type: manual
inputs:
- name: note_text
type: string
required: true
steps:
- name: create_note
type: security.createNote
with:
text: "${{ inputs.note_text }}"
document_id: "${{ event.alerts[0]._id }}"
```
Example: close alerts and attach a triage note to each.
```yaml
name: Close alerts with triage note
enabled: true
description: Closes the selected alert(s) and adds a triage note to each
triggers:
- type: manual
steps:
- name: close_alerts
type: security.setAlertStatus
with:
alert_ids: "${{ event.alerts | map: '_id' | default: [] }}"
status: closed
close_reason: true_positive
- name: add_triage_note
type: foreach
foreach: "${{ event.alerts | json }}"
steps:
- name: create_note
type: security.createNote
with:
text: "This alert is being closed because it's been triaged accordingly."
document_id: "${{ foreach.item._id }}"
```
---
**`security.getNotes`** — Retrieve all notes attached to a specific alert, attack, or document.
Takes a single `document_id`. Returns `total_count` plus a `notes` array. Each note exposes its text, author, and timestamps so downstream steps can, for example, check whether a note already exists before writing a new one.
| Parameter | Location | Type | Required | Description |
| --------- | -------- | ---- | -------- | ----------- |
| `document_id` | `with` | `string` (Elasticsearch `_id`, max 256 chars) | Yes | The `_id` of the alert, attack, or document to retrieve notes for. |
**Output**
| Field | Type | Description |
| ----- | ---- | ----------- |
| `success` | `boolean` | Whether the fetch succeeded. |
| `total_count` | `number` | The total number of notes returned. |
| `notes` | `array` | The notes attached to the document. |
Each entry in `notes`:
| Field | Type | Description |
| ----- | ---- | ----------- |
| `note_id` | `string` (nullable) | The `savedObjectId` of the note. |
| `text` | `string` (nullable) | The text content of the note. |
| `document_id` | `string` (nullable) | The Elasticsearch `_id` of the document the note is attached to. |
| `timeline_id` | `string` (nullable) | The `savedObjectId` of the Timeline the note belongs to, if any. |
| `created_by` | `string` (nullable) | The user who created the note. |
| `created` | `number` (nullable) | When the note was created, as a 13-digit Epoch timestamp. |
| `updated_by` | `string` (nullable) | The user who last updated the note. |
| `updated` | `number` (nullable) | When the note was last updated, as a 13-digit Epoch timestamp. |
| `version` | `string` (nullable) | The version of the note. |
```yaml
- name: get_alert_notes
type: security.getNotes
with:
document_id: "{{ variables.alert_id }}"
```
Example: fetch all notes on a document and print their content.
```yaml
name: Print notes on a document
enabled: true
description: Fetches all notes on a document and prints their content
triggers:
- type: alert
- type: manual
steps:
- name: get_notes
type: security.getNotes
with:
document_id: "{{ event.alerts[0]._id }}"
- name: print_notes
type: foreach
foreach: "${{ steps.get_notes.output.notes | json }}"
steps:
- name: print_note
type: console
with:
message: "Note {{ foreach.item.note_id }}: {{ foreach.item.text }}"
```
---
**`security.updateNote`** — Update the text of an existing note on an alert, attack, or document.
Updates the text of an existing note identified by `note_id` on the document (`document_id`) it is attached to. The note's document association is preserved. If the note also belongs to a Timeline, that association is preserved as well (the handler fetches the note first and echoes its current `timelineId` back unchanged). The note version is resolved server-side — callers do not supply a version.
If the note is not found on the given `document_id`, the step **fails** rather than updating.
| Parameter | Location | Type | Required | Description |
| --------- | -------- | ---- | -------- | ----------- |
| `note_id` | `with` | `string` (`savedObjectId`, max 256 chars) | Yes | The `savedObjectId` of the note to update. |
| `document_id` | `with` | `string` (Elasticsearch `_id`, max 256 chars) | Yes | The `_id` of the alert, attack, or document the note is attached to. Used to locate the note and preserve its associations. |
| `text` | `with` | `string` (markdown, 1–60,000 chars) | Yes | The new text content of the note. Markdown is supported. This **replaces** the existing text (it is not an append). |
**Output**
| Field | Type | Description |
| ----- | ---- | ----------- |
| `success` | `boolean` | Whether the note was updated. |
| `note_id` | `string` (optional) | The `savedObjectId` of the updated note. |
| `message` | `string` (optional) | Human-readable result message. |
```yaml
- name: update_note
type: security.updateNote
with:
note_id: "{{ steps.create_alert_note.output.note_id }}"
document_id: "{{ variables.alert_id }}"
text: "Updated with enrichment findings: {{ steps.enrich.output.summary }}"
```
Example: retrieve all notes on a document, then append run-time text to each. Note that `text` is a full replacement — the workflow must re-send the original text plus the addition.
```yaml
name: Append text to all notes on a document
enabled: true
description: Reads all notes on a document and appends run-time text to each
triggers:
- type: alert
- type: manual
inputs:
- name: additional_text
type: string
required: true
steps:
- name: get_notes
type: security.getNotes
with:
document_id: "{{ event.alerts[0]._id }}"
- name: append_to_each_note
type: foreach
foreach: "${{ steps.get_notes.output.notes | json }}"
steps:
- name: update_note
type: security.updateNote
with:
note_id: "{{ foreach.item.note_id }}"
document_id: "{{ event.alerts[0]._id }}"
text: "{{ foreach.item.text }}\n\n{{ inputs.additional_text }}"
```
---
**`security.deleteNote`** — Delete one or more notes, identified by their note IDs.
Deletes one or more notes identified by their `savedObjectId`. Accepts either a single note ID or a list of IDs for bulk deletion. Unlike the other three steps, this step does **not** take a `document_id`.
| Parameter | Location | Type | Required | Description |
| --------- | -------- | ---- | -------- | ----------- |
| `ids` | `with` | `string` or `string[]` (`savedObjectId`, max 256 chars each; array min length 1) | Yes | A single note `savedObjectId` or a list of IDs for bulk deletion. |
**Output**
| Field | Type | Description |
| ----- | ---- | ----------- |
| `success` | `boolean` | Whether the delete succeeded. |
| `message` | `string` (optional) | Human-readable result message (includes the count of deleted notes). |
```yaml
- name: delete_note
type: security.deleteNote
with:
ids: "{{ variables.note_id }}"
```
```yaml
- name: delete_notes
type: security.deleteNote
with:
ids:
- "note-1"
- "note-2"
```
Example: retrieve all notes on a document, then delete them.
```yaml
name: Delete all notes on a document
enabled: true
description: Gets all notes on a document and deletes them
triggers:
- type: alert
- type: manual
steps:
- name: get_notes
type: security.getNotes
with:
document_id: "{{ event.alerts[0]._id }}"
- name: delete_notes
type: security.deleteNote
with:
ids: "${{ steps.get_notes.output.notes | map: 'note_id' }}"
```
### 2. Update the Security action steps overview
Update [explore-analyze/workflows/steps/security.md](https://www.elastic.co/docs/explore-analyze/workflows/steps/security) (published).
The page currently lists three families: **Alert triage**, **Attack triage**, **Detection rules**. Add a fourth category section — **Notes** — following the same pattern as the existing family blurbs (short description + "Use … to:" bullet list + `Refer to …` link to the new page). Suggested content:
- Create, retrieve, update, and delete notes on alerts, attacks, or documents (`security.createNote`, `security.getNotes`, `security.updateNote`, `security.deleteNote`)
- Link to the new `explore-analyze/workflows/steps/notes.md` page.
Also add the Notes page to the existing **Related** section on `security.md`.
### 3. Update the Action steps overview page
Update [explore-analyze/workflows/steps/action-steps.md](https://www.elastic.co/docs/explore-analyze/workflows/steps/action-steps) (published).
The **Security** category blurb currently lists alert/attack triage and detection-rule enable/disable. Add a bullet for notes, for example:
- Create, retrieve, update, and delete notes on alerts, attacks, or documents (`security.createNote`, `security.getNotes`, `security.updateNote`, `security.deleteNote`)
And add `Refer to … Notes action steps` alongside the existing Alert triage / Attack triage / Detection rules links.
### 4. Update the step-type index and cheat sheet
- **[reference/step-types.md](https://www.elastic.co/docs/explore-analyze/workflows/reference/step-types) (A–Z index):** add rows for all four `security.*` note steps under the Security category, marked as Technical Preview since 9.6 (same badge style as `entityStore.updateAssetCriticality` / `waitForApproval`). Suggested one-liners:
- `security.createNote` — Create a note on an alert, attack, or document.
- `security.getNotes` — Retrieve all notes attached to an alert, attack, or document.
- `security.updateNote` — Update the text of an existing note on an alert, attack, or document.
- `security.deleteNote` — Delete one or more notes by note ID.
- **[reference/cheat-sheet.md](https://www.elastic.co/docs/explore-analyze/workflows/reference/cheat-sheet):** add a **"Manage notes"** row to the "Step menu by intent" table:
- Want to… **Manage notes** → `security.createNote`, `security.getNotes`, `security.updateNote`, `security.deleteNote`
### 5. Cross-reference from the product Notes page (optional but useful)
The product page [Investigate notes](https://www.elastic.co/docs/solutions/security/investigate/notes) documents the UI only. Add a short pointer that notes on alerts, attacks, and documents can also be created, listed, updated, and deleted from a workflow via the `security.*` note steps, linking to the new Notes action steps page.
---
## Timeline & environment
- **Stack release:** 9.6
- **Serverless:** Week of August 24, 2026
- All four steps are **Technical Preview** in 9.6 / Serverless. No feature flag — they are active by default. Tag everything `stack: preview 9.6+` / `serverless: preview`.
## Resources
- **Epic:** [security-team#17663](https://github.com/elastic/security-team/issues/17663) — Notes Workflow Steps
- **Product issues:**
- [kibana#284977](https://github.com/elastic/kibana/issues/284977) — add create note workflow step
- [kibana#284981](https://github.com/elastic/kibana/issues/284981) — get notes (track via PR)
- [kibana#284978](https://github.com/elastic/kibana/issues/284978) — update note
- [kibana#284980](https://github.com/elastic/kibana/issues/284980) — delete note
- **Existing reference to mirror:** [Detection rules action steps](https://www.elastic.co/docs/explore-analyze/workflows/steps/detection-rules) (family page) and [Alert triage action steps](https://www.elastic.co/docs/explore-analyze/workflows/steps/alert-triage) (parameter tables + YAML)
- **Existing overview to update:** [Security action steps](https://www.elastic.co/docs/explore-analyze/workflows/steps/security)
- **Product Notes UI:** [Investigate notes](https://www.elastic.co/docs/solutions/security/investigate/notes)
- **Related (out of scope for this ticket):** [docs-content#8161](https://github.com/elastic/docs-content/issues/8161) — event-driven `noteCreated` / `noteUpdated` triggers. Cross-link from the Notes steps page if those triggers are documented, but do not fold trigger docs into this issue.
### Kibana implementation PRs (all merged)
- `security.createNote` — [kibana#284719](https://github.com/elastic/kibana/pull/284719)
- `security.getNotes` — [kibana#284720](https://github.com/elastic/kibana/pull/284720)
- `security.updateNote` — [kibana#284721](https://github.com/elastic/kibana/pull/284721)
- `security.deleteNote` — [kibana#284722](https://github.com/elastic/kibana/pull/284722)
## Consolidation notes
This issue is the **single source of truth** for the notes `security.*` workflow-step docs. It supersedes and absorbs the Docs Quest Scanner issues that tracked the same steps one-PR-at-a-time (those issues had a less complete step list — `security.createNote` never got a scanner ticket — and pointed at the overview page rather than a dedicated Notes family page). Absorbed into this issue:
- [docs-content#8164](https://github.com/elastic/docs-content/issues/8164) — Retrieve notes attached to alerts and documents from a workflow step (`security.getNotes`)
- [docs-content#8162](https://github.com/elastic/docs-content/issues/8162) — Update existing notes from a workflow step (`security.updateNote`)
- [docs-content#8163](https://github.com/elastic/docs-content/issues/8163) — Delete alert and document notes from a workflow step (`security.deleteNote`)
**Reconciled notes vs. the scanner tickets:**
- All four steps (including `security.createNote`, which had no scanner ticket) should be documented together as one **Notes** family, matching Alert triage / Attack triage / Detection rules.
- `security.updateNote` requires **`document_id` in addition to `note_id` and `text`**. The scanner ticket for #8162 only mentioned `note_id` and `text`. Document all three.
- Step output field names are snake_case (`note_id`, `text`, `document_id`). Do not document the raw Notes API names (`noteId`, `note`, `eventId`).
- Feature status is **Technical Preview** (code: `stability: 'tech_preview'`), matching the scanner tickets. Do not mark these GA.
---
### Which deployment methods does this change impact?
Elastic On-Prem and Cloud (all)
### Feature differences
All four note steps are Technical Preview in 9.6 and Serverless. Behavior is identical across deployment methods. No feature flag.
### What Elastic Stack release is this request related to?
9.6
### Serverless release
Week of August 24, 2026
### Collaboration model
The documentation team will create the first draft. Full step schemas, parameter tables, output fields, and YAML examples (taken directly from the implementation) are provided above to make drafting straightforward.
### Point of contact.
**Main contact:** @PhilippeOberti
**Stakeholders:** @elastic/security-threat-hunting
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.