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
#{VAR.Name} only. Extended adds filters, conditionals, and corrected escaping and nested-token handling in package files.
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 namedSiteName.#{CONTEXT.EnvironmentName}reads the current deployment environment name.STEP.andOUTPUT.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:
#{VAR.VariableName | FilterName argument}
Chain filters with more pipes. They run left to right, after nested references in the variable value have resolved:
#{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}turnsprod-siteintoPROD-SITE.ToLower— lowercase using invariant culture.Trim— remove whitespace from both ends.Trim startorTrim 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— takelengthcharacters starting at index 0.#{VAR.Value | Substring 3}turnsabcdefghintoabc.Substring start length— takelengthcharacters from the zero-basedstartindex. Negative or out-of-range values fail the deployment.Truncate length— keep at mostlengthcharacters 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
ToBase64andFromBase64— 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.
#{if VAR.EnableSsl}
https://#{VAR.Host}
#{else}
http://#{VAR.Host}
#{/if}
Use unless to invert the test:
#{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:
#{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:
##{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
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-JawsVariableReplaceConvert-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.
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 multipleelsebranches. - 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.