Attribute Types

6 minute read

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:

  1. Primitive Types — String, Number, Bool
  2. Collection Types — List, Map, Set
  3. 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.

Next: Blocks vs Attributes →


Previous: ← Schema Design Overview

Last updated: June 30, 2026

Updated: