> ## Documentation Index
> Fetch the complete documentation index at: https://docs.opal.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Work with scripts in the OpalScript editor

> Create, test, version, and monitor OpalScript automations from the editor.

The OpalScript editor is where you write, test, and manage your scripts. This page walks through the editor, from creating a script to watching it run in production.

## Prerequisites

* You must be an Opal Admin or Read-only Admin to open the OpalScript editor.
* Read [Overview](/docs/opalscript-overview) first if you haven't created a script yet.

## The editor at a glance

Navigate to **OpalScript** > **Editor**.

<img src="https://mintcdn.com/opalsecurity/p6jR0DbT5YtN9wb9/images/docs/opalscript-editor-1.png?fit=max&auto=format&n=p6jR0DbT5YtN9wb9&q=85&s=b8b8550b3513f333e649dbbc3679dba8" alt="The OpalScript editor with an empty script open. The left pane lists saved scripts with their type and last-edited time. The center pane holds the code. Above it, the script name &#x22;Untitled Script&#x22; sits over a metadata row reading &#x22;On Request&#x22;, &#x22;Latest (v1)&#x22;, and &#x22;No runs yet&#x22;. Assistant and Save buttons are at top right, and a collapsed Debug panel with Test run and Clear buttons runs along the bottom." width="3456" height="1868" data-path="images/docs/opalscript-editor-1.png" />

| Area                 | What it's for                                                                                                                      |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Script list (left)   | Every script in your organization, with its type and when it was last edited. Filter by name, or select **+** to create a new one. |
| Editor (center)      | The script body, with syntax highlighting and autocomplete for Opal's modules.                                                     |
| Metadata row         | Three links under the script name. See below.                                                                                      |
| **Save** (top right) | Saves your changes as a new version.                                                                                               |
| **Debug** (bottom)   | Test run output. Select **Test run** to open the test dialog, or **Clear** to empty the panel.                                     |

The script name itself is editable—select it to rename the script.

### The metadata row

The row under the script name is three shortcuts, not just labels:

| Segment                          | What it shows                                                                                                             | Where it takes you                                       |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| **On Request**                   | When this script type fires. Delegation condition scripts read **On Delegation**, context provider scripts **On Review**. | The list of resources and groups that trigger the script |
| **Latest (v1)**                  | Which version you're viewing                                                                                              | Version history                                          |
| **No runs yet** / **Last run …** | When the script last executed                                                                                             | The Runs page, filtered to this script                   |

<Note>
  Renaming a script doesn't break anything it's attached to. Triggers follow the script, not its name.
</Note>

## Create a script

1. Select **+** above the script list.

2. Choose a script type from the dropdown at the top left of the **New script** dialog—**Request review**, delegation condition, or context provider.

3. Pick a starting point. **Blank script** under **Start from scratch** gives you an empty file; the templates below it are grouped by what they do, and selecting one previews its full source on the right.

4. Select an **Owner**, then select **Use template**.

   <img src="https://mintcdn.com/opalsecurity/p6jR0DbT5YtN9wb9/images/docs/opalscript-editor-2.png?fit=max&auto=format&n=p6jR0DbT5YtN9wb9&q=85&s=6adb1b686daea3859ed8ec6f4380559e" alt="The New script dialog. A Request review type dropdown sits top left, above template groups labeled Start from scratch, Auto-approve, Block, and Escalate. The Low-risk auto-approve template is selected. The right side shows an Owner field set to &#x22;Analytics Owner&#x22; and a read-only preview of the template's code. Cancel and Use template buttons are at the bottom." width="3456" height="1868" data-path="images/docs/opalscript-editor-2.png" />

   The owner you select owns the service user that Opal provisions behind the script.

<Tip>
  Templates are the fastest way to see what OpalScript can do. Select each one to read its source—even if you start from a blank script, the templates show the idioms for common patterns.
</Tip>

## Test a script before you use it

Test runs execute your script against a synthetic request. Nothing real is approved, denied, or commented on.

1. Select **Test run** in the **Debug** panel at the bottom of the editor.

2. Build the request to simulate:

   | Field                            | What it does                                                                                                                                                         |
   | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   | **Requester** (required)         | The user the synthetic request comes from. Your script reads this as `request.requester_id`.                                                                         |
   | **Simulate time**                | Runs the script as if it were a different time of day. Use this to test logic that depends on business hours.                                                        |
   | **Resource or group** (required) | What the synthetic request asks for.                                                                                                                                 |
   | **Access level**                 | The access level requested, for resources that have more than one.                                                                                                   |
   | **Duration** (required)          | **Default** uses the entity's configured duration; **Custom** lets you set days, hours, and minutes. Your script reads this as `request.requested_duration_minutes`. |
   | **Reason**                       | The justification text. Optional here, but required by scripts that gate on `request.reason`.                                                                        |

   <img src="https://mintcdn.com/opalsecurity/p6jR0DbT5YtN9wb9/images/docs/opalscript-editor-3.png?fit=max&auto=format&n=p6jR0DbT5YtN9wb9&q=85&s=15b76c36440ba87d9c1ce16ef7c97e05" alt="The Test run dialog, headed &#x22;Synthetic access request&#x22;. Requester is set to a test user, Simulate time to 10:19 PM, Resource or group to &#x22;Production Database&#x22;, and Access level to &#x22;readonly&#x22;. Duration is set to Custom with 0 days, 1 hour, 0 minutes, and the Reason field is empty. Cancel and Run test buttons are at the bottom." width="3456" height="1868" data-path="images/docs/opalscript-editor-3.png" />

3. Select **Run test** and read the output in the **Debug** panel.

   <img src="https://mintcdn.com/opalsecurity/wRCWGfeB4mjO9nlR/images/docs/opalscript-editor-4.png?fit=max&auto=format&n=wRCWGfeB4mjO9nlR&q=85&s=902c718ad27f4c256643e16ef4b52ea0" alt="The Debug panel after a test run, labeled with the requester and &#x22;Production Database&#x22;. An INFO line reads &#x22;Script started - Low-risk auto-approve - v1&#x22;. Two DEBUG lines show line and column numbers next to the calls made, including a resource_sensitivity lookup returning &#x22;CRITICAL&#x22; and the resulting actions.comment call. A WARN line reads &#x22;completed in 47ms - no decision (no approve/deny)&#x22;." width="3456" height="1868" data-path="images/docs/opalscript-editor-4.png" />

Each line is tagged by level and, for calls your script made, prefixed with the line and column it came from. The closing line gives the runtime and the decision the script reached.

<Warning>
  A run that ends with **no decision (no approve/deny)** did not decide anything. `actions.comment()` leaves a note without deciding, so the request carries on to its normal human reviewers. That's the intended behavior for "send this to a human" branches—but if you expected an approval, this warning is the tell.
</Warning>

## Save a script that's already in use

Saving takes effect immediately—there's no staging step. So when you save a script that live resources, groups, or delegations already depend on, Opal asks you to confirm first.

The dialog is titled **This script is in use** and names what the save will affect:

| Script type          | Message                                                                                                                            |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Request review       | This script reviews access requests for *1 resource*. Saving takes effect immediately and changes how those requests are reviewed. |
| Delegation condition | This script gates *2 delegations*. Saving takes effect immediately and changes which requests they forward.                        |

<img src="https://mintcdn.com/opalsecurity/wRCWGfeB4mjO9nlR/images/docs/opalscript-editor-5.png?fit=max&auto=format&n=wRCWGfeB4mjO9nlR&q=85&s=b2cd05688fe32576915d8b78fb19beaa" alt="The &#x22;This script is in use&#x22; dialog over the editor. It reads &#x22;This script reviews access requests for 1 resource. Saving takes effect immediately and changes how those requests are reviewed.&#x22; Below the text is a View resources link, and Cancel and Save changes buttons." width="3456" height="1868" data-path="images/docs/opalscript-editor-5.png" />

You have three choices:

* **Save changes** applies the new version right away. Every run after that point uses the new logic.
* **Cancel** returns you to the editor with your changes intact but unsaved.
* **View resources** (or **View delegations**) opens the drawer so you can check exactly what's affected before deciding.

The dialog only appears when something live depends on the script. If nothing is attached to it yet, saving is silent. For delegation condition scripts, delegations that have already ended don't count—only delegations still in effect trigger the warning.

<Note>
  Every run uses the latest saved version of the script. A run is never pinned to the version that was current when its request was submitted, so a save can change how in-flight requests are handled.
</Note>

## Versions and diffs

Every save creates a new version. Select the version segment in the metadata row to open the history.

<img src="https://mintcdn.com/opalsecurity/wRCWGfeB4mjO9nlR/images/docs/opalscript-editor-6.png?fit=max&auto=format&n=wRCWGfeB4mjO9nlR&q=85&s=6df80080eae482c9b9603b2c56ac5fdc" alt="The version history popover, expanded over a split diff. A Unified / Split toggle sits at the top. Below it, a Base column of radio buttons lets you pick which version to compare against, with v1 dated Sep 16 and v2 dated Sep 20 tagged LATEST. Behind the popover, the editor shows v1 on the left and v2 on the right, with removed comment lines highlighted. The metadata row reads &#x22;v1 to v2&#x22;." width="3456" height="1868" data-path="images/docs/opalscript-editor-6.png" />

From the history you can:

* Select a version to view it. The editor becomes read-only and **Save** becomes **Restore**.
* Compare two versions. The **Base** column picks what to compare against; **Unified** shows changes inline and **Split** puts the versions side by side. The metadata row changes to `v1 → v2` while you're comparing.
* Select **Restore** to make an earlier version current. Restoring creates a new version rather than deleting the ones after it.

## Unsaved changes

The editor keeps your in-progress edits locally, even if you close the tab. When you come back, a banner tells you that unsaved changes were restored, and you can either save them or select **Discard** to return to the last saved version.

Leaving the editor with unsaved changes prompts you to confirm first.

## See where a script is used

Select the trigger segment (**On Request**) in the metadata row to open the drawer listing everything that causes the script to run. For request review scripts it's titled **Resources & groups**; for delegation condition scripts, **Delegations**.

<img src="https://mintcdn.com/opalsecurity/wRCWGfeB4mjO9nlR/images/docs/opalscript-editor-7.png?fit=max&auto=format&n=wRCWGfeB4mjO9nlR&q=85&s=fdf52a03aed700755ed2cd041c0bf3c9" alt="The Resources & groups drawer open on the right side of the editor. It reads &#x22;This script runs when an access request is made for any of these resources or groups&#x22; above a count of 1 resource or group. A single card is labeled RESOURCE, names &#x22;test (Prod AWS Account)&#x22;, carries DEFAULT, STAGE 1, and AND badges, and lists &#x22;OpalScript: Low-risk auto-approve&#x22; as the reviewer." width="3456" height="1868" data-path="images/docs/opalscript-editor-7.png" />

Each card names the resource or group, the request configuration the script is attached to, the approval stage it sits on, the stage's approval logic, and the other reviewers on that stage.

An empty drawer means nothing triggers the script yet—it won't run until you add it as a reviewer on a resource or group.

<Tip>
  Check this drawer first when a script isn't running. Most of the time the script is fine and simply isn't attached to anything.
</Tip>

## Monitor runs

Select the last-run segment in the metadata row to see this script's execution history, or navigate to **OpalScript** > **Runs** for runs across all scripts.

<img src="https://mintcdn.com/opalsecurity/wRCWGfeB4mjO9nlR/images/docs/opalscript-editor-8.png?fit=max&auto=format&n=wRCWGfeB4mjO9nlR&q=85&s=4d14b10ba51ac97657a14132a1d39162" alt="The OpalScript Runs page listing script executions, with filters for Script, resource or group, Requester, Result, and Run After across the top. Columns are Result, Script, Requester, Assets, Runtime, and Run at, sorted by Run at descending. Every visible row carries a green SUCCESS badge, and the Assets column names each group alongside its source system, such as Okta or Active Directory. Requester names are obscured." width="3456" height="1868" data-path="images/docs/opalscript-editor-8.png" />

Each row is one execution:

| Column        | What it shows                                                          |
| ------------- | ---------------------------------------------------------------------- |
| **Result**    | Whether the run succeeded, failed, or is still pending                 |
| **Script**    | Which script ran                                                       |
| **Requester** | Who submitted the request that triggered it                            |
| **Assets**    | The resources and groups the request was for, with their source system |
| **Runtime**   | How long the script took, in milliseconds                              |
| **Run at**    | When it ran. Sorted newest first by default.                           |

Filter by script, resource or group, requester, result, or date—useful for answering "did this script act on that request?"

### Kill a pending run

A run that is still **pending** shows a red kill button at the end of its row. Selecting it asks you to confirm:

> This stops the OpalScript run and denies its linked request. This can't be undone.

<Warning>
  Killing a run doesn't just stop the script—it **denies the access request** the run was reviewing. The requester will need to submit again.
</Warning>
