diff --git a/packages/forms/docs/12-repeater.md b/packages/forms/docs/12-repeater.md
index 2fb0be88d97..642b8619328 100644
--- a/packages/forms/docs/12-repeater.md
+++ b/packages/forms/docs/12-repeater.md
@@ -439,6 +439,71 @@ Repeater::make('qualifications')
You can inject various utilities into the function passed to `mutateRelationshipDataBeforeSaveUsing()` as parameters.
+### Running code after creating a related item
+
+You may run code after a new related item is created in the database using the `afterCreate()` method. This method accepts a closure that receives the current item's data in a `$data` variable and the newly created record in a `$record` variable. This is useful when you need the record's ID to perform additional operations, such as attaching pivot data:
+
+```php
+use Filament\Forms\Components\Repeater;
+use Illuminate\Database\Eloquent\Model;
+
+Repeater::make('variants')
+ ->relationship()
+ ->schema([
+ // ...
+ ])
+ ->afterCreate(function (array $data, Model $record): void {
+ if (isset($data['attributes'])) {
+ $record->attributes()->attach($data['attributes']);
+ }
+ })
+```
+
+You can inject various utilities into the function passed to `afterCreate()` as parameters.
+
+### Running code after updating a related item
+
+You may run code after an existing related item is updated in the database using the `afterUpdate()` method. This method accepts a closure that receives the current item's data in a `$data` variable and the updated record in a `$record` variable:
+
+```php
+use Filament\Forms\Components\Repeater;
+use Illuminate\Database\Eloquent\Model;
+
+Repeater::make('variants')
+ ->relationship()
+ ->schema([
+ // ...
+ ])
+ ->afterUpdate(function (array $data, Model $record): void {
+ if (isset($data['attributes'])) {
+ $record->attributes()->sync($data['attributes']);
+ }
+ })
+```
+
+You can inject various utilities into the function passed to `afterUpdate()` as parameters.
+
+### Running code after deleting a related item
+
+You may run code after a related item is deleted from the database using the `afterDelete()` method. This method accepts a closure that receives the record that was just deleted in a `$record` variable. The record will no longer exist in the database at this point, but you can still access its attributes, such as its ID:
+
+```php
+use Filament\Forms\Components\Repeater;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Storage;
+
+Repeater::make('attachments')
+ ->relationship()
+ ->schema([
+ // ...
+ ])
+ ->afterDelete(function (Model $record): void {
+ Storage::delete($record->file_path);
+ })
+```
+
+You can inject various utilities into the function passed to `afterDelete()` as parameters.
+
### Modifying related records after retrieval
You may filter or modify the related records of a repeater after they are retrieved from the database using the `modifyRecordsUsing` argument. This method accepts a function that receives a `Collection` of related records. You should return the modified collection.
diff --git a/packages/forms/src/Components/Repeater.php b/packages/forms/src/Components/Repeater.php
index ee7a0318621..967409b47b3 100644
--- a/packages/forms/src/Components/Repeater.php
+++ b/packages/forms/src/Components/Repeater.php
@@ -102,6 +102,12 @@ class Repeater extends Field implements CanConcealComponents, HasExtraItemAction
protected ?Closure $mutateRelationshipDataBeforeSaveUsing = null;
+ protected ?Closure $afterCreate = null;
+
+ protected ?Closure $afterUpdate = null;
+
+ protected ?Closure $afterDelete = null;
+
/**
* @var array | null
*/
@@ -956,7 +962,10 @@ public function relationship(string | Closure | null $name = null, ?Closure $mod
$relationship
->whereKey($recordsToDelete)
->get()
- ->each(static fn (Model $record) => $record->delete());
+ ->each(static function (Model $record) use ($component): void {
+ $record->delete();
+ $component->callAfterDelete($record);
+ });
}
$itemOrder = 1;
@@ -984,6 +993,8 @@ public function relationship(string | Closure | null $name = null, ?Closure $mod
$translatableContentDriver->updateRecord($record, $itemData) :
$record->fill($itemData)->save();
+ $component->callAfterUpdate($itemData, $record);
+
continue;
}
@@ -1004,6 +1015,7 @@ public function relationship(string | Closure | null $name = null, ?Closure $mod
$record = $relationship->save($record);
$item->model($record)->saveRelationships();
+ $component->callAfterCreate($itemData, $record);
$existingRecords->push($record);
}
@@ -1353,6 +1365,83 @@ public function mutateRelationshipDataBeforeSave(array $data, Model $record): ?a
return $data;
}
+ public function afterCreate(?Closure $callback): static
+ {
+ $this->afterCreate = $callback;
+
+ return $this;
+ }
+
+ public function afterUpdate(?Closure $callback): static
+ {
+ $this->afterUpdate = $callback;
+
+ return $this;
+ }
+
+ public function afterDelete(?Closure $callback): static
+ {
+ $this->afterDelete = $callback;
+
+ return $this;
+ }
+
+ /**
+ * @param array $data
+ */
+ protected function callAfterCreate(array $data, Model $record): void
+ {
+ if ($this->afterCreate instanceof Closure) {
+ $this->evaluate(
+ $this->afterCreate,
+ namedInjections: [
+ 'data' => $data,
+ 'record' => $record,
+ ],
+ typedInjections: [
+ Model::class => $record,
+ $record::class => $record,
+ ],
+ );
+ }
+ }
+
+ /**
+ * @param array $data
+ */
+ protected function callAfterUpdate(array $data, Model $record): void
+ {
+ if ($this->afterUpdate instanceof Closure) {
+ $this->evaluate(
+ $this->afterUpdate,
+ namedInjections: [
+ 'data' => $data,
+ 'record' => $record,
+ ],
+ typedInjections: [
+ Model::class => $record,
+ $record::class => $record,
+ ],
+ );
+ }
+ }
+
+ protected function callAfterDelete(Model $record): void
+ {
+ if ($this->afterDelete instanceof Closure) {
+ $this->evaluate(
+ $this->afterDelete,
+ namedInjections: [
+ 'record' => $record,
+ ],
+ typedInjections: [
+ Model::class => $record,
+ $record::class => $record,
+ ],
+ );
+ }
+ }
+
public function canConcealComponents(): bool
{
return $this->isCollapsible();