Skip to main content
GORM stores the last error from an operation on db.Error. Use the standard errors.Is function to check for specific sentinel errors — this works correctly even when errors are wrapped.
result := db.First(&user, 100)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
    // record does not exist
}
All GORM errors are package-level var values, not types. Always use errors.Is rather than direct equality (==) so that wrapping is handled correctly.

Error reference

Message: record not foundOccurs when: First, Take, or Last is called and no record matches the conditions.How to handle:
var user User
if err := db.First(&user, "name = ?", "nobody").Error; err != nil {
    if errors.Is(err, gorm.ErrRecordNotFound) {
        // return 404 or a default value
    }
    // unexpected error
}
Find does not return this error when the result set is empty — it returns a nil error with RowsAffected == 0.
Message: invalid transactionOccurs when: Commit or Rollback is called on a *DB that is not inside an active transaction, or when Begin cannot start a transaction because the pool does not implement TxBeginner or ConnPoolBeginner.
tx := db.Begin()
if errors.Is(tx.Error, gorm.ErrInvalidTransaction) {
    // pool does not support transactions
}
Message: not implementedOccurs when: A dialector or migrator method is called that the driver has not implemented.
Message: WHERE conditions requiredOccurs when: An Update, Updates, or Delete is executed without any WHERE conditions and Config.AllowGlobalUpdate is false (the default).
// This will fail with ErrMissingWhereClause
db.Delete(&User{})

// Correct — add a condition
db.Where("age > ?", 18).Delete(&User{})

// Or explicitly allow global operations
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
Message: unsupported relationsOccurs when: db.Association(column) is called with a field name that does not correspond to a known relationship on the model schema.
Message: primary key requiredOccurs when: An operation requires a primary key (e.g., FindInBatches pagination, or association operations) but the model has none defined or the value is zero.
Message: model value requiredOccurs when: A Model or target value is required for an operation but was not provided.
Message: model accessible fields requiredOccurs when: GORM cannot find any accessible (readable/writable) fields on the model, usually because the struct has no exported fields.
Message: sub query requiredOccurs when: A clause expects a subquery value (e.g., using *DB as a condition argument) but none was provided.
Message: unsupported dataOccurs when: A value passed to a GORM method is of a type that cannot be handled (e.g., an unsupported type in Select args).
Message: unsupported driverOccurs when: db.SavePoint or db.RollbackTo is called and the dialector does not implement SavePointerDialectorInterface.
Message: registeredOccurs when: db.Use(plugin) is called with a plugin whose Name() is already registered.
if err := db.Use(&MyPlugin{}); errors.Is(err, gorm.ErrRegistered) {
    // plugin is already loaded
}
Message: invalid fieldOccurs when: A referenced field name does not exist on the schema.
Message: empty slice foundOccurs when: An empty slice is passed to Create or a related operation that requires at least one element.
Message: dry run mode unsupportedOccurs when: Row() or Rows() is called while dry-run mode is active. Dry run can only generate SQL for operations that return a *DB; it cannot simulate raw row scanning.
Message: invalid dbOccurs when: db.DB() cannot resolve the underlying *sql.DB from the current connection pool — for example, when the pool is a custom type that does not implement GetDBConnector.
Message: invalid value, should be pointer to struct or sliceOccurs when: A non-pointer or otherwise unsuitable value is passed to a method that requires a pointer to a struct or slice (e.g., Association.Append with an un-addressable struct).
Message: invalid association values, length doesn't matchOccurs when: Association.Append or Association.Replace is called with a slice of values whose length does not match the number of parent records.
Message: preload is not allowed when count is usedOccurs when: Preload is combined with Count in the same query chain. Preloading makes no sense for a count query.
// This will fail
db.Preload("Orders").Count(&count)

// Correct — separate the count from the preload query
db.Model(&User{}).Count(&count)
Message: duplicated key not allowedOccurs when: A Create or Save violates a unique key constraint. Only returned when Config.TranslateError is true and the dialector implements ErrorTranslator.
result := db.Create(&user)
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
    // handle duplicate (e.g., return HTTP 409)
}
Message: violates foreign key constraintOccurs when: An insert or update violates a foreign key constraint. Only returned when Config.TranslateError is true and the dialector implements ErrorTranslator.
result := db.Create(&order) // where order.UserID does not exist
if errors.Is(result.Error, gorm.ErrForeignKeyViolated) {
    // handle referential integrity error
}
Message: violates check constraintOccurs when: An insert or update violates a CHECK constraint defined on the table. Only returned when Config.TranslateError is true and the dialector implements ErrorTranslator.
result := db.Create(&product) // where price < 0 violates CHECK (price >= 0)
if errors.Is(result.Error, gorm.ErrCheckConstraintViolated) {
    // handle constraint violation
}

Error handling patterns

// ErrRecordNotFound — safe "not found" check
var user User
if err := db.First(&user, id).Error; err != nil {
    if errors.Is(err, gorm.ErrRecordNotFound) {
        return nil, fmt.Errorf("user %d not found", id)
    }
    return nil, fmt.Errorf("database error: %w", err)
}
Enable Config.TranslateError: true to convert raw driver errors into typed GORM sentinel errors. Without it, ErrDuplicatedKey, ErrForeignKeyViolated, and ErrCheckConstraintViolated are never returned — you receive the raw *pq.Error or equivalent.

Build docs developers (and LLMs) love