# Using Variable Filters & Substitution Modes

Source: https://www.jawsdeploy.net/guides/variable-filters-and-modes

Transform values, render conditional configuration, and decide exactly what a deployment should do with unresolved tokens — without surprising existing workspaces.

Jaws replaces variable references such as `#{VAR.SiteName}` in deployment scripts, step properties, variable values, and supported files inside packages. Project variables use the `VAR.` prefix; built-in deployment values use `CONTEXT.`. **Extended** syntax lets a reference also transform the value or control whether a block is rendered.

This is configured per workspace. Existing workspaces are pinned to **Legacy** during upgrade, preserving the original plain-token behavior. Newly created workspaces default to **Extended** unless their creator explicitly chooses Legacy.

## The two independent workspace settings

- **Substitution syntax**: **Legacy** recognizes plain references such as `#{VAR.Name}` only. **Extended** adds filters, conditionals, and corrected escaping and nested-token handling in package files.
- **Unresolved tokens**: **Silent** leaves missing tokens in place. **Warn** also logs them. **Strict** fails before unresolved output is used.

> **// Recommended rollout - Legacy → Warn → Extended → Strict**
> 
> For an existing workspace, first leave syntax on Legacy and change unresolved tokens to **Warn**. Run representative deployments, define misspelled or missing variables, and escape text that only looks like a token. Update agents used by file-replacement steps. Then switch to Extended. Adopt Strict only after the warning set is clean.

## Configure a workspace

Open **Settings → Workspaces**, edit the workspace, and find **Variable substitution**. Choose the syntax and unresolved-token behavior, then save.

When creating a workspace in the UI, you can choose Legacy or Extended immediately. The REST API and MCP `create_workspace` tool accept the same optional `variableSyntaxMode` value. Omitting it creates an Extended workspace. A missing or invalid stored setting falls back to Legacy, which is the safe direction.

## Create an Extended workspace explicitly

The response includes the selected `variableSyntaxMode`.

```
POST /api/workspace
Authorization: Basic <base64(serviceAccountId:apiKey)>
Content-Type: application/json

{
  "name": "Production",
  "slug": "production",
  "variableSyntaxMode": "Extended"
}
```

## Use the correct variable namespace

Use the complete name in deployment scripts, step properties, and variable values:

- `#{VAR.SiteName}` reads the project variable named `SiteName`.
- `#{CONTEXT.EnvironmentName}` reads the current deployment environment name.
- `STEP.` and `OUTPUT.` references keep their corresponding prefixes.

The only shorthand is in files processed by the package file-replacement helpers: there, a project variable can be written as either `#{VAR.SiteName}` or `#{SiteName}`. The short form is an alias added specifically for file replacement; it does not apply to scripts, step properties, or nested variable values. Prefer the prefixed form everywhere so a reference remains valid when moved.

## Filter syntax

A filter follows the complete variable name after a pipe:

```text
#{VAR.VariableName | FilterName argument}
```

Chain filters with more pipes. They run left to right, after nested references in the variable value have resolved:

```text
#{VAR.SiteName | Trim | ToLower | Replace "[^a-z0-9-]" "-"}
```

Filter names are case-insensitive. Arguments are separated by whitespace; wrap an argument in double quotes when it contains spaces, a pipe, or a closing brace. A variable whose exact name includes the pipe text wins over filter parsing, preserving unusual existing variable names.

## Normalize text

- `ToUpper` — uppercase using invariant culture. `#{VAR.Site | ToUpper}` turns `prod-site` into `PROD-SITE`.
- `ToLower` — lowercase using invariant culture.
- `Trim` — remove whitespace from both ends.
- `Trim start` or `Trim end` — remove whitespace from one end. Other arguments are rejected.

## Replace, slice, truncate, and format

- `Replace "pattern" "replacement"` — regular-expression replacement. The replacement is optional; omit it to remove matches. Example: `#{VAR.Build | Replace "[^0-9.]" ""}`.
- `Substring length` — take `length` characters starting at index 0. `#{VAR.Value | Substring 3}` turns `abcdefgh` into `abc`.
- `Substring start length` — take `length` characters from the zero-based `start` index. Negative or out-of-range values fail the deployment.
- `Truncate length` — keep at most `length` characters and append `...` only when truncation occurs.
- `Format "format"` — apply an invariant .NET numeric or date format, for example `#{VAR.Price | Format N2}` or `#{CONTEXT.DeploymentDate | Format "yyyy/MM/dd"}`. A value that is neither a number nor a date is left unchanged.

## Prepare a value for its destination

- `ToBase64` and `FromBase64` — encode or decode UTF-8 text. Invalid base64 is a syntax error.
- `JsonEscape` — escape the inside of a JSON string literal without adding surrounding quotes.
- `XmlEscape` — escape XML-sensitive characters such as `<`, `>`, and `&`.
- `HtmlEscape` — HTML-encode the value.
- `UriEscape` — percent-encode unsafe characters while keeping URI structure such as `:/?&=` intact.
- `UriDataEscape` — encode a value for one URI component or query parameter, including structural characters.

## Produce True or False for conditions

Comparison filters use ordinal, case-sensitive matching and return the strings `True` or `False`.

- `StartsWith "text"` — whether the value begins with the argument.
- `EndsWith "text"` — whether the value ends with the argument.
- `Contains "text"` — whether the value contains the argument.
- `Match "pattern"` — whether the value matches the supplied regular expression.

## Common configuration transformations

```
# VAR.SiteName = " Production API "
#{VAR.SiteName | Trim | ToLower}                   # production api

# Built-in deployment context
#{CONTEXT.EnvironmentName | ToLower}               # production

# VAR.Host = "api 01"
#{VAR.Host | UriDataEscape}                         # api%2001

# VAR.Version = "v12.4.0"
#{VAR.Version | Replace "^v" "" | StartsWith "12"} # True

# VAR.SettingsJson contains quotes and backslashes
"settings": "#{VAR.SettingsJson | JsonEscape}"
```

## Conditional blocks

Conditionals are available in Extended mode only. A condition without a comparison is truthy unless it is undefined, empty, whitespace, `False` (case-insensitive), or `0`. Missing variables inside a condition are deliberately false and are not reported as unresolved.

```text
#{if VAR.EnableSsl}
https://#{VAR.Host}
#{else}
http://#{VAR.Host}
#{/if}
```

Use `unless` to invert the test:

```text
#{unless VAR.SkipMigrations}
run-migrations=true
#{/unless}
```

Conditions can compare two variables or compare a variable with a double-quoted literal using `==` or `!=`. Equality is ordinal and case-sensitive. An operand may also use filters:

```text
#{if CONTEXT.EnvironmentName == "Production"}prod=true#{/if}
#{if VAR.Region != VAR.DefaultRegion}crossRegion=true#{/if}
#{if CONTEXT.EnvironmentName | Contains "Prod"}protected=true#{/if}
```

Blocks may span lines and nest. Each `if` must close with `#{/if}` and each `unless` with `#{/unless}`. `#{else}` is optional and may appear once per block. Iteration syntax such as `#{each ...}` is not supported and is rejected explicitly.

## Literal token-like text and escaping

Write an extra leading hash when text must remain literal:

```text
##{VAR.Name}                 → #{VAR.Name}
##{VAR.Name | ToUpper}       → #{VAR.Name | ToUpper}
##{if VAR.FeatureEnabled}    → #{if VAR.FeatureEnabled}
```

This matters for templates containing token-like text intended for another templating system. Escaped tokens are not treated as unresolved.

## Unresolved-token modes

- **Silent**: Leave the original token in the output and say nothing. This is the default for Legacy when no explicit policy exists.
- **Warn**: Leave the token in place and add a deployment warning naming the token and where it appeared. This is the default for Extended when no explicit policy exists.
- **Strict**: Fail before the step runs or before transformed package files are written. Use only after warnings have been cleared.

Warnings are deduplicated so the same unresolved value does not flood the deployment log across every step and machine. Tokens in a branch that actually renders are checked; tokens in skipped branches are not. A missing name used only as an `if` or `unless` condition is expected and is not warned about.

An unresolved filtered token such as `#{VAR.Missing | ToUpper}` remains intact and follows the selected policy. A resolved token is transformed only after its complete nested value has been resolved.

## Where substitution runs

The server resolves string values in the deployment step context before dispatch, including scripts and bound step properties. Package-deployment helpers can also replace variables inside files on the agent:

- `Convert-JawsVariableReplace`
- `Convert-JawsJsonHierarchicalVariableReplace`

Extended mode is carried to the agent so package files use the same parser and unresolved-token policy as server-side values. Under Strict, all matching files are transformed in memory and checked before any file is written, avoiding a half-transformed package.

> **// Agent compatibility - Update agents before Extended package-file replacement.**
> 
> An older agent can ignore the new mode fields and would apply Legacy replacement inside package files. Jaws therefore blocks an Extended step that can call either file-replacement helper when the target agent does not advertise variable-filter support. Detection also covers calls made through imported project script modules. Server-side substitution and Legacy workspaces continue normally.

## Authoring mistakes fail loudly in Extended mode

- Unknown filters and missing or extra arguments.
- Invalid numeric arguments, out-of-range substrings, invalid base64, or invalid regular expressions.
- Missing conditions, unclosed blocks, mismatched closing tags, stray `else`, or multiple `else` branches.
- Unsupported iteration syntax.
- An unclosed quoted filter argument prevents the text from being recognized as a token; it remains literal, so inspect token-like text when auditing warnings.

## Move an existing workspace safely

- Keep substitution syntax on Legacy and set unresolved tokens to Warn.
- Run representative deployments for every environment and target role.
- Define or correct genuine missing variables; escape intentional token-like text with `##{...}`.
- Update agents used by steps or script modules that replace variables inside package files.
- Switch syntax to Extended and verify rendered scripts, properties, and package configuration.
- Optionally change unresolved tokens to Strict after the warning set remains empty.

> **Related - Keep reading**
> 
> - [Variables & Secrets](https://www.jawsdeploy.net/features/variables) — scopes, secrets, nesting, and output variables.
> - [Variable Resolution Rules](https://www.jawsdeploy.net/guides/variable-resolution-rules) — which value wins when scopes overlap.
> - [Nested and Referenced Variables](https://www.jawsdeploy.net/guides/nested-and-referenced-variables) — composing values from other variables.
> - [Create a workspace](https://www.jawsdeploy.net/rest-api/workspaces-create) — select `variableSyntaxMode` through the REST API.

