Updated June 2026. Tested on Laravel 13 and PHP 8.4.

A foreign key is how the database itself enforces that a row in one table points at a real row in another. Laravel makes adding one short, and it keeps your data honest so you never end up with, say, a vehicle pointing at a brand that no longer exists.

We will use a fleet example. Each vehicle belongs to a brand, and each brand has many vehicles. So the vehicles table needs a brand_id foreign key that references the vehicle_brands table.

Add the foreign key in a migration

Create a migration that changes the existing vehicles table.

php artisan make:migration add_brand_id_to_vehicles --table=vehicles

Open the new file and add the column and its foreign key in the up method. Modern Laravel does both in one readable line.

public function up(): void
{
    Schema::table('vehicles', function (Blueprint $table) {
        $table->foreignId('brand_id')
            ->after('id')
            ->constrained('vehicle_brands')
            ->cascadeOnUpdate()
            ->cascadeOnDelete();
    });
}

Here is what each part does. foreignId('brand_id') creates an unsigned big integer column, which is the right type to reference an id. constrained('vehicle_brands') makes it a real foreign key pointing at that table's id column. You only pass the table name because the column is not the conventional vehicle_brand_id; if you had named it that, constrained() would work the rest out on its own.

The two cascade calls decide what happens to a vehicle when its brand changes or is removed. cascadeOnDelete means deleting a brand deletes its vehicles too. Your other options are restrictOnDelete to block the delete, and nullOnDelete to set the column to null instead.

Roll it back cleanly

In down, drop the constraint and column. dropConstrainedForeignId removes the foreign key and the column together, so you do not have to name the constraint by hand.

public function down(): void
{
    Schema::table('vehicles', function (Blueprint $table) {
        $table->dropConstrainedForeignId('brand_id');
    });
}

Run the migration.

php artisan migrate

Match it with model relationships

The foreign key handles integrity at the database level. To navigate the relationship in code, add the Eloquent methods. On the Brand model:

use Illuminate\Database\Eloquent\Relations\HasMany;

public function vehicles(): HasMany
{
    return $this->hasMany(Vehicle::class);
}

And the inverse on the Vehicle model:

use Illuminate\Database\Eloquent\Relations\BelongsTo;

public function brand(): BelongsTo
{
    return $this->belongsTo(Brand::class);
}

Now the database guarantees every vehicle's brand exists, and your code can move between them with $vehicle->brand and $brand->vehicles. Questions welcome in the comments.