# Variable Replacement in Files

Source: https://www.jawsdeploy.net/guides/variable-replacement-in-files

Push resolved variable values into your config files at deploy time - with token placeholders, JSON hierarchy matching, and globbed file lists.

Defining a variable in Jaws Deploy is only half the job. A variable like `ConnectionStrings:Default` or `Cache.Endpoint` lives in the Jaws database, scoped to a workspace, project, environment, tag, or target. At deploy time you usually need that value to land *inside a file* on the target machine - an `appsettings.json`, a `web.config`, an `.env`, a YAML manifest, a plain text template. This guide explains the three mechanisms Jaws Deploy uses to push variable values into files, and how to configure the file lists they run against.

All three are configured on the **Deploy package** step template, under its **Transformations** property group. If you assemble your own step templates, the same building blocks are available as the `Convert-JawsVariableReplace`, `Convert-JawsJsonHierarchicalVariableReplace`, and `Convert-JawsXmlTransform` / `Convert-JawsJsonTransform` functions from the built-in `JawsCommon` module.

> **// Mental model - Variables live in Jaws. Replacement writes them into your files.**
> 
> Your build artifact ships with placeholders or with default values. During deployment, Jaws resolves each variable for the current environment and target (see [Variable Resolution Rules](https://www.jawsdeploy.net/guides/variable-resolution-rules)), then rewrites the files you nominate so the deployed copy carries real, environment-specific values - without rebuilding the package.

## The three mechanisms

## Where you configure this

Open the **Deploy package** step in your deployment process and expand the **Transformations** group. You will find three multi-line fields, each taking a newline-separated list of files:

- **Config transforms** - `[json|xml]: source => target` transform definitions.
- **Replace variables in files** - files to run token-based `#{...}` replacement against.
- **Replace variables in JSON files located based on hierarchical variable names** - JSON files to run hierarchy-based replacement against.

All paths are **relative to the package root** (the extracted contents of your package), and all three fields support **globbing**. The step runs them in a fixed order with optional custom-script hooks in between - see [Processing order](#processing-order) below.

## Listing and globbing files

Each Transformations field is a list, **one entry per line**. Blank lines are ignored and surrounding whitespace is trimmed, so you can format the list for readability.

Paths are resolved against the package root, then expanded as **globs** before any replacement happens. Globbing means you do not have to enumerate every file:

- `*` matches any run of characters within a single path segment.
- `**` matches across directory boundaries (any depth).
- A literal path with no wildcards matches exactly one file.

If a glob matches several files, the replacement runs against every match. If it matches nothing, that line is simply a no-op - which is also the most common reason a replacement "silently does nothing": the path is wrong relative to the package root, or the build did not include the file.

## Examples of file list entries

Each line is relative to the package root. Use backslashes (Windows agents) consistently.

```
# exact file
appsettings.json

# every appsettings.{env}.json in the root
appsettings.*.json

# every web.config at any depth
**\web.config

# every .json under config\, any depth
config\**\*.json

# a single nested file
services\api\appsettings.Production.json
```

## Token-based replacement

List a file under **Replace variables in files** and Jaws scans its text for `#{...}` tokens, replacing each one with the resolved variable value. It works on **any text file** - JSON, XML, YAML, `.env`, `.conf`, `.ps1`, an HTML template - because it is a pure text substitution.

A few rules worth knowing:

- **Prefixed and bare names both work.** User variables are stored internally with a `VAR.` prefix. A variable you named `ApiBaseUrl` can be referenced as either `#{ApiBaseUrl}` or `#{VAR.ApiBaseUrl}`. Use the bare form unless you need to disambiguate from a system parameter.
- **Names are restricted to a token charset.** A token name may contain letters, digits, and the characters `. - _ :` and spaces. Anything else ends the token, so `#{My-Var.v2}` is valid but `#{a+b}` is not treated as a token.
- **Nested / recursive resolution.** If a variable value itself contains `#{...}`, it is resolved too. `installPath = C:\#{appName}\#{version}` expands fully. Circular references are detected and fail the deployment rather than looping.
- **Unknown tokens are left untouched.** If no variable matches, the literal `#{...}` text stays in the file - a useful signal that something is misspelled or out of scope.

## Before and after

File listed under "Replace variables in files". Variables resolved for the Production environment.

```
// appsettings.json  (in the package, with tokens)
{
  "ApiBaseUrl": "#{ApiBaseUrl}",
  "ConnectionStrings": {
    "Default": "Server=#{DbHost};Database=#{DbName};"
  },
  "FeatureFlags": { "NewCheckout": "#{Features.NewCheckout}" }
}

// after deployment to Production
{
  "ApiBaseUrl": "https://api.example.com",
  "ConnectionStrings": {
    "Default": "Server=prod-sql-01;Database=shop;"
  },
  "FeatureFlags": { "NewCheckout": "true" }
}
```

> **// Escaping - Need a literal #{...} in the file?**
> 
> Double the leading hash. `##{thing}` is the escape sequence: Jaws emits a literal `#{thing}` and skips substitution. This matters when your file legitimately contains `#{...}` syntax meant for another tool (some templating engines, shell here-docs, etc.).

## Hierarchy-based replacement (JSON)

List a JSON file under **Replace variables in JSON files located based on hierarchical variable names** and Jaws takes a different approach: instead of looking for placeholders, it **locates existing nodes by name**. The variable name maps to a JSON path, where each colon (`:`) is one level of nesting.

To set the value of `bar` in `{ "foo": { "bar": 123 } }`, create a variable named `foo:bar` with the new value. Jaws walks the document, finds the node at `foo` -> `bar`, and replaces it. No edit to the file's source is needed - the JSON you built ships unmodified and Jaws rewrites the matching nodes at deploy time.

More rules:

- **Only user variables participate.** Hierarchy replacement considers variables with the internal `VAR.` prefix - i.e. the variables you defined. System parameters are ignored.
- **Missing nodes are skipped.** If the JSON has no node at the variable's path, nothing happens for that variable. Hierarchy replacement never *adds* keys; it only updates ones that already exist.
- **Scalars replace scalars.** A string, number, or boolean value replaces the target value in place, preserving JSON type where possible.
- **You can inject whole objects/arrays.** If the target node is an array or object and your variable's value is a string of valid JSON, Jaws parses it and substitutes the parsed structure. If the value is not valid JSON in that case, the deployment fails with a clear error.

## Variable names map to JSON paths

File listed under the hierarchical-replacement field. The JSON ships with real defaults - no tokens.

```
// appsettings.json  (shipped as-is, no placeholders)
{
  "Logging": { "LogLevel": { "Default": "Information" } },
  "AllowedHosts": "*",
  "Cors": { "Origins": ["http://localhost:3000"] }
}

// Jaws variables defined for Production:
//   Logging:LogLevel:Default = "Warning"
//   AllowedHosts             = "app.example.com"
//   Cors:Origins             = ["https://app.example.com"]   (value is valid JSON)

// after deployment to Production
{
  "Logging": { "LogLevel": { "Default": "Warning" } },
  "AllowedHosts": "app.example.com",
  "Cors": { "Origins": ["https://app.example.com"] }
}
```

> **// Token vs hierarchy - Which one should I use?**
> 
> Use **token-based** when you control the file and are happy to drop `#{...}` placeholders into it - it is the most flexible and works on any text format. Use **hierarchy-based** when you cannot or do not want to template the file (for example a stock `appsettings.json` you would rather ship unedited), and the values you need to change already exist as JSON nodes. They are not exclusive: you can list the same JSON file in both fields and let token replacement run first, then hierarchy replacement tidy up the structured nodes.

## Processing order

The Deploy package step always runs the transformations in the same order, so you can layer them predictably. Between each stage there is a custom-script hook you can use for anything the built-in steps do not cover:

1. **Extract package** to the work folder.
2. *Hook: After package is extracted.*
3. **Config transforms** (XDT / JSON merge) - restructure files first.
4. *Hook: After config transform is applied.*
5. **Token replacement** - `#{...}` substitution across the listed files.
6. **JSON hierarchy replacement** - locate-and-replace by variable name.
7. *Hook: After variables are replaced in files.*
8. **Copy to installation directory** (optionally purging it first).
9. *Hook: After package is deployed.*

Because token replacement runs before hierarchy replacement, a `#{...}` token can supply the value that a later hierarchy pass reads - but not the other way round. Keep that ordering in mind when a file is processed by more than one mechanism.

## Common mistakes

- **Wrong base path.** All file lists are relative to the *package root*, not the agent working directory or the install directory. Match the folder layout inside the package.
- **Glob matched nothing.** A typo or a file the build excluded means the line is a silent no-op. Check the deployment log - each processed file is logged by name.
- **Expecting hierarchy replacement to add keys.** It only updates nodes that already exist. To introduce a brand-new section, use a config (JSON merge) transform instead.
- **Secret values in plain files.** Replacement writes the resolved value to disk in clear text. That is expected for config, but do not point replacement at files that get committed or shipped back to an artifact store.
- **Forgetting the escape.** A genuine `#{...}` that belongs to another tool will be eaten by token replacement unless you write it as `##{...}`.

> **Next - Related reading**
> 
> - [The Power of Variables](https://www.jawsdeploy.net/guides/the-power-of-variables) - why variables sit at the centre of the Jaws model.
> - [Variable Resolution Rules](https://www.jawsdeploy.net/guides/variable-resolution-rules) - how a value is chosen when several scopes match.
> - [Nested and Referenced Variables](https://www.jawsdeploy.net/guides/nested-and-referenced-variables) - composing variables from other variables.

