Laravel-migraties: kolom toevoegen, terugdraaien, verwijderen

Inleiding Kolommen of tabellen met de hand aan je database toevoegen is een intimiderend karwei en leidt meestal tot verschillen tussen je omgevingen. Met Laravel-migraties houd je je database onder versiebeheer, zodat alle leden van je team kunnen beschikken over een

Laravel-migraties: kolom toevoegen, terugdraaien, verwijderen
Kort antwoord

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:

bash
php artisan make:migration create_tasks_table

De 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:

database/migrations/2026_09_02_100000_create_tasks_table.php
<?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');
    }
};
Zien jouw migraties er anders uit?

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:

bash
php artisan migrate

Een 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:

bash
php artisan make:migration add_notes_to_tasks_table --table=tasks

Het gegenereerde bestand bevat een lege Schema::table() in beide richtingen. Vul ze in: de kolom in up(), het verwijderen ervan in down().

database/migrations/2026_09_02_194036_add_notes_to_tasks_table.php
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');
    });
}
bash
php artisan migrate
code
  2026_09_02_194036_add_notes_to_tasks_table .................... 47.43ms DONE

Twee 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.

after() werkt niet overal

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:

bash
php artisan migrate:status
code
  2026_09_02_100000_create_tasks_table ............................... [1] Ran
  2026_09_02_194036_add_notes_to_tasks_table ......................... Pending

Een migratie met status Pending is nooit gedraaid: dat bestand mag je meteen weggooien. Een migratie met status Ran moet je eerst terugdraaien.

bash
# draait alleen de laatste migratie terug
php artisan migrate:rollback --step=1

# draait de hele laatste batch terug
php artisan migrate:rollback

De 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:

bash
# draait alles terug en speelt alles opnieuw af
php artisan migrate:refresh

# verwijdert alle tabellen en speelt daarna alles opnieuw af
php artisan migrate:fresh
migrate:fresh wist je data

migrate: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.

bash
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.php

Ze is al uitgevoerd (status Ran): draai haar eerst terug en verwijder daarna het bestand.

bash
php artisan migrate:rollback --step=1
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.php
Als de migratie al naar een andere omgeving is gegaan

Het 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:

php
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.

change() herschrijft de hele definitie

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

Een al gedraaide migratie aanpassen De andere omgevingen spelen hem niet opnieuw af en houden het oude schema. Maak altijd een nieuwe migratie.
Kolom NOT NULL zonder standaardwaarde Op een tabel die al rijen bevat mislukt de migratie: de database weet niet wat ze in het bestaande moet schrijven. Voeg nullable() of default() toe.
De methode down() vergeten De migratie wordt onomkeerbaar en het kleinste stapje terug wordt handwerk.
migrate:fresh in productie Het commando verwijdert alle tabellen, ook die welke door geen enkele migratie worden beheerd, zonder bevestiging.
after() genegeerd buiten MySQL Op SQLite komt de kolom zonder foutmelding achteraan de tabel. Functioneel maakt het niets uit, maar het schema komt niet overeen met wat er staat geschreven.
change() herschrijft de hele definitie Attributen die je niet herhaalt ben je kwijt: een nullable kolom met standaardwaarde raakt beide kwijt als je ze niet opnieuw declareert.

LaravelMigrationsPHPSQL

Damien Flandrin Webdeveloper sinds 2010, maker van Gekkode en Email Impact. Elk artikel wordt vóór publicatie getest op een echt project. Contact
Nieuwsbrief

Nieuwe tests, tutorials en projecten, per e-mail.

Reproduceerbare tests, geversioneerde code, gedateerde resultaten. Nooit spam.