> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://rivet.chrisboulton.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://rivet.chrisboulton.com/_mcp/server.

# Templates and heredocs

> Render configuration text with interpolation, conditionals, loops, and confined files.

Templates render during compilation. Only resulting values and asset bytes enter the bundle; applying needs neither a template engine nor source files. Use `jsonencode` or `yamlencode` for structured formats, and templates for formats such as INI or unit files.

## Inline templates and heredocs

Quoted strings and heredocs use the same renderer. `${expression}` converts strings, finite numbers, and booleans to text. Null, collections, and handles require explicit encoding or conversion. `<<EOF` preserves indentation; `<<-EOF` permits indentation stripping.

```hcl
variable "enabled" {
  type = bool
  default = true
}
file "settings" {
  path = "/etc/rivet-template.conf"
  content = <<-EOF
    # Managed by Rivet
    enabled=${var.enabled}
    %{ if var.enabled ~}
    %{ for name in ["app", "ops"] ~}
    account=${name}
    %{ endfor ~}
    %{ else ~}
    disabled=true
    %{ endif ~}
  EOF
}
```

## File templates

Create `configuration/assets/config.tftpl`:

**`assets/config.tftpl`**

```text title="assets/config.tftpl"
# Managed by Rivet
%{ for key, value in settings ~}
${key}=${value}
%{ endfor ~}
```

Call it from `configuration/main.hcl`:

```hcl
locals {
  settings = { address = cidrhost("192.0.2.0/24", 10), enabled = true }
}
file "settings" {
  path = "/etc/rivet-template.ini"
  content = templatefile("assets/config.tftpl", { settings = local.settings })
  mode = "0644"
}
```

`templatefile(path, vars)` exposes only the string-keyed `vars` map, accessed as bare names such as `settings`. It does not implicitly expose `var`, `local`, resources, facts, or caller loop variables. Pass anything it needs in `vars`. Functions are still available. Local reads resolve against the declaring expression's source root, including inside child modules, and preserve source provenance when a resource attribute supplies a template string. Template files are regular UTF-8 files subject to the [confined read rules](/language/functions/filesystem); recursive inclusion fails.

The repository fixture is runnable without host mutation:

```sh
rivet validate examples/templates
rivet compile examples/templates --facts examples/file/target.json --out templates.bundle
```

## String templates and escapes

`templatestring(text, vars)` uses the same isolated scope and accepts evaluated strings directly. This differs from Terraform's reference-only restriction. Quoted HCL text undergoes its own rendering pass, so escape markers intended for the second pass:

```hcl
file "greeting" {
  path = "/etc/rivet-greeting"
  content = templatestring("Hello, $${name}!", { name = "world" })
}
```

`$${` emits literal `${`, and `%%{` emits literal `%{`. To write a directive for `templatestring`, use `%%{ if enabled }` in the outer HCL string.

## Directives and whitespace

| Form                                                | Behavior                                                |
| --------------------------------------------------- | ------------------------------------------------------- |
| `%{ if condition } ... %{ else } ... %{ endif }`    | Boolean condition, optional else, selected branch only. |
| `%{ for value in collection } ... %{ endfor }`      | Iterate a list, set, or map; maps use sorted key order. |
| `%{ for key, value in collection } ... %{ endfor }` | Map key and value or list index and value bindings.     |
| `%{~ ... }` and `%{ ... ~}`                         | Strip adjacent whitespace before or after a marker.     |
| `${~ expression ~}`                                 | Interpolation with adjacent whitespace stripping.       |

Directives can nest. Loop variables have lexical scope and can shadow outer bindings; the original binding is restored after the loop. Inline HCL templates retain configuration namespaces, while `templatefile` and `templatestring` see only their `vars` map. Directive conditions and collections accept any [expression](/language/expressions), including operators and `for` expressions. Sets bind each element as both key and value.

Templates preserve dependency and sensitivity provenance. Rendering is bounded to 16 MiB, depth 128, and 100,000 cumulative loop iterations per compilation. Syntax errors report safe source locations and categories without template text or rendered values. See [functions](/language/functions#provenance-and-limits).