Terraform Variable Validation Prompt
Add Terraform variable validation — types, validation blocks, sensitive, nullable, custom error messages.
- Target user
- Terraform engineers writing robust modules
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a senior Terraform engineer who has added validation to variables — catching bad inputs early with clear error messages.
I will provide:
- The variable
- Validation needs
- Current type
Your job:
1. **Type system**:
- Primitives: string, number, bool
- Collections: list, set, map
- Objects: structured types
- Optional fields (1.3+): `optional(type, default)`
2. **For validation blocks**:
- `validation { condition, error_message }`
- Multiple per variable
- Evaluated at plan
3. **For type validation**:
- `type = string` enforces string
- Complex types catch nested errors
4. **For sensitive**:
- `sensitive = true` masks in output
- Still in state
5. **For nullable**:
- `nullable = false` — must have value
6. **For default**:
- Optional + default → not required
7. **For custom validations**:
- Regex
- Range
- Set membership
- Cross-field via locals
8. **For module input contracts**:
- Document via description
- Validate at boundary
- Fail fast
Mark DESTRUCTIVE: too-strict validation blocking valid inputs, allowing wrong types (skipping), defaulting sensitive without warning.
---
Variable: [PASTE]
Validation needs: [DESCRIBE]
Why this prompt works
Validation catches errors early. This prompt walks patterns.
How to use it
- Type appropriately.
- Validate inputs.
- Clear error messages.
- Document with description.
Patterns
String with regex
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be one of: dev, staging, prod."
}
}
variable "cidr_block" {
description = "VPC CIDR block (RFC 1918)"
type = string
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "cidr_block must be a valid CIDR notation."
}
validation {
condition = can(regex("^(10|172|192)", var.cidr_block))
error_message = "cidr_block should be RFC 1918 (10.x, 172.16-31.x, 192.168.x)."
}
}
Numeric range
variable "instance_count" {
description = "Number of instances"
type = number
default = 1
validation {
condition = var.instance_count >= 1 && var.instance_count <= 100
error_message = "instance_count must be between 1 and 100."
}
}
variable "retention_days" {
description = "Log retention in days"
type = number
default = 30
validation {
condition = contains([1, 3, 5, 7, 14, 30, 60, 90, 180, 365, 400, 545, 731, 1827, 3653], var.retention_days)
error_message = "retention_days must be a valid CloudWatch Logs retention period."
}
}
Object type with optional fields
variable "database" {
description = "Database configuration"
type = object({
engine = string
instance_class = string
allocated_storage = number
multi_az = optional(bool, false)
backup_retention = optional(number, 7)
deletion_protection = optional(bool, true)
tags = optional(map(string), {})
})
validation {
condition = contains(["mysql", "postgres", "mariadb"], var.database.engine)
error_message = "database.engine must be mysql, postgres, or mariadb."
}
validation {
condition = var.database.allocated_storage >= 20 && var.database.allocated_storage <= 65536
error_message = "database.allocated_storage must be between 20 and 65536 GB."
}
}
List validation
variable "allowed_cidrs" {
description = "List of allowed CIDR blocks"
type = list(string)
default = []
validation {
condition = alltrue([for cidr in var.allowed_cidrs : can(cidrhost(cidr, 0))])
error_message = "All entries in allowed_cidrs must be valid CIDR blocks."
}
validation {
condition = length(var.allowed_cidrs) <= 50
error_message = "Maximum 50 CIDRs allowed (security group rule limit)."
}
}
Map validation
variable "tags" {
description = "Resource tags (must include required keys)"
type = map(string)
default = {}
validation {
condition = alltrue([
for k in ["Environment", "Owner", "CostCenter"] : contains(keys(var.tags), k)
])
error_message = "tags must include Environment, Owner, and CostCenter."
}
}
Sensitive + nullable
variable "db_password" {
description = "Database master password"
type = string
sensitive = true
nullable = false
validation {
condition = length(var.db_password) >= 16
error_message = "db_password must be at least 16 characters."
}
}
variable "api_key" {
description = "External API key"
type = string
sensitive = true
}
Cross-variable validation (via locals + check)
variable "min_size" { type = number }
variable "max_size" { type = number }
locals {
size_validation = var.min_size <= var.max_size ? true : tobool("min_size must be <= max_size")
}
# Or use lifecycle precondition on a resource
resource "aws_autoscaling_group" "web" {
min_size = var.min_size
max_size = var.max_size
lifecycle {
precondition {
condition = var.min_size <= var.max_size
error_message = "min_size must be less than or equal to max_size."
}
}
}
Common findings this catches
- No validation → silent bad inputs.
- Validation error message unclear → user confused.
- Sensitive missing → secrets in plan output.
- Default hides required input → use nullable: false.
- Cross-variable check skipped → preconditions.
- List validation iterates all → expensive at scale.
- Optional vs nullable confusion → clarify intent.
When to escalate
- Complex validation across modules — design.
- Performance with large list inputs — profile.
- Compliance requirements — strict typing.
Related prompts
-
Terraform Module Composition Prompt
Design Terraform modules — input/output contracts, composition, versioning, public vs private registry, when to abstract.
-
Terraform Secrets & Sensitive Variables Prompt
Manage secrets in Terraform — sensitive flag, ephemeral resources, external secret managers, plan/state masking.