100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Go

Custom Errors and errors.Is/As in Go

Build custom error types in Go and use errors.Is and errors.As to inspect and match wrapped errors.

Error HandlingIntermediate9 min readJul 8, 2026
Analogies

Introduction

Basic errors created with errors.New only carry a text message. Real applications often need richer error information, such as an error code, a field name, or the original cause. Go lets you define custom error types by implementing the Error() string method on any type. Combined with error wrapping (fmt.Errorf with %w), the standard library functions errors.Is and errors.As let calling code inspect an error chain to check for a specific sentinel error or extract a specific error type.

🏏

Cricket analogy: A basic errors.New is like a scoreboard simply flashing 'OUT', while a custom error type with fields is like the third umpire's report specifying it was an lbw review with a specific ball-tracking reason, and errors.Is/As let a captain check the exact dismissal type down the review chain.

Syntax

go
type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Msg)
}

var ErrNotFound = errors.New("not found")

if errors.Is(err, ErrNotFound) {
    // matched a sentinel error, possibly wrapped
}

var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println(ve.Field)
}

Explanation

A custom error type is just a struct (or any type) with an Error() string method, satisfying the error interface. Sentinel errors are package-level error values, like var ErrNotFound = errors.New("not found"), used as known markers to compare against. errors.Is(err, target) walks the error chain (following wrapped errors created with %w) and reports whether any error in the chain equals the target, which is useful for sentinel errors. errors.As(err, &target) walks the chain looking for an error whose concrete type matches target, and if found, assigns it into target so you can access its fields, which is useful for custom error types like ValidationError.

🏏

Cricket analogy: A sentinel error like ErrNotFound is like a standing rule 'no result' declared for a rain-out, checked with errors.Is by comparing against that known outcome, while errors.As is like extracting the full match report (a ValidationError) to read specific details such as overs bowled when rain struck.

Example

go
package main

import (
    "errors"
    "fmt"
)

type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Msg)
}

var ErrNotFound = errors.New("record not found")

func lookup(id int) error {
    if id < 0 {
        return &ValidationError{Field: "id", Msg: "must be non-negative"}
    }
    if id == 0 {
        return fmt.Errorf("lookup failed: %w", ErrNotFound)
    }
    return nil
}

func main() {
    err := lookup(0)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("matched sentinel: record not found")
    }

    err2 := lookup(-1)
    var ve *ValidationError
    if errors.As(err2, &ve) {
        fmt.Println("invalid field:", ve.Field)
    }
}

Output

go
matched sentinel: record not found
invalid field: id

Key Takeaways

  • A custom error type is any type implementing Error() string.
  • Sentinel errors are package-level error values used as comparison markers.
  • errors.Is checks whether a wrapped error chain contains a specific sentinel error.
  • errors.As extracts a specific error type from a wrapped chain into a target variable.
  • fmt.Errorf with %w is what makes an error chain traversable by errors.Is/As.
  • Custom error types can carry structured data like field names or error codes.

Practice what you learned

Was this page helpful?

Topics covered

#Go#GoProgrammingStudyNotes#Programming#CustomErrorsAndErrorsIsAsInGo#Custom#Errors#Syntax#Explanation#StudyNotes#SkillVeris