go-gorm/gorm · warning
record not found
Error message
record not found
What it means
logger.ErrRecordNotFound is the sentinel error 'record not found' defined in the logger package (mirroring gorm.ErrRecordNotFound). It is returned by First/Take/Last finishers when the query matched zero rows and the Statement has the RecordNotFound condition set. It is not a driver error - GORM synthesizes it after RowsAffected == 0.
Source
Thrown at logger/logger.go:17
// Package logger provides a logger interface and its implementation for GORM.
package logger
import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"time"
"gorm.io/gorm/utils"
)
// ErrRecordNotFound record not found error
var ErrRecordNotFound = errors.New("record not found")
// Colors
const (
Reset = "\033[0m"
Red = "\033[31m"
Green = "\033[32m"
Yellow = "\033[33m"
Blue = "\033[34m"
Magenta = "\033[35m"
Cyan = "\033[36m"
White = "\033[37m"
BlueBold = "\033[34;1m"
MagentaBold = "\033[35;1m"
RedBold = "\033[31;1m"
YellowBold = "\033[33;1m"
)
// LogLevel log levelView on GitHub (pinned to 1d6ce99528)
Solutions
- Treat ErrRecordNotFound as an expected branch: check errors.Is(err, gorm.ErrRecordNotFound) and return a 404/empty result instead of a 500.
- Verify the ID/conditions actually match a row (row may be soft-deleted - query with Unscoped if recovery UI needs it).
- If an empty result is normal for your flow, use Find(&users) which returns no error for zero rows.
- For race-prone reads, wrap the read+act in a transaction or use upsert (clause.OnConflict) instead of read-then-insert.
Example fix
// before
err := db.First(&user, id).Error
if err != nil { return 500 }
// after
err := db.First(&user, id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrNotFound // 404 to caller
}
if err != nil { return err } Defensive patterns
Strategy: try-catch
Type guard
func isNotFound(err error) bool { return errors.Is(err, gorm.ErrRecordNotFound) } Try / catch
err := db.First(&user, id).Error
switch {
case err == nil:
case errors.Is(err, gorm.ErrRecordNotFound):
return ErrNotFound // expected: 404
default:
return err // unexpected: 500
} Prevention
- Never treat First's error as fatal without first excluding ErrRecordNotFound via errors.Is.
- Use Find for 'zero rows is fine' flows; reserve First for 'must exist' flows.
- In race-prone flows prefer upserts or transactions over read-then-act.
When it happens
Trigger: Calling db.First(&user, id), db.Take, or db.Last on a table where no row matches the WHERE clause; also Session with AllowGlobalUpdate off does not matter here - any First-family read returning 0 rows yields this error.
Common situations: Looking up a record by an ID that was deleted or never existed; filtering with conditions that match nothing (typo'd status value, soft-deleted rows hidden by a soft_delete plugin); race where another request deleted the row between check and read; using Find semantics (which do NOT error) vs First semantics (which do) inconsistently.
Related errors
- unsupported data type: Table not set, please set it like: db
- violates check constraint
- unsupported relationship
- failed to get schema
- not support
AI-assisted analysis of go-gorm/gorm@1d6ce99528 (2026-08-15).
Data as JSON: /api/errors/5b3c097695f5172a.
Report an issue: GitHub.