Skip to main content
GORM wraps single create, update, and delete operations in a transaction by default to ensure data integrity. You can also manage transactions explicitly for multi-step operations.

Automatic transactions

Every call to Create, Update, and Delete runs inside an implicit transaction. If the operation succeeds it commits; if anything fails it rolls back. Disable this behavior globally when you need better performance for high-throughput, single-operation workloads:
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    SkipDefaultTransaction: true,
})
Or disable it for a single session:
tx := db.Session(&gorm.Session{SkipDefaultTransaction: true})
tx.Create(&user) // no wrapping transaction

Transaction block

Use db.Transaction to run multiple operations inside a single transaction. Return an error to roll back; return nil to commit.
err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&User{Name: "Alice"}).Error; err != nil {
        return err // rollback
    }

    if err := tx.Create(&Order{UserID: 1, Amount: 100}).Error; err != nil {
        return err // rollback
    }

    return nil // commit
})
GORM automatically rolls back the transaction if the callback panics, in addition to when an error is returned.

Manual transactions

Use Begin, Commit, and Rollback when you need explicit control over transaction boundaries.
tx := db.Begin()

if err := tx.Create(&user).Error; err != nil {
    tx.Rollback()
    return err
}

if err := tx.Update("email", user.Email).Error; err != nil {
    tx.Rollback()
    return err
}

return tx.Commit().Error
Pass *sql.TxOptions to Begin to set isolation level or read-only mode:
import "database/sql"

tx := db.Begin(&sql.TxOptions{
    Isolation: sql.LevelSerializable,
    ReadOnly:  false,
})

Nested transactions

When you call db.Transaction inside an existing transaction, GORM automatically creates a savepoint instead of starting a new transaction. Rolling back only undoes the work done since that savepoint.
err := db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&User{Name: "outer"})

    // nested — uses a savepoint internally
    err := tx.Transaction(func(tx2 *gorm.DB) error {
        tx2.Create(&User{Name: "inner"})
        return errors.New("inner failed") // rolls back only the inner work
    })

    // outer transaction continues
    tx.Create(&User{Name: "outer-2"})
    return nil
})
Disable nested transaction support to treat every db.Transaction call as a top-level transaction:
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    DisableNestedTransaction: true,
})

Savepoints

Create and roll back to named savepoints manually with SavePoint and RollbackTo.
tx := db.Begin()

tx.Create(&User{Name: "Alice"})
tx.SavePoint("sp1")

tx.Create(&User{Name: "Bob"})
tx.RollbackTo("sp1") // undoes Bob, keeps Alice

tx.Commit()
Savepoints are not supported by all database drivers. Check your driver’s documentation before relying on them.

Transaction timeout

Set a default timeout for all transactions so that long-running operations are automatically cancelled:
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
    DefaultTransactionTimeout: 30 * time.Second,
})
If the transaction’s context already has a deadline, GORM respects it and does not apply DefaultTransactionTimeout.

Using context with transactions

Pass a context to a transaction to support cancellation and deadlines:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user).Error; err != nil {
        return err
    }
    return nil
})

Build docs developers (and LLMs) love