Es reicht nicht, den Löschen-Button in Filament auszublenden. Damit verhinderst du lediglich einen Klick an genau dieser Stelle. Andere Actions, ein Import, ein Konsolenbefehl oder später ergänzter Anwendungscode können denselben Datensatz weiterhin löschen. Eine belastbare Löschsperre braucht deshalb eine fachliche Regel, eine technische Absicherung und eine verständliche Rückmeldung im Adminpanel.
Als Beispiel dient eine Kundenverwaltung: Ein Kunde kann gelöscht werden, solange keine Rechnungen mit ihm verknüpft sind. Sobald mindestens eine Rechnung existiert, bleibt der Kunde erhalten. Das ist eine konkrete Regel, die du auf Bestellungen, Projekte, Supportfälle oder andere Beziehungen übertragen kannst.
Die Löschregel zuerst eindeutig formulieren
Bevor du Filament konfigurierst, musst du festlegen, welche Beziehungen eine Löschung tatsächlich verhindern. Nicht jede Verknüpfung ist automatisch ein Grund für eine dauerhafte Sperre.
Bei einem Kunden könnten beispielsweise folgende Regeln gelten:
- Vorhandene Rechnungen verhindern die Löschung.
- Entwürfe ohne weitere Bedeutung dürfen zusammen mit dem Kunden gelöscht werden.
- Interne Notizen dürfen bestehen bleiben oder separat bereinigt werden.
- Ein Kunde mit historisch relevanten Daten wird archiviert statt gelöscht.
Für das Tutorial gilt bewusst nur eine Regel: Ein Kunde mit mindestens einer Rechnung darf nicht gelöscht werden. Diese Formulierung ist wichtig, weil daraus sowohl die Datenbankregel als auch die Meldung im Adminpanel entsteht.
Die Beziehung mit einem restriktiven Fremdschlüssel absichern
Die Datenbank ist bei einer echten Löschung die letzte Schutzschicht. Der Fremdschlüssel der Tabelle invoices sollte deshalb verhindern, dass ein referenzierter Kunde entfernt wird.
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->foreignId('customer_id')
->constrained()
->restrictOnDelete();
$table->string('number');
$table->timestamps();
});restrictOnDelete() weist die Datenbank an, das Löschen eines Kunden abzulehnen, solange mindestens eine Rechnung auf ihn verweist. Ein versehentliches $customer->delete() kann die Beziehung damit nicht stillschweigend zerstören.
Verwende hier kein cascadeOnDelete(), wenn Rechnungen erhalten bleiben müssen. Eine Kaskade würde beim Löschen des Kunden auch die zugehörigen Rechnungen entfernen. Das ist technisch bequem und fachlich mitunter eine ausgesprochen teure Abkürzung.
Einen vorhandenen Fremdschlüssel ändern
Existiert die Tabelle bereits, musst du den bisherigen Fremdschlüssel in einer neuen Migration entfernen und mit der gewünschten Löschregel neu anlegen. Prüfe vorher den tatsächlichen Namen des Constraints und das bisherige Verhalten. Besonders ein Wechsel von einer Kaskade zu einer Restriktion sollte nicht nebenbei in einer allgemeinen Sammelmigration verschwinden.
Schema::table('invoices', function (Blueprint $table) {
$table->dropForeign(['customer_id']);
$table->foreign('customer_id')
->references('id')
->on('customers')
->restrictOnDelete();
});Vor dem Deployment solltest du außerdem prüfen, ob verwaiste Datensätze existieren. Ein neuer Fremdschlüssel kann nicht sauber angelegt werden, wenn Rechnungen auf Kunden-IDs zeigen, die es längst nicht mehr gibt.
Den fachlichen Grund im Modell verfügbar machen
Die Datenbank kann eine Löschung ablehnen, aber keine hilfreiche Filament-Meldung formulieren. Dafür bekommt das Kundenmodell eine Methode, die den fachlichen Sperrgrund liefert.
use Illuminate\Database\Eloquent\Relations\HasMany;
class Customer extends Model
{
public function invoices(): HasMany
{
return $this->hasMany(Invoice::class);
}
public function deletionBlockReason(): ?string
{
if ($this->invoices()->exists()) {
return 'Der Kunde kann nicht gelöscht werden, weil Rechnungen vorhanden sind.';
}
return null;
}
}Die Methode gibt entweder einen konkreten Grund oder null zurück. Das ist flexibler als ein reines true oder false, weil Filament denselben Text direkt anzeigen kann.
exists() reicht für diese Prüfung aus. Du musst weder alle Rechnungen laden noch ihre Anzahl ermitteln. Für die Entscheidung ist nur relevant, ob mindestens ein blockierender Datensatz vorhanden ist.
Wenn später weitere Beziehungen hinzukommen, bleibt die Methode der zentrale Ort für die fachliche Erklärung:
public function deletionBlockReason(): ?string
{
if ($this->invoices()->exists()) {
return 'Der Kunde kann nicht gelöscht werden, weil Rechnungen vorhanden sind.';
}
if ($this->projects()->exists()) {
return 'Der Kunde kann nicht gelöscht werden, solange Projekte zugeordnet sind.';
}
return null;
}Die Reihenfolge sollte der fachlichen Bedeutung folgen. Du brauchst für die Oberfläche meist keinen Bericht über jede vorhandene Beziehung. Ein klarer erster Grund genügt, solange die Meldung dem Nutzer sagt, was als Nächstes zu tun ist.
Die Delete Action in Filament vor der Ausführung stoppen
Auf der Bearbeitungsseite kannst du die normale Filament Delete Action vor ihrer Ausführung prüfen. Ist ein Sperrgrund vorhanden, zeigt das Adminpanel eine Fehlermeldung und beendet die Action.
use App\Models\Customer;
use Filament\Actions\DeleteAction;
use Filament\Notifications\Notification;
protected function getHeaderActions(): array
{
return [
DeleteAction::make()
->before(function (DeleteAction $action, Customer $record): void {
$reason = $record->deletionBlockReason();
if ($reason === null) {
return;
}
Notification::make()
->title('Kunde kann nicht gelöscht werden')
->body($reason)
->danger()
->send();
$action->halt();
}),
];
}Die genaue Position dieser Action hängt davon ab, wie deine Resource aufgebaut ist. Auf einer klassischen Bearbeitungsseite liegt sie häufig in getHeaderActions(). Verwendest du die Delete Action zusätzlich als Tabellenaktion, muss dieselbe Prüfung auch dort eingebunden werden.
Die Filament-Prüfung verbessert die Bedienung, ersetzt aber den Fremdschlüssel nicht. Zwischen Prüfung und Löschung kann sich der Datenbestand ändern. Außerdem kennt ein Konsolenbefehl die Action überhaupt nicht. Die Datenbank bleibt deshalb für echte Löschvorgänge die verbindliche Absicherung.
Ausblenden, deaktivieren oder beim Klick erklären?
Ein vollständig ausgeblendeter Löschen-Button wirkt auf Nutzer oft wie ein Berechtigungsproblem oder ein Fehler im Adminpanel. Bei einer fachlich bedingten Sperre ist eine sichtbare Erklärung meist hilfreicher.
Du hast drei mögliche Verhaltensweisen:
- Ausblenden: sinnvoll, wenn der Nutzer grundsätzlich keine Löschberechtigung besitzt.
- Deaktivieren: sinnvoll, wenn ein Tooltip oder ein anderer sichtbarer Hinweis den Grund erklärt.
- Beim Klick stoppen: sinnvoll, wenn Filament eine ausführliche Meldung anzeigen soll.
Berechtigung und fachliche Löschbarkeit sind zwei verschiedene Fragen. Eine Policy beantwortet, ob ein Nutzer Kunden löschen darf. deletionBlockReason() beantwortet, ob dieser konkrete Kunde aufgrund seiner Beziehungen gelöscht werden kann. Vermischst du beides, wird später schwer nachvollziehbar, warum eine Action fehlt.
Bulk-Löschungen bewusst behandeln
Eine Bulk Action kann viele Kunden in einem Durchlauf löschen. Darunter können löschbare und gesperrte Datensätze gemischt sein. Die übliche Standardaktion liefert für diesen Fall selten die fachlich beste Rückmeldung.
Für eine Kundenverwaltung mit relevanten Beziehungen ist die sichere Ausgangslösung einfach: Entferne die Bulk Delete Action, bis das gewünschte Verhalten ausdrücklich festgelegt ist. Das verhindert, dass eine einzelne gesperrte Beziehung einen schwer verständlichen Datenbankfehler für die gesamte Auswahl erzeugt.
Wenn eine Mehrfachlöschung wirklich benötigt wird, musst du vorher entscheiden:
- Soll die gesamte Auswahl abgebrochen werden, sobald ein Kunde gesperrt ist?
- Sollen löschbare Kunden entfernt und gesperrte Kunden übersprungen werden?
- Welche Rückmeldung soll die Namen oder IDs der übersprungenen Datensätze enthalten?
- Muss der Vorgang vollständig in einer Datenbanktransaktion laufen?
Ein stilles Überspringen ist ungünstig. Nach der Action sieht es sonst so aus, als hätte Filament einige Datensätze zufällig behalten. Wenn du Teilergebnisse zulässt, braucht die Abschlussmeldung eine klare Zusammenfassung.
Soft Deletes brauchen eine eigene Entscheidung
Verwendet das Kundenmodell Laravels SoftDeletes, wird beim normalen Löschen kein SQL-DELETE ausgeführt. Laravel setzt lediglich deleted_at. Ein restriktiver Fremdschlüssel greift dabei nicht, weil der Kundendatensatz weiterhin in der Tabelle vorhanden ist.
Du musst deshalb festlegen, ob ein Kunde mit Rechnungen archiviert werden darf. Häufig ist genau das erwünscht: Der Kunde verschwindet aus der normalen Arbeitsansicht, bleibt für historische Rechnungen aber erhalten. Dann sollte die Oberfläche den Vorgang konsequent als Archivieren bezeichnen und nicht so tun, als würden Daten endgültig gelöscht.
Soll auch das Soft Delete gesperrt werden, brauchst du zusätzlich eine Prüfung im zentralen Löschablauf. Eine mögliche letzte Anwendungsschranke ist ein Model Event:
use DomainException;
protected static function booted(): void
{
static::deleting(function (Customer $customer): void {
$reason = $customer->deletionBlockReason();
if ($reason !== null) {
throw new DomainException($reason);
}
});
}Die Filament Action sollte trotzdem vorher prüfen und eine lesbare Notification anzeigen. Die Exception ist für Aufrufe gedacht, die den vorgesehenen UI-Ablauf umgehen. Beachte außerdem, dass massenweise Eloquent-Updates oder Query-Builder-Aufrufe Model Events umgehen können. Fachlich relevante Löschungen sollten daher über einzelne Modelle oder einen dafür vorgesehenen Anwendungsservice laufen.
Die Löschsperre gezielt testen
Teste nicht nur, ob der Button sichtbar ist. Entscheidend ist, dass die Regel auch außerhalb der konkreten Filament-Seite Bestand hat.
Mindestens diese Fälle gehören in die Tests:
- Ein Kunde ohne Rechnungen kann gelöscht werden.
deletionBlockReason()liefert bei vorhandener Rechnung einen verständlichen Grund.- Die Filament Action stoppt und zeigt die vorgesehene Notification.
- Eine direkte echte Löschung wird vom Fremdschlüssel verhindert.
- Bei Soft Deletes verhält sich die Anwendung entsprechend der festgelegten Archivierungsregel.
- Eine gegebenenfalls vorhandene Bulk Action behandelt gemischte Auswahlen nachvollziehbar.
Für den Datenbanktest müssen Fremdschlüssel auch in der Testumgebung aktiv sein. Andernfalls bestätigt der Test nur die Konfiguration der Testdatenbank, nicht die beabsichtigte Schutzwirkung.
Wann Archivieren die bessere Lösung ist
Wenn Kunden, Projekte oder Vorgänge bereits Teil nachvollziehbarer Geschäftsdaten sind, ist eine endgültige Löschung häufig das falsche Bedienmodell. Ein Status wie archiviert oder ein Soft Delete bewahrt Beziehungen und hält den aktiven Arbeitsbereich trotzdem übersichtlich.
Eine echte Löschung passt eher zu fehlerhaft angelegten Datensätzen, Testeinträgen oder Objekten ohne historische Bedeutung. Diese Grenze sollte im Adminpanel erkennbar sein. Sonst wird aus „aufräumen“ schnell „war das noch wichtig?“ – eine Frage, die Datenbanken traditionell eher knapp beantworten.
Fazit
Eine Filament Action allein ist keine verlässliche Löschsperre. Formuliere zuerst, welche Beziehung die Löschung verhindert, sichere echte Löschvorgänge mit einem restriktiven Fremdschlüssel ab und stelle den fachlichen Sperrgrund zentral im Modell bereit. Filament verwendet diesen Grund anschließend für eine verständliche Rückmeldung.
Bulk Actions und Soft Deletes brauchen jeweils eine bewusste Zusatzentscheidung. Wenn du diese beiden Fälle nicht stillschweigend dem Standardverhalten überlässt, bleibt die Regel auch dann nachvollziehbar, wenn das Adminpanel später wächst oder weitere Zugriffswege hinzukommen.
Häufige Fragen
Kurz beantwortet, damit du schneller einschätzen kannst, was für dein Projekt wichtig ist.
Reicht es, die Delete Action in Filament auszublenden?
Nein. Das Ausblenden schützt nur diese Oberfläche. Andere Actions, Konsolenbefehle oder Anwendungscode können den Datensatz weiterhin löschen. Für echte Löschungen sollte zusätzlich ein restriktiver Fremdschlüssel bestehen.
Warum verhindert der Fremdschlüssel ein Soft Delete nicht?
Bei einem Soft Delete bleibt der Datensatz in der Tabelle und erhält lediglich einen Wert in deleted_at. Da kein SQL-DELETE ausgeführt wird, greift die Löschregel des Fremdschlüssels nicht.
Sollte Filament gesperrte Löschaktionen ausblenden oder erklären?
Fehlende Berechtigungen kannst du durch eine ausgeblendete Action abbilden. Bei einer fachlichen Sperre ist eine sichtbare Action mit verständlicher Meldung meist hilfreicher, weil der Nutzer den konkreten Grund erfährt.
Wie sollte eine Bulk-Löschung mit gesperrten Datensätzen umgehen?
Lege ausdrücklich fest, ob die gesamte Auswahl abgebrochen oder nur löschbare Datensätze entfernt werden. Bei Teilergebnissen muss Filament klar melden, welche Datensätze übersprungen wurden und warum.

