> For the complete documentation index, see [llms.txt](https://cortex-docs.paloaltonetworks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cortex-docs.paloaltonetworks.com/cortex-cloud-runtime-security/cases-and-issues/investigation-and-response/automation/playbooks/best-practices-for-playbooks.md).

# Best practices for playbooks

Use these practices to build clear, efficient, and reliable playbooks.

Review them before creating workflows or optimizing existing playbooks.

### Build your playbook

<details>

<summary>Use clear task names and descriptions</summary>

Describe tasks clearly for people unfamiliar with the workflow. Apply this to task names, descriptions, and the playbook description.

Users should understand the playbook by reading task names. They should not need to open every task.

| Clear                          | Unclear      |
| ------------------------------ | ------------ |
| **Check if the IP is Private** | **IP Check** |

</details>

<details>

<summary>Define inputs and outputs properly</summary>

* **Group related input fields.** Grouping provides context and clarifies each playbook flow.
* **Use PascalCase for input names.** Keep inherently capitalized terms uppercase. For example, use `EntityID` and `MITRETechnique`.
* **Define output sub-keys.** When configuring playbook outputs, configure sub-keys as much as possible, do not limit configuration to only the root keys. For example, instead of outputting `File`, output `File.Name`, `File.Size`, etc. This helps when viewing the outputs of the playbook within another playbook.

</details>

<details>

<summary>Configure task inputs correctly</summary>

Avoid Cortex XDR Transform Language (DT) in **Get** input definitions.

Use a filter or transformer for complex processing whenever possible. Use [DT](https://xsoar.pan.dev/docs/integrations/dt) only when it simplifies the playbook or improves performance significantly.

</details>

<details>

<summary>Define playbook logic carefully</summary>

**Avoid race conditions**

Do not run multiple context-setting scripts simultaneously for the same key. This includes `Set` and `SetAndHandleEmpty`.

Concurrent writes can overwrite data. Run tasks sequentially or use scripts that append values.

**Identify input sources**

Confirm whether task data is **As value** or **From Previous Tasks**. The latter reads from context.

**Filter inputs efficiently**

Tasks take their inputs from the context, not directly from the previous tasks (even if it says from previous tasks). For an example of a task not receiving the right context, see this bug (since fixed) in a playbook:

![enrichmenttasks.png](https://docs-cortex.paloaltonetworks.com/api/khub/maps/5CAbsl8idaK8R43ZLhoTOw/resources/1vaQml0mrAZyZ~fxt1fCGA-5CAbsl8idaK8R43ZLhoTOw/content?v=6e49f33cde08c43e\&Ft-Calling-App=ft/turnkey-portal)

The playbook begins by classifying the emails as internal or external. It then checks the reputation of external email addresses if any were found. That happens on the right side of the image. We expect that branch to run only if external addresses are found.

![emailaddressbug.png](https://docs-cortex.paloaltonetworks.com/api/khub/maps/5CAbsl8idaK8R43ZLhoTOw/resources/vdy_6vEs6~jIcJL8Azo2xw-5CAbsl8idaK8R43ZLhoTOw/content?v=a53a55d9090b47aa\&Ft-Calling-App=ft/turnkey-portal)

However, we did not apply a filter to the last task that gets the reputation on the right side:

This means that if both internal and external email addresses are found, we proceed with both branches (internal and external) of the playbook, and the task that gets the reputation runs without an applied filter, effectively taking all the emails we have in the inputs. The correct task input should have been:

![transformerfortask.png](/files/WJ5zagy2lFVvilFGbxsg)

**Ignore case for input names**

Use the `ignore-case` option when possible. This avoids failures from value casing differences, such as `True` and `true`.

![ignorecase.png](https://docs-cortex.paloaltonetworks.com/api/khub/maps/5CAbsl8idaK8R43ZLhoTOw/resources/I5jCK_WE0S3ImhHz4n1BYQ-5CAbsl8idaK8R43ZLhoTOw/content?v=456da03807ce06f8\&Ft-Calling-App=ft/turnkey-portal)

* When working with two lists, if you need multiple items from list A, which are also in list B, use the `in` filter instead of the `equals` or `contains` filters.

  | Correct Method                                                                                                                           | Incorrect Method                                                                                                                                           |
  | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | <p>Get the IP addresses that <code>are in</code> the list of inputs.</p><p><img src="/files/FLEGrqdbVJM6xWzDQnsQ" alt="iparein.png"></p> | Get the IP addresses where the addresses `contain` the list. This is incorrect because they don't contain the list, they contain individual items from it. |
* Differentiate between checking if `a specific element exists` versus checking if `an` element equals something. This is a common mistake that can lead to tests working in some situations, but not all.

  | Correct Method                                                                                                                                                                                                                                                   | Incorrect Method                                                                                                                                                                                                                                  |
  | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | <p>Check if <code>any object</code> where the NetworkType is External <code>exists</code>.</p><p><a href="/spaces/mxWuY3s7AUvWfzCV9p1A/pages/5cUJMsG9oPPstm4uLBir#build-your-playbook"><img src="/files/Y04SVcxeHzHAYuc7BzKA" alt="conditionforyes.png"></a></p> | <p>Check if the NetworkType <code>of the IP object is External</code>. This is incorrect because the IP object may contain multiple IPs, some internal and some external.</p><p><img src="/files/jsc9jIsCZhTPWdtuYC14" alt="getexternal.png"></p> |
* Run `one or more tasks` based on the `object types` versus running `either one task or the other` based on `the type of one object`.

  | Correct Method                                                                                                                                                                                                                                                                                                                           | Incorrect Method |
  | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
  | <p>Check the existence of both object types and run tasks for the types found.</p><p><img src="https://docs-cortex.paloaltonetworks.com/api/khub/maps/5CAbsl8idaK8R43ZLhoTOw/resources/tg~DozGfVUl2RxmgSphyWA-5CAbsl8idaK8R43ZLhoTOw/content?v=d33b2102534befb4&#x26;Ft-Calling-App=ft/turnkey-portal" alt="checkexistence2025.png"></p> |                  |

</details>

<details>

<summary>Define loops correctly</summary>

Use [playbook loops](/cortex-xsiam/configure-cortex-xsiam/automations/playbooks/build-your-playbook/customize-your-playbook/configure-a-sub-playbook-loop.md) only when actions require specific data pairs.

| Correct Method Example                                                                                                            | Incorrect Method Example                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Either use filters and transformers or loop through each separate indicator to verify they're creating the correct relationships. | <p>A user has a playbook that creates relationships for multiple indicator types. All indicator types and malware families are in their <code>${inputs.Domain}</code> and <code>${inputs.MFam}</code> playbook inputs.</p><p>The user wrongly assumes that when creating the relationships, the correct malware families in <code>${inputs.MFam}</code> correspond to the correct domains in ${inputs.Domain}.</p><p><img src="https://docs-cortex.paloaltonetworks.com/api/khub/maps/5CAbsl8idaK8R43ZLhoTOw/resources/xxoWscZ_zjX2OptTaH31RQ-5CAbsl8idaK8R43ZLhoTOw/content?v=cc0c54d4e61eab26&#x26;Ft-Calling-App=ft/turnkey-portal" alt="inputa_2025.png"></p> |

</details>

### Optimize design and performance

<details>

<summary>Use the latest playbook and script versions</summary>

When resuming playbook work, verify that you use the latest version. Reattach detached playbooks and update them before editing.

If you retain a custom version, review release notes. Copy relevant out-of-the-box changes into your version.

{% hint style="warning" %}
Reattaching a detached playbook overwrites customizations when it updates.
{% endhint %}

Update scripts and integration commands to their current versions. A yellow triangle identifies deprecated or outdated tasks.

![](https://docs-cortex.paloaltonetworks.com/api/khub/maps/5CAbsl8idaK8R43ZLhoTOw/resources/uN_WeIeLirDIUny76HNNjQ-5CAbsl8idaK8R43ZLhoTOw/content?Ft-Calling-App=ft%2Fturnkey-portal\&Ft-Calling-App-Version=5.3.24\&filename=img-d46b16bc7e2ae120f9bb3a140ec4c5fb.png)

</details>

<details>

<summary>Break up large playbooks</summary>

If a playbook has more than thirty tasks, consider breaking the tasks into multiple sub-playbooks. Sub-playbooks can be reused, managed easily when upgrading, and they make it easier to follow the main playbook.

Sub-playbooks are playbooks that are used from within a parent playbook, as building blocks. The parent playbook is the main playbook that runs on the investigation, and each sub-playbook has a specific goal/responsibility.

* Parent playbooks usually have a `closeInvestigation` task at the end because they are the main playbook for that issue.
* Parent playbooks usually contain inputs that are passed down to sub-playbooks. Certain `True`/`False` flags may come from the parent playbook inputs.

</details>

<details>

<summary>Remove unused tasks</summary>

Remove tasks not connected to the workflow from production playbooks.

</details>

<details>

<summary>Run in quiet mode</summary>

Use quiet mode to reduce issue size and improve execution speed. Use it for indicator enrichment in job-based playbooks.

</details>

<details>

<summary>Extract indicators only when needed</summary>

When indicator extraction is enabled for a playbook task, the task by default tries to extract all indicator types from the task Results. (The Results entry is the information printed to the War Room, not the outputs of the task). Extracting all indicator types can slow down the playbook, so it is important to only extract indicators as needed.

For example, for the ParseEmailFilesV2 script which prints email information to the War Room, extraction should be enabled in order to extract email addresses, URLs, and other indicators. However, if your task runs the Sleep script, there is no point in extracting indicators.

Set **Indicator Extraction mode** to **None** in the task **Advanced** tab when extraction is unnecessary.

</details>

<details>

<summary>Use retries</summary>

Use retries when a task may temporarily fail but should later succeed. Retries help with network interruptions, service downtime, and rate limits.

{% hint style="info" %}
Retries do not support data collection email delivery errors. They only retry automation execution failures.
{% endhint %}

</details>

<details>

<summary>Minimize disk, CPU, and API usage</summary>

Review the following:

* Can tasks run in parallel rather than sequentially?
* Are timeouts, search windows, and intervals realistic?
* Can one API call replace multiple calls?
* Can an integration accept arrays instead of repeated task runs?
* Is the data already available instead of being stored twice?
* Can you avoid a loop or unnecessary extraction?

</details>

<details>

<summary>Get data from XQL datasets</summary>

Use XQL to track playbook and script performance. These datasets support queries and dashboards:

* `playbook_tasks`: Failed and retried tasks.
* `playbook_runs`: Playbook runs and statuses.
* `scripts_and_commands_metrics`: Scripts and commands used in playbook tasks.

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://cortex-docs.paloaltonetworks.com/cortex-cloud-runtime-security/cases-and-issues/investigation-and-response/automation/playbooks/best-practices-for-playbooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
