***

title: Common DSL & Mapper Patterns
position: 3
excerpt: Recipes for the patterns that come up most in real Agent Studio builds.
deprecated: false
hidden: false
---------------------

For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.moveworks.com/agent-studio/configuration-languages/llms.txt. For full documentation content, see https://docs.moveworks.com/agent-studio/configuration-languages/llms-full.txt.

Practical recipes for DSL and Data Mapper patterns that come up constantly in real builds. Each pattern includes a use case and a working example you can adapt.

<Callout intent="info">
  For the full function references, see the [DSL Reference](/agent-studio/core-platform/configuration-languages/moveworks-dsl-reference) and the [Data Mapper Reference](/agent-studio/core-platform/configuration-languages/moveworks-bender-language-reference).
</Callout>

# Data Mapper Patterns

## Omit null or empty keys from a payload

APIs often reject requests with `null` fields. Use `EVAL()` with `$FILTER` to build an object and strip out any keys that are null or empty before sending.

**Use case:** Building a calendar event payload where `recurrence` and `location` are optional.

```yaml
event_payload:
  EVAL():
    args:
      obj:
        attendees: data.attendees
        description: data.event_description
        end: data.end_time
        location: data.location_display_name
        recurrence: data.recurrence
        start: data.start_time
        summary: data.title
    expression: obj.$FILTER(val => val)
```

If `location` and `recurrence` are null, they disappear from the payload entirely.

## Transform a list of objects

Use `MAP()` to reshape each item in an array — rename fields, flatten nested values, or add computed fields.

**Use case:** Rename API field names to user-friendly names for SDA.

```yaml
accounts:
  MAP():
    items: data.raw_accounts
    converter:
      account_name: item.Name
      annual_revenue: item.AnnualRevenue__c
      owner_email: item.Owner.Email
      stage: item.StageName
```

## Conditionally include a field

Use `CONDITIONAL()` to include a field only when a condition is met — useful for building dynamic query filters.

**Use case:** Only add a date filter to a SOQL query when the user provided a date.

```yaml
query_parts:
  date_filter:
    CONDITIONAL():
      condition: data.close_date_before != "ANY"
      on_pass: $CONCAT(["CloseDate >= ", data.close_date_before])
      on_fail: '""'
```

## Merge data from multiple actions

Use `MERGE()` to combine objects from different action outputs into a single result — useful for compound actions that fetch from multiple sources.

**Use case:** Combine user data from one action with company data from another.

```yaml
return:
  output_mapper:
    enriched_users:
      MAP():
        items: data.user_list
        converter:
          MERGE():
            - item
            - company: data.company_info.name
              office: data.company_info.hq_address
```

## Map a code to a human-readable label

Use `LOOKUP()` when an API returns status codes or category IDs that need to be translated for the user.

**Use case:** Convert ServiceNow incident state numbers to labels.

```yaml
status_label:
  LOOKUP():
    key: data.incident.state
    mapping:
      '1': "'New'"
      '2': "'In Progress'"
      '3': "'On Hold'"
      '6': "'Resolved'"
      '7': "'Closed'"
    default: "'Unknown'"
```

## Build a dynamic URL

Use `RENDER()` or `$CONCAT` to construct URLs from dynamic data — for linking back to source records.

**Use case:** Generate a clickable link to a ServiceNow ticket.

```yaml
ticket_url:
  RENDER():
    template: "https://{{instance}}.service-now.com/nav_to.do?uri=incident.do?sys_id={{sys_id}}"
    args:
      instance: data.snow_instance
      sys_id: data.incident.sys_id
```

## Sort results

Use `SORT()` to order an array by a field.

```yaml
sorted_tickets:
  SORT():
    items: data.tickets
    key: item.created_at
```

Add `reverse: 'true'` for descending order.

# DSL Patterns

## Validate a date is in the future

Use in a slot validation policy to reject past dates.

```
$PARSE_TIME(value) >= $TIME()
```

## Validate a date is within a range

Check that a date is at least 7 days from now (e.g., for travel booking lead time).

```
$PARSE_TIME(value) >= $TIME().$ADD_DATE(0, 0, 7)
```

## Check if a value is in a list

Use `in` for allowlist/blocklist validation.

```
value in ["US", "CA", "UK", "DE"]
```

Or with `$ANY` for more complex matching:

```
["urgent", "critical", "blocker"].$ANY(level => data.priority.$LOWERCASE() == level)
```

## String formatting and transformation

Chain string functions for normalization:

```
data.user_input.$TRIM().$LOWERCASE()
```

Build a full name from parts:

```
$CONCAT([data.first_name, " ", data.last_name])
```

## Serialize an object to a string

When passing structured data to `mw.generate_text_action` (which only accepts string input), use `$STRINGIFY_JSON`:

```
$STRINGIFY_JSON(data.payload)
```

## Count items matching a condition

```
data.tickets.$FILTER(t => t.status == "open").$LENGTH()
```

## Conditional logic in compound action switch expressions

Use comparison operators in `switch` conditions:

```yaml
- switch:
    cases:
      - condition: data.amount < 500
        steps:
          - action:
              action_name: auto_approve
              output_key: approval_result
      - condition: data.amount >= 500 AND data.amount < 5000
        steps:
          - action:
              action_name: manager_approval
              output_key: approval_result
    default:
      steps:
        - action:
            action_name: vp_approval
            output_key: approval_result
```

# Combined Patterns

These patterns use both DSL expressions inside Data Mappers.

## Dynamic message with data from multiple sources

Use `RENDER()` (mapper) with DSL expressions in the args:

```yaml
message:
  RENDER():
    template: |
      Hi {{name}}, your {{request_type}} request ({{id}}) has been {{status}}.
      {{comment}}
    args:
      name: data.user.first_name
      request_type: data.request.type.$TITLECASE()
      id: data.request.display_id
      status: data.approval_result.status.$LOWERCASE()
      comment:
        CONDITIONAL():
          condition: data.approval_result.denied_by[0].comment != null
          on_pass: $CONCAT(["Reason: ", data.approval_result.denied_by[0].comment])
          on_fail: '""'
```

## Filter and transform in one step

Use DSL `$FILTER` inside a mapper `MAP()`:

```yaml
active_high_value_accounts:
  MAP():
    items: data.accounts.$FILTER(a => a.status == "Active" AND a.annual_revenue > 100000)
    converter:
      name: item.name
      revenue: item.annual_revenue
      owner: item.owner_email
```