• HashiCorp Developer

  • HashiCorp Cloud Platform
  • Terraform
  • Packer
  • Consul
  • Vault
  • Boundary
  • Nomad
  • Waypoint
  • Vagrant
Terraform
  • Install
  • Tutorials
    • About the Docs
    • Configuration Language
    • Terraform CLI
    • Terraform Cloud
    • Terraform Enterprise
    • CDK for Terraform
    • Provider Use
    • Plugin Development
    • Registry Publishing
    • Integration Program
  • Registry(opens in new tab)
  • Try Cloud(opens in new tab)
  • Sign up
Plugin Development

Framework

Skip to main content
  • Framework
  • Provider Servers
    • Terraform Concepts
    • Schemas
    • Attributes
    • Blocks
    • Paths
    • Path Expressions
    • Accessing Terraform Data
    • Writing Data
    • Conversion Rules
    • Custom Types
  • Returning Errors and Warnings
  • Validation
  • Acceptance Tests
  • Debugging

  • Resources

  • Tutorial Library
  • Certifications
  • Community Forum
    (opens in new tab)
  • Support
    (opens in new tab)
  • GitHub
    (opens in new tab)
  • Terraform Registry
    (opens in new tab)
  1. Developer
  2. Terraform
  3. Plugin Development
  4. Framework
  5. Handling Data
  6. Custom Types
  • Plugin Framework
  • v1.0.x
  • v0.17.x
  • v0.16.x
  • v0.15.x
  • v0.14.x
  • v0.13.x
  • v0.12.x
  • v0.11.x
  • v0.10.x
  • v0.9.x
  • v0.8.x
  • v0.7.x

»Custom Types

You can use custom types for both attributes and blocks.

Important: Specifying plan customization for attribute types is not yet supported, limiting their utility. Support is expected in the near future.

attr.Type Interface

Use the attr.Type interface to implement an attribute type. It tells Terraform about its constraints and tells the framework how to create new attribute values from the information Terraform supplies. attr.Type has the following methods.

MethodDescription
TerraformTypeReturns the tftypes.Type value that describes its type constraints. This is how Terraform will know what type of values it can accept.
ValueFromTerraformReturns an attribute value from the tftypes.Value that Terraform supplies, or to return an error if it cannot. This error should not be used for validation purposes, and is expected to indicate programmer error, not practitioner error.
EqualReturns true if the attribute type is considered equal to the passed attribute type.

AttributePathStepper Interface

All attribute types must implement the tftypes.AttributePathStepper interface, so the framework can access element or attribute types using attribute paths.

xattr.TypeWithValidation Interface

If validation for type values is desired, use the xattr.TypeWithValidation interface to include validation logic for type values. The framework will call this functionality when validating all values based on the schema.

MethodDescription
ValidateReturns any warning or error diagnostics for the given value.

Type-Specific Interfaces

CaseInterfaceDescription
Elements of the same typeTypeWithElementTypeAttribute types that contain elements of the same type, like maps and lists, are required to implement attr.TypeWithElementType, which adds WithElementType and ElementType methods to the attr.Type interface. WithElementType must return a copy of the attribute type, but with its element type set to the passed type. ElementType must return the attribute type's element type.
Elements of different typesTypeWithElementTypesAttribute types that contain elements of differing types, like tuples, are required to implement the attr.TypeWithElementTypes, which adds WithElementTypes and ElementTypes methods to the attr.Type interface. WithElementTypes must return a copy of the attribute type, but with its element types set to the passed element types. ElementTypes must return the attribute type's element types.
Contain attributesTypeWithAttributeTypesAttribute types that contain attributes, like objects, are required to implement the attr.TypeWithAttributeTypes interface, which adds WithAttributeTypes and AttributeTypes methods to the attr.Type interface. WithAttributeTypes must return a copy of the attribute type, but with its attribute types set to the passed attribute types. AttributeTypes must return the attribute type's attribute types.

attr.Value Interface

Use the attr.Value interface to implement an attribute value. It tells the framework how to express that attribute value in a way that Terraform will understand. attr.Value has the following methods.

MethodDescription
ToTerraformValueReturns a Go type that is valid input for tftypes.NewValue for the tftypes.Type specified by the attr.Type that creates the attr.Value.
EqualReturns true if the passed attribute value should be considered to the attribute value the method is being called on. The passed attribute value is not guaranteed to be of the same Go type.

Custom Type and Value

A minimal implementation of a custom type for ListType and List that leverages embedding looks as follows:

type CustomListType struct {
    types.ListType
}

func (c CustomListType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
    val, err := c.ListType.ValueFromTerraform(ctx, in)

    return CustomListValue{
        // unchecked type assertion
        val.(types.List),
    }, err
}

type CustomListValue struct {
    types.List
}

func (c CustomListValue) DoSomething(ctx context.Context) {
    tflog.Info(ctx, "called DoSomething on CustomListValue")
}

Terraform Configuration

Using the custom type does not require any changes to the Terraform configuration.

resource "example_resource" "example" {
  list_attribute = ["list-element", "list-element"]

  list_nested_attribute = [
    {
      int64_attribute = 9223372036854775807
      list_attribute  = ["list-element", "list-element"]
    },
    {
      int64_attribute = 9223372036854775807
      list_attribute  = ["list-element", "list-element"]
    }
  ]

  list_nested_block {
    bool_attribute    = true
    float64_attribute = 1234.5
    int64_attribute   = 9223372036854775807
    list_attribute    = ["list-element", "list-element"]
    list_nested_nested_block {
      bool_attribute = true
    }
    list_nested_nested_block {
      bool_attribute = false
    }
  }
  list_nested_block {
    bool_attribute    = true
    float64_attribute = 1234.5
    int64_attribute   = 9223372036854775807
    list_attribute    = ["list-element", "list-element"]
    list_nested_nested_block {
      bool_attribute = true
    }
    list_nested_nested_block {
      bool_attribute = false
    }
  }
}

Schema

Use the custom type in the schema as follows:

func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "list_attribute": schema.ListAttribute{
                Optional:    true,
                ElementType: types.StringType,
                CustomType: CustomListType{
                    types.ListType{
                        ElemType: types.StringType,
                    },
                },
            },

            "list_nested_attribute": schema.ListNestedAttribute{
                Optional: true,
                CustomType: CustomListType{
                    types.ListType{
                        ElemType: types.ObjectType{
                            AttrTypes: map[string]attr.Type{
                                "int64_attribute": types.Int64Type,
                                "list_attribute": types.ListType{
                                    ElemType: types.StringType,
                                },
                            },
                        },
                    },
                },
                NestedObject: schema.NestedAttributeObject{
                    Attributes: map[string]schema.Attribute{
                        "int64_attribute": schema.Int64Attribute{
                            Optional: true,
                        },
                        "list_attribute": schema.ListAttribute{
                            Optional:    true,
                            ElementType: types.StringType,
                        },
                    },
                },
            },
        },

        Blocks: map[string]schema.Block{
            "list_nested_block": schema.ListNestedBlock{
                CustomType: CustomListType{
                    types.ListType{
                        ElemType: types.ObjectType{
                            AttrTypes: map[string]attr.Type{
                                "bool_attribute":    types.BoolType,
                                "float64_attribute": types.Float64Type,
                                "int64_attribute":   types.Int64Type,
                                "list_attribute": types.ListType{
                                    ElemType: types.StringType,
                                },
                                "list_nested_nested_block": types.ListType{
                                    ElemType: types.ObjectType{
                                        AttrTypes: map[string]attr.Type{
                                            "bool_attribute": types.BoolType,
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
                NestedObject: schema.NestedBlockObject{
                    Attributes: map[string]schema.Attribute{
                        "bool_attribute": schema.BoolAttribute{
                            Optional: true,
                        },
                        "float64_attribute": schema.Float64Attribute{
                            Optional: true,
                        },

                        "int64_attribute": schema.Int64Attribute{
                            Optional: true,
                        },
                        "list_attribute": schema.ListAttribute{
                            Optional:    true,
                            ElementType: types.StringType,
                        },
                    },
                    Blocks: map[string]schema.Block{
                        "list_nested_nested_block": schema.ListNestedBlock{
                            NestedObject: schema.NestedBlockObject{
                                Attributes: map[string]schema.Attribute{
                                    "bool_attribute": schema.BoolAttribute{
                                        Optional: true,
                                    },
                                },
                            },
                        },
                    },
                },
            },
        },
    }
}

Model

The custom type value is then used within the model.

Where previously the model would have looked as follows:

type exampleResourceData struct {
    ListAttribute         types.List `tfsdk:"list_attribute"`
    ListNestedAttribute   types.List `tfsdk:"list_nested_attribute"`
    ListNestedBlock       types.List `tfsdk:"list_nested_block"`
}

The custom type value is used by updating the model to:

type exampleResourceData struct {
    ListAttribute         CustomListValue `tfsdk:"list_attribute"`
    ListNestedAttribute   CustomListValue `tfsdk:"list_nested_attribute"`
    ListNestedBlock       CustomListValue `tfsdk:"list_nested_block"`
}

Create

The functions on CustomListValue are then available.

func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
    var data exampleResourceData

    diags := req.Config.Get(ctx, &data)
    resp.Diagnostics.Append(diags...)

    if resp.Diagnostics.HasError() {
        return
    }

    data.ListAttribute.DoSomething(ctx)
    data.ListNestedAttribute.DoSomething(ctx)
    data.ListNestedBlock.DoSomething(ctx)

    /*...*/
}
Edit this page on GitHub

On this page

  1. Custom Types
  2. Custom Type and Value
  3. Terraform Configuration
  4. Schema
  5. Model
  6. Create
Give Feedback(opens in new tab)
  • Certifications
  • System Status
  • Terms of Use
  • Security
  • Privacy
  • Trademark Policy
  • Trade Controls
  • Give Feedback(opens in new tab)