---
page_title: 'Plugin Development - Framework: Errors and Warnings'
description: |-
  How to return errors and warnings from the Terraform provider development
  framework.
---

# Returning Errors and Warnings

Providers use `Diagnostics` to surface errors and warnings to practitioners,
such as contextual messages returned from Terraform CLI at the end of
command output:

```console
$ terraform plan
# ... other plan output ...
╷
│ Error: Summary
│ 
│   on example.tf line #:
│    #: source configuration line
│ 
│ Details
╵
```

In the framework, you may encounter them in response structs or as returns from
functions or methods:

```go
func (m myResource) Create(ctx context.Context,
	req resource.CreateRequest, resp *resource.CreateResponse)
```

This is the most common form for Diagnostics: a slice that has one or more
errors appended to it. This approach allows your provider to inform
practitioners about all relevant errors and warnings at the same time, allowing
practitioners to fix their configuration or environment more quickly. You
should only append to Diagnostics slices and never replace or remove
information from them.

The next section will detail the concepts and typical behaviors of
diagnostics, while the final section will outline the typical methods for
working with diagnostics, using functionality from the available
[`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag).

## Diagnostic Concepts

### Severity

`Severity` specifies whether the diagnostic is an error or a warning. Neither Terraform, nor the framework, supports other severity levels. Use [logging](/terraform/plugin/log/writing) for debugging or informational purposes.

- An **error** will be displayed to the practitioner and halt Terraform's
  execution, not continuing to apply changes to later resources in the graph.
  We recommend using errors to inform practitioners about a situation the
  provider could not recover from.
- A **warning** will be displayed to the practitioner, but will not halt
  further execution, and is considered informative only. We recommend using
  warnings to inform practitioners about suboptimal situations that the
  practitioner should resolve to ensure stable functioning (e.g., deprecations)
  or to inform practitioners about possible unexpected behaviors.

### Summary

`Summary` is a short, practitioner-oriented description of the problem. Good
summaries are general—they don't contain specific details about
values—and concise. For example, "Error creating resource", "Invalid
value for foo", or "Field foo is deprecated".

### Detail

`Detail` is a longer, more specific practitioner-oriented description of
precisely what went wrong. Good details are specific—they tell the
practitioner exactly what they need to fix and how. For example, "The API
is currently unavailable, please try the request again.", "foo can only contain
letters, numbers, and digits.", or "foo has been deprecated in favor of bar.
Please update your configuration to use bar instead. foo will be removed in a
future release.".

### Attribute

`Attribute` identifies the specific part of a configuration that caused the
error or warning. Only diagnostics that pertain to a whole attribute or a
specific attribute value will include this information.

### Argument

`Argument` identifies the specific function argument position that caused the
error or warning. Only diagnostics that pertain to a function argument will
include this information.

## How Errors Affect State

**Returning an error diagnostic does not stop the state from being updated**.
Terraform will still persist the returned state even when an error diagnostic
is returned with it. This is to allow Terraform to persist the values that have
already been modified when a resource modification requires multiple API
requests or an API request fails after an earlier one succeeded.

When returning error diagnostics, we recommend resetting the state in the
response to the prior state available in the configuration.

## diag Package

The framework provides the `diag` package for interacting with diagnostics.
While the [Go documentation](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag)
contains the complete functionality, this section will highlight the most
common methods.

### Working With Existing Diagnostics

#### Append

When receiving `diag.Diagnostics` from a function or method, such as
`Config.Get()` or `State.Set()`, these should typically be appended to the
response diagnostics for the method. This can be accomplished with the
[`Append(in ...diag.Diagnostics)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.Append).

For example:

```go
func (m myResource) Create(ctx context.Context,
    req resource.CreateRequest, resp *resource.CreateResponse) {
    // ... prior logic ...
    diags := req.Config.Get(ctx, &resourceData)
    resp.Diagnostics.Append(diags...)
    // ... further logic ...
}
```

This method automatically ignores `nil` or empty slice diagnostics and
deduplicates where possible.

#### HasError

The most typical form of diagnostics checking is ensuring that execution should
not stop due to encountering an error, potentially causing further confusing
errors or crashes. The [`HasError()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.HasError)
will check each of the diagnostics for error severity and return true if found.

For example:

```go
func (m myResource) Create(ctx context.Context,
    req resource.CreateRequest, resp *resource.CreateResponse) {
    // ... prior logic ...
    diags := req.Config.Get(ctx, &resourceData)
    resp.Diagnostics.Append(diags...)

    if resp.Diagnostics.HasError() {
      return
    }
    // ... further logic ...
}
```

In this example, you will note that we opted to check `resp.Diagnostics`
instead of `diags`. Technically checking either is correct, however, checking
the response diagnostics can help ensure that any response will include the
expected diagnostics.

### Creating Diagnostics

When working with logic outside the framework, such as interacting with the
vendor or `net/http` library to make the actual calls to manage infrastructure
or creating custom plan modifiers and validators, it will be necessary to
create diagnostics. The `diag` package provides helper methods and allows
custom abstractions as described below.

To craft the summary of a diagnostic, it is recommended to use a concise title
or single sentence that immediately can allow the practitioner to determine
the error cause and when it occurred.

To craft the details portion of diagnostics, it is recommended to provide
practitioners (and potentially you as the maintainer) as much contextual,
troubleshooting, and next action information as possible. These details can
use newlines for easier readability where necessary.

For example, with the top line as a summary and below as details:

```text
API Error Reading Resource
```

```text
An unexpected error was encountered while reading the resource.

Please check that the credentials being used are active and have sufficient
permissions to perform the Example API call against the resource.

Region: example
ID: example123
API Response: 403 Access Denied
```

#### AddError and AddWarning

When creating diagnostics that affect an entire data source, provider, or
resource, and where a `diag.Diagnostics` is already available such as within
a response type, the [`AddError(summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddError)
and [`AddWarning(summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddWarning)
can append a new error or warning diagnostic.

For example:

```go
func (m myResource) Create(ctx context.Context,
    req resource.CreateRequest, resp *resource.CreateResponse) {
    // ... prior logic ...
    resp, err := http.Post("https://example.com")

    if err != nil {
      resp.Diagnostics.AddError(
        "API Error Creating Resource",
        fmt.Sprintf("... details ... %s", err)
      )
      return
    }
    // ... further logic ...
}
```

#### AddAttributeError and AddAttributeWarning

When creating diagnostics that affect only a single attribute, which is
typical of attribute-level plan modifiers and validators, the
[`AddAttributeError(path path.Path, summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddAttributeError)
and [`AddAttributeWarning(path path.Path, summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddAttributeWarning)
can append a new error or warning diagnostic pointing specifically at the
attribute path. This provides additional context to practitioners, such as
showing the specific line(s) and value(s) of configuration where possible.

For example:

```go
func (s exampleType) Validate(ctx context.Context, in tftypes.Value, path path.Path) diag.Diagnostics {
    var diags diag.Diagnostics

    if !in.Type().Is(tftypes.Set{}) {
        err := fmt.Errorf()
        diags.AddAttributeError(
            path,
            "Example Type Validation Error",
            "An unexpected error was encountered trying to validate an attribute value. "+
                "This is always an error in the provider. "+
                "Please report the following to the provider developer:\n\n"+
                fmt.Sprintf("Expected Set value, received %T with value: %v", in, in),
        )
        return diags
    }
    // ... further logic ...
```

### Consistent Diagnostic Creation

Create a helper function in your provider code using the diagnostic creation functions available in the [`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag) to generate consistent diagnostics for types of errors/warnings. It is also possible to use [custom diagnostics types](#custom-diagnostics-types) to accomplish this same goal.

The [`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag) provides these functions to create various diagnostics:

| Function | Description |
|---|---|
| [`diag.NewArgumentErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewArgumentErrorDiagnostic) | Create a new error diagnostic with a function argument position. |
| [`diag.NewArgumentWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewArgumentWarningDiagnostic) | Create a new warning diagnostic with a function argument position. |
| [`diag.NewAttributeErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewAttributeErrorDiagnostic) | Create a new error diagnostic with a [path](/terraform/plugin/framework/handling-data/paths). |
| [`diag.NewAttributeWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewAttributeWarningDiagnostic) | Create a new warning diagnostic with a [path](/terraform/plugin/framework/handling-data/paths). |
| [`diag.NewErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewErrorDiagnostic) | Create a new error diagnostic without a [path](/terraform/plugin/framework/handling-data/paths). |
| [`diag.NewWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewWarningDiagnostic) | Create a new warning diagnostic without a [path](/terraform/plugin/framework/handling-data/paths). |

In this example, the provider code is setup to always convert `error` returns from the API SDK to a consistent error diagnostic.

```go
func APIErrorDiagnostic(err error) diag.Diagnostic {
  return diag.NewErrorDiagnostic(
    "Unexpected API Error",
    "While calling the API, an unexpected error was returned in the response. "+
      "Please contact support if you are unsure how to resolve the error.\n\n"+
      "Error: "+err.Error(),
  )
}
```

This enables calling code in the provider, such as:

```go
func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp resource.ReadResponse) {
  // ... other logic ...

  apiResp, err := examplesdk.Read(/* ... */) // example API SDK call that may return an error

  if err != nil {
    resp.Diagnostics.Append(APIErrorDiagnostic(err))

    return
  }

  // ... further logic ...
}
```

## Custom Diagnostics Types

Advanced provider developers may want to store additional data in diagnostics for other logic or create custom diagnostics that include specialized logic.

The [`diag.Diagnostic` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostic) that can be implemented with these methods:

```go
type Diagnostic interface {
    Severity()        Severity
    Summary()         string
    Detail()          string
    Equal(Diagnostic) bool
}
```

To include attribute path information, the [`diag.DiagnosticWithPath` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#DiagnosticWithPath) can be implemented with the additional `Path()` method:

```go
type DiagnosticWithPath interface {
    Diagnostic
    Path() path.Path
}
```

To include function argument information, the [`diag.DiagnosticWithFunctionArgument` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#DiagnosticWithFunctionArgument) can be implemented with the additional `FunctionArgument()` method:

```go
type DiagnosticWithFunctionArgument interface {
    Diagnostic
    FunctionArgument() int
}
```

In this example, a custom diagnostic type stores an underlying `error` that caused the diagnostic:

```go
// UnderlyingErrorDiagnostic is an error diagnostic
// which also stores the underlying error.
type UnderlyingErrorDiagnostic struct {
  Detail          string
  Summary         string
  UnderlyingError error
}

func (d UnderlyingErrorDiagnostic) Detail() string {
  return d.Detail
}

func (d UnderlyingErrorDiagnostic) Equal(o SpecialDiagnostic) bool {
  if d.Detail() != o.Detail() {
    return false
  }

  if d.Summary() != o.Summary() {
    return false
  }

  if d.UnderlyingError == nil {
    return o.UnderlyingError == nil
  }

  if o.UnderlyingError == nil {
    return false
  }

  if d.UnderlyingError.Error() != o.UnderlyingError.Error() {
    return false
  }

  return true
}

func (d UnderlyingErrorDiagnostic) Severity() diag.Severity {
  return diag.SeverityError
}

func (d UnderlyingErrorDiagnostic) Summary() string {
  return d.Summary
}
```
