> 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-xsoar-8-saas/configure-cortex-xsoar/playbooks/best-practices.md).

# Best practices

The following guidelines are best practices for building playbooks as well as optimizing playbook design and performance. Whether you are just starting or are creating advanced workflows, we recommend reviewing these recommendations carefully so your playbooks have a clear logical flow and run correctly and efficiently.

#### Best practices for building your playbook

<details>

<summary>Use the Use Case Builder to define your use case</summary>

The [Use Case Builder content pack](https://cortex.marketplace.pan.dev/marketplace/details/Use_Case_Builder/) helps you streamline the use case design process, including building your playbook. It contains tools to help you measure and track use cases through your automation journey and quickly autogenerate OOTB playbooks and custom workflows.

For a detailed example of designing and building a use case, watch [this video series](https://youtube.com/playlist?list=PLD6FJ8WNiIqUVEA2e5LZhmqNnwFcFhDTZ).

</details>

<details>

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

Describe tasks clearly. Tasks should be clear to someone not familiar with the playbook workflow. This applies to task names, task descriptions, and the playbook description. When naming tasks, the guideline should be that users can understand what the playbook does by reading the task names, without having to open individual tasks to view the details.

| Clear                                     | Unclear                 |
| ----------------------------------------- | ----------------------- |
| Task name: **Check if the IP is Private** | Task name: **IP Check** |

</details>

<details>

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

* **Group related input fields**.

  Grouping inputs organizes the input fields and provides clarity and context to understand which inputs are relevant to which playbook flow.
* **Use Pascal case for input names**.

  Use the PascalCase convention for inputs, keeping in mind that inherently capitalized terms should be kept in upper case. For example, the `Entity ID` input should be named `EntityID` and `MITRE Technique` should be `MITRETechnique`.
* **Define outputs properly**.

  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 playbook task inputs correctly</summary>

* **Avoid using Cortex XSOAR Transform Language (DT) in the Get input field definition**.

  If you need to use [DT](https://xsoar.pan.dev/docs/integrations/dt) for complex processing and you think a new filter or transformer would provide a better alternative to your DT solution, you can request the feature or contribute it. Consider using DT only if it can drastically simplify the playbook or improve performance.

</details>

<details>

<summary>Define playbook logic carefully</summary>

In each task, make sure appropriate logical operations are performed on input data. For example:

* **Avoid race conditions**.

  Be aware of potential race conditions. When you want to add multiple values to the same key, do not use multiple tasks that run `Set`, `SetAndHandleEmpty`, or any other script that sets data in context at the same time, because a race condition can cause your data to be overwritten by the same tasks. This is especially problematic when trying to append data. Instead, run the tasks one after the other or use scripts to append the data instead of setting a new value to the key.
* **Determine where inputs are coming from**.

  Verify whether the data you're getting is `As value` (simple value) or `From Previous Tasks` (from context).
* **Filter your inputs correctly so the task runs 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](/files/Lv50A23A6ZigWr7lWtE1)

  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](/files/cU2kmDEnTUGbLozcpNt6)

  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/w5Oxwzd7ubjxt6lj6A1r)
* **Select Ignore case for input names**.

  Use `ignore-case` option where possible, especially when checking Boolean playbook inputs such as `True` which users may end up configuring as `true` with a lowercase t:

  ![ignorecase.png](/files/P58v4i54iSxGBcywJ8M0)
* 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/6GN82olRC3VMLgBckKwJ" alt="iparein.png" data-size="original"></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><img src="/files/1aypFHFKxIfTVZT9kCS7" alt="conditionforyes.png" data-size="original"></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/0mUjF8UtNIyiTnClGkxk" alt="getexternal.png" data-size="original"></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="/files/rxWRHj19GyTWDKnx5UOt" alt="checkexistence2025.png" data-size="original"></p> | <p>Check if there is either an internal or an external IP, and take only one path even if both types exist.</p><p><img src="/files/yhiKh0GGqFgdotY6wfov" alt="checkeithertype2025.png" data-size="original"></p> |

</details>

<details>

<summary>Define playbook loops correctly</summary>

Use [playbook loops](/cortex-xsoar-8-saas/configure-cortex-xsoar/playbooks/customize-your-playbook/configure-a-sub-playbook.md#UUID-f2bd9b56-d170-f2c9-0b2f-bae222afdbb9_section-idm458337861071363421684068716) only where needed. Loops are needed when certain actions have to be performed on specific pairs of data.

| 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="/files/kfVUvY2peVMWkLx1KyXO" alt="inputa_2025.png" data-size="original"></p> |

</details>

<details>

<summary>Add a task to check that integrations are enabled</summary>

Use the `IsIntegrationEnabled` script in your playbook to make sure any integrations you need to run are enabled.

</details>

#### Best practices for optimizing playbook design and performance

In order to minimize your incident response time and make sure the system runs optimally, it's important to follow design and performance guidelines.

<details>

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

Playbooks

When returning to work on a playbook after a break, verify you’re working on the latest version. Reattach the playbook if it’s detached, and update it to ensure you’re not editing an older version and introducing regressions. If you don’t want to reattach your playbook, or you’re still working on your custom version, we recommend reviewing the release notes to see what changes were made to the out-of-the-box playbook and copying those changes to your version.

Scripts

Update scripts and integration commands in playbook tasks to their most current version. Scripts that have updates or are deprecated are designated by a yellow triangle.

{% hint style="info" %}

### Tip

You can configure your user preferences to automatically receive notifications about deprecated playbooks, sub-playbooks, and scripts. For more information, see [User preferences](/cortex-xsoar-8-saas/troubleshoot-and-reference/reference/user-preferences.md).
{% endhint %}

![update\_scripts.png](/files/61BLCLV4fmAlYoGubRth)

</details>

<details>

<summary>Break up large playbooks into sub-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.

Playbooks that are triggered by an incident/job are considered a parent 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 incident.
* 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 playbook tasks</summary>

For production playbooks, remove playbook tasks that are not connected to the playbook workflow.

</details>

<details>

<summary>Set the playbook to run in quiet mode</summary>

Run playbooks in quiet mode to reduce the incident size and execute playbooks faster. For playbooks running in jobs, indicator enrichment should be done in quiet mode.

</details>

<details>

<summary>Only extract indicators 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 the **Indicator Extraction mode** to None in the playbook task **Advanced** tab.

</details>

<details>

<summary>Use retries and polling</summary>

Retries and polling help ensure smoother playbook execution and more efficient progress tracking. The following describes when it is relevant to use retries and polling.

Retries

Use retries when a task might temporarily fail but is expected to succeed later. This helps handle issues like network glitches, service downtime, or rate limits by retrying the task again after a short wait.

Polling

Use polling to monitor a process or condition over time, especially when waiting for a specific outcome before proceeding such as waiting for an asynchronous task to complete. It periodically checks if a required condition is complete, ensuring your playbook moves forward only when ready. Common uses include waiting for a job to finish or a system to reach a certain state.

</details>

<details>

<summary>Additional playbook optimization tips</summary>

Consider the following:

* Do I need to do this action in multiple tasks?
* Can these tasks run in parallel instead of synchronously?
* Where applicable, am I setting realistic timeouts, search windows, intervals?
* Can I consolidate the API calls into one call? If not, can an integration enhancement solve this by accepting arrays as input instead of running multiple times for each input?
* Am I unnecessarily storing the same data twice? Do I have the data I need already stored?
* Where applicable, can I run this playbook without a loop?
* What extractions are running in my incident?
  * If your task requires extracted indicators, change the indicator extraction mode to inline. Use this mode carefully because it can affect performance. In addition, it is important to customize and limit the indicators extracted from incident fields of the incident type you are ingesting in the incident type settings **Indicator Extraction Rules**.
  * When creating new incident fields that do not need to be searched, double check whether they should be searchable under the relevant checkbox. Example of fields that should be searchable: `Endpoint ID`, `Is Admin`. Example of fields that should not be searchable: `Additional Notes`, `Alert Summary`.

</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-xsoar-8-saas/configure-cortex-xsoar/playbooks/best-practices.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.
