> 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.

# Variables, locals, and outputs

> Typed inputs, collection values, isolated module arguments, and catalog outputs.

## Variables and external inputs

Declare an input type and optionally a literal default. Omitting the default makes the input required. Root inputs come from a YAML or JSON `--vars-file`, a [data hierarchy](/guides/data-hierarchy), or both; module inputs come from the caller's arguments. A `.json` filename selects strict JSON; other names select YAML. No automatic discovery occurs.

```hcl
variable "message" {
  type        = string
  default     = "managed by rivet"
  nullable    = false
  sensitive   = false
  description = "Text written to the managed file"
}

file "message" {
  path    = "/etc/rivet-message"
  content = "${var.message}\n"
}
```

To override that default, save this as `inputs.yaml`:

```yaml
message: production configuration
```

The equivalent JSON object is `{"message":"production configuration"}` in a file ending in `.json`.

```sh
rivet validate ./configuration --vars-file inputs.yaml
rivet compile ./configuration --vars-file inputs.yaml --facts facts.json --out host.bundle
```

| Attribute     | Default  | Rule                                                                                    |
| ------------- | -------- | --------------------------------------------------------------------------------------- |
| `type`        | Required | Scalar, resource handle, or collection type below.                                      |
| `default`     | Omitted  | Literal typed value; omission requires an input. References/functions are not defaults. |
| `nullable`    | `true`   | Literal boolean; false rejects a null input/default.                                    |
| `sensitive`   | `false`  | Literal boolean; sensitivity propagates into derived values and collection keys.        |
| `description` | Omitted  | Literal string.                                                                         |

The hierarchy resolves low-to-high priority, then `--vars-file` replaces each supplied variable completely. HCL defaults apply only when no source supplies the variable; they do not fill partial hierarchy objects. Supplied values override defaults. Explicit null follows `nullable`; it does not fall back to the default. Unknown inputs, duplicate input mapping keys at any depth, wrong types, fractional/out-of-range input numbers, and implicit coercions fail. Input files must be regular YAML/JSON files with string keys and exactly one document. Duplicate keys, custom tags, non-finite numbers, and excessive alias expansion fail. The manifest, layers, decrypted output, and override file share a 16 MiB read budget. Quote numeric-looking strings such as modes (`"0644"`). Data files cannot fabricate a resource handle.

There is no environment-variable input, automatic variable-file discovery, `--var`, or variable validation block. Protect input files containing secrets alongside the resulting plaintext bundle. SOPS layers automatically mark supplied variables sensitive; the mark survives later plaintext overrides. `validate --explain-inputs` reports value-free provenance. See [hierarchies and SOPS](/guides/data-hierarchy).

## Types

| Type                                                 | Meaning                                                                                             |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `string`, `bool`, `number`                           | Scalars; input and literal numbers are signed 64-bit integers. Numeric facts may be number outputs. |
| `resource("file")`                                   | A handle of a registered resource type; available for module arguments and outputs.                 |
| `list(T)`                                            | Ordered homogeneous elements.                                                                       |
| `set(T)`                                             | Deduplicated elements, deterministically sorted; equal values merge provenance/sensitivity.         |
| `map(T)`                                             | String keys with homogeneous values.                                                                |
| `object({ name = string, note = optional(string) })` | Exact named members. Unknown members fail; missing optional members normalize to null.              |
| `tuple([string, number])`                            | Ordered positions with exact length and positional types.                                           |

`optional(T)` is only an object-member annotation. Collections may contain nested types or resource handles; the bundle reader validates output handles recursively. Lists/tuples preserve order. Top-level nullability is controlled by the variable policy. External inputs reject null collection elements, map values, and required object fields; optional object members may be omitted or null. Partial deep-merge layers normalize optional omissions only after final resolution. Module arguments and outputs and optional resource fields retain their own typed policies.

```hcl
variable "accounts" {
  type = map(object({ home = string, note = optional(string) }))
  default = { app = { home = "/var/lib/app" } }
}

variable "names" {
  type    = set(string)
  default = ["app", "ops", "app"]
}

output "account_home" {
  type  = string
  value = var.accounts["app"].home
}

output "names" {
  type  = set(string)
  value = var.names
}
```

## Locals

```hcl
locals {
  message_path = "/etc/rivet-message"
  text         = "Target ${facts.distribution}"
}

file "summary" {
  path    = local.message_path
  content = local.text
}
```

Locals are immutable expressions referenced as `local.name`. They support forward references, collections, handles, and sensitivity and dependency propagation. Duplicate names and cycles fail. Each module has private locals and variables; a child cannot implicitly read the parent.

## Outputs

```hcl
file "config" {
  path    = "/etc/rivet-config"
  content = "enabled\n"
}

output "config_path" {
  type        = string
  value       = file.config.path
  description = "Where the config is managed"
}

output "config_resource" {
  type  = resource("file")
  value = file.config
}
```

Outputs require `type` and `value`, with optional literal description and sensitivity policies. They are compile-time values, not execution results. A child module's outputs are its interface, read as `module.name.output`; only root outputs enter the bundle. Whole-module references cannot be outputs.

There is no `rivet output` command. `graph` excludes output values. Sensitive outputs remain confidential plaintext inside the protected bundle; `sensitive = false` does not clear inherited sensitivity. See [modules](/guides/modules) and [expansion](/language/expansion).