Migrations Laravel : ajouter une colonne, annuler, supprimer

Introduction L’ajout manuel de colonnes ou de tables à votre base de données peut être un processus intimidant et conduit le plus souvent à des incohérences entre vos différents environnements. Les migrations Laravel vous permettent de contrôler la version de votre base de données afin que tous les membres de votre équipe puissent disposer d’un

Migrations Laravel : ajouter une colonne, annuler, supprimer
Réponse rapide

Une migration se supprime en effaçant son fichier, mais seulement si elle n’a jamais tourné. Vérifiez avec php artisan migrate:status : si elle est Ran, lancez d’abord php artisan migrate:rollback --step=1, puis supprimez le fichier. Pour ajouter une colonne à une table existante, ne modifiez jamais l’ancienne migration : créez-en une nouvelle avec --table.

Les migrations versionnent votre schéma de base comme Git versionne votre code : chaque changement est un fichier, rejouable sur n’importe quel environnement. Trois opérations couvrent l’essentiel du quotidien : ajouter une colonne à une table qui existe déjà, annuler une migration, et supprimer un fichier qu’on n’aurait pas dû créer.

Article du parcours Développement web. Toutes les commandes et sorties ci-dessous ont été exécutées sur Laravel 13.30.1 avec PHP 8.4.25.

Créer une migration

La commande suit une convention de nommage qui détermine le contenu du fichier généré :

bash
php artisan make:migration create_tasks_table

Le nom commence par create, suivi du nom de la table au pluriel, suivi de table. Laravel reconnaît ce motif et pré-remplit Schema::create() avec id() et timestamps(), la colonne title est ajoutée ici pour l’exemple, et les commentaires générés ont été retirés :

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');
    }
};
Si vos migrations ne ressemblent pas à ça

Depuis Laravel 8, les migrations sont des classes anonymes retournées par return new class extends Migration. Les anciennes migrations nommées, du type class CreateTasksTable extends Migration, continuent de fonctionner mais ne sont plus générées. De même, $table->bigIncrements('id') a laissé la place à $table->id(), et les méthodes up() et down() déclarent désormais un type de retour void.

Si la table doit porter des dates de création et de modification, $table->timestamps() les ajoute : leur absence provoque l’erreur « Unknown column ‘updated_at’ ».

Exécuter la migration crée la table :

bash
php artisan migrate

Ajouter une colonne à une table existante

Une fois le schéma en place, les requêtes Eloquent courantes font le reste. Ne modifiez jamais une migration déjà exécutée : vos collègues et vos serveurs l’ont jouée, ils ne la rejoueront pas. Créez une nouvelle migration, en indiquant la table concernée avec --table :

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

Le fichier généré contient un Schema::table() vide dans les deux sens. Remplissez-les : la colonne dans up(), sa suppression dans 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

Deux précautions valent d’être rappelées ici.

Rendez la colonne nullable, ou donnez-lui une valeur par défaut. Sur une table qui contient déjà des lignes, une colonne NOT NULL sans valeur par défaut fait échouer la migration : la base ne sait pas quoi mettre dans les lignes existantes.

Écrivez toujours le down(). C’est lui qui rend la migration réversible. Une migration sans down() transforme le moindre retour arrière en intervention manuelle.

after() ne fonctionne pas partout

Positionner une colonne avec after() est propre à MySQL et MariaDB. Sur SQLite, l’instruction est acceptée sans erreur mais ignorée : la colonne est ajoutée en fin de table. Vérifié sur SQLite 3.46.1, où notes se retrouve après updated_at malgré le after('title'). L’ordre des colonnes n’a aucune incidence fonctionnelle.

Annuler une migration déjà exécutée

Commencez par regarder où vous en êtes. migrate:status liste chaque migration avec son lot et son état :

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

Une migration Pending n’a jamais été jouée : son fichier peut être supprimé directement. Une migration Ran doit d’abord être annulée.

bash
# annule la dernière migration seulement
php artisan migrate:rollback --step=1

# annule tout le dernier lot
php artisan migrate:rollback

Le rollback exécute la méthode down(). Sur l’exemple précédent, la colonne notes disparaît bien de la table et la migration repasse en Pending. C’est à ce moment, et pas avant, que vous pouvez supprimer le fichier.

Deux commandes voisines existent, à réserver au développement :

bash
# annule tout et rejoue tout
php artisan migrate:refresh

# supprime toutes les tables, puis rejoue tout
php artisan migrate:fresh
migrate:fresh efface les données

migrate:fresh supprime les tables, y compris celles qu’aucune migration ne gère. Sur un serveur de production, la commande détruit la base sans confirmation possible une fois lancée. Réservez-la strictement au développement local.

Supprimer une migration

Il n’existe pas de make:migration --delete : une migration se supprime en effaçant son fichier, dans database/migrations, dossier que renvoie database_path() parmi les chemins de l’application. La seule question est de savoir si elle a déjà tourné.

Elle n’a jamais été exécutée (état Pending) : supprimez le fichier, rien d’autre.

bash
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.php

Elle a déjà été exécutée (état Ran) : annulez-la d’abord, puis supprimez le fichier.

bash
php artisan migrate:rollback --step=1
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.php
Si la migration est déjà partie sur un autre environnement

Supprimer le fichier n’efface pas la ligne correspondante dans la table migrations des bases où elle a déjà tourné. Ces environnements garderont la colonne, sans plus aucune migration qui l’explique. Dans ce cas, ne supprimez rien : écrivez une nouvelle migration qui défait le changement. Le principe est le même qu’avec Git, où l’on préfère un commit d’annulation à une réécriture d’historique déjà publié.

Modifier une colonne existante

Pour changer un type, une longueur ou la nullabilité, redéclarez la colonne et ajoutez change() :

php
public function up(): void
{
    Schema::table('tasks', function (Blueprint $table) {
        $table->string('title', 500)->change();
    });
}

Bonne nouvelle pour qui revient d’une version ancienne : le paquet doctrine/dbal, longtemps obligatoire pour cette opération, ne l’est plus depuis Laravel 11. Vérifié sur Laravel 13.30.1, où change() fonctionne sans dépendance supplémentaire.

change() réécrit toute la définition

Les attributs que vous ne répétez pas sont perdus. Une colonne ->nullable()->default('x') modifiée par un simple ->string('title', 500)->change() redevient non nullable et sans valeur par défaut. Redéclarez la définition complète à chaque fois.

Erreurs fréquentes

Modifier une migration déjà exécutée Les autres environnements ne la rejoueront pas et garderont l’ancien schéma. Créez toujours une nouvelle migration.
Colonne NOT NULL sans valeur par défaut Sur une table qui contient déjà des lignes, la migration échoue : la base ne sait pas quoi écrire dans l’existant. Ajoutez nullable() ou default().
Oublier la méthode down() La migration devient irréversible et le moindre retour arrière passe par une intervention manuelle.
migrate:fresh en production La commande supprime toutes les tables, y compris celles qu’aucune migration ne gère, sans confirmation.
after() ignoré hors MySQL Sur SQLite, la colonne est ajoutée en fin de table sans erreur. Sans effet fonctionnel, mais le schéma ne correspond pas à ce qui est écrit.
change() réécrit toute la définition Les attributs non répétés sont perdus : une colonne nullable avec valeur par défaut les perd si vous ne les redéclarez pas.

LaravelMigrationsPHPSQL

Damien Flandrin Développeur web depuis 2010, créateur de Gekkode et d’Email Impact. Chaque article est testé sur un projet réel avant publication. Contact
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.