| --- | |
| description: >- | |
| Learn about the JSON-compatible language syntax, including file structure, | |
| expression mapping, block mapping, and block-type-specific exceptions. | |
| --- | |
| # JSON Configuration Syntax | |
| Most OpenTofu configurations are written in | |
| [the native OpenTofu language syntax](../../language/syntax/configuration.mdx), which is designed to be | |
| relatively easy for humans to read and update. | |
| OpenTofu also supports an alternative syntax that is JSON-compatible. This | |
| syntax is useful when generating portions of a configuration programmatically, | |
| since existing JSON libraries can be used to prepare the generated | |
| configuration files. | |
| The JSON syntax is defined in terms of the native syntax. Everything that can | |
| be expressed in native syntax can also be expressed in JSON syntax, but some | |
| constructs are more complex to represent in JSON due to limitations of the | |
| JSON grammar. | |
| OpenTofu expects native syntax for files named with a `.tf` or `.tofu` suffix, | |
| and JSON syntax for files named with a `.tf.json` or `.tofu.json` suffix. | |
| The low-level JSON syntax, just as with the native syntax, is defined in terms | |
| of a specification called _HCL_. It is not necessary to know all of the details | |
| of HCL syntax or its JSON mapping in order to use OpenTofu, and so this page | |
| summarizes the most important differences between native and JSON syntax. | |
| If you are interested, you can find a full definition of HCL's JSON syntax | |
| in [its specification](https://github.com/hashicorp/hcl/blob/main/json/spec.md). | |
| ### Extension Precedence | |
| When both `.tf.json` and `.tofu.json` files with the same base name are present | |
| in a directory, OpenTofu will prioritize the `.tofu.json` file and ignore the | |
| `.tf.json` file. For example: | |
| - If both `foo.tf.json` and `foo.tofu.json` exist in the same directory, | |
| OpenTofu will only load `foo.tofu.json` and ignore `foo.tf.json`. | |
| This ensures that `.tofu.json` files always take precedence over `.tf.json` | |
| files when both are available. This scenario can be useful for module authors | |
| who want their modules to support both OpenTofu and Terraform. | |
| ## JSON File Structure | |
| At the root of any JSON-based OpenTofu configuration is a JSON object. The | |
| properties of this object correspond to the top-level block types of the | |
| OpenTofu language. For example: | |
| ```json | |
| { | |
| "variable": { | |
| "example": { | |
| "default": "hello" | |
| } | |
| } | |
| } | |
| ``` | |
| Each top-level object property must match the name of one of the expected | |
| top-level block types. Block types that expect labels, such as `variable` | |
| shown above, are represented by one nested object value for each level | |
| of label. `resource` blocks expect two labels, so two levels of nesting | |
| are required: | |
| ```json | |
| { | |
| "resource": { | |
| "aws_instance": { | |
| "example": { | |
| "instance_type": "t2.micro", | |
| "ami": "ami-abc123" | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| After any nested objects representing the labels, finally one more nested | |
| object represents the body of the block itself. In the above examples, the | |
| `default` argument for `variable "example"` and the `instance_type` and | |
| `ami` arguments for `resource "aws_instance" "example"` are specified. | |
| Taken together, the above two configuration files are equivalent to the | |
| following blocks in the native syntax: | |
| ```hcl | |
| variable "example" { | |
| default = "hello" | |
| } | |
| resource "aws_instance" "example" { | |
| instance_type = "t2.micro" | |
| ami = "ami-abc123" | |
| } | |
| ``` | |
| Within each top-level block type the rules for mapping to JSON are slightly | |
| different (see the [block-type-specific exceptions](#block-type-specific-exceptions) below), but the following general rules apply in most cases: | |
| * The JSON object representing the block body contains properties that | |
| correspond either to argument names or to nested block type names. | |
| * Where a property corresponds to an argument that accepts | |
| [arbitrary expressions](../../language/expressions/index.mdx) in the native syntax, the | |
| property value is mapped to an expression as described under | |
| [_Expression Mapping_](#expression-mapping) below. For arguments that | |
| do _not_ accept arbitrary expressions, the interpretation of the property | |
| value depends on the argument, as described in the | |
| [block-type-specific exceptions](#block-type-specific-exceptions) | |
| given later in this page. | |
| * Where a property name corresponds to an expected nested block type name, | |
| the value is interpreted as described under | |
| [_Nested Block Mapping_](#nested-block-mapping) below, unless otherwise | |
| stated in [the block-type-specific exceptions](#block-type-specific-exceptions) | |
| given later in this page. | |
| ## Expression Mapping | |
| Since JSON grammar is not able to represent all of the OpenTofu language | |
| [expression syntax](../../language/expressions/index.mdx), JSON values interpreted as expressions | |
| are mapped as follows: | |
| | JSON | OpenTofu Language Interpretation | | |
| |---------|-------------------------------------------------------------------------------------------------------------------| | |
| | Boolean | A literal `bool` value. | | |
| | Number | A literal `number` value. | | |
| | String | Parsed as a [string template](../expressions/strings.mdx#string-templates) and then evaluated as described below. | | |
| | Object | Each property value is mapped per this table, producing an `object(...)` value with suitable attribute types. | | |
| | Array | Each element is mapped per this table, producing a `tuple(...)` value with suitable element types. | | |
| | Null | A literal `null`. | | |
| When a JSON string is encountered in a location where arbitrary expressions are | |
| expected, its value is first parsed as a [string template][] | |
| and then it is evaluated to produce the final result. | |
| If the given template consists _only_ of a single interpolation sequence, | |
| the result of its expression is taken directly, without first converting it | |
| to a string. This allows non-string expressions to be used within the | |
| JSON syntax: | |
| ```json | |
| { | |
| "output": { | |
| "example": { | |
| "value": "${aws_instance.example}" | |
| } | |
| } | |
| } | |
| ``` | |
| The `output "example"` declared above has the object value representing the | |
| given `aws_instance` resource block as its value, rather than a string value. | |
| This special behavior does not apply if any literal or control sequences appear | |
| in the template; in these other situations, a string value is always produced. | |
| ## Nested Block Mapping | |
| When a JSON object property is named after a nested block type, the value | |
| of this property represents one or more blocks of that type. The value of | |
| the property must be either a JSON object or a JSON array. | |
| The simplest situation is representing only a single block of the given type | |
| when that type expects no labels, as with the `lifecycle` nested block used | |
| within `resource` blocks: | |
| ```json | |
| { | |
| "resource": { | |
| "aws_instance": { | |
| "example": { | |
| "lifecycle": { | |
| "create_before_destroy": true | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| The above is equivalent to the following native syntax configuration: | |
| ```hcl | |
| resource "aws_instance" "example" { | |
| lifecycle { | |
| create_before_destroy = true | |
| } | |
| } | |
| ``` | |
| When the nested block type requires one or more labels, or when multiple | |
| blocks of the same type can be given, the mapping gets a little more | |
| complicated. For example, the `provisioner` nested block type used | |
| within `resource` blocks expects a label giving the provisioner to use, | |
| and the ordering of provisioner blocks is significant to decide the order | |
| of operations. | |
| The following native syntax example shows a `resource` block with a number | |
| of provisioners of different types: | |
| ```hcl | |
| resource "aws_instance" "example" { | |
| # (resource configuration omitted for brevity) | |
| provisioner "local-exec" { | |
| command = "echo 'Hello World' >example.txt" | |
| } | |
| provisioner "file" { | |
| source = "example.txt" | |
| destination = "/tmp/example.txt" | |
| } | |
| provisioner "remote-exec" { | |
| inline = [ | |
| "sudo install-something -f /tmp/example.txt", | |
| ] | |
| } | |
| } | |
| ``` | |
| In order to preserve the order of these blocks, you must use a JSON array | |
| as the direct value of the property representing this block type, as in | |
| this JSON equivalent of the above: | |
| ```json | |
| { | |
| "resource": { | |
| "aws_instance": { | |
| "example": { | |
| "provisioner": [ | |
| { | |
| "local-exec": { | |
| "command": "echo 'Hello World' >example.txt" | |
| } | |
| }, | |
| { | |
| "file": { | |
| "source": "example.txt", | |
| "destination": "/tmp/example.txt" | |
| } | |
| }, | |
| { | |
| "remote-exec": { | |
| "inline": ["sudo install-something -f /tmp/example.txt"] | |
| } | |
| } | |
| ] | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| Each element of the `provisioner` array is an object with a single property | |
| whose name represents the label for each `provisioner` block. For block types | |
| that expect multiple labels, this pattern of alternating array and object | |
| nesting can be used for each additional level. | |
| If a nested block type requires labels but the order does _not_ matter, you | |
| may omit the array and provide just a single object whose property names | |
| correspond to unique block labels. This is allowed as a shorthand for the above | |
| for simple cases, but the alternating array and object approach is the most | |
| general. We recommend using the most general form if systematically converting | |
| from native syntax to JSON, to ensure that the meaning of the configuration is | |
| preserved exactly. | |
| ### Comment Properties | |
| Although we do not recommend hand-editing of JSON syntax configuration files | |
| \-- this format is primarily intended for programmatic generation and consumption -- | |
| a limited form of _comments_ are allowed inside JSON objects that represent | |
| block bodies using a special property name: | |
| ```json | |
| { | |
| "resource": { | |
| "aws_instance": { | |
| "example": { | |
| "//": "This instance runs the scheduled tasks for backup", | |
| "instance_type": "t2.micro", | |
| "ami": "ami-abc123" | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| In any object that represents a block body, properties named `"//"` are | |
| ignored by OpenTofu entirely. This exception does _not_ apply to objects | |
| that are being [interpreted as expressions](#expression-mapping), where this | |
| would be interpreted as an object type attribute named `"//"`. | |
| This special property name can also be used at the root of a JSON-based | |
| configuration file. This can be useful to note which program created the file. | |
| ```json | |
| { | |
| "//": "This file is generated by generate-outputs.py. DO NOT HAND-EDIT!", | |
| "output": { | |
| "example": { | |
| "value": "${aws_instance.example}" | |
| } | |
| } | |
| } | |
| ``` | |
| ## Block-type-specific Exceptions | |
| Certain arguments within specific block types are processed in a special way | |
| by OpenTofu, and so their mapping to the JSON syntax does not follow the | |
| general rules described above. The following sub-sections describe the special | |
| mapping rules that apply to each top-level block type. | |
| ### `resource` and `data` blocks | |
| Some meta-arguments for the `resource` and `data` block types take direct | |
| references to objects, or literal keywords. When represented in JSON, the | |
| reference or keyword is given as a JSON string with no additional surrounding | |
| spaces or symbols. | |
| For example, the `provider` meta-argument takes a `<PROVIDER>.<ALIAS>` reference | |
| to a provider configuration, which appears unquoted in the native syntax but | |
| must be presented as a string in the JSON syntax: | |
| ```json | |
| { | |
| "resource": { | |
| "aws_instance": { | |
| "example": { | |
| "provider": "aws.foo" | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| This special processing applies to the following meta-arguments: | |
| * `provider`: a single string, as shown above | |
| * `depends_on`: an array of strings containing references to named entities, | |
| like `["aws_instance.example"]`. | |
| * `ignore_changes` within the `lifecycle` block: if set to `all`, a single | |
| string `"all"` must be given. Otherwise, an array of JSON strings containing | |
| property references must be used, like `["ami"]`. | |
| Special processing also applies to the `type` argument of any `connection` | |
| blocks, whether directly inside the `resource` block or nested inside | |
| `provisioner` blocks: the given string is interpreted literally, and not | |
| parsed and evaluated as a string template. | |
| ### `variable` blocks | |
| All arguments inside `variable` blocks have non-standard mappings to JSON: | |
| * `type`: a string containing a type expression, like `"string"` or `"list(string)"`. | |
| * `default`: a literal JSON value that can be converted to the given type. | |
| Strings within this value are taken literally and _not_ interpreted as | |
| string templates. | |
| * `description`: a literal JSON string, _not_ interpreted as a template. | |
| ```json | |
| { | |
| "variable": { | |
| "example": { | |
| "type": "string", | |
| "default": "hello" | |
| } | |
| } | |
| } | |
| ``` | |
| ### `output` blocks | |
| The `description` and `sensitive` arguments are interpreted as literal JSON | |
| values. The `description` string is not interpreted as a string template. | |
| The `value` argument is [interpreted as an expression](#expression-mapping). | |
| ```json | |
| { | |
| "output": { | |
| "example": { | |
| "value": "${aws_instance.example}" | |
| } | |
| } | |
| } | |
| ``` | |
| ### `locals` blocks | |
| The value of the JSON object property representing the locals block type | |
| must be a JSON object whose property names are the local value names to | |
| declare: | |
| ```json | |
| { | |
| "locals": { | |
| "greeting": "Hello, ${var.name}" | |
| } | |
| } | |
| ``` | |
| The value of each of these nested properties is | |
| [interpreted as an expression](#expression-mapping). | |
| ### `module` blocks | |
| The `providers` meta-argument must be given as a JSON object whose properties | |
| are the compact provider addresses to expose into the child module and whose | |
| values are the provider addresses to use from the current module, both | |
| given as literal strings: | |
| ```json | |
| { | |
| "module": { | |
| "example": { | |
| "source": "hashicorp/consul/azurerm", | |
| "version": "= 1.0.0", | |
| "providers": { | |
| "aws": "aws.usw1" | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| ### `provider` blocks | |
| The `alias` and `version` meta-arguments must be given as literal strings. The | |
| values are not interpreted as string templates. | |
| ```json | |
| { | |
| "provider": { | |
| "aws": [ | |
| { | |
| "region": "us-east-1" | |
| }, | |
| { | |
| "alias": "usw1", | |
| "region": "us-west-1" | |
| } | |
| ] | |
| } | |
| } | |
| ``` | |
| ### `terraform` blocks | |
| Settings within `terraform` blocks are _generally_ interpreted literally, except | |
| for the `backend` and `encryption` blocks, which support expressions. Other settings do not | |
| accept named object references or function calls and therefore do not treat | |
| string values as string templates. | |
| #### `backend` block | |
| Since only one `backend` block is allowed per `terraform` block, the compact | |
| block mapping can be used to represent it, with a nested object containing | |
| a single property whose name represents the backend type. | |
| ```json | |
| { | |
| "terraform": { | |
| "required_version": ">= 0.12.0", | |
| "backend": { | |
| "s3": { | |
| "region": "us-west-2", | |
| "bucket": "acme-tofu-states" | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| #### `encryption` block | |
| As visible in the [encryption documentation](../state/encryption.mdx), | |
| the `encryption` block supports the following children blocks: | |
| * `key_provider` | |
| * `method` | |
| * `state` | |
| * `plan` | |
| * `remote_state_data_sources` | |
| The `key_provider` and `method` can refer to other named blocks, like variables, while `state`, `plan` and | |
| `remote_state_data_sources` can refer only to the other blocks inside the `encryption` block. | |
| A `key_provider` can refer to variables to be able to provide key information dynamically: | |
| ```json | |
| { | |
| "variable": { | |
| "state_plan_passphrase": { | |
| "type": "string", | |
| "default": "myultrasecretpassphrase1!" | |
| } | |
| }, | |
| "terraform": { | |
| "encryption": { | |
| "key_provider": { | |
| "pbkdf2": { | |
| "state_plan": { | |
| "passphrase": "${var.state_plan_passphrase}" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| A `method` block can refer to `key_provider` blocks by using static string references or regular terraform | |
| interpolated expressions: | |
| ```json | |
| { | |
| "terraform": { | |
| "encryption": { | |
| "key_provider": { | |
| "pbkdf2": { | |
| "state_plan": { | |
| "passphrase": "myultrasecretpassphrase1!" | |
| } | |
| } | |
| }, | |
| "method": { | |
| "aes_gcm": { | |
| "my_key_for_state": { | |
| "keys": "${key_provider.pbkdf2.state_plan}" | |
| }, | |
| "my_key_for_plan": { | |
| "keys": "key_provider.pbkdf2.state_plan" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| The `state`, `plan` and `remote_state_data_sources` can strictly refer to `method` blocks by using string | |
| references. These are not expressions, are only references to the methods so the evaluation of these are not | |
| performed in the same way expressions are evaluated in OpenTofu: | |
| ```json | |
| { | |
| "variable": { | |
| "remote_state_passphrase": { | |
| "type": "string", | |
| "default": "mysecrettestpassword!" | |
| }, | |
| "state_plan_passphrase": { | |
| "type": "string", | |
| "default": "mysecrettestpassword2!" | |
| } | |
| }, | |
| "terraform": { | |
| "encryption": { | |
| "key_provider": { | |
| "pbkdf2": { | |
| "remote_state": { | |
| "passphrase": "remotestateultrasecretpassphrase!1" | |
| }, | |
| "state_plan": { | |
| "passphrase": "stateplanultrasecretpassphrase!1" | |
| } | |
| } | |
| }, | |
| "method": { | |
| "aes_gcm": { | |
| "remote_state": { | |
| "keys": "${key_provider.pbkdf2.remote_state_key}" | |
| }, | |
| "state_plan": { | |
| "keys": "key_provider.pbkdf2.state_plan" | |
| } | |
| }, | |
| "unencrypted": { | |
| "unencrypted": {} | |
| } | |
| }, | |
| "state": { | |
| "method": "method.aes_gcm.state_plan", | |
| "fallback": { | |
| "method": "method.unencrypted.unencrypted" | |
| } | |
| }, | |
| "plan": { | |
| "method": "method.aes_gcm.state_plan", | |
| "fallback": { | |
| "method": "method.unencrypted.unencrypted" | |
| } | |
| }, | |
| "remote_state_data_sources": { | |
| "default": { | |
| "method": "method.aes_gcm.remote_state" | |
| }, | |
| "remote_state_data_source": { | |
| "another_state": { | |
| "method": "method.aes_gcm.remote_state" | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| "data": { | |
| "terraform_remote_state": { | |
| "another_state": { | |
| "backend": "local", | |
| "config": { | |
| "path": "<path to an encrypted state>" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ``` |