go-migrationGo Migration
Seeders
Documentation

Seeder Rollback

Undo seeded data by implementing the RollbackableSeeder interface and using runner.Rollback() or runner.Truncate().

Seeder Rollback

New in v1.0.0: The RollbackableSeeder interface, runner.Rollback(), runner.Truncate(), and the db:seed:rollback / db:seed:truncate CLI commands are all introduced in v1.0.0.

Sometimes you need to undo seeded data — for example, cleaning up test data or resetting a table to a known state. The rollback system provides two approaches: custom rollback logic and table truncation.

The RollbackableSeeder Interface

Implement the RollbackableSeeder interface to define custom rollback logic for a seeder:

go
type RollbackableSeeder interface {
    Seeder
    Rollback(db *sql.DB) error
}
MethodSignatureDescription
RollbackRollback(db *sql.DB) errorUndoes the data inserted by Run

The RollbackableSeeder interface extends the base Seeder interface, so you still need to implement Run.

Defining a Rollbackable Seeder

seeders/user_seeder.go
package seeders

import (
    "database/sql"
)

type UserSeeder struct{}

func (s *UserSeeder) Run(db *sql.DB) error {
    _, err := db.Exec(
        "INSERT INTO users (name, email) VALUES ($1, $2)",
        "Alice", "alice@example.com",
    )
    return err
}

func (s *UserSeeder) Rollback(db *sql.DB) error {
    _, err := db.Exec("DELETE FROM users WHERE email = $1", "alice@example.com")
    return err
}

The Rollback method should reverse whatever Run did. This gives you full control over the cleanup logic.

Rolling Back a Seeder

Programmatic API

Use runner.Rollback() to roll back a specific seeder by its registered name:

go
reg := seeder.NewRegistry()
_ = reg.Register("UserSeeder", &seeders.UserSeeder{})

runner := seeder.NewRunner(reg, db, nil)

// Roll back the UserSeeder
if err := runner.Rollback("UserSeeder"); err != nil {
    log.Fatal(err)
}

If the seeder doesn't implement RollbackableSeeder, runner.Rollback() returns an error.

CLI

Use the db:seed:rollback command:

bash
# Roll back a specific seeder
go-migration db:seed:rollback --class=UserSeeder

runner.Rollback() returns an error if the specified seeder does not implement the RollbackableSeeder interface. Make sure your seeder has a Rollback method before attempting to roll it back.

Truncating a Table

For a simpler approach, use runner.Truncate() to delete all rows from a table:

go
// Delete all rows from the "users" table
if err := runner.Truncate("users"); err != nil {
    log.Fatal(err)
}

Truncate executes a DELETE FROM statement on the specified table. This is useful when you want to clear a table without writing custom rollback logic.

The table name is quoted for the runner's dialect, with the delimiter escaped — "users" on PostgreSQL and SQLite, `users` on MySQL. The dialect is set with seeder.WithDialect when creating the runner (default PostgreSQL), so on MySQL create the runner like this:

go
runner := seeder.NewRunner(reg, db, nil, seeder.WithDialect(seeder.DialectMySQL))

Before v1.2.0, Truncate always used PostgreSQL-style double quotes, which MySQL rejects by default. With WithDialect(seeder.DialectMySQL) — set automatically by the db:seed:truncate CLI command — it now works on MySQL.

runner.Truncate() uses DELETE FROM rather than SQL TRUNCATE for broader compatibility across databases and to respect foreign key constraints.

Complete Example

main.go
package main

import (
    "database/sql"
    "log"

    _ "github.com/lib/pq"

    "github.com/gopackx/go-migration/pkg/seeder"
    "your-project/seeders"
)

func main() {
    db, err := sql.Open("postgres", "postgres://user:password@localhost:5432/mydb?sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    reg := seeder.NewRegistry()
    _ = reg.Register("UserSeeder", &seeders.UserSeeder{})
    _ = reg.Register("PostSeeder", &seeders.PostSeeder{})

    runner := seeder.NewRunner(reg, db, nil)

    // Seed the database
    if err := runner.RunAll(); err != nil {
        log.Fatal(err)
    }

    // Later, roll back a specific seeder
    if err := runner.Rollback("UserSeeder"); err != nil {
        log.Fatal(err)
    }

    // Or truncate an entire table
    if err := runner.Truncate("posts"); err != nil {
        log.Fatal(err)
    }

    log.Println("Rollback complete!")
}

What's Next?

  • Batch Insert — insert large datasets efficiently
  • Seeder Tags — group seeders with tags and run them selectively