Attribute Types
Attribute Types Overview
The Plugin Framework provides a rich type system for defining resource and data source schemas. Understanding these types is fundamental to building well-designed providers.
In this chapter, you’ll learn about all available attribute types, when to use each, and common patterns.
Type Categories
Terraform attribute types fall into three categories:
- Primitive Types — String, Number, Bool
- Collection Types — List, Map, Set
- Complex Types — Object, Dynamic
Primitive Types
String Attribute
The most common attribute type for text values.
Definition:
"name": schema.StringAttribute{
Required: true,
MarkdownDescription: "Name of the resource",
},
HCL Usage:
resource "mycloud_environment" "example" {
name = "production"
}
Use cases:
- Names and identifiers
- Descriptions and labels
- URLs and endpoints
- Enum-like values (with validation)
- JSON strings
- Formatted strings (timestamps, versions)
Example with validation:
"region": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.OneOf("us-east-1", "us-west-2", "eu-west-1"),
},
},
Number Attribute
For numeric values (integers and floats).
Definition:
"replicas": schema.NumberAttribute{
Optional: true,
Computed: true,
MarkdownDescription: "Number of replicas",
},
HCL Usage:
resource "mycloud_environment" "example" {
replicas = 3
cpu_limit = 2.5
}
Important: Numbers in Terraform are arbitrary-precision decimals, not Go int or float64.
Working with numbers:
// Read from config
var replicas types.Number
diags := req.Config.GetAttribute(ctx, path.Root("replicas"), &replicas)
// Convert to Go int64
replicasValue := replicas.ValueBigFloat()
replicasInt, _ := replicasValue.Int64()
// Or use helper
replicasInt := int(replicas.ValueBigFloat().MustFloat64())
Use cases:
- Counts and quantities
- Port numbers
- Timeouts and intervals
- Percentages
- Sizes (memory, disk)
- Decimal values (CPU limits)
Example with validation:
"port": schema.NumberAttribute{
Required: true,
Validators: []validator.Number{
numbervalidator.Between(1, 65535),
},
},
Bool Attribute
For boolean (true/false) values.
Definition:
"enabled": schema.BoolAttribute{
Optional: true,
Computed: true,
MarkdownDescription: "Whether the resource is enabled",
},
HCL Usage:
resource "mycloud_environment" "example" {
enabled = true
auto_scaling = false
}
Working with booleans:
// Read from config
var enabled types.Bool
diags := req.Config.GetAttribute(ctx, path.Root("enabled"), &enabled)
// Check value
if enabled.ValueBool() {
// Feature is enabled
}
// Check if value is null (not set)
if enabled.IsNull() {
// Use default behavior
}
Use cases:
- Feature flags
- Enable/disable toggles
- Boolean conditions
- True/false states
Common pattern with defaults:
func (r *MyResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
var plan MyResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
// Set default if not specified
if plan.Enabled.IsNull() {
plan.Enabled = types.BoolValue(true)
}
diags = resp.Plan.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
}
Collection Types
List Attribute
Ordered collection of elements of the same type.
Definition:
"tags": schema.ListAttribute{
ElementType: types.StringType,
Optional: true,
MarkdownDescription: "List of tags",
},
HCL Usage:
resource "mycloud_environment" "example" {
tags = ["production", "web", "frontend"]
}
Working with lists:
// Read list
var tags types.List
diags := req.Config.GetAttribute(ctx, path.Root("tags"), &tags)
// Convert to Go slice
var tagValues []string
diags = tags.ElementsAs(ctx, &tagValues, false)
// Or iterate elements
for _, elem := range tags.Elements() {
tag := elem.(types.String).ValueString()
// Process tag
}
// Create list
tagsList, diags := types.ListValue(
types.StringType,
[]attr.Value{
types.StringValue("tag1"),
types.StringValue("tag2"),
},
)
Use cases:
- Ordered collections
- Tags and labels
- Multiple values of same type
- Priority-ordered items
Example with nested elements:
"availability_zones": schema.ListAttribute{
ElementType: types.StringType,
Required: true,
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
listvalidator.SizeAtMost(3),
},
},
Map Attribute
Key-value pairs where all values are the same type.
Definition:
"env_vars": schema.MapAttribute{
ElementType: types.StringType,
Optional: true,
MarkdownDescription: "Environment variables",
},
HCL Usage:
resource "mycloud_environment" "example" {
env_vars = {
NODE_ENV = "production"
LOG_LEVEL = "info"
DEBUG = "false"
}
}
Working with maps:
// Read map
var envVars types.Map
diags := req.Config.GetAttribute(ctx, path.Root("env_vars"), &envVars)
// Convert to Go map
var envVarValues map[string]string
diags = envVars.ElementsAs(ctx, &envVarValues, false)
// Or iterate
for key, val := range envVars.Elements() {
value := val.(types.String).ValueString()
// Process key-value pair
}
// Create map
envMap, diags := types.MapValue(
types.StringType,
map[string]attr.Value{
"KEY1": types.StringValue("value1"),
"KEY2": types.StringValue("value2"),
},
)
Use cases:
- Key-value configurations
- Environment variables
- Tags with values
- Metadata
- Labels
Example with validation:
"labels": schema.MapAttribute{
ElementType: types.StringType,
Optional: true,
Validators: []validator.Map{
mapvalidator.SizeAtMost(50),
mapvalidator.KeysAre(
stringvalidator.LengthBetween(1, 63),
),
},
},
Set Attribute
Unordered collection of unique elements.
Definition:
"security_groups": schema.SetAttribute{
ElementType: types.StringType,
Optional: true,
MarkdownDescription: "Set of security group IDs",
},
HCL Usage:
resource "mycloud_environment" "example" {
security_groups = ["sg-123", "sg-456", "sg-789"]
}
Note: In HCL, sets look like lists, but Terraform treats them as unordered and deduplicates elements.
Working with sets:
// Read set
var securityGroups types.Set
diags := req.Config.GetAttribute(ctx, path.Root("security_groups"), &securityGroups)
// Convert to Go slice
var sgValues []string
diags = securityGroups.ElementsAs(ctx, &sgValues, false)
// Create set
sgSet, diags := types.SetValue(
types.StringType,
[]attr.Value{
types.StringValue("sg-123"),
types.StringValue("sg-456"),
},
)
Use cases:
- Collections where order doesn’t matter
- Unique identifiers (no duplicates)
- Security groups, IAM roles
- Tags where duplicates should be prevented
List vs Set:
// Use List when:
// - Order matters
// - Duplicates allowed
// - Sequential processing
// Use Set when:
// - Order doesn't matter
// - Duplicates should be prevented
// - Membership checking
Complex Types
Object Attribute
Fixed structure with named fields of potentially different types.
Definition:
"config": schema.SingleNestedAttribute{
Optional: true,
Attributes: map[string]schema.Attribute{
"memory_gb": schema.NumberAttribute{
Required: true,
},
"cpu_cores": schema.NumberAttribute{
Required: true,
},
"disk_size_gb": schema.NumberAttribute{
Optional: true,
Computed: true,
},
},
},
HCL Usage:
resource "mycloud_environment" "example" {
config = {
memory_gb = 4
cpu_cores = 2
disk_size_gb = 100
}
}
Or using block syntax:
resource "mycloud_environment" "example" {
config {
memory_gb = 4
cpu_cores = 2
}
}
Use cases:
- Structured configuration
- Related fields grouped together
- Complex nested data
Type Comparison Table
| Type | Ordered | Unique | Same Element Type | Use Case |
|---|---|---|---|---|
| String | N/A | N/A | N/A | Text values |
| Number | N/A | N/A | N/A | Numeric values |
| Bool | N/A | N/A | N/A | True/false |
| List | ✅ Yes | ❌ No | ✅ Yes | Ordered collection |
| Map | ❌ No | ✅ Keys | ✅ Yes | Key-value pairs |
| Set | ❌ No | ✅ Yes | ✅ Yes | Unique collection |
| Object | N/A | N/A | ❌ No | Structured data |
Choosing the Right Type
Decision Tree
Is it a single value?
├─ Text? → String
├─ Number? → Number
└─ Boolean? → Bool
Is it multiple values of same type?
├─ Order matters?
│ ├─ Yes → List
│ └─ No → Set
└─ Key-value pairs? → Map
Is it structured data?
└─ Object (SingleNestedAttribute)
Common Patterns
Pattern 1: Optional with Default
"port": schema.NumberAttribute{
Optional: true,
Computed: true,
},
// In Create or ModifyPlan:
if plan.Port.IsNull() {
plan.Port = types.NumberValue(big.NewFloat(8080))
}
Pattern 2: List of Strings
"dns_servers": schema.ListAttribute{
ElementType: types.StringType,
Required: true,
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
listvalidator.ValueStringsAre(
stringvalidator.RegexMatches(
regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`),
"must be valid IP address",
),
),
},
},
Pattern 3: Map with String Values
"metadata": schema.MapAttribute{
ElementType: types.StringType,
Optional: true,
Validators: []validator.Map{
mapvalidator.SizeAtMost(50),
},
},
Pattern 4: Set of Unique IDs
"subnet_ids": schema.SetAttribute{
ElementType: types.StringType,
Required: true,
Validators: []validator.Set{
setvalidator.SizeAtLeast(1),
setvalidator.ValueStringsAre(
stringvalidator.RegexMatches(
regexp.MustCompile(`^subnet-[a-z0-9]+$`),
"must be valid subnet ID",
),
),
},
},
Null vs Empty vs Zero
Understanding the difference:
// String
types.StringNull() // Not set
types.StringValue("") // Empty string
types.StringValue("x") // Has value
// Number
types.NumberNull() // Not set
types.NumberValue(big.NewFloat(0)) // Zero
// Bool
types.BoolNull() // Not set
types.BoolValue(false) // False (explicitly set)
// List
types.ListNull(types.StringType) // Not set
types.ListValue(types.StringType, []attr.Value{}) // Empty list
// Map
types.MapNull(types.StringType) // Not set
types.MapValue(types.StringType, map[string]attr.Value{}) // Empty map
Why it matters:
// User didn't specify → null
// User specified empty → empty value
// Provider computed → computed value
if config.Tags.IsNull() {
// User didn't specify tags
} else if len(config.Tags.Elements()) == 0 {
// User specified tags = []
}
Type Conversion
String to Number
var portStr types.String
// ... get portStr from config
portFloat, err := strconv.ParseFloat(portStr.ValueString(), 64)
portNum := types.NumberValue(big.NewFloat(portFloat))
Number to String
var port types.Number
// ... get port from config
portInt, _ := port.ValueBigFloat().Int64()
portStr := types.StringValue(strconv.FormatInt(portInt, 10))
Best Practices
1. Choose Semantic Types
// ✅ Good: Semantic type
"enabled": schema.BoolAttribute{...}
// ❌ Bad: String for boolean
"enabled": schema.StringAttribute{...} // User sets "true" or "false"
2. Use Collections Appropriately
// ✅ Good: Set for unique items
"security_group_ids": schema.SetAttribute{...}
// ❌ Bad: List allows duplicates
"security_group_ids": schema.ListAttribute{...}
3. Validate Early
"port": schema.NumberAttribute{
Required: true,
Validators: []validator.Number{
numbervalidator.Between(1, 65535),
},
},
4. Document Types
"timeout": schema.NumberAttribute{
Optional: true,
MarkdownDescription: "Timeout in seconds (default: 30)",
},
Key Takeaways
- ✅ Terraform has rich type system beyond strings
- ✅ Choose types that match data semantics
- ✅ Lists preserve order, Sets ensure uniqueness
- ✅ Maps are for key-value pairs
- ✅ Objects group related fields
- ✅ Null, empty, and zero are different
- ✅ Type choice affects user experience
- ✅ Validate early with appropriate validators
What’s Next?
Now that you understand attribute types, let’s explore blocks and when to use them.
Previous: ← Schema Design Overview
Last updated: June 30, 2026