Documentation

Database migrations
without the pain.

GoMigrate is a friendly wrapper around golang-migrate with short commands, YAML config, and an interactive terminal UI. Works as a CLI and as a Go library.

Drivers MySQL · PostgreSQL Go 1.22+ License MIT

00 Installation

GoMigrate has three audiences, each with a different installation path. Most developers fall into one of the first two — you only need to clone the repo if you want to contribute code.

Path A
CLI Users
You want to use the gomigrate command in your terminal.
Path B
Library Users
You want to import gomigrate into your Go app.
Path C
Contributors
You want to modify the code or submit a PR.

Path A — Install the CLI

With Go 1.22+ installed, a single command:

$ go install github.com/taqnihub/gomigrate@latest

Verify the installation:

$ gomigrate --version
gomigrate version 0.1.0
PATH Note

Make sure $(go env GOPATH)/bin is in your $PATH. If gomigrate isn't found after install, add this to your shell config:

$ export PATH="$PATH:$(go env GOPATH)/bin"

Path B — Use as a Go library

Inside your Go project, add it as a dependency:

$ go get github.com/taqnihub/gomigrate@latest

Then import the packages you need:

import (
    "github.com/taqnihub/gomigrate/config"
    "github.com/taqnihub/gomigrate/migrate"
)

See Part Two below for usage examples.

Two packages, one library

config loads and validates configuration. migrate contains the engine and operations (Up, Down, Version, etc.). Import either or both.

Path C — Clone the repo (contributors only)

This path is only for developers who want to contribute code, fix bugs, or modify the library itself. You don't need this to use gomigrate.

$ git clone https://github.com/taqnihub/gomigrate.git
$ cd gomigrate
$ go build -o gomigrate .
$ ./gomigrate --help
Contribute

See CONTRIBUTING.md for the development workflow, code style expectations, and how to run tests.

01 Quick Start

Copy-paste this into your terminal and you'll be running migrations in under a minute. Every step is intentional — skip none.

# 1. Install the CLI
$ go install github.com/taqnihub/gomigrate@latest

# 2. Verify it's installed
$ gomigrate --version

# 3. Initialize gomigrate in your project (interactive wizard)
$ gomigrate init

# 4. Create your first migration (generates .up.sql and .down.sql)
$ gomigrate create add_users_table

# 5. Edit the generated SQL files in ./db/migrations/
#    (add your CREATE TABLE, ALTER TABLE, etc.)

# 6. Check what's pending
$ gomigrate status

# 7. Apply all pending migrations
$ gomigrate up

# 8. Verify everything was applied
$ gomigrate status

# 9. If you need to undo the last migration
$ gomigrate down

# 10. Or launch the interactive TUI menu
$ gomigrate
That's it

No Docker commands. No 200-character invocations. You're already more productive than someone using raw golang-migrate.

BEGIN · PART 01

PART ONE Using the CLI

The fastest way to use GoMigrate is as a standalone command-line tool. Install it once, drop a config file in your project, and run short, memorable commands instead of 200-character Docker invocations.

Complete command reference

Every command gomigrate understands, grouped by purpose. Each row shows the command syntax, a description, and a concrete example you can run.

Command Description Example
Setup
gomigrate --version Show gomigrate version and build info gomigrate --version
gomigrate --help Show help for all commands gomigrate --help
gomigrate <cmd> --help Show help for a specific subcommand gomigrate up --help
gomigrate init Interactive wizard to create .gomigrate.yml in the current directory gomigrate init
gomigrate init --force Overwrite existing config without prompting gomigrate init --force
Migrations
gomigrate create <name> Generate a pair of timestamped up/down SQL files. Refuses duplicate names. gomigrate create add_users_table
gomigrate up Apply all pending migrations in order gomigrate up
gomigrate up [n] Apply only the next N pending migrations gomigrate up 3
gomigrate down Revert the most recently applied migration (safe default) gomigrate down
gomigrate down [n] Revert the last N applied migrations gomigrate down 2
gomigrate down --all Revert ALL applied migrations — destructive, use with care gomigrate down --all
Inspection
gomigrate status Show all migrations with applied vs pending status gomigrate status
Recovery
gomigrate force <version> Force-set version without running SQL. Fixes dirty state after a failed migration. gomigrate force 20260417103045
Interactive Mode
gomigrate Launch the TUI menu when called with no arguments. Navigate with arrow keys. gomigrate

Global flags (work with any command)

Flag Short Description Example
--config <path> -c Use a specific config file instead of searching gomigrate -c prod.yml up
--driver <name> -d Override database driver (mysql or postgres) gomigrate -d postgres status
--dir <path> Override migrations directory gomigrate --dir ./db/migs up
--verbose -v Show detailed output for debugging gomigrate -v up
--no-interactive Disable TUI and colors — ideal for CI/CD pipelines gomigrate --no-interactive up

Initialize a project

Inside the root of your project, run gomigrate init. You'll be walked through an interactive wizard that asks for your driver, connection details, and migrations directory.

$ gomigrate init

🚀 Welcome to GoMigrate

? Database driver:
  > MySQL
    PostgreSQL

? Host: localhost
? Port: 3306
? Database name: myapp
? Username: root
? Password: ********
? Migrations directory: ./db/migrations

✓ GoMigrate initialized!
  Config:      /path/to/project/.gomigrate.yml
  Migrations:  /path/to/project/db/migrations

This creates two things:

  • .gomigrate.yml — a config file with your connection details
  • ./db/migrations/ — an empty directory where your SQL files will live

Creating a migration

Run gomigrate create with a descriptive name. Two timestamped SQL files will be generated — one for applying the change, one for reverting it.

$ gomigrate create add_users_table

✨ Creating Migration

  ✓ Created migration files

  UP:    db/migrations/20260417103045_add_users_table.up.sql
  DOWN:  db/migrations/20260417103045_add_users_table.down.sql

  Next: edit the files with your SQL, then run  gomigrate up

Naming conventions

PatternExample
create_<table>_tablecreate_users_table
add_<column>_to_<table>add_email_to_users
drop_<column>_from_<table>drop_legacy_id_from_orders
add_index_<name>add_index_email_to_users

Now open the generated files and write your SQL. Example for users:

-- up.sql
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(255) NOT NULL UNIQUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- down.sql
DROP TABLE IF EXISTS users;
Safeguard

GoMigrate refuses to create two migrations with the same name and tells you which existing files conflict. This prevents silent duplicates that crash your migrations at runtime.

Applying & reverting

To apply every pending migration in order, just run:

$ gomigrate up

⚡ Applying Migrations

  Database:  mysql on localhost:3306/myapp

  Will apply 3 migration(s):
    →  20260417103045  add_users_table
    →  20260417103112  add_products_table
    →  20260417103158  add_orders_table

    ✓  20260417103045  add_users_table
    ✓  20260417103112  add_products_table
    ✓  20260417103158  add_orders_table

  ✓ Applied 3 migration(s) in 12ms
  Version:   0 → 20260417103158

Apply only the next N migrations by passing a number:

$ gomigrate up 1    # apply just the next one
$ gomigrate up 3    # apply the next three

Reverting

To undo the most recent migration:

$ gomigrate down

⬇  Reverting Migrations

  Database:  mysql on localhost:3306/myapp

  Will revert 1 migration(s):
    ←  20260417103158  add_orders_table

    ✓  20260417103158  add_orders_table

  ✓ Reverted 1 migration(s) in 6ms
  Version:   20260417103158 → 20260417103112

Revert N migrations, or use --all to revert everything:

$ gomigrate down 3          # revert the last three
$ gomigrate down --all      # revert ALL (destructive)
Warning

down --all drops every table managed by GoMigrate. Always double-check the connected database before running it in production.

Checking status

See which migrations are applied and which are pending:

$ gomigrate status

📋 Migration Status

  Database:   mysql on localhost:3306/myapp
  Directory:  ./db/migrations

   VERSION           NAME                      STATUS
   ──────────────────────────────────────────────────────
   20260417103045    add_users_table           ✓ applied
   20260417103112    add_products_table        ✓ applied
   20260417103158    add_orders_table          ○ pending

  3 total · 2 applied · 1 pending

Fixing a dirty state

If a migration fails halfway through, GoMigrate marks the database as dirty and refuses to run further migrations until you resolve it. First, manually fix the schema in your database. Then tell GoMigrate what version the schema actually matches:

$ gomigrate force 20260417103112

🔧 Forcing Migration Version

  ✓ Version forced to 20260417103112

  ⚠ No SQL was executed.
     Make sure your database schema matches this version!
Important

force doesn't run any SQL. It only updates the schema_migrations table. You must ensure your actual schema matches the version you claim to be at.

Interactive mode

Run gomigrate with no arguments to get a keyboard-navigable menu:

$ gomigrate

⚡ GoMigrate
mysql · localhost:3306 · myapp

What would you like to do?

  > ▶  Apply migrations
    ◀  Revert last migration
    ✚  Create new migration
    📋 View status
    🔧 Force version
    q  Quit

↑/↓ navigate · enter select · q quit

Use arrow keys (or j/k for vim users) to move, Enter to select, and q or Esc to quit.

Configuration

GoMigrate reads settings from multiple sources, applied in this order (highest priority first):

  1. CLI flags — e.g. --driver postgres
  2. Environment variables with the GOMIGRATE_ prefix
  3. .gomigrate.yml in the current directory
  4. .gomigrate.yml in your home directory
  5. Sensible defaults

Config file reference

# .gomigrate.yml
driver: mysql              # mysql or postgres
host: localhost
port: 3306
database: myapp
user: root
password: secret
migrations_dir: ./db/migrations

# Optional
ssl_mode: disable            # disable | require | verify-full
timezone: UTC
lock_timeout: 15s

Using environment variables

Any config value can be overridden with a GOMIGRATE_-prefixed variable. This is how you should handle secrets in CI/CD:

$ export GOMIGRATE_DRIVER=postgres
$ export GOMIGRATE_HOST=prod-db.example.com
$ export GOMIGRATE_PASSWORD="$DB_PASSWORD"
$ gomigrate up
END · PART 01

PART TWO As a Go library

Everything the CLI does is also available as a Go library. Import config and migrate packages directly into your application to run migrations programmatically — perfect for auto-migrating on app startup, embedding migrations in your deployment pipeline, or building your own migration tooling.

You already installed the library in the Installation section above — if you skipped that, just run go get github.com/taqnihub/gomigrate@latest inside your Go project.

PackagePurpose
github.com/taqnihub/gomigrate/config Load and validate configuration from files, env vars, or programmatically.
github.com/taqnihub/gomigrate/migrate Run migrations — Up, Down, Version, Create, Force, List.

Basic usage

The canonical five-line example:

package main

import (
    "log"

    "github.com/taqnihub/gomigrate/config"
    "github.com/taqnihub/gomigrate/migrate"
)

func main() {
    cfg, _ := config.Load("")            // reads .gomigrate.yml + env vars
    engine, _ := migrate.New(cfg)
    defer engine.Close()

    if err := engine.Up(); err != nil {
        log.Fatal(err)
    }
}

This does exactly what gomigrate up does on the command line — but from inside your own Go program.

Auto-migrate on app startup

A common pattern: have your web server apply any pending migrations before it starts serving traffic. This guarantees your schema is always in sync with your deployed code.

package main

import (
    "errors"
    "log"
    "net/http"

    "github.com/taqnihub/gomigrate/config"
    "github.com/taqnihub/gomigrate/migrate"
)

func main() {
    // 1. Load config
    cfg, err := config.Load("")
    if err != nil {
        log.Fatalf("config: %v", err)
    }

    // 2. Create engine and apply pending migrations
    engine, err := migrate.New(cfg)
    if err != nil {
        log.Fatalf("migrate init: %v", err)
    }
    defer engine.Close()

    if err := engine.Up(); err != nil {
        // ErrNoChange means we're already up-to-date — that's fine
        if !errors.Is(err, migrate.ErrNoChange) {
            log.Fatalf("migration failed: %v", err)
        }
    }

    // 3. Log current state
    version, dirty, _ := engine.Version()
    log.Printf("DB at version %d (dirty=%v)", version, dirty)

    // 4. Start your server
    log.Fatal(http.ListenAndServe(":8080", nil))
}
Pattern

Use errors.Is(err, migrate.ErrNoChange) to ignore the "nothing to apply" case. Every other error should halt startup — you don't want to serve traffic with an outdated schema.

Programmatic configuration

You don't need a YAML file. Build a config.Config struct directly — useful when your app composes config from multiple sources (secret managers, Consul, AWS Parameter Store, etc.):

package main

import (
    "log"
    "os"

    "github.com/taqnihub/gomigrate/config"
    "github.com/taqnihub/gomigrate/migrate"
)

func main() {
    cfg := &config.Config{
        Driver:        "postgres",
        Host:          os.Getenv("DB_HOST"),
        Port:          5432,
        Database:      os.Getenv("DB_NAME"),
        User:          os.Getenv("DB_USER"),
        Password:      os.Getenv("DB_PASSWORD"),
        MigrationsDir: "./db/migrations",
        SSLMode:       "require",
    }

    if err := cfg.Validate(); err != nil {
        log.Fatalf("invalid config: %v", err)
    }

    engine, err := migrate.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
    defer engine.Close()

    if err := engine.Up(); err != nil {
        log.Fatal(err)
    }
}

API reference

Package: config

Function / MethodDescription
config.Load(path string) (*Config, error) Loads config from the given path, env vars, and defaults. Pass "" to use default search paths.
config.Default() *Config Returns a *Config populated with sensible defaults.
(*Config).Validate() error Returns an error if required fields are missing or invalid.
(*Config).DSN() (string, error) Builds the driver-specific connection string.

Package: migrate

Function / MethodDescription
migrate.New(cfg *config.Config) (*Engine, error) Creates a new migration engine. The migrations directory is auto-created if missing.
(*Engine).Close() error Releases database and source resources. Call via defer.
(*Engine).Up() error Apply all pending migrations. Returns ErrNoChange if already up-to-date.
(*Engine).UpN(n int) error Apply the next n pending migrations.
(*Engine).Down(n int) error Revert the last n applied migrations.
(*Engine).DownAll() error Revert every applied migration. Destructive — use with caution.
(*Engine).Version() (uint, bool, error) Returns current version, whether dirty, and any error.
(*Engine).Force(version int) error Force-set the version without running SQL. Use to recover from dirty state.
(*Engine).Create(name string) (up, down string, err error) Create a new pair of up/down SQL files. Returns the paths.
(*Engine).List() ([]MigrationFile, error) List all migration files on disk, sorted ascending by version.

Sentinel errors

ErrorMeaning
migrate.ErrNoChange Returned by Up, Down, etc. when there's nothing to do. Check with errors.Is(err, migrate.ErrNoChange).
END · DOCUMENTATION

MIT © taqnihub · Built with ⚡ golang-migrate, cobra, viper & bubbletea.