Custom Error Types
A custom error type is created by implementing the Error() string method on a struct. Use this when you want an error to carry additional information, such as an error code or a field name.
Syntax
// Define a struct that implements the error interface.
type MyError struct {
Field Type
}
// Implement the Error() string method. This satisfies the error interface.
func (e *MyError) Error() string {
return "error message"
}
// Optionally implement Unwrap() to allow unwrapping the wrapped error.
func (e *MyError) Unwrap() error {
return e.Cause
}
Implementation Patterns
| Pattern | Description |
|---|---|
Implement Error() string | The only requirement of the error interface. Any type with this method is automatically treated as an error. |
Implement Unwrap() error | Implement this when wrapping another error. errors.Is() and errors.As() use this method to search the error chain. |
Use with errors.As() | Use errors.As(err, &target) to access a specific custom error type and retrieve its additional fields. |
| Pointer receiver vs. value receiver | Generally implement with a pointer receiver (*MyError). A value receiver also works, but be careful with nil pointer handling. |
Sample Code
package main
import (
"errors"
"fmt"
)
// A custom error type representing an HTTP error.
type HTTPError struct {
StatusCode int
Message string
Cause error // The wrapped underlying error.
}
// Implements the error interface.
func (e *HTTPError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("HTTP %d: %s (caused by: %v)", e.StatusCode, e.Message, e.Cause)
}
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Message)
}
// Implements Unwrap() to support error chain traversal.
func (e *HTTPError) Unwrap() error {
return e.Cause
}
// A custom error type representing a validation error.
type ValidationError struct {
Field string
Value any
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on '%s' (value=%v): %s", e.Field, e.Value, e.Message)
}
// A function that processes a request.
func processRequest(userID int) error {
if userID <= 0 {
return &ValidationError{
Field: "userID",
Value: userID,
Message: "must be a positive integer",
}
}
if userID > 1000 {
cause := errors.New("record not found in database")
return &HTTPError{StatusCode: 404, Message: "user not found", Cause: cause}
}
return nil
}
func main() {
// Example of a validation error.
err := processRequest(-5)
fmt.Println(err)
// Prints: "validation failed on 'userID' (value=-5): must be a positive integer"
// Use errors.As() to retrieve fields from the custom error type.
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field: %s\n", valErr.Field) // Prints: "Field: userID"
fmt.Printf("Value: %v\n", valErr.Value) // Prints: "Value: -5"
}
// Example of an HTTP error (with Unwrap support).
err = processRequest(9999)
fmt.Println(err)
// Prints: "HTTP 404: user not found (caused by: record not found in database)"
var httpErr *HTTPError
if errors.As(err, &httpErr) {
fmt.Printf("Status code: %d\n", httpErr.StatusCode) // Prints: "Status code: 404"
}
// errors.Is() can also search through unwrapped errors.
dbErr := errors.New("record not found in database")
wrapped := &HTTPError{StatusCode: 404, Message: "not found", Cause: dbErr}
fmt.Println(errors.Is(wrapped, dbErr)) // Prints: "true"
}
Notes
A custom error type can hold structured details — such as a status code, field name, or original value — in addition to the error kind itself. Combined with errors.As(), callers can access these details directly.
Always create custom errors using a pointer to the type (*MyError). A value type (MyError) will work, but comparisons with nil may produce unexpected results.
For wrapping errors and adding context, you can also use fmt.Errorf() / %w. For information on how to inspect errors, see error type / errors package.
If you find any errors or copyright issues, please contact us.