fmt.Errorf() / %w
fmt.Errorf() creates a formatted error message. Using the %w verb, you can wrap an existing error to add context while preserving the original error information.
Syntax
import "fmt"
// Creates a formatted error.
err := fmt.Errorf("format", args...)
// Wraps an error with %w (makes it searchable with errors.Is / errors.As).
wrapped := fmt.Errorf("context info: %w", originalErr)
// Embeds an error message with %v (not wrapped, so errors.Is cannot find it).
notWrapped := fmt.Errorf("context info: %v", originalErr)
Difference between %w and %v
| Verb | Description |
|---|---|
| %w | Wraps the error. The resulting error has an Unwrap() method, allowing errors.Is() and errors.As() to recursively search the original error. |
| %v | Embeds only the error's message string. No wrapping occurs, so errors.Is() cannot find the original error. |
| Error chain | Multiple levels of wrapping are supported. errors.Is() traverses the entire chain to find a matching error. |
Sample Code
package main
import (
"errors"
"fmt"
)
// Defines a sentinel error.
var ErrDatabase = errors.New("database error")
// A low-level function.
func queryDB(id int) error {
if id <= 0 {
return ErrDatabase
}
return nil
}
// A middle-layer function that adds context information.
func getUser(id int) error {
if err := queryDB(id); err != nil {
// Wraps with %w to build an error chain.
return fmt.Errorf("getUser(id=%d): %w", id, err)
}
return nil
}
// A higher-layer function that wraps further.
func handleRequest(id int) error {
if err := getUser(id); err != nil {
return fmt.Errorf("handleRequest: %w", err)
}
return nil
}
func main() {
err := handleRequest(-1)
if err != nil {
// Prints the full error message.
fmt.Println(err)
// Outputs: "handleRequest: getUser(id=-1): database error"
// errors.Is() traverses the chain and finds ErrDatabase.
if errors.Is(err, ErrDatabase) {
fmt.Println("The root cause is a database error")
}
// errors.Unwrap() unwraps one level at a time.
inner := errors.Unwrap(err)
fmt.Println(inner) // Outputs: "getUser(id=-1): database error"
inner2 := errors.Unwrap(inner)
fmt.Println(inner2) // Outputs: "database error"
}
// %v does not wrap, so errors.Is() cannot find the original error.
notWrapped := fmt.Errorf("failed: %v", ErrDatabase)
fmt.Println(errors.Is(notWrapped, ErrDatabase)) // Outputs: "false"
// %w wraps the error, so errors.Is() can find it.
wrapped := fmt.Errorf("failed: %w", ErrDatabase)
fmt.Println(errors.Is(wrapped, ErrDatabase)) // Outputs: "true"
}
Notes
Wrapping errors with %w is the standard Go approach for adding context — such as which function failed or what arguments were used — while still allowing callers to inspect the underlying error type. Reading the error message alone tells you the full path the error traveled.
Only one %w can be used in a single fmt.Errorf() call (Go 1.20 and later allow multiple, but it is best to stick to one to keep things simple).
Use errors.Is() / errors.As() to check errors. For attaching richer error information using custom error types, see Custom Error Types.
If you find any errors or copyright issues, please contact us.