Scopes let you define named query conditions as plain Go functions and apply them dynamically to any query. This keeps query logic reusable, testable, and easy to compose.
Defining a scope
A scope is any function with the signature func(*gorm.DB) *gorm.DB. Add conditions to the incoming db and return it.
func ActiveUsers(db *gorm.DB) *gorm.DB {
return db.Where("active = ?", true)
}
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("amount > ?", 1000)
}
Applying scopes
Pass one or more scope functions to db.Scopes. They are applied in order before the query executes.
var users []User
db.Scopes(ActiveUsers).Find(&users)
// SELECT * FROM users WHERE active = true
var orders []Order
db.Scopes(AmountGreaterThan1000, ActiveUsers).Find(&orders)
// SELECT * FROM orders WHERE amount > 1000 AND active = true
Parameterized scopes
When a scope needs runtime parameters, write a function that accepts the parameters and returns a scope function.
func OrderStatus(status []string) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("status IN (?)", status)
}
}
func PriceBetween(low, high float64) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("price BETWEEN ? AND ?", low, high)
}
}
Composing scopes
Combine simple and parameterized scopes freely on a single query.
var orders []Order
db.Scopes(
AmountGreaterThan1000,
OrderStatus([]string{"paid", "shipped"}),
PriceBetween(10.0, 500.0),
).Find(&orders)
// SELECT * FROM orders
// WHERE amount > 1000
// AND status IN ('paid', 'shipped')
// AND price BETWEEN 10 AND 500
Using scopes with different query types
Scopes work with any GORM finisher — not just Find.
// Count
var count int64
db.Model(&Order{}).Scopes(AmountGreaterThan1000).Count(&count)
// Update
db.Model(&Order{}).Scopes(OrderStatus([]string{"pending"})).Update("status", "cancelled")
// Delete
db.Scopes(AmountGreaterThan1000).Delete(&Order{})
// First
var order Order
db.Scopes(AmountGreaterThan1000).First(&order)
Parameterized scopes are a natural fit for pagination.
func Paginate(page, pageSize int) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
// Usage
var users []User
db.Scopes(Paginate(2, 20)).Find(&users)
// SELECT * FROM users LIMIT 20 OFFSET 20
Scopes are evaluated lazily — they run just before the query executes, which means you can build up a chain of scopes and pass the *gorm.DB around before calling a finisher.