
Uma migração apaga-se eliminando o seu ficheiro, mas só se nunca tiver corrido. Verifica com php artisan migrate:status: se estiver em Ran, lança primeiro php artisan migrate:rollback --step=1 e só depois apaga o ficheiro. Para acrescentar uma coluna a uma tabela existente, nunca alteres a migração antiga: cria uma nova com --table.
As migrações versionam o esquema da tua base de dados como o Git versiona o teu código: cada alteração é um ficheiro, que se pode voltar a correr em qualquer ambiente. Três operações cobrem o essencial do dia a dia: acrescentar uma coluna a uma tabela que já existe, reverter uma migração e apagar um ficheiro que nunca devia ter sido criado.
Artigo do percurso Desenvolvimento web. Todos os comandos e todas as saídas abaixo foram executados em Laravel 13.30.1 com PHP 8.4.25.
Criar uma migração
O comando segue uma convenção de nomes que determina o conteúdo do ficheiro gerado:
php artisan make:migration create_tasks_tableO nome começa por create, seguido do nome da tabela no plural, seguido de table. O Laravel reconhece este padrão e pré-preenche Schema::create() com id() e timestamps(), a coluna title é acrescentada aqui para o exemplo, e os comentários gerados foram retirados:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tasks');
}
};Desde o Laravel 8, as migrações são classes anónimas devolvidas por return new class extends Migration. As migrações antigas com nome, do tipo class CreateTasksTable extends Migration, continuam a funcionar mas já não são geradas. Do mesmo modo, $table->bigIncrements('id') deu lugar a $table->id(), e os métodos up() e down() declaram agora um tipo de retorno void.
Se a tabela precisar de datas de criação e de alteração, $table->timestamps() acrescenta-as: é a ausência delas que provoca o erro «Unknown column ‘updated_at’».
Executar a migração cria a tabela:
php artisan migrateAcrescentar uma coluna a uma tabela existente
Com o esquema no sítio, as consultas Eloquent do dia a dia fazem o resto. Nunca alteres uma migração que já correu: os teus colegas e os teus servidores já a executaram e não a voltarão a executar. Cria uma migração nova, indicando a tabela em causa com --table:
php artisan make:migration add_notes_to_tasks_table --table=tasksO ficheiro gerado contém um Schema::table() vazio nos dois sentidos. Preenche-os: a coluna no up(), a sua remoção no down().
public function up(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->text('notes')->nullable()->after('title');
});
}
public function down(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->dropColumn('notes');
});
}php artisan migrate 2026_09_02_194036_add_notes_to_tasks_table .................... 47.43ms DONEHá duas precauções que vale a pena recordar aqui.
Torna a coluna nullable, ou dá-lhe um valor por omissão. Numa tabela que já contém linhas, uma coluna NOT NULL sem valor por omissão faz falhar a migração: a base de dados não sabe o que escrever nas linhas existentes.
Escreve sempre o down(). É ele que torna a migração reversível. Uma migração sem down() transforma o mais pequeno passo atrás numa intervenção manual.
Posicionar uma coluna com after() é próprio do MySQL e do MariaDB. No SQLite, a instrução é aceite sem erro mas ignorada: a coluna é acrescentada no fim da tabela. Verificado em SQLite 3.46.1, onde notes acaba depois de updated_at apesar do after('title'). A ordem das colunas não tem qualquer efeito funcional.
Reverter uma migração já executada
Começa por ver onde estás. O migrate:status lista cada migração com o seu lote e o seu estado:
php artisan migrate:status 2026_09_02_100000_create_tasks_table ............................... [1] Ran
2026_09_02_194036_add_notes_to_tasks_table ......................... PendingUma migração Pending nunca foi executada: o ficheiro pode ser apagado diretamente. Uma migração Ran tem de ser revertida primeiro.
# reverte apenas a última migração
php artisan migrate:rollback --step=1
# reverte todo o último lote
php artisan migrate:rollbackO rollback executa o método down(). No exemplo anterior, a coluna notes desaparece mesmo da tabela e a migração volta a Pending. É nessa altura, e não antes, que podes apagar o ficheiro.
Existem dois comandos vizinhos, a reservar para o desenvolvimento:
# reverte tudo e volta a correr tudo
php artisan migrate:refresh
# elimina todas as tabelas e volta a correr tudo
php artisan migrate:freshO migrate:fresh elimina as tabelas, incluindo as que nenhuma migração gere. Num servidor de produção, o comando destrói a base de dados sem confirmação possível depois de lançado. Reserva-o estritamente para o desenvolvimento local.
Apagar uma migração
Não existe nenhum make:migration --delete: uma migração apaga-se eliminando o seu ficheiro, em database/migrations, a pasta que database_path() devolve entre os caminhos da aplicação. A única questão é saber se já correu.
Nunca foi executada (estado Pending): apaga o ficheiro, mais nada.
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.phpJá foi executada (estado Ran): reverte-a primeiro e só depois apaga o ficheiro.
php artisan migrate:rollback --step=1
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.phpApagar o ficheiro não elimina a linha correspondente na tabela migrations das bases onde ela já correu. Esses ambientes ficam com a coluna, sem nenhuma migração que a explique. Nesse caso, não apagues nada: escreve uma nova migração que desfaça a alteração. O princípio é o mesmo do Git, onde se prefere um commit de anulação a reescrever um histórico já publicado.
Alterar uma coluna existente
Para mudar um tipo, um comprimento ou a nulabilidade, volta a declarar a coluna e acrescenta change():
public function up(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->string('title', 500)->change();
});
}Boa notícia para quem vem de uma versão antiga: o pacote doctrine/dbal, durante muito tempo obrigatório para esta operação, deixou de o ser a partir do Laravel 11. Verificado em Laravel 13.30.1, onde change() funciona sem dependência adicional.
Os atributos que não repetires perdem-se. Uma coluna ->nullable()->default('x') alterada por um simples ->string('title', 500)->change() volta a ser não nula e sem valor por omissão. Volta a declarar a definição completa de cada vez.
Erros frequentes
nullable() ou default().

