
Een migratie verwijder je door haar bestand te wissen, maar alleen als ze nooit heeft gedraaid. Controleer dat met php artisan migrate:status: staat ze op Ran, draai dan eerst php artisan migrate:rollback --step=1 en verwijder daarna het bestand. Om een kolom aan een bestaande tabel toe te voegen, pas je nooit de oude migratie aan: maak er een nieuwe met --table.
Migraties versioneren je databaseschema zoals Git je code versioneert: elke wijziging is een bestand dat je op elke omgeving opnieuw kunt afspelen. Drie handelingen dekken het dagelijkse werk: een kolom toevoegen aan een tabel die al bestaat, een migratie terugdraaien, en een bestand verwijderen dat je nooit had moeten aanmaken.
Artikel uit het leertraject Webontwikkeling. Alle commando’s en uitvoer hieronder zijn gedraaid op Laravel 13.30.1 met PHP 8.4.25.
Een migratie aanmaken
Het commando volgt een naamgevingsconventie die bepaalt wat er in het gegenereerde bestand komt:
php artisan make:migration create_tasks_tableDe naam begint met create, gevolgd door de tabelnaam in het meervoud, gevolgd door table. Laravel herkent dit patroon en vult Schema::create() vooraf in met id() en timestamps(), de kolom title is hier voor het voorbeeld toegevoegd, en de gegenereerde commentaren zijn verwijderd:
<?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');
}
};Sinds Laravel 8 zijn migraties anonieme klassen die worden teruggegeven door return new class extends Migration. Oude migraties met een naam, van het type class CreateTasksTable extends Migration, blijven werken maar worden niet meer gegenereerd. Ook $table->bigIncrements('id') heeft plaatsgemaakt voor $table->id(), en de methodes up() en down() declareren voortaan het retourtype void.
Moet de tabel een aanmaak- en een wijzigingsdatum bijhouden, dan voegt $table->timestamps() ze toe: ontbreken ze, dan krijg je de fout ‘Unknown column ‘updated_at’’.
De migratie draaien maakt de tabel aan:
php artisan migrateEen kolom toevoegen aan een bestaande tabel
Staat het schema er eenmaal, dan doen de gangbare Eloquent-queries de rest. Pas nooit een migratie aan die al is gedraaid: je collega’s en je servers hebben hem afgespeeld en zullen dat niet nog eens doen. Maak een nieuwe migratie en geef de betreffende tabel op met --table:
php artisan make:migration add_notes_to_tasks_table --table=tasksHet gegenereerde bestand bevat een lege Schema::table() in beide richtingen. Vul ze in: de kolom in up(), het verwijderen ervan in 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 DONETwee dingen zijn het waard hier herhaald te worden.
Maak de kolom nullable, of geef hem een standaardwaarde. Op een tabel die al rijen bevat, laat een kolom NOT NULL zonder standaardwaarde de migratie mislukken: de database weet niet wat ze in de bestaande rijen moet zetten.
Schrijf altijd de down(). Die maakt de migratie omkeerbaar. Een migratie zonder down() verandert het kleinste stapje terug in handwerk.
Een kolom positioneren met after() is eigen aan MySQL en MariaDB. Op SQLite wordt de instructie zonder foutmelding geaccepteerd, maar genegeerd: de kolom komt achteraan de tabel te staan. Gecontroleerd op SQLite 3.46.1, waar notes ondanks de after('title') na updated_at terechtkomt. De volgorde van de kolommen heeft geen enkel functioneel gevolg.
Een al gedraaide migratie terugdraaien
Kijk eerst waar je staat. migrate:status toont elke migratie met haar batch en haar status:
php artisan migrate:status 2026_09_02_100000_create_tasks_table ............................... [1] Ran
2026_09_02_194036_add_notes_to_tasks_table ......................... PendingEen migratie met status Pending is nooit gedraaid: dat bestand mag je meteen weggooien. Een migratie met status Ran moet je eerst terugdraaien.
# draait alleen de laatste migratie terug
php artisan migrate:rollback --step=1
# draait de hele laatste batch terug
php artisan migrate:rollbackDe rollback voert de methode down() uit. In het voorbeeld hierboven verdwijnt de kolom notes netjes uit de tabel en springt de migratie terug op Pending. Pas op dat moment, en geen moment eerder, mag je het bestand verwijderen.
Er bestaan twee verwante commando’s, alleen voor ontwikkeling:
# draait alles terug en speelt alles opnieuw af
php artisan migrate:refresh
# verwijdert alle tabellen en speelt daarna alles opnieuw af
php artisan migrate:freshmigrate:fresh verwijdert de tabellen, ook die welke door geen enkele migratie worden beheerd. Op een productieserver sloopt het commando de database, en zodra het draait is er geen bevestiging meer mogelijk. Houd het strikt bij lokale ontwikkeling.
Een migratie verwijderen
Er bestaat geen make:migration --delete: een migratie verwijder je door haar bestand te wissen, in database/migrations, de map die database_path() teruggeeft bij de paden van de applicatie. De enige vraag is of ze al heeft gedraaid.
Ze is nooit uitgevoerd (status Pending): verwijder het bestand, meer niet.
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.phpZe is al uitgevoerd (status Ran): draai haar eerst terug en verwijder daarna het bestand.
php artisan migrate:rollback --step=1
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.phpHet bestand verwijderen wist niet de bijbehorende regel in de tabel migrations van de databases waar ze al heeft gedraaid. Die omgevingen houden de kolom, zonder dat er nog een migratie is die hem verklaart. Verwijder in dat geval niets: schrijf een nieuwe migratie die de wijziging ongedaan maakt. Het principe is hetzelfde als bij Git, waar een revert-commit de voorkeur krijgt boven het herschrijven van al gepubliceerde historie.
Een bestaande kolom wijzigen
Wil je een type, een lengte of de nullability veranderen, declareer de kolom dan opnieuw en zet er change() achter:
public function up(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->string('title', 500)->change();
});
}Goed nieuws voor wie van een oude versie komt: het pakket doctrine/dbal, jarenlang verplicht voor deze operatie, is dat sinds Laravel 11 niet meer. Gecontroleerd op Laravel 13.30.1, waar change() zonder extra dependency werkt.
Attributen die je niet herhaalt, ben je kwijt. Een kolom ->nullable()->default('x') die je aanpast met alleen ->string('title', 500)->change(), is daarna niet meer nullable en heeft geen standaardwaarde meer. Declareer elke keer de volledige definitie opnieuw.
Veelgemaakte fouten
nullable() of default() toe.

