Pop — API Surface#

API types#

  • Library — the primary surface; one Go import (github.com/gobuffalo/pop/v6) gives access to all ORM, migration, and config functionality.
  • CLIsoda binary built on Cobra; wraps the library API for database management tasks.

Library API#

API style#

Fluent / method-chaining on two central types (*Connection and *Query), supplemented by package-level free functions for bootstrapping and configuration. The style mirrors ActiveRecord idioms (Rails heritage) rather than the functional-options or builder-struct style common in newer Go libraries.

Public packages exported for consumer use#

PackagePurpose
github.com/gobuffalo/pop/v6Root package — everything (Connection, Query, Model, Migrator, config, callbacks). Single import for all ORM functionality.
github.com/gobuffalo/pop/v6/associationsEager-loading association types and interfaces (Association, AssociationSortable, etc.). Typically not imported directly by consumers.
github.com/gobuffalo/pop/v6/columnsMaps struct fields to database columns; exported for tooling that needs column metadata (ForStruct, ForStructWithAlias, NewColumns).
github.com/gobuffalo/pop/v6/slicesScannable slice types (Map, etc.) for use as model field types.
github.com/gobuffalo/pop/v6/fixMigration file fixup utilities used by the soda fix command.
github.com/gobuffalo/pop/v6/genny/…Code-generation generators (config, fizz, model) used by soda generate.
github.com/gobuffalo/pop/v6/loggingLog level constants; consumers can override the logger via pop.SetLogger.

Connection — primary user-facing type#

All CRUD operations are methods on *Connection. Consumers obtain one via pop.Connect(env) or pop.Connections[env].

Bootstrapping#

func LoadConfigFile() error                                // reads database.yml, populates Connections map
func LookupPaths() []string                               // paths searched for database.yml
func AddLookupPaths(paths ...string) error                // add custom lookup paths
func LoadFrom(r io.Reader) error                          // load config from reader
func ParseConfig(r io.Reader) (map[string]*ConnectionDetails, error)
func NewConnection(deets *ConnectionDetails) (*Connection, error) // low-level; prefer Connect
func Connect(e string) (*Connection, error)               // shorthand: LoadConfigFile + get named connection

Connection lifecycle#

func (c *Connection) Open() error
func (c *Connection) Close() error
func (c *Connection) String() string
func (c *Connection) URL() string
func (c *Connection) Context() context.Context
func (c *Connection) MigrationURL() string
func (c *Connection) MigrationTableName() string
func (c *Connection) WithContext(ctx context.Context) *Connection   // context propagation

Transactions#

func (c *Connection) Transaction(fn func(tx *Connection) error) error          // auto-commit/rollback
func (c *Connection) Rollback(fn func(tx *Connection))                         // always rollback (test helper)
func (c *Connection) NewTransaction() (*Connection, error)                     // manual transaction
func (c *Connection) NewTransactionContext(ctx context.Context) (*Connection, error)
func (c *Connection) NewTransactionContextOptions(ctx context.Context, options *sql.TxOptions) (*Connection, error)

Query building (shortcut forms on Connection)#

func (c *Connection) Q() *Query                                   // blank query on this connection
func Q(c *Connection) *Query                                      // package-level alias
func (c *Connection) RawQuery(stmt string, args ...interface{}) *Query
func (c *Connection) Where(stmt string, args ...interface{}) *Query
func (c *Connection) Order(stmt string, args ...interface{}) *Query
func (c *Connection) Limit(limit int) *Query
func (c *Connection) Select(fields ...string) *Query
func (c *Connection) Eager(fields ...string) *Connection          // eager-load associations
func (c *Connection) EagerPreload(fields ...string) *Query        // preload path
func (c *Connection) Scope(sf ScopeFunc) *Query                   // reusable query modifier

CRUD — finders#

func (c *Connection) Find(model interface{}, id interface{}) error
func (c *Connection) First(model interface{}) error
func (c *Connection) Last(model interface{}) error
func (c *Connection) All(models interface{}) error
func (c *Connection) Count(model interface{}) (int, error)
func (c *Connection) Load(model interface{}, fields ...string) error  // post-load association fill

CRUD — executors#

func (c *Connection) Save(model interface{}, excludeColumns ...string) error
func (c *Connection) ValidateAndSave(model interface{}, excludeColumns ...string) (*validate.Errors, error)
func (c *Connection) Create(model interface{}, excludeColumns ...string) error
func (c *Connection) ValidateAndCreate(model interface{}, excludeColumns ...string) (*validate.Errors, error)
func (c *Connection) Update(model interface{}, excludeColumns ...string) error
func (c *Connection) ValidateAndUpdate(model interface{}, excludeColumns ...string) (*validate.Errors, error)
func (c *Connection) UpdateColumns(model interface{}, columnNames ...string) error
func (c *Connection) Destroy(model interface{}) error
func (c *Connection) Reload(model interface{}) error
func (c *Connection) TruncateAll() error

Associations (BelongsTo helpers)#

func (c *Connection) BelongsTo(model interface{}) *Query
func (c *Connection) BelongsToAs(model interface{}, as string) *Query
func (c *Connection) BelongsToThrough(bt, thru interface{}) *Query

Pagination#

func (c *Connection) Paginate(page int, perPage int) *Query
func (c *Connection) PaginateFromParams(params PaginationParams) *Query
func NewPaginator(page int, perPage int) *Paginator
func NewPaginatorFromParams(params PaginationParams) *Paginator

Query — fluent builder type#

*Query accumulates SQL clauses and terminates with a finder or executor call. Methods return *Query for chaining.

Clause builders#

func (q *Query) Where(stmt string, args ...interface{}) *Query
func (q *Query) Order(stmt string, args ...interface{}) *Query
func (q *Query) Limit(limit int) *Query
func (q *Query) Select(fields ...string) *Query
func (q *Query) GroupBy(field string, fields ...string) *Query
func (q *Query) Having(condition string, args ...interface{}) *Query
func (q *Query) Join(table, on string, args ...interface{}) *Query
func (q *Query) LeftJoin(table, on string, args ...interface{}) *Query
func (q *Query) RightJoin(table, on string, args ...interface{}) *Query
func (q *Query) InnerJoin(table, on string, args ...interface{}) *Query
func (q *Query) LeftOuterJoin(table, on string, args ...interface{}) *Query
func (q *Query) RightOuterJoin(table, on string, args ...interface{}) *Query
func (q *Query) RawQuery(stmt string, args ...interface{}) *Query
func (q *Query) Eager(fields ...string) *Query
func (q *Query) EagerPreload(fields ...string) *Query
func (q *Query) Scope(sf ScopeFunc) *Query
func (q *Query) BelongsTo(model interface{}) *Query
func (q *Query) BelongsToAs(model interface{}, as string) *Query
func (q *Query) BelongsToThrough(bt, thru interface{}) *Query
func (q *Query) Paginate(page int, perPage int) *Query
func (q *Query) PaginateFromParams(params PaginationParams) *Query

Terminal operations#

func (q *Query) Find(model interface{}, id interface{}) error
func (q *Query) First(model interface{}) error
func (q *Query) Last(model interface{}) error
func (q *Query) All(models interface{}) error
func (q *Query) Exists(model interface{}) (bool, error)
func (q *Query) Exec() error
func (q *Query) ExecWithCount() (int, error)
func (q *Query) Delete(model interface{}) error
func (q *Query) UpdateQuery(model interface{}, columnNames ...string) (int64, error)

Migrations#

func NewMigrator(c *Connection) Migrator
func NewFileMigrator(path string, c *Connection) (FileMigrator, error)
func NewMigrationBox(fsys fs.FS, c *Connection) (MigrationBox, error)  // embed.FS support
func CreateSchemaMigrations(c *Connection) error
func MigrationContent(mf Migration, c *Connection, r io.Reader, usingTemplate bool) (string, error)

Migrator exposes: Up(step int), Down(step int), Status(w io.Writer), Reset().


Database management#

func CreateDB(c *Connection) error
func DropDB(c *Connection) error

Global configuration#

var Debug bool                       // enable SQL logging
var ConfigName string                // override "database.yml" filename
var Connections map[string]*Connection  // populated by LoadConfigFile
func SetLogger(logger func(level logging.Level, s string, args ...interface{}))
func SetTxLogger(logger func(level logging.Level, anon interface{}, s string, args ...interface{}))
func SetEagerMode(eagerMode EagerMode)
func DialectSupported(d string) bool
func CanonicalDialect(synonym string) string

Extension interfaces (implement on model structs)#

Pop’s extensibility point for consumers is a rich set of optional interfaces that model structs can implement. Pop checks for these via type assertions during CRUD operations; no registration is needed.

Table naming#

type TableNameAble interface {
    TableName() string
}
type TableNameAbleWithContext interface {
    TableName(ctx context.Context) string
}

Lifecycle callbacks (callbacks.go)#

type BeforeValidateable interface { BeforeValidate(*Connection) error }
type AfterFindable       interface { AfterFind(*Connection) error }
type AfterEagerFindable  interface { AfterEagerFind(*Connection) error }
type BeforeSaveable      interface { BeforeSave(*Connection) error }
type AfterSaveable       interface { AfterSave(*Connection) error }
type BeforeCreateable    interface { BeforeCreate(*Connection) error }
type AfterCreateable     interface { AfterCreate(*Connection) error }
type BeforeUpdateable    interface { BeforeUpdate(*Connection) error }
type AfterUpdateable     interface { AfterUpdate(*Connection) error }
type BeforeDestroyable   interface { BeforeDestroy(*Connection) error }
type AfterDestroyable    interface { AfterDestroy(*Connection) error }

Validation hooks (validations.go)#

type validateable        interface { Validate(*Connection) (*validate.Errors, error) }
type validateCreateable  interface { ValidateCreate(*Connection) (*validate.Errors, error) }
type validateSaveable    interface { ValidateSave(*Connection) (*validate.Errors, error) }
type validateUpdateable  interface { ValidateUpdate(*Connection) (*validate.Errors, error) }

Note: Validate, ValidateCreate, ValidateSave, ValidateUpdate are the public names consumers implement; the interfaces themselves are private but the method signatures are public and documented in generated model stubs.

Scope function type#

type ScopeFunc func(*Query) *Query    // reusable query modifier

Backward compatibility#

Pop is currently on v6 (module path github.com/gobuffalo/pop/v6). The v5→v6 transition introduced breaking changes (context support, contextStore). Within a major version, the public API is stable. The root package acts as a stable facade; internal dialect details are hidden behind private interfaces.


CLI (soda)#

Framework#

Cobra (github.com/spf13/cobra)

Global flags#

-v, --version          Print pop version
-c, --config string    Path to database.yml
-e, --env string       Target environment (default "development"; falls back to $GO_ENV)
-d, --debug            Enable verbose SQL logging

Command tree#

soda
├── create                          Create all databases in database.yml
├── drop                            Drop all databases in database.yml
├── migrate
│   ├── up      [-n N]              Run N pending migrations (default: all)
│   ├── down    [-n N]              Rollback N migrations (default: 1)
│   ├── status                      Show migration status table
│   └── reset                       Drop, create, and migrate
├── generate  (alias: g)
│   ├── config                      Scaffold a database.yml configuration file
│   ├── fizz   [name]               Create an empty .fizz migration pair (up/down)
│   ├── sql    [name]               Create an empty .sql migration pair (up/down)
│   └── model  [name] [attrs...]    Generate Go model + migration (alias: m)
│         --struct-tag      json|xml|jsonapi  (default: json)
│         --migration-type  fizz|sql          (default: fizz)
│         --skip-migration  -s               Skip migration generation
│         --models-path     string           (default: "models")
├── schema
│   ├── dump   [-o file]            Dump current schema to stdout or file
│   └── load   [-i file]            Load schema from file
├── fix                             Migrate deprecated .fizz syntax
└── version                         Print pop version

Flag patterns#

  • Persistent flags (--config, --env, --debug) are defined on RootCmd and inherited by all subcommands.
  • PersistentPreRun on the root command handles config loading for all subcommands; migrate uses PersistentPreRunE to additionally validate the migration directory exists.
  • Environment priority: --env flag > $GO_ENV env var > "development" default.

Plugin / Extension system#

There is no plugin loader at runtime. Extensibility operates through two mechanisms:

  1. Interface-based model hooks — the callback and validation interfaces listed above let consumer model structs inject behavior at each lifecycle point without modifying pop’s code.

  2. Dialect registration via init() — new database dialects can be added by implementing the private dialect interface and registering a factory in the newConnection map from an init() function. This is how the five built-in dialects (PostgreSQL, MySQL, MariaDB, SQLite, CockroachDB) are wired in, and how a third-party dialect could be added at compile time.

  3. genny code-generation generators — the genny/… sub-packages expose New(*Options) (*genny.Generator, error) constructors that third-party tools (e.g., Buffalo) can compose into their own scaffolding pipelines.


Notable API design observations#

  • Mirror API on Connection and Query: Most read operations (Find, First, All, Where, …) exist on both *Connection (immediate shorthand) and *Query (chainable form). This creates a dual-entry API that is convenient but slightly redundant.
  • interface{} instead of generics: All model parameters are interface{}; Pop predates generics and uses reflection throughout. A generics-based API (Find[T any](...)) would be a natural future direction.
  • No HTTP or gRPC surface: Pop is purely a database library; no network server is exposed. The only RPC-style communication is the optional instrumented SQL driver for distributed tracing.
  • *validate.Errors return style: Rather than returning a single error for validation failures, ValidateAndCreate and siblings return a separate *validate.Errors struct alongside the Go error. Consumers must check both.