GORM — record not found error on a lookup
Handle GORM ErrRecordNotFound by using Find for optional lookups and checking errors explicitly.
Symptoms
- record not found
- First or Take returns gorm.ErrRecordNotFound
- The error appears for valid but absent rows
- Code treats a missing row as a failure
Possible causes
- First or Take returns ErrRecordNotFound when no row matches
- The query filters differ from the stored data
- Soft-deleted rows are excluded by default
- Callers mistake an empty result for an infrastructure error
Diagnosis steps
Confirm the query actually matches rows
db.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Model(&User{}).Where("id = ?", id).First(&u) })Interpretation
ToSQL prints the generated statement, revealing filter or table issues.
Use Find when absence is acceptable
db.Where("id = ?", id).Find(&users)Interpretation
Find returns an empty slice rather than an error for no matches, which suits optional lookups.
Check soft delete behavior
grep -n "gorm.DeletedAt\|Unscoped" models/*.goInterpretation
Soft-deleted rows are filtered unless you use Unscoped, which explains a missing row that exists in the table.
Root cause
The ORM reported ErrRecordNotFound because no row matched the query after defaults and soft-delete filtering, which the caller treated as an error rather than an expected empty result.
Fix
Use Find for lookups where absence is valid, check errors with errors.Is against gorm.ErrRecordNotFound, and use Unscoped when soft-deleted rows should be included.
Verify the fix
Run the lookup with a known-absent and a known-present value and confirm each path behaves as intended, and confirm soft-deleted rows appear only when Unscoped is used.
Symptom walkthrough
A lookup returns record not found, and callers may treat it as an unexpected failure. GORM returns gorm.ErrRecordNotFound from First and Take when nothing matches, and it also filters soft-deleted rows by default. The message is accurate; the question is whether absence is an error or a normal outcome for that call site.
Fix in practice
Use Find when the caller can tolerate an empty result, and compare errors with errors.Is to distinguish not-found from real failures. When a row exists but is soft-deleted, use Unscoped to include it. Decide the semantics at each call site rather than blanket-handling the error.
Verification
Look up a known-absent and a known-present identifier and confirm each path behaves as designed, then confirm a soft-deleted row appears only with Unscoped. Correct behavior on both cases confirms the handling.
Diagnostic commands
Check the behavior with commands like these:
db.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Model(&User{}).Where("id = ?", id).First(&u) })
db.Where("id = ?", id).Find(&users)
grep -n "gorm.DeletedAt\|Unscoped" models/*.go