Terraform Tutorial — String Interpolation and Types

2 minute read

HCL has a rich type system and powerful string interpolation. This page covers how to build dynamic strings, work with heredocs, and understand the primitive types that underpin every Terraform value.


String Interpolation

String interpolation lets you embed expressions inside strings using ${} syntax:

variable "project" {
  default = "web-app"
}

variable "environment" {
  default = "dev"
}

resource "aws_s3_bucket" "app" {
  bucket = "${var.project}-${var.environment}-assets"
  # Resolves to: "web-app-dev-assets"
}

The ${} wrapper evaluates the expression inside and inserts the result into the string.

When Interpolation is NOT Needed

If the entire value is a single reference (no surrounding text), skip the interpolation:

# ✅ Correct — direct reference, no interpolation needed
region = var.region

# ❌ Unnecessary — wrapping a single reference in interpolation
region = "${var.region}"

Both work, but the direct reference is cleaner. terraform fmt won’t remove the unnecessary interpolation, but linters will flag it.


Heredoc Strings

For multi-line strings, use heredoc syntax:

resource "aws_iam_policy" "example" {
  name   = "example-policy"
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}
EOF
}

The <<EOF starts the heredoc, and EOF on its own line ends it. You can use any marker — EOF, POLICY, SCRIPT, etc.

Indented Heredoc

Use <<- to strip leading whitespace, keeping your code indented nicely:

resource "aws_instance" "web" {
  user_data = <<-SCRIPT
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    systemctl start nginx
  SCRIPT
}

The <<- variant removes the common leading whitespace from all lines, so the resulting string starts at column 0.


Primitive Types

HCL has three primitive types:

String

name = "my-bucket"

Strings are enclosed in double quotes. They support interpolation and escape sequences (\n, \t, \\, \").

Number

port    = 8080
pi      = 3.14159
timeout = 300

Numbers can be integers or floating-point. No quotes — quoted numbers are strings.

Boolean

enable_versioning = true
public_access     = false

Only two values: true and false (lowercase, no quotes).


Type Conversion Functions

Sometimes you need to convert between types:

# Convert number to string
name_suffix = tostring(var.count_index)

# Convert string to number
port = tonumber(var.port_string)

# Convert string to boolean
enabled = tobool(var.enable_string)

Terraform also performs implicit type conversion in many cases. If a resource expects a string but you pass a number, Terraform converts it automatically.


The null Value

null represents the absence of a value. It’s useful for conditionally omitting optional arguments:

variable "custom_domain" {
  type    = string
  default = null
}

resource "aws_s3_bucket_website_configuration" "site" {
  count  = var.custom_domain != null ? 1 : 0
  bucket = aws_s3_bucket.site.id

  index_document {
    suffix = "index.html"
  }
}

When var.custom_domain is null, the website configuration resource isn’t created at all.


Template Directives

For advanced string building, HCL supports template directives inside strings:

Conditional Directive

output "message" {
  value = "Environment: %{if var.environment == "prod"}PRODUCTION%{else}${var.environment}%{endif}"
}

Loop Directive

output "server_list" {
  value = <<-EOT
    Servers:
    %{for name in var.server_names}
    - ${name}
    %{endfor}
  EOT
}

Template directives are powerful but can make strings hard to read. For complex templates, consider using the templatefile() function with a separate template file.


What’s Next?

You know how to work with strings and primitive types. Next, let’s explore collection types — lists, maps, and objects — that let you manage groups of values.


← Blocks, Arguments & Expressions Lists, Maps & Objects →