Laravel Migrations onDelete Restrict not working

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Laravel Migrations: Why onDelete('restrict') Seems Broken in Your Foreign Key Setup

Hello developers! Dealing with database integrity constraints, especially foreign keys, is a cornerstone of robust application development. When setting up relationships in Laravel migrations, ensuring that data remains consistent across your tables is paramount. Today, we are diving into a very common point of confusion: why onDelete('restrict') seems to fail when you expect it to prevent the deletion of parent records based on child dependencies.

If you are experiencing issues where deleting a record from the parent table does not restrict the deletion because child records still exist, the problem usually isn't with Laravel or the migration syntax itself, but rather how the underlying database engine is configured or how the constraint was ultimately applied.

Let’s dissect your specific scenario and figure out the correct way to enforce these rules.

Understanding onDelete('restrict') in MySQL/PostgreSQL

The onDelete clause defines the action the database should take on dependent child rows when a parent row is deleted. The options are typically:

  1. restrict (or NO ACTION): Prevents the deletion of the parent record if any referencing child records exist. This is the strictest setting, which aligns perfectly with your goal—preventing data loss.
  2. cascade: Deletes all dependent child records when the parent record is deleted.
  3. set null: Sets the foreign key columns in the child records to NULL if the parent record is deleted (this requires the foreign key column to be nullable).

When you use onDelete('restrict'), the database should handle this restriction automatically at the SQL level. If it’s not working, we need to investigate where the failure point lies: in the migration execution, the table structure, or the application layer interaction.

Reviewing Your Migration Code

Let's look closely at the structure you provided for your events (parent) and editions (child) tables.

Events Table Migration

// Events (parent)
Schema::create('events', function (Blueprint $table) {
    $table->increments('event_id');
    // ... other fields
    $table->timestamps();
});

Editions Table Migration

// Editions (Child)
Schema::create('editions', function (Blueprint $table) {
    $table->increments('edition_id');
    $table->integer('event_id')->unsigned(); // Foreign key column
    // ... other fields
    $table->timestamps();
});

Schema::table('editions', function($table) {
    $table->foreign('event_id')
          ->references('event_id')
          ->on('events')
          ->onDelete('restrict') // The constraint definition
          ->onUpdate('restrict');
});

The syntax you used for defining the foreign key constraint within a separate Schema::table call is correct for Laravel migrations. However, sometimes when setting up constraints this way, especially in older database versions or specific configurations, it's safer and more explicit to define the relationship directly on the table creation if possible, or to ensure the column types match exactly.

The Solution: Enforcing Integrity Correctly

If onDelete('restrict') is failing, it strongly suggests that either a record was deleted manually outside of Laravel (bypassing the migration), or the database setup requires an additional step for absolute certainty.

Best Practice 1: Ensure Data Consistency Before Deletion

Before attempting to delete an event, your application logic must explicitly check for related editions. This is crucial because relying solely on database constraints is the final safety net, but application-level checks provide immediate feedback and better error messages to the user.

When a user tries to delete an Event:

  1. Query the editions table: SELECT * FROM editions WHERE event_id = [event_id].
  2. If rows are returned, throw an exception and halt the deletion process.
  3. Only if no related records exist, proceed with deleting the parent record from the events table.

Best Practice 2: Consider Database-Level Constraints Alone (The Ultimate Safety Net)

While Laravel handles the migration definition beautifully, always remember that the ultimate source of truth for data integrity resides in the database itself. Ensure your database configuration is set up to strictly enforce these rules. For complex relational setups like this, relying on robust schema design—as advocated by principles seen in frameworks like those provided by Laravel—is key.

If you are working with PostgreSQL or MySQL, ensure that the columns involved in the foreign key have the exact same data types and constraints as their references. Mismatches can sometimes confuse the constraint mechanism.

Conclusion

The issue you are facing with onDelete('restrict') is often a symptom of an underlying data integrity failure rather than a flaw in the Laravel migration code itself. While the migration correctly defines the intent, the enforcement relies entirely on the database engine correctly interpreting and applying that rule during DML operations (like DELETE).

For mission-critical applications, always layer application logic checks on top of database constraints. By combining strict foreign key definitions with proactive application validation, you ensure that your data remains perfectly consistent, regardless of how the deletion request is initiated. Happy coding!