A Go-first database migration tool with a Django-style workflow. Define your schema in YAML, generate type-safe Go migration files, and run them in-process β no Go toolchain required at runtime, no compiled binary to ship.
How migrations run. Generated migration files are real
.gosource β your IDE,gopls, andgo vettreat them as ordinary Go. At runtimemorphic migratedoes not invokego build. Instead it loads each.gofile with yaegi, an embedded Go interpreter, and runs the migrations in the morphic process. The language inside migration files is "Go-like" β yaegi implements the Go spec but is not the officialgccompiler, so a few features (cgo, deep reflection, some generics edge cases) work differently. For migrations generated bymorphic generatethis is invisible; for hand-edited migrations that import third-party packages, see Extending the yaegi Symbol Map.
- π Type-safe at edit time: Migrations are real
.gofiles interpreted by yaegi β your IDE,gopls, andgo vetcatch errors before they ever run - β‘ No build step at runtime: yaegi interprets the
.gofiles directly; nogo build, no temporary binary, no GOWORK juggling - ποΈ Database-agnostic schema: Write YAML once, deploy to PostgreSQL, MySQL, SQLite, or SQL Server β scripting migrations in Go (rather than raw SQL) means the same migration code works across all supported databases
- π DAG-based ordering: Migrations form a dependency graph so parallel branches merge cleanly
- π Auto change detection: Diff YAML schemas, generate only what changed
β οΈ Safe destructive ops: Field removals, table drops, and renames require explicit review- π Optional standalone binary: The generated
migrations/directory is still a buildable Go module, so you cango buildit for IDE type-checking or as an escape hatch
go install github.com/ocomsoft/morphic@latestcd your-project
morphic initThis creates:
your-project/
βββ migrations/
βββ main.go β optional fallback entry point (`go build` still works)
βββ go.mod β dedicated migrations module (used by your IDE / gopls)
schema/schema.yaml:
defaults:
postgresql:
new_uuid: gen_random_uuid()
mysql:
new_uuid: uuid()
sqlite:
new_uuid: (lower(hex(randomblob(16))))
tables:
- name: users
fields:
- name: id
type: uuid
primary_key: true
default: new_uuid
- name: email
type: varchar
length: 255
nullable: false
- name: created_at
type: timestamp
auto_create: true
- name: posts
fields:
- name: id
type: uuid
primary_key: true
default: new_uuid
- name: title
type: varchar
length: 200
nullable: false
- name: user_id
type: foreign_key
foreign_key:
table: users
on_delete: CASCADEThe defaults section maps symbolic names (like new_uuid) to database-specific SQL expressions. Fields reference them by name β morphic resolves the correct expression for each target database at migration time.
morphic generate --name "initial"
# Creates: migrations/0001_initial.goexport DATABASE_URL="postgresql://user:pass@localhost/mydb"
morphic migrate upmorphic migrate interprets the migration files in-process using yaegi and runs the embedded migration App. No go build, no temporary binary.
# 1. Edit your YAML schema
vim schema/schema.yaml
# 2. Preview what will be generated
morphic generate --dry-run
# 3. Generate the migration
morphic generate --name "add user preferences"
# Creates: migrations/0004_add_user_preferences.go
# 4. Review the SQL before applying
morphic migrate showsql
# 5. Apply
morphic migrate up
# 6. Verify
morphic migrate statusmorphic migrate interprets the migration files in-process via yaegi and runs the embedded App. All arguments are forwarded:
morphic migrate up # apply all pending
morphic migrate up --to 0003_add_index # apply up to a specific migration
morphic migrate down # roll back one
morphic migrate down --steps 3 # roll back multiple
morphic migrate status # show applied / pending
morphic migrate showsql # print SQL without running it
morphic migrate fake 0001_initial # mark applied without running SQL
morphic migrate dag # show migration dependency graphyour-project/
βββ schema/
β βββ schema.yaml β your YAML schema definition
βββ migrations/
β βββ main.go β binary entry point (generated by init)
β βββ go.mod β dedicated module (generated by init)
β βββ 0001_initial.go β migration files (generated by morphic)
β βββ 0002_add_posts.go
β βββ 0003_add_index.go
βββ go.mod
βββ main.go
| Database | Status | Notes |
|---|---|---|
| PostgreSQL | β Full | UUID, JSONB, arrays, advanced types |
| MySQL | β Supported | JSON, AUTO_INCREMENT, InnoDB |
| SQLite | β Supported | Simplified types, basic constraints |
| SQL Server | β Supported | UNIQUEIDENTIFIER, NVARCHAR, BIT |
| Amazon Redshift | β Provider ready | SUPER JSON, IDENTITY sequences |
| ClickHouse | β Provider ready | MergeTree engine, Nullable types |
| TiDB | β Provider ready | MySQL-compatible, distributed |
| Vertica | β Provider ready | Columnar analytics |
| YDB (Yandex) | β Provider ready | Optional, native JSON |
| Turso | β Provider ready | Edge SQLite |
| StarRocks | β Provider ready | MPP analytics, OLAP |
| Aurora DSQL | β Provider ready | AWS serverless, PostgreSQL-compatible |
PostgreSQL has been tested against real database instances. All other providers have comprehensive unit tests but may need additional validation for production.
When a field removal, table drop, or rename is detected, morphic shows an interactive prompt before generating:
β Destructive: field_removed on "users" (field: "legacy_col")
SQL: ALTER TABLE "users" DROP COLUMN "legacy_col"
> Generate β include operation in migration
Review β include with // REVIEW comment
Omit β skip SQL; schema state still advances (SchemaOnly)
IgnoreErrors β include with IgnoreErrors: true
Exit β cancel migration generation
Scope: [This only] All remaining All of this type
β/β select β’ tab scope β’ enter confirm β’ esc cancel
Use Tab to cycle the scope β This only, All remaining (apply your choice to every subsequent destructive op), or All of this type (e.g. apply to all field_removed ops but still prompt for table_removed).
When two developers generate migrations concurrently the DAG develops branches:
0001_initial
βββ 0002_add_messaging (developer A)
βββ 0003_add_payments (developer B)
Resolve with a merge migration:
morphic generate --merge
# Creates: migrations/0004_merge_0002_add_messaging_and_0003_add_payments.gomorphic migrate reads connection details from DATABASE_URL and DB_TYPE:
export DATABASE_URL="postgresql://user:pass@localhost/mydb"
export DB_TYPE=postgresql # optional, defaults to postgresqlIf you prefer the optional fallback path of compiling migrations/ into a standalone binary, edit migrations/main.go to read additional vars (DB_HOST, DB_PORT, DB_USER, β¦) β see the Manual Build Guide.
migrations/morphic.config.yaml:
database:
type: postgresql
migration:
directory: migrations
include_down_sql: true
output:
verbose: falseSee the Configuration Guide for complete options.
- Installation Guide
- Schema Format Guide β complete YAML schema reference
- Configuration Guide
- Manual Build Guide β GOWORK/GOTOOLCHAIN details for CI/CD
- Extending the yaegi Symbol Map β let interpreted migrations import third-party packages
- Claude Code Skill β auto-triggered AI assistant skill for schema-first migrations
Schema & Generation
| Command | Description |
|---|---|
| init | Initialize migrations directory and config |
| generate | Generate migration files from YAML schema changes |
| generate empty | Create a blank migration for custom operations |
| generate dump-data | Generate a data-seeding migration from live DB |
Migration Runtime
| Command | Description |
|---|---|
| migrate | Run migrations in-process via yaegi |
Inspection & Debugging
| Command | Description |
|---|---|
| schema-diff | Show drift between YAML schema and migration state |
| db-diff | Compare live DB schema against migration state |
| current-state | Show reconstructed schema state as YAML |
| schema-to-sql | Convert merged YAML schema to SQL |
| schema-to-diagram | Generate Markdown docs with diagrams |
Conversion Tools
| Command | Description |
|---|---|
| db-to-schema | Reverse-engineer YAML schema from existing DB |
| struct-to-schema | Convert Go structs to YAML schema |
| find-includes | Discover schema includes from Go modules |
morphic includes a Claude Code skill that auto-triggers whenever Claude detects database schema work in your Go project. It enforces the correct workflow: edit schema/schema.yaml β generate migrations β avoid raw SQL.
Option 1: Personal skill (recommended β available in all Go projects):
cp -r skills/ ~/.claude/skills/go-morphicOption 2: Plugin (if the repo is registered as a Claude Code plugin):
The .claude-plugin/ directory is detected automatically when installed.
- Auto-triggers when you ask Claude to add tables, fields, indexes, foreign keys, or modify columns
- Guides Claude through init β schema edit β migration generation β SQL preview β verification
- Provides quick reference for field types, properties, indexes, defaults, and type mappings
- Enforces schema-first workflow β RunSQL only as a last resort
- Covers both new project bootstrapping and ongoing schema changes
See skills/SKILL.md for the full skill content and docs/claude-code-skill.md for detailed usage.
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Add tests for new functionality
- Ensure all tests pass:
go test ./... - Submit a pull request
MIT License β see LICENSE file for details.
Ready to get started? Run morphic init in your project directory.