> ## 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.

# Choose a request configuration with OpalScript

> Write request configuration condition scripts in OpalScript to decide which request configuration applies when a user requests a resource or group.

A resource or group can have several [request configurations](https://docs.opal.dev/docs/configure-reviewers.md#approval-workflows), each with its own approval flow. Normally, Opal picks one by checking whether the requester is in certain groups or is requesting certain roles. A request configuration condition script lets you make that choice with logic that groups and roles can't express, such as a user's tags or the access they already hold.

A request configuration condition script doesn't approve or deny anything. It's a read-only check that answers one question: does this request configuration apply to this user and this item? It calls `actions.match()` when it does.

> [!NOTE]
>
> Opal runs the script every time it picks a configuration, including when a user browses the catalog, not only when they submit a request. Each run has a 1-second time limit, so keep scripts short and avoid long loops.

## How Opal picks a configuration

Opal checks a resource's or group's request configurations in priority order, from the top of the list down:

1. For a configuration that uses a script, Opal runs the script. If the script calls `actions.match()`, Opal uses that configuration and stops. If the script ends without calling it, Opal moves on to the next configuration.
2. For a configuration that uses groups and roles, Opal checks those as usual.
3. If nothing matches, Opal uses the default configuration.

A configuration uses **either** a script **or** groups and roles, never both.

> [!WARNING]
>
> Request configuration condition scripts are fail-closed. If a script fails (for example, it raises an error or runs out of time), Opal doesn't fall back to another configuration. The item becomes **unavailable** to that user, and the request form tells them that the configuration's OpalScript condition failed, names the script, and asks them to contact their Opal admin. The error itself isn't shown to the user.

## Write a request configuration condition script

1. Navigate to **OpalScript** > **Editor** and select **+**.
2. Choose **Request configuration** as the script type, then pick **Blank script** or one of the [templates](#templates).
3. Read the user and the item from the `context` module, and call `actions.match()` when the configuration should apply.

```python theme={null}
target_user_id = context.get_target_user_id()

if target_user_id != None:
    user = entity.get_user(target_user_id)
    if user.tags.get("department") == "finance":
        actions.match()
```

### context module

There's no request yet when Opal picks a configuration, so the script can't read a reason, a duration, or a requester. It reads only the following. Each function returns `None` when the value doesn't apply.

| Function | Returns |
| - | - |
| `context.get_target_user_id()` | The ID of the user who would receive the access |
| `context.get_resource_id()` | The ID of the requested resource, or `None` when a group is requested |
| `context.get_group_id()` | The ID of the requested group, or `None` when a resource is requested |
| `context.get_access_level_remote_id()` | The remote ID of the requested access level |
| `context.get_access_level_name()` | The display name of the requested access level |

> [!NOTE]
>
> The catalog asks whether an item can be requested at all, before an access level is chosen. There, `get_access_level_remote_id()` and `get_access_level_name()` return `None`. Write your script so that it still works when there's no access level.

### actions module

actions.match()

Selects this request configuration. This call is terminal: the script ends when it runs. A script that ends without calling `actions.match()` doesn't match.

### Other modules

Request configuration condition scripts can use these [utility modules](https://docs.opal.dev/docs/opalscript-utilitymodules.md):

* `entity`: look up users, groups, and resources, including their tags
* `access`: check whether a user already has access to a resource or group
* `risk`: look up the risk sensitivity of a resource or group
* `time`: work with timestamps

Modules that are slow or that change data, such as `requests`, `http`, `notifications`, `tickets`, and `groups`, aren't available.

## Use a request configuration condition script

1. Open the resource or group, then edit its request configuration. You can't use a script on the default configuration.
2. Under **Match by OpalScript**, select your script. If the configuration already has groups or roles selected, clear them first.

   ![The Edit dialog for a resource, open on a request configuration. The Match by groups and roles section, with empty Requesting groups and Roles fields, sits above a divider labeled "Or use an OpalScript instead". Below the divider, a red box highlights the Match by OpalScript section and its "Select a script" dropdown. The Approval Flow section follows, with Cancel and Save buttons at the bottom right.](https://mintcdn.com/opalsecurity/w6jmchuLZ3tbYW04/images/docs/opalscript-request-configuration-1.png?fit=max\&auto=format\&n=w6jmchuLZ3tbYW04\&q=85\&s=ff9965f317976f58665a6751d6f5363c)
3. Save the configuration, and check its priority. Opal runs scripts in priority order, so a configuration with a script only takes effect if no configuration above it matches first.

To see which request configurations use a script, open the script in the editor and select **On Request Configuration** in the metadata row. See [See where a script is used](https://docs.opal.dev/docs/opalscript-editor.md#see-where-a-script-is-used).

## Test a request configuration condition script

Use **Test run** in the editor, as described in [Test a script before you use it](https://docs.opal.dev/docs/opalscript-editor.md#test-a-script-before-you-use-it). The last line of the **Debug** panel says whether the script matched: **configuration applies** or **configuration does not apply**.

When a user submits a request, Opal records each condition script run, and you can find them under **OpalScript** > **Runs**.

## Templates

Match by user tag

Apply the configuration when one of the user's tags, such as a location synced from your identity provider, holds one of the listed values. Edit the two constants at the top.

```python theme={null}
USER_TAG_KEY = "state"  # tag key on the user profile, as synced from your identity provider
MATCH_VALUES = ["california"]  # lowercase; abbreviations such as "ca" need their own entries

target_user_id = context.get_target_user_id()

if target_user_id != None:
    user = entity.get_user(target_user_id)
    raw_tag_value = user.tags.get(USER_TAG_KEY)

    # A tag key can hold a single value, None, or a list of values.
    if raw_tag_value == None:
        tag_values = []
    elif type(raw_tag_value) == "list":
        tag_values = raw_tag_value
    else:
        tag_values = [raw_tag_value]

    normalized_values = [str(value).strip().lower() for value in tag_values]
    has_matching_value = any([value in MATCH_VALUES for value in normalized_values])

    if has_matching_value:
        actions.match()
```

Match conflicting access

Apply the configuration when the user already holds a resource or group that conflicts with the item being requested, for separation of duties. Pair it with a stricter configuration, such as one with an extra approval stage, instead of blocking the request outright.

Because the script also runs when the catalog loads, keep `CONFLICTS` to a handful of entries.

```python theme={null}
# Requested resource or group ID -> IDs it conflicts with.
CONFLICTS = {
    "REPLACE-WITH-REQUESTED-ITEM-ID": ["REPLACE-WITH-CONFLICTING-ITEM-ID"],
}

target_user_id = context.get_target_user_id()
item_id = context.get_resource_id() or context.get_group_id()

if target_user_id != None and item_id != None:
    for conflicting_id in CONFLICTS.get(item_id, []):
        if access.check_access(target_user_id, conflicting_id):
            actions.match()
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.