PHPackages                             yousefaman/filament-autosave - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. yousefaman/filament-autosave

ActiveLibrary

yousefaman/filament-autosave
============================

Automatic form saving for Filament with a visual status indicator, undo, and drafts

v1.0.0(today)11↑2900%MITPHPPHP ^8.2CI passing

Since Jul 27Pushed todayCompare

[ Source](https://github.com/yousef-aman/filament-autosave)[ Packagist](https://packagist.org/packages/yousefaman/filament-autosave)[ Docs](https://github.com/yousef-aman/filament-autosave)[ RSS](/packages/yousefaman-filament-autosave/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (4)Versions (2)Used By (0)

Filament Autosave
=================

[](#filament-autosave)

Automatic form saving for Filament v4 and v5 with a visual status indicator and one-step Undo on Edit pages.

- **Edit pages** — changes are written to the database after a debounce.
- **Create and custom pages** — unsubmitted changes are stored as a draft in Laravel Cache. When the user returns, they can *Restore* or *Discard* the draft.

 [![Filament Autosave Demo](art/demo.png)](art/demo.png)

Requirements
------------

[](#requirements)

- PHP 8.2+
- Filament v4 or v5
- Livewire v3 (with Filament v4) or v4 (with Filament v5)

Laravel is not constrained by this package; it comes from Filament, which accepts 11.28+, 12 and 13. CI covers Laravel 12 and 13 only — Composer 2.9+ refuses to install any Laravel 11 release, because the whole 11.x line is past its security-support window.

### Tested combinations

[](#tested-combinations)

PHPFilamentLaravel8.2, 8.3, 8.4v4, v5128.4v4, v5138.2v4.0 (lowest dependencies)12Installation
------------

[](#installation)

```
composer require yousefaman/filament-autosave
```

Register the plugin in your panel provider:

```
use YousefAman\FilamentAutosave\AutosavePlugin;

public function panel(Panel $panel): Panel
{
    return $panel->plugin(AutosavePlugin::make());
}
```

The status indicator ships its own CSS/JS assets. If you copy Filament's assets into `public/` for production, re-run:

```
php artisan filament:assets
```

Publish the config (optional):

```
php artisan vendor:publish --tag="filament-autosave-config"
```

Publish the translations (optional):

```
php artisan vendor:publish --tag="filament-autosave-translations"
```

Publish the indicator view to customize its markup (optional):

```
php artisan vendor:publish --tag="filament-autosave-views"
```

Usage
-----

[](#usage)

### Edit pages

[](#edit-pages)

```
use Filament\Resources\Pages\EditRecord;
use YousefAman\FilamentAutosave\HasAutosave;

class EditArticle extends EditRecord
{
    use HasAutosave;

    protected static string $resource = ArticleResource::class;
}
```

The form autosaves 1.5 s after the last keystroke. After each save, an **Undo** button appears briefly to revert that write.

Autosave persists the form's **dehydrated** state — the same values Filament would write on a normal save (`dehydrateStateUsing()` transforms and casts applied, `dehydrated(false)` fields skipped) — but without running validation, so an incomplete form never blocks the save. What that means per field type:

FieldAutosaved?Plain column-backed field✓Plain (non-relationship) `Repeater`, `CheckboxList` — stores to its own column✓Single `Select::relationship()` (`BelongsTo`) — Filament dehydrates the foreign key✓Multiple `Select`, `BelongsToMany`, `Repeater::relationship()`, and anything else Filament persists through `saveRelationships()`✗ — on explicit submit only`->dehydrated(false)`✗Because validation is skipped, field-level rules (`minLength`, `maxValue`, etc.) are **not** enforced during autosave — only on explicit submit. Two things are always enforced, because skipping them would let autosave persist what a normal save rejects:

- A `required` field left blank is skipped rather than written, so it never violates a `NOT NULL` column.
- A `Select`, `CheckboxList` or `ToggleButtons` value outside the field's own options (including options scoped to the current tenant or team) is skipped.

You can enforce further rules non-blockingly via `getAutosaveValidationRules()`(see below).

### Fields nested under a state path

[](#fields-nested-under-a-state-path)

A field inside a `Group`/`Section` with a `statePath()`, or inside a `Repeater`, lives under a nested key (`settings.api_key`, `items.*.secret`). That whole top-level key is written as one column value, so autosave writes it **only when every field beneath it survived** the rules above. If one nested field is skipped — a password, an out-of-options `Select`, a blank `required` — the entire container is left untouched rather than written back without it, which would destroy the stored value of the skipped field.

Practically: a `statePath()`ed group or a `Repeater` that contains a `->password()` field is never autosaved. The rest of the form still is.

Create-page drafts are re-filled into the form instead of written to a column, so there is nothing to destroy — a nested secret is pruned from the draft and its siblings are kept.

### Create pages

[](#create-pages)

```
use Filament\Resources\Pages\CreateRecord;
use YousefAman\FilamentAutosave\HasAutosaveForCreate;

class CreateArticle extends CreateRecord
{
    use HasAutosaveForCreate;

    protected static string $resource = ArticleResource::class;
}
```

### Custom Filament pages

[](#custom-filament-pages)

For pages without an Eloquent record. The form must use the default `data`state path (`->statePath('data')`), which is what the status indicator watches:

```
use Filament\Pages\Page;
use YousefAman\FilamentAutosave\HasAutosaveForCreate;

class UserPreferences extends Page
{
    use HasAutosaveForCreate;

    public ?array $data = [];

    public function save(): void
    {
        $data = $this->form->getState();
        // ... persist however you like

        $this->clearAutosaveDraft();
    }
}
```

Call `$this->clearAutosaveDraft()` from your save handler to drop the draft once the user submits.

Configuration
-------------

[](#configuration)

Each option supports the levels listed below, merged so the **later** wins (`config → plugin → page`), except `except`, which is unioned across all levels.

Optionconfigpluginpage`debounce`✓✓✓`except`✓✓✓`cache_ttl`✓✓—`show_timestamp`✓✓—`indicatorPosition`—✓—`exceptPages`—✓—> Page-level options are **methods**. Do not redeclare a property the trait already defines (`$autosaveEnabled`, `$autosaveSnapshotHash`, `$autosaveDebounceMs`, `$autosaveCanUndo`, `$autosaveHasDraft`): PHP treats a redeclaration with a different default as an incompatible trait composition and raises a fatal error.

### Debounce

[](#debounce)

```
AutosavePlugin::make()->debounce(2000);       // plugin-wide

// per-page
protected function autosaveDebounce(): ?int
{
    return 2000;
}

// config/filament-autosave.php
'debounce' => 1500,
```

### Exclude fields

[](#exclude-fields)

```
AutosavePlugin::make()->except(['internal_notes']);

// per-page — unioned with the plugin and config lists
protected function autosaveExcept(): array
{
    return ['internal_notes'];
}

// config/filament-autosave.php
'except' => ['password', 'password_confirmation'],
```

### Exclude specific pages

[](#exclude-specific-pages)

Suppress the indicator on individual pages that use one of the traits (an alternative to `shouldAutosave()` on the page itself):

```
AutosavePlugin::make()
    ->exceptPages([EditPayment::class]);
```

### Draft TTL (Create pages)

[](#draft-ttl-create-pages)

```
AutosavePlugin::make()->cacheTtl(48);         // hours
'cache_ttl' => 24,
```

### Indicator

[](#indicator)

```
AutosavePlugin::make()
    ->showTimestamp(false)
    ->indicatorPosition('after');
```

`indicatorPosition` places the indicator before or after the page header actions. A page that overrides `getHeader()`, or that has no heading, header actions and breadcrumbs at all, renders no header — there the indicator falls back to the end of the page rather than disappearing along with autosave.

### Per-page disable

[](#per-page-disable)

```
class EditSensitive extends EditRecord
{
    use HasAutosave;

    protected function shouldAutosave(): bool
    {
        return false;
    }
}
```

`shouldAutosave()` is authoritative on the server, so a disabled page cannot be switched back on from the browser.

Lifecycle hooks
---------------

[](#lifecycle-hooks)

`beforeAutosave()` and `getAutosaveValidationRules()` apply to both Edit and Create/custom pages. `afterAutosave()` runs on **Edit pages only** — it receives the saved record, which Create pages don't have until submit.

```
protected function beforeAutosave(array $data): array
{
    return $data;
}

protected function afterAutosave(object $record): void
{
    Cache::forget("user-{$record->id}");
}

/** @return array */
protected function getAutosaveValidationRules(): array
{
    return [
        'slug' => ['required', 'string', 'max:120'],
    ];
}
```

Every rule whose field is part of the payload is checked; a field that fails is silently skipped so autosave never blocks the user. Full form validation still runs at submit time.

Rule keys may target nested paths (`'items.*.qty' => ['integer', 'min:1']`). A failure there skips the whole top-level key (`items`) for the same reason nested containers are all-or-nothing — see [above](#fields-nested-under-a-state-path).

Autosave runs `mutateFormDataBeforeSave()` and writes inside a database transaction, matching Filament's `save()`. It deliberately does **not** fire Filament's `beforeSave`/`afterSave` hooks or the `RecordUpdated`/`RecordSaved`events — use `afterAutosave()` for work that should run on every autosave. It re-baselines Filament's unsaved-changes tracking whenever the write covered every filled field, so a panel using `->unsavedChangesAlerts()` won't warn about changes autosave already wrote.

Undo (Edit pages)
-----------------

[](#undo-edit-pages)

After a successful autosave, the indicator shows an **Undo** button for 5 seconds. Clicking it:

1. Reads the pre-save values from a short-lived cache entry (30 min by default, override via `protected function getUndoTtlMinutes(): int`).
2. Writes them back through `handleRecordUpdate()`.
3. Re-fills the form.
4. Clears the undo snapshot.

Undo only replays a snapshot created by the *same live page instance*, so a snapshot left behind by an earlier page load can't roll back an edit made since. The snapshot covers real columns only — an accessor/mutator-backed field is not undoable.

Files and sensitive data
------------------------

[](#files-and-sensitive-data)

Dropped from every autosave payload automatically:

- `TemporaryUploadedFile` instances (pending file uploads).
- Any `TextInput` marked `->password()` **or** `->type('password')`, at any depth. On an Edit page autosave would otherwise commit a half-typed secret; on a Create page it would sit in the draft cache in plain text.
- The `except` list (config + plugin + page), applied to both the autosave write and the draft restore.
- Client-submitted keys that don't map to a declared form field — at every depth, so a crafted payload cannot smuggle extra keys into a JSON column either.

`except` matches **top-level** field names only; to drop a field nested inside a `Repeater` row or a `statePath()`ed group, mark it `->password()` or `->dehydrated(false)`.

Because a skipped field is genuinely unsaved, an autosave that had to skip a field the user actually filled in does **not** re-baseline Filament's unsaved-changes tracking — so `->unsavedChangesAlerts()` still warns before navigation instead of reporting the typed secret as saved.

`->dehydrated(false)` keeps a field out of **Edit-page writes**, but *not* out of Create-page drafts — those are built from raw form state precisely so the user gets back exactly what they typed. For a secret on a Create page, use `except` (or `->password()`, which is handled for you).

Translations
------------

[](#translations)

Publish translations with `--tag="filament-autosave-translations"` to customize any of the indicator labels (`unsaved`, `saving`, `saved`, `saved_at`, `undo`, `undone`, `error`, `draft_available`, `restore`, `discard`, `restored`).

Testing
-------

[](#testing)

```
composer test                          # everything
vendor/bin/pest --testsuite=Unit       # traits and manager, no panel
vendor/bin/pest --testsuite=Integration  # real EditRecord/CreateRecord in a booted panel
```

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/59014877?v=4)[Yousef Aman](/maintainers/yousef-aman)[@yousef-aman](https://github.com/yousef-aman)

---

Top Contributors

[![yousef-aman](https://avatars.githubusercontent.com/u/59014877?v=4)](https://github.com/yousef-aman "yousef-aman (20 commits)")

---

Tags

laravellivewireFormsfilamentautosave

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/yousefaman-filament-autosave/health.svg)

```
[![Health](https://phpackages.com/badges/yousefaman-filament-autosave/health.svg)](https://phpackages.com/packages/yousefaman-filament-autosave)
```

###  Alternatives

[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.8k](/packages/rawilk-profile-filament-plugin)[backstage/mails

View logged mails and events in a beautiful Filament UI.

16321.5k](/packages/backstage-mails)[stephenjude/filament-jetstream

A Laravel starter kit built with Filament inspired by Jetstream.

17760.2k3](/packages/stephenjude-filament-jetstream)[stephenjude/filament-two-factor-authentication

Filament Two Factor Authentication: Google 2FA + Passkey Authentication

84215.9k9](/packages/stephenjude-filament-two-factor-authentication)[relaticle/custom-fields

User Defined Custom Fields for Laravel Filament

16354.2k](/packages/relaticle-custom-fields)[croustibat/filament-jobs-monitor

Background Jobs monitoring like Horizon for all drivers for FilamentPHP

274333.4k9](/packages/croustibat-filament-jobs-monitor)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
