Skip to main content

Overview

It’s possible to define custom error types by implementing the Error() method on them. Custom error types allow you to attach additional structured data to errors and provide more context about what went wrong.

Creating Custom Error Types

A custom error type usually has the suffix “Error” and implements the error interface:
type argError struct {
    arg     int
    message string
}

// Adding this Error method makes argError implement
// the error interface
func (e *argError) Error() string {
    return fmt.Sprintf("%d - %s", e.arg, e.message)
}
Custom error types should implement the Error() string method to satisfy the error interface.

Using Custom Errors

Once defined, you can return custom errors from your functions:
func f(arg int) (int, error) {
    if arg == 42 {
        // Return our custom error
        return -1, &argError{arg, "can't work with it"}
    }
    return arg + 3, nil
}

Type Assertion with errors.AsType

errors.AsType is a more advanced version of errors.Is. It checks that a given error (or any error in its chain) matches a specific error type and converts to a value of that type:
_, err := f(42)
if ae, ok := errors.AsType[*argError](err); ok {
    fmt.Println(ae.arg)
    fmt.Println(ae.message)
} else {
    fmt.Println("err doesn't match argError")
}
errors.AsType returns two values: the converted error value and a boolean indicating whether the conversion succeeded.

Complete Example

package main

import (
	"errors"
	"fmt"
)

// A custom error type usually has the suffix "Error"
type argError struct {
	arg     int
	message string
}

// Adding this Error method makes argError implement
// the error interface
func (e *argError) Error() string {
	return fmt.Sprintf("%d - %s", e.arg, e.message)
}

func f(arg int) (int, error) {
	if arg == 42 {
		// Return our custom error
		return -1, &argError{arg, "can't work with it"}
	}
	return arg + 3, nil
}

func main() {
	// errors.AsType is a more advanced version of errors.Is.
	// It checks that a given error (or any error in its chain)
	// matches a specific error type and converts to a value
	// of that type, also returning true. If there's no match, the
	// second return value is false.
	_, err := f(42)
	if ae, ok := errors.AsType[*argError](err); ok {
		fmt.Println(ae.arg)
		fmt.Println(ae.message)
	} else {
		fmt.Println("err doesn't match argError")
	}
}

Output

$ go run custom-errors.go
42
can't work with it

When to Use Custom Errors

Use custom errors when you need to attach additional structured information to errors, such as error codes, timestamps, or contextual data that callers might need to access programmatically.
Custom errors are useful when different error types require different handling logic. Callers can use type assertions to determine the specific error type and respond accordingly.
Libraries and packages often define custom error types to give users a clear, structured way to handle different error conditions that the package might encounter.

Best Practices

Name with Error suffix

Convention: custom error types should end with “Error” (e.g., ValidationError, TimeoutError).

Export when needed

Export custom error types (capitalize them) when callers need to perform type assertions.

Include context

Store relevant contextual information in struct fields for better debugging.

Use pointer receivers

Return pointers to custom errors (*argError) rather than values for consistency.

Comparison with Standard Errors

FeatureStandard ErrorsCustom Errors
Creationerrors.New("message")&CustomError{...}
Type checkingerrors.Is()errors.AsType()
Structured dataNoYes
Use caseSimple error messagesComplex error handling with context

Build docs developers (and LLMs) love