FilamentLaravel

      Filament PHP: When to Use restrictOnDelete() in Laravel Migrations (and Why It Matters)

      In relational databases, the biggest source of “silent data corruption” is deleting a parent record that still has dependent child records. Laravel’s restrictOnDelete() is a database-level safety mechanism that prevents that scenario.

      In your migration:

      $table->foreignId('continent_id')
          ->constrained()
          ->restrictOnDelete();
      

      this means: a continent cannot be deleted if any country still references it.


      What restrictOnDelete() Actually Does

      restrictOnDelete() sets the foreign key constraint’s ON DELETE rule to RESTRICT.

      At the database level, RESTRICT means:

      • If a continents.id is referenced by any row in countries.continent_id
      • and someone tries to delete that continent
      • the database will reject the delete operation with a foreign key constraint error

      This protection applies regardless of whether the delete is triggered from:

      • Filament UI
      • Laravel code (Model::delete())
      • raw SQL
      • a queue job
      • an admin script
      • another service hitting the same DB

      When You Should Use restrictOnDelete()

      1) When Child Records Must Never Become Orphans

      If your domain rules say a Country must always belong to a valid Continent, then deleting the continent while countries exist is invalid.

      This is exactly your case:

      • countries.continent_id must always reference a real continent
      • therefore the continent must not be deletable while countries exist

      2) When You Want the Database to Enforce Integrity (Not Only Your App)

      UI-level protection (like hiding delete buttons in Filament) is helpful, but it is not enough on its own.

      Without DB constraints, a delete can still happen from:

      • tinker
      • seeders
      • a buggy job
      • another admin panel
      • direct SQL access

      restrictOnDelete() makes your DB a “final gatekeeper”.

      3) When You Don’t Want Cascading Deletes

      If you used cascadeOnDelete(), deleting a continent would also delete all related countries.

      That’s dangerous when:

      • countries are important historical records
      • other tables reference countries
      • you want explicit control over what gets deleted

      Using RESTRICT forces a conscious workflow:

      1. move countries to another continent, or
      2. delete countries intentionally, or
      3. block deletion altogether

      4) When You Combine It With Soft Deletes

      Your countries table uses softDeletes().

      That’s important: soft deleting a country keeps the row in the database, which means the foreign key still exists.

      So with restrictOnDelete():

      • even if countries are “soft deleted”, they still reference the continent
      • the DB will still block deleting that continent

      This is usually a good thing if you want:

      • recoverability
      • audit history
      • prevention of accidental hard deletes

      If your business rule is “a continent can be deleted only when there are no countries at all (even soft-deleted ones)”, RESTRICT matches that perfectly.


      Advantages of restrictOnDelete() (Practical Benefits)

      Database-level safety

      The database enforces the rule even if application logic fails.

      Prevents accidental mass data loss

      It blocks a delete rather than deleting related data automatically.

      Makes domain rules explicit

      Your schema documents your business rule: “countries depend on continents”.

      Protects you from future code changes

      Even if someone removes policy logic or Filament rules later, the DB still protects integrity.

      Works across all entry points

      Anything that touches the DB must respect it.


      restrictOnDelete() vs Other Options

      cascadeOnDelete()

      Best when:

      • child records are meaningless without parent
      • deleting parent should automatically remove all children
        Not recommended for high-value child records.

      nullOnDelete()

      Best when:

      • child can exist without parent
      • foreign key is nullable
        Example: optional relationships, archive references.

      restrictOnDelete()

      Best when:

      • child must always have parent
      • you want to block parent deletion if children exist
        This is the strictest and safest option for relational integrity.

      How This Relates to Filament Policies

      If you already hide delete buttons in Filament using policies, restrictOnDelete() complements that approach:

      • Policy = user-facing safety + good UX
      • DB constraint (RESTRICT) = last-line safety

      Even if a bug or bypass occurs, the database will still stop invalid deletes.


      Summary

      Use restrictOnDelete() when you want to ensure:

      • the parent record cannot be deleted while it has dependent children
      • your application is protected from accidental or unauthorized deletions
      • data integrity is enforced at the database level, not only in the UI

      In your schema (Country -> Continent), restrictOnDelete() is a strong, correct choice because a country should never be left without a valid continent.

      Hi, I’m elliotkartvel