Scopes
A scope is a reusable, named read filter — the unit a team packages common conditions in (ActiveOnly, OwnedBy(user), Published) and shares across queries. The orm Repo applies scopes with Repo.Scopes(...), and because each is just a function over the query builder, scopes compose by chaining. This is the same idea as gorm’s Scopes, made type-safe.
For exhaustive API detail, see the reference at pkg.go.dev/liteorm.org/orm.
What a scope is
Section titled “What a scope is”orm.Scope[T] is a function that receives the read’s builder and returns it, having added conditions:
type Scope[T any] func(*query.SelectBuilder[T]) *query.SelectBuilder[T]Because it takes and returns the builder, a scope can add a Where, an OrderBy, a Limit, or anything else the builder exposes — and several scopes chain into one read. A parameterless scope is just such a function; a parameterized one is a constructor that returns a closure:
// A bare scope: no arguments, references only the row.func Published(b *query.SelectBuilder[Post]) *query.SelectBuilder[Post] { return b.Where("published_at IS NOT NULL")}
// A parameterized scope: a constructor that closes over its argument.func OwnedBy(id int64) orm.Scope[Post] { return func(b *query.SelectBuilder[Post]) *query.SelectBuilder[Post] { return b.Where("author_id = ?", id) }}Applying and composing scopes
Section titled “Applying and composing scopes”Pass one or more scopes to Repo.Scopes; they apply in order, and the result is a Repo view you finish with any read verb. The soft-delete scope still applies underneath, so a scoped read never leaks deleted rows by accident:
posts := orm.NewRepo[Post](db)
mine, _ := posts. Scopes(Published, OwnedBy(ada.ID)). OrderBy("created_at DESC"). Limit(10). Find(ctx)Scopes returns a new Repo view and leaves the original untouched — the scope slice is copied, so sibling views never alias each other’s chain. That means you can build a base view once and branch off it:
active := posts.Scopes(Published)
recent, _ := active.OrderBy("created_at DESC").Limit(10).Find(ctx)count, _ := active.Scopes(OwnedBy(ada.ID)).Count(ctx)Scopes composes freely with the Repo’s inline read surface (Where, Filter, OrderBy, Limit, Offset) and the finishers (Find, First, Count, Exists) — reach for a named scope when the filter is reused, and inline Where/Filter for a one-off. See CRUD with the Repo for that surface.
Scopes and soft delete
Section titled “Scopes and soft delete”Soft-delete visibility is its own tri-state scope, orthogonal to the ones you write: IncludeDeleted() and OnlyDeleted() return Repo views that widen or invert the default “live rows only” filter. They chain with your scopes, so you can combine a business filter with a delete-visibility scope in the same read:
// published posts, including soft-deleted onesarchived, _ := posts.IncludeDeleted().Scopes(Published).Find(ctx)The tri-state scopes are covered in full — with ForceDelete and Restore — in soft delete.
Where to next
Section titled “Where to next”- CRUD with the Repo — the inline read surface scopes compose with.
- Soft delete — the tri-state delete-visibility scopes.
- The query builder — the builder a scope transforms; drop to it for joins, unions, and grouping.