---
page_title: 'Plugin Development - Framework: Validation'
description: How to validate configuration values using the provider development framework.
---

# Validation

The framework can return [diagnostics](/terraform/plugin/framework/diagnostics) feedback for values in provider, resource, and data source configurations or [errors](/terraform/plugin/framework/functions/errors) feedback for values in function parameters. This allows you to write validations that give users feedback about required syntax, types, and acceptable values.

This page describes single attribute, parameter, and type validation concepts that can be used in any data source schema, provider schema, resource schema, or function definition. Further documentation is available for other configuration validation concepts:

- [Data source validation](/terraform/plugin/framework/data-sources/validate-configuration) for multiple attributes declaratively or imperatively.
- [Provider validation](/terraform/plugin/framework/providers/validate-configuration) for multiple attributes declaratively or imperatively.
- [Resource validation](/terraform/plugin/framework/resources/validate-configuration) for multiple attributes declaratively or imperatively.

-> **Note:** When implementing validation logic, configuration values may be [unknown](/terraform/plugin/framework/types#unknown) based on the source of the value. Implementations must account for this case, typically by returning early without returning new diagnostics.

During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan), [`terraform apply`](/terraform/cli/commands/apply) and [`terraform destroy`](/terraform/cli/commands/destroy) commands, Terraform calls the provider [`ValidateProviderConfig`](/terraform/plugin/framework/internals/rpcs#validateproviderconfig-rpc), [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) and [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPCs.

## Default Terraform CLI Validation

The [Terraform configuration language](/terraform/language) is declarative and an implementation of [HashiCorp Configuration Language](https://github.com/hashicorp/hcl) (HCL). The Terraform CLI is responsible for reading and parsing configurations for validity, based on Terraform's concepts such as `resource` blocks and associated syntax. The Terraform CLI automatically handles basic validation of value type and behavior information based on the provider, resource, or data source schema. For example, the Terraform CLI returns an error when a string value is given where a list value is expected and also when a required attribute is missing from a configuration.

Terraform CLI syntax and basic schema checks occur during the [`terraform apply`](/terraform/cli/commands/apply), [`terraform destroy`](/terraform/cli/commands/destroy), [`terraform plan`](/terraform/cli/commands/plan), and [`terraform validate`](/terraform/cli/commands/validate) commands. Any additional validation you define with the framework occurs directly after these checks are complete.

## Attribute Validation

You can introduce validation on attributes using the generic framework-defined types such as [`types.String`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#String). To do this, supply the `Validators` field with a list of validations, and the framework will return diagnostics from all validators. For example:

```go
// Typically within the schema.Schema returned by Schema() for a provider,
// resource, or data source.
schema.StringAttribute{
    // ... other Attribute configuration ...

    Validators: []validator.String{
        // These are example validators from terraform-plugin-framework-validators
        stringvalidator.LengthBetween(10, 256),
        stringvalidator.RegexMatches(
            regexp.MustCompile(`^[a-z0-9]+$`),
            "must contain only lowercase alphanumeric characters",
        ),
    },
}
```

All validators in the slice will always be run, regardless of whether previous validators returned an error or not.

### Common Use Case Attribute Validators

You can implement attribute validators from the [terraform-plugin-framework-validators Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators), which contains validation handling for many common use cases such as string contents and integer ranges.

### Creating Attribute Validators

If there is not an attribute validator in `terraform-plugin-framework-validators` that meets a specific use case, a provider-defined attribute validator can be created.

To create an attribute validator, you must implement at least one of the [`validator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/schema/validator) interfaces. For example:

```go
type stringLengthBetweenValidator struct {
    Max int
    Min int
}

// Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact.
func (v stringLengthBetweenValidator) Description(ctx context.Context) string {
    return fmt.Sprintf("string length must be between %d and %d", v.Min, v.Max)
}

// MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact.
func (v stringLengthBetweenValidator) MarkdownDescription(ctx context.Context) string {
    return fmt.Sprintf("string length must be between `%d` and `%d`", v.Min, v.Max)
}

// Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics.
func (v stringLengthBetweenValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
    // If the value is unknown or null, there is nothing to validate.
    if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() {
        return
    }

    strLen := len(req.ConfigValue.ValueString())

    if strLen < v.Min || strLen > v.Max {
        resp.Diagnostics.AddAttributeError(
            req.AttributePath,
            "Invalid String Length",
            fmt.Sprintf("String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen),
        )

        return
    }
}
```

Optionally and depending on the complexity, it may be desirable to also create a helper function to instantiate the validator. For example:

```go
func stringLengthBetween(minLength int, maxLength int) stringLengthBetweenValidator {
    return stringLengthBetweenValidator{
        Max: maxLength,
        Min: minLength,
    }
}
```

#### Path Based Attribute Validators

Attribute validators that need to accept [paths](/terraform/plugin/framework/paths) to reference other attribute data should instead prefer [path expressions](/terraform/plugin/framework/path-expressions). This allows consumers to use either absolute paths starting at the root of a [schema](/terraform/plugin/framework/schemas), or relative paths based on the current attribute path where the validator is called.

Path expressions may represent one or more actual paths in the data. To find those paths, the process is called path matching. Depending on the actual data, a path match may return a parent path for null or unknown values, since any underlying paths of those null or unknown values would also represent the same value. This framework behavior is used to prevent false positives of returning no paths for null or unknown values.

The general structure for working with path expressions in an attribute validator is:

- Merge the given path expression(s) with the current attribute path expression, such as calling the request type `PathExpression` field [`MergeExpressions()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression.MergeExpressions).
- Loop through each merged path expression to get the matching paths within the data, such as calling the request type `Config` field [`PathMatches()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Config.PathMatches).
- Loop through each matched path to get the generic `attr.Value` value, such as calling the request type `Config` field [`GetAttribute()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Config.GetAttribute).
- Perform null and unknown value checks on the `attr.Value`, such as the [`IsNull()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value.IsNull) and [`IsUnknown()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value.IsUnknown).
- If the `attr.Value` is not null and not unknown, then use [`tfsdk.ValueAs()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ValueAs) using the expected value implementation as the target.

The following example shows a generic path based attribute validator that returns an error if `types.Int64` values at the given path expressions are less than the current attribute `types.Int64` value.

```go
// Ensure our implementation satisfies the validator.Int64 interface.
var _ validator.Int64 = &int64IsGreaterThanValidator{}

// int64IsGreaterThanValidator is the underlying type implementing Int64IsGreaterThan.
type int64IsGreaterThanValidator struct {
	expressions path.Expressions
}

// Description returns a plaintext string describing the validator.
func (v int64IsGreaterThanValidator) Description(_ context.Context) string {
	return fmt.Sprintf("If configured, must be greater than %s attributes", v.expressions)
}

// MarkdownDescription returns a Markdown formatted string describing the validator.
func (v int64IsGreaterThanValidator) MarkdownDescription(ctx context.Context) string {
	return v.Description(ctx)
}

// Validate performs the validation logic for the validator.
func (v int64IsGreaterThanValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) {
	// If the current attribute configuration is null or unknown, there
	// cannot be any value comparisons, so exit early without error.
	if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
		return
	}

	// Combine the given path expressions with the current attribute path
	// expression. This call automatically handles relative and absolute
	// expressions.
	expressions := req.AttributePathExpression.MergeExpressions(v.expressions...)

	// For each expression, find matching paths.
	for _, expression := range expressions {
		// Find paths matching the expression in the configuration data.
		matchedPaths, diags := req.Config.PathMatches(ctx, expression)

		resp.Diagnostics.Append(diags...)

		// Collect all errors
		if diags.HasError() {
			continue
		}

		// For each matched path, get the data and compare.
		for _, matchedPath := range matchedPaths {
			// Fetch the generic attr.Value at the given path. This ensures any
			// potential parent value of a different type, which can be a null
			// or unknown value, can be safely checked without raising a type
			// conversion error.
			var matchedPathValue attr.Value

			diags := req.Config.GetAttribute(ctx, matchedPath, &matchedPathValue)

			resp.Diagnostics.Append(diags...)

			// Collect all errors
			if diags.HasError() {
				continue
			}

			// If the matched path value is null or unknown, we cannot compare
			// values, so continue to other matched paths.
			if matchedPathValue.IsNull() || matchedPathValue.IsUnknown() {
				continue
			}

			// Now that we know the matched path value is not null or unknown,
			// it is safe to attempt converting it to the intended attr.Value
			// implementation, in this case a types.Int64 value.
			var matchedPathConfig types.Int64

			diags = tfsdk.ValueAs(ctx, matchedPathValue, &matchedPathConfig)

			resp.Diagnostics.Append(diags...)

			// If the matched path value was not able to be converted from
			// attr.Value to the intended types.Int64 implementation, it most
			// likely means that the path expression was not pointing at a
			// types.Int64Type attribute. Collect the error and continue to
			// other matched paths.
			if diags.HasError() {
				continue
			}

			if matchedPathConfig.ValueInt64() >= attributeConfig.ValueInt64() {
				resp.Diagnostics.AddAttributeError(
					matchedPath,
					"Invalid Attribute Value",
					fmt.Sprintf("Must be less than %s value: %d", req.AttributePath, attributeConfig.ValueInt64()),
				)
			}
		}
	}
}

// Int64IsGreaterThan checks that any Int64 values in the paths described by the
// path.Expression are less than the current attribute value.
func Int64IsGreaterThan(expressions ...path.Expression) validator.Int64 {
	return &int64IsGreaterThanValidator{
		expressions: expressions,
	}
}
```

## Parameter Validation

You can introduce validation on function parameters using the generic framework-defined types such as [`types.String`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#String). To do this, supply the `Validators` field with a list of validations, and the framework will return errors from all validators. For example:

```go
// Typically within the function.Definition for a function.
function.StringParameter{
	// ... other Parameter configuration ...

    Validators: []function.StringParameterValidator{
        stringvalidator.LengthBetween(10, 256),
    },
},
```

All validators in the slice will always be run, regardless of whether previous validators returned an error or not.

### Creating Parameter Validators

To create a parameter validator, you must implement at least one of the [`function` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function) `<Type>ParameterValidator` interfaces. For example:

```go
// Ensure the implementation satisfies the expected interfaces
var (
    _ function.StringParameterValidator = stringLengthBetweenValidator{}
)

type stringLengthBetweenValidator struct {
    Max int
    Min int
}

// Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics.
func (v stringLengthBetweenValidator) ValidateParameterString(ctx context.Context, req validator.StringParameterValidatorRequest, resp *validator.StringParameterValidatorResponse) {
	// If the value is unknown or null, there is nothing to validate.
    if req.Value.IsUnknown() || req.Value.IsNull() {
        return
    }

    strLen := len(req.Value.ValueString())

    if strLen < v.Min || strLen > v.Max {
		resp.Error = function.NewArgumentFuncError(
			req.ArgumentPosition,
            fmt.Sprintf("Invalid String Length: String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen),
        )

        return
    }
}
```

Optionally and depending on the complexity, it may be desirable to also create a helper function to instantiate the validator. For example:

```go
func stringLengthBetween(minLength int, maxLength int) stringLengthBetweenValidator {
    return stringLengthBetweenValidator{
        Max: maxLength,
        Min: minLength,
    }
}
```

A single validator type can be used as both an attribute validator and a parameter validator, as long as the validator implements the appropriate interfaces. For example:

```go
var (
    _ validator.String = stringLengthBetweenValidator{}
    _ function.StringParameterValidator = stringLengthBetweenValidator{}

)
type stringLengthBetweenValidator struct {
    Max int
    Min int
}

// Description returns a plain text description of the attribute validator's behavior, suitable for a practitioner to understand its impact.
func (v stringLengthBetweenValidator) Description(ctx context.Context) string {
    return fmt.Sprintf("string length must be between %d and %d", v.Min, v.Max)
}

// MarkdownDescription returns a markdown formatted description of the attribute validator's behavior, suitable for a practitioner to understand its impact.
func (v stringLengthBetweenValidator) MarkdownDescription(ctx context.Context) string {
    return fmt.Sprintf("string length must be between `%d` and `%d`", v.Min, v.Max)
}

// Validate runs the main validation logic of the attribute validator, reading configuration data out of `req` and updating `resp` with diagnostics.
func (v stringLengthBetweenValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
    // If the value is unknown or null, there is nothing to validate.
    if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() {
        return
    }

    strLen := len(req.ConfigValue.ValueString())

    if strLen < v.Min || strLen > v.Max {
        resp.Diagnostics.AddAttributeError(
            req.AttributePath,
            "Invalid String Length",
            fmt.Sprintf("String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen),
        )

        return
    }
}

// Validate runs the main validation logic of the parameter validator, reading configuration data out of `req` and updating `resp` with diagnostics.
func (v stringLengthBetweenValidator) ValidateParameterString(ctx context.Context, req validator.StringParameterValidatorRequest, resp *validator.StringParameterValidatorResponse) {
	// If the value is unknown or null, there is nothing to validate.
    if req.Value.IsUnknown() || req.Value.IsNull() {
        return
    }

    strLen := len(req.Value.ValueString())

    if strLen < v.Min || strLen > v.Max {
		resp.Error = function.NewArgumentFuncError(
			req.ArgumentPosition,
            fmt.Sprintf("Invalid String Length: String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen),
        )

        return
    }
}
```

## Value Validation

Validation of custom value types can be used for both attribute values and provider-defined function parameter values. This can be useful if you have consistent validation rules for a specific value type across multiple attributes or function parameters.

When you implement validation on a custom value type associated with a schema attribute, you do not need to declare the same validation on the attribute, but you can supply additional validations in that manner. For example:

```go
// Typically within the schema.Schema returned by Schema() for a provider,
// resource, or data source.
schema.StringAttribute{
    // ... other Attribute configuration ...

    // This is an example type with a corresponding custom value type
    // which implements its own validation
    CustomType: computeInstanceIdentifierType,

    // This is optional, example validation that is checked in addition
    // to any validation performed by the custom value type
    Validators: []validator.String{
        stringvalidator.LengthBetween(10, 256),
    },
}
```

### Defining Value Validation

To support validation for a custom value type, you must implement [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute) for attribute validation, or [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) for provider-defined function parameter validation.

Both interfaces can be implemented if the same custom value type is used for both attributes and function parameters, for example:

```go
// Ensure the implementation satisfies the expected interfaces
var (
    _ basetypes.StringValuable = computeInstanceIdentifierValue{}
    _ xattr.ValidateableAttribute = computeInstanceIdentifierValue{}
    _ function.ValidateableParameter = computeInstanceIdentifierValue{}
)

// Other methods to implement the attr.Value interface are omitted for brevity
type computeInstanceIdentifierValue struct {
    basetypes.StringValue
}

// Implementation of the xattr.ValidateableAttribute interface
func (v computeInstanceIdentifierValue) ValidateAttribute(ctx context.Context, req xattr.ValidateAttributeRequest, resp *xattr.ValidateAttributeResponse) {
    if v.IsNull() || v.IsUnknown() {
        return
    }

    if !v.isValid(v.ValueString()) {
        resp.Diagnostics.AddAttributeError(
            req.Path,
            "Compute Instance Type Validation Error",
            fmt.Sprintf("Missing `instance-` prefix, got: %s", v.ValueString()),
        )

        return
    }
}

// Implementation of the function.ValidateableParameter interface
func (v computeInstanceIdentifierValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) {
    if v.IsNull() || v.IsUnknown() {
        return
    }

    if !v.isValid(v.ValueString()) {
        resp.Error = function.NewArgumentFuncError(
            req.Position,
            fmt.Sprintf("Compute Instance Type Validation Error: Missing `instance-` prefix, got: %s", v.ValueString()),
        )

        return
    }
}

func (v computeInstanceIdentifierValue) isValid(in string) bool {
    return strings.HasPrefix(in, "instance-")
}
```

## Type Validation

<Note>

`Value` validation should be used in preference to `Type` validation. Refer to [Value Validation](#value-validation) for more information.

</Note>

You may want to create a custom type to simplify schemas if your provider contains common attribute values with consistent validation rules. When you implement validation on a type, you do not need to declare the same validation on the attribute, but you can supply additional validations in that manner. For example:

```go
// Typically within the schema.Schema returned by Schema() for a provider,
// resource, or data source.
schema.StringAttribute{
    // ... other Attribute configuration ...

    // This is an example type which implements its own validation
    CustomType: computeInstanceIdentifierType,

    // This is optional, example validation that is checked in addition
    // to any validation performed by the type
    Validators: []validator.String{
        stringvalidator.LengthBetween(10, 256),
    },
}
```

### Defining Type Validation

<Note>

The [`xattr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate) has been deprecated. Refer to [Defining Value Validation](#defining-value-validation) for more information about using [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute), and [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) instead.

</Note>

To support validation within a type, you must implement the [`xattr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate). For example:

```go
// Ensure type satisfies xattr.TypeWithValidate interface
var _ xattr.TypeWithValidate = computeInstanceIdentifierType{}

// Other methods to implement the attr.Type interface are omitted for brevity
type computeInstanceIdentifierType struct {}

func (t computeInstanceIdentifierType) Validate(ctx context.Context, tfValue tftypes.Value, path path.Path) diag.Diagnostics {
    var diags diag.Diagnostics

    if !tfValue.Type().Equal(tftypes.String) {
        diags.AddAttributeError(
            path,
            "Compute Instance Type Validation Error",
            fmt.Sprintf("Expected String value, received %T with value: %v", tfValue, tfValue),
        )
        return diags
    }

    if !tfValue.IsKnown() || tfValue.IsNull() {
        return diags
    }

    var value string
    err := tfValue.As(&value)

    if err != nil {
        diags.AddAttributeError(
            path,
            "Compute Instance Type Validation Error",
            fmt.Sprintf("Cannot convert value to string: %s", err),
        )
        return diags
    }

    if !strings.HasPrefix(value, "instance-") {
        diags.AddAttributeError(
            path,
            "Compute Instance Type Validation Error",
            fmt.Sprintf("Missing `instance-` prefix, got: %s", value),
        )
        return diags
    }
}
```
