A database migration tool with a Django-style workflow. Define your schema in Starlark, generate 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
.starscripts — a Python-like DSL powered by Starlark-Go. At runtimemorphic migrateevaluates each.starfile in-process and executes the migration operations against your database. The language is concise and readable, with typed builtins for common schema operations.
- Readable schema & migrations: Starlark is a Python-like language — schemas and migrations read like documentation
- No build step at runtime: Starlark files are interpreted directly; no
go build, no temporary binary - Database-agnostic schema: Write once, deploy to PostgreSQL, MySQL, SQLite, or SQL Server — symbolic defaults and type mappings resolve per-database at migration time
- DAG-based ordering: Migrations form a dependency graph so parallel branches merge cleanly
- Auto change detection: Diff schemas, generate only what changed
- Safe destructive ops: Field removals, table drops, and renames require explicit review
go install github.com/ocomsoft/morphic@latestcd your-project
morphic initThis creates:
your-project/
└── migrations/
└── morphic.config.yaml ← migration configuration
schema/schema.star:
database("myapp", "1.0.0")
defaults("postgresql", {
"new_uuid": "gen_random_uuid()",
"now": "CURRENT_TIMESTAMP",
"today": "CURRENT_DATE",
"zero": "0",
"false": "false",
"true": "true",
"blank": "''",
})
table("users",
fields = [
uuid("id", primary_key=True, default="new_uuid"),
varchar("email", 255),
varchar("name", 100, nullable=True),
timestamp("created_at", auto_create=True),
],
indexes = [
index("users_email_idx", ["email"], unique=True),
],
)
table("posts",
fields = [
uuid("id", primary_key=True, default="new_uuid"),
varchar("title", 200),
text("body", nullable=True),
timestamp("created_at", default="now"),
foreign_key("user_id", fk("users", on_delete="CASCADE")),
],
)The defaults function 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.starexport DATABASE_URL="postgresql://user:pass@localhost/mydb"
morphic migrate upmorphic migrate interprets the migration files in-process and runs the embedded migration App. No go build, no temporary binary.
# 1. Edit your schema
vim schema/schema.star
# 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.star
# 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 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 graphMigrations use Starlark with typed builtins for every common schema operation:
migration(
name = "0003_add_job",
dependencies = ["0002_create_parts"],
operations = [
create_table("job",
fields = [
uuid("id", primary_key=True, default="new_uuid"),
varchar("title", 120),
text("description", nullable=True),
integer("priority", default="zero"),
timestamp("created_date", default="now"),
foreign_key("contact_id", fk("contact", on_delete="CASCADE")),
],
indexes = [
index("job_status_idx", ["status"]),
],
),
],
)See the Starlark Migration Format for the full DSL reference including all field types, operations, and patterns.
your-project/
├── schema/
│ └── schema.star ← your Starlark schema definition
├── migrations/
│ ├── morphic.config.yaml ← migration configuration
│ ├── 0001_initial.star ← migration files (generated by morphic)
│ ├── 0002_add_posts.star
│ └── 0003_add_index.star
├── 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.starmorphic 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 postgresqlmigrations/morphic.config.yaml:
database:
type: postgresql
migration:
directory: migrations
output:
verbose: falseSee the Configuration Guide for complete options.
- Installation Guide
- Schema Format Guide — schema reference
- Starlark Migration Format — full DSL reference for migration files
- Configuration Guide
- 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 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 Starlark interpreter |
Inspection & Debugging
| Command | Description |
|---|---|
| schema-diff | Show drift between schema and migration state |
| db-diff | Compare live DB schema against migration state |
| current-state | Show reconstructed schema state |
| schema-to-sql | Convert merged schema to SQL |
| schema-to-diagram | Generate Markdown docs with diagrams |
| version | Show version and build info |
Conversion Tools
| Command | Description |
|---|---|
| db-to-schema | Reverse-engineer schema from existing DB |
| struct-to-schema | Convert Go structs to schema |
| find-includes | Discover schema includes from Go modules |
| from-makemigrations | Convert Go migrations to Starlark format |
| yaml2dsl | Convert YAML schema to Starlark DSL |
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.star → 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.