ronssij/filament-simple-draft
Filament Simple Draft adds lightweight draft and publish support to Filament resources. Save unpublished changes, preview drafts, and publish when ready without duplicating records or building complex workflows.
Minimal steps
composer require ronssij/filament-simple-draft — on a Filament 3/4/5 app the new ^3.0|^4.0|^5.0 constraint on filament/support resolves without flags.php artisan vendor:publish --tag="filament-simple-draft-migrations" && php artisan migrate.Where to look first
published_at timestamp) and for any query scopes it registers.composer.json of the package for the service provider / plugin registration name if it isn't auto-discovered.First use case
Take a single PostResource with an existing posts table. Add the trait, add the publish action to the table's row and header actions, and confirm an editor can save a draft that is invisible to the public-facing Post::all() while still visible in the Filament table. That one loop exercises the trait, the migration, the scope, and the action — everything else in the package layers on top of it.
Trait on the model, plugin on the panel
Keep the model trait as the single place draft semantics live, and register the Filament plugin/actions once per panel in the PanelProvider. Resources then opt in individually, which lets you roll drafts out to PostResource first and PageResource later without a flag day.
Publish as an explicit, authorized action
Treat "publish" as a privileged operation distinct from "edit". Gate the action (or the underlying method) behind its own policy ability — publish rather than update — so a contributor can save drafts while only an editor can make them public. This is the pattern most teams want and the one most likely to be missing by default.
Preview flows
The common day-to-day requirement is "let the author see the draft on the real front end before publishing." Implement this as a separate route guarded by auth plus a policy check on the model, not by loosening the global scope. If you need shareable previews, use URL::temporarySignedRoute() and check $request->hasValidSignature() — do not put draft visibility behind a token in the query string.
Draft-aware queries at the edges
Rather than relying solely on a global scope, make the public entry points explicit: a published() scope on the model, a repository/query object, or a PublishedPost read model. Then the admin table can deliberately call withoutGlobalScope(...) or withDrafts(). This keeps the "which contexts see drafts" question answerable by reading the code.
Testing the workflow
Use Livewire tests against the table action to assert the state transition end-to-end (livewire(ManagePosts::class)->callTableAction(PublishAction::class, $post)), and a plain model test asserting that a draft is excluded from your public query. Both tests survive package upgrades better than asserting on internal column names.
Scheduled publishing
If your domain needs "publish at a time," don't bolt it onto the publish action. Keep the package's immediate publish for the UI, and add a separate published_at column plus a scheduled command that promotes rows. Mixing the two into one column tends to make the admin table ambiguous about what "draft" means.
Upgrade path (v1.1 → v1.2)
This release is additive — the only change is a widened filament/support constraint plus a README banner. There is no migration, no config change, no code change. Bump the constraint and run your existing test suite; the release notes state the plugin was verified against Filament v5.7.6 + Livewire v4.4.0 with no plugin code changes.
The widened constraint is on filament/support, not filament/filament
^3.0|^4.0|^5.0 applies to the low-level support package. Composer will still resolve filament/filament and friends from your app's own constraint. If you see a conflict, it is almost always because filament/support is pinned by a transitive requirement elsewhere — inspect with composer why filament/support rather than assuming the plugin is at fault.
Filament 5 implies Livewire 4
Per the release notes, Filament v5 exists mainly to support Livewire v4 without changing the Filament API surface. Practically: if your app is still on Livewire 3, stay on Filament 4 and plugin v1.1.x. Do not attempt a Livewire major bump as part of a plugin upgrade — sequence it separately so you can attribute any breakage.
Drafts and unique constraints
If slug (or email, or any business key) is unique, a draft occupying the same value as a published row will collide at insert time. Decide the rule up front: either drop uniqueness on drafts and enforce it only among published rows (partial index on Postgres, generated column on MySQL), or suffix drafts and rewrite the slug on publish.
Global scopes vs. the admin panel
A scope that hides drafts globally is convenient for the public site and a trap in the admin table and in ->count() calls in dashboard widgets. Prefer explicit scopes with an obvious opt-out, and audit every aggregate/report query when you adopt drafts — silent under-counting is the classic failure mode.
Uploads made in draft
Files attached to a draft are real objects in storage regardless of publish state. Plan for orphan cleanup (a scheduled command deleting media whose owner never published within N days) and make sure your CDN/storage rules do not serve draft-attached assets at guessable paths.
Side effects should not fire on draft save
Observers, notifications, sitemap regeneration, and search indexing should key off the publish transition, not saved. Wire them to the publish action or to a published_at change check (wasChanged('published_at')), otherwise every autosave of a draft sends mail or pings a webhook.
Authorization is two questions
"Can this user edit this record?" and "can this user publish it?" are different. A single policy check on the resource covers the table row but usually not the action, so test both.
Cache invalidation on publish
Publishing is the moment your cached public page becomes stale. Hook cache busting to the publish path explicitly; do not rely on the model's generic saved event if drafts are saved frequently.
Understand the scope of the package
The name says "simple," and it should be read literally: expect one draft state per record, not revision history, not concurrent drafts, not a diff/review workflow. If the requirement grows into editorial versioning, reach for a dedicated package or a revisions table rather than stretching this one.
The banner image is cosmetic
The other v1.2.0 change is a README banner. No functional impact and nothing to verify.
Extension points worth knowing before you need them
Confirm whether the package lets you override the action classes or views via publishing (vendor:publish --tag=...-views) — customizing the publish button's label, color, or confirmation modal is the most frequently requested tweak and is much cheaper through an override than a fork.
How can I help you explore Laravel packages today?