Migrations
Database migrations in PHP and SQL formats.
Migrations let you version-control your database schema. VelvetCMS supports both PHP migrations (using the Schema Builder) and raw SQL migrations.
Migration Location #
Migrations live in database/migrations/. Modules can also include their own migrations.
Running Migrations #
./velvet migrate
This runs all pending migrations in filename order.
PHP Migrations #
PHP migrations use the Schema Builder for database-agnostic schema changes:
<?php
// database/migrations/001_create_posts_table.php
use VelvetCMS\Database\Schema\Schema;
use VelvetCMS\Database\Schema\Blueprint;
return new class {
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->text('content');
$table->string('status', 20)->default('draft');
$table->timestamps();
$table->index('status');
});
}
};
The migration file must return an object with an up() method.
SQL Migrations #
For raw SQL, create .sql or .up.sql files:
-- database/migrations/002_add_published_at.up.sql
ALTER TABLE posts ADD COLUMN published_at TIMESTAMP NULL;
CREATE INDEX idx_posts_published_at ON posts(published_at);
Driver-Specific SQL #
Different databases have different SQL dialects. You can provide driver-specific versions:
003_create_fulltext_index.sqlite.up.sql
003_create_fulltext_index.mysql.up.sql
003_create_fulltext_index.pgsql.up.sql
The migrator picks the most specific file for your current driver:
{name}.{driver}.up.sql(e.g.,003_create_index.mysql.up.sql){name}.up.sql(generic SQL){name}.php(PHP migration)
Naming Convention #
Migrations run in filename order. Use numeric prefixes to control the sequence:
001_create_users_table.php
002_create_posts_table.php
003_add_author_to_posts.php
010_create_comments_table.php
Leave gaps in numbering to allow for future insertions.
Migration Tracking #
VelvetCMS tracks which migrations have run in a migrations table. Each migration runs exactly once, even if you call migrate multiple times.
Batch Tracking #
Migrations run together are grouped into a batch. This is useful for understanding when migrations were applied.
Creating Tables #
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email')->unique();
$table->string('password');
$table->boolean('active')->default(true);
$table->timestamps();
});
Dropping Tables #
Schema::drop('old_table');
// Only if exists (no error if missing)
Schema::dropIfExists('old_table');
Module Migrations #
Modules can include their own migrations in database/migrations/. When the module is loaded, its migrations are discovered and run with the core migrations.
Tips #
- Keep migrations small - one logical change per migration
- Never edit a migration that's been run - create a new one instead
- Test migrations on a copy - especially for production databases
- Use PHP migrations for portability - they work across all supported databases