How do you create a custom error type in Go?
Quick Answer
Define a type (usually a struct) with an Error() string method, satisfying the built-in error interface: type ValidationError struct { Field, Msg string }; func (e *ValidationError) Error() string { return e.Field + ": " + e.Msg }. Return it as a normal error (return &ValidationError{...}), and callers use errors.As to check for it and extract its fields when they need the extra structured data it carries. A custom error type is worth the extra code specifically when callers need programmatic access to details beyond a plain message string — like a field name, an HTTP status code, or a retry-after duration — which a plain sentinel error (just an opaque value) can't provide.
Detailed Answer
A custom error type is just a normal Go type with one required method. The value comes from what extra data it can carry.
Defining one
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Msg)
}
Using a pointer receiver (*ValidationError) is the common convention, so errors.As and comparisons behave consistently, and so the error can be wrapped/unwrapped cleanly.
Returning and consuming it
func validateAge(age int) error {
if age < 0 {
return &ValidationError{Field: "age", Msg: "must be non-negative"}
}
return nil
}
err := validateAge(-5)
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Println("problem field:", valErr.Field) // "age" — real structured data, not just a string
// could also drive different HTTP status codes, retry logic, etc. based on valErr's fields
}
Adding an Unwrap method for chaining
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string {
return fmt.Sprintf("query %q failed: %v", e.Query, e.Err)
}
func (e *QueryError) Unwrap() error {
return e.Err // lets errors.Is/errors.As see through to the wrapped cause
}
Implementing Unwrap() error on a custom type makes it participate in errors.Is/errors.As chains, exactly like fmt.Errorf("%w", ...) does automatically.
When it's worth the extra code
If every caller only ever needs a message and an identity check, a sentinel error is simpler and sufficient. Reach for a custom type once callers need to branch on something structured. That could be an HTTP status code, a field name to highlight in a UI, or a duration to wait before retrying.