oddvalue/laravel-drafts
Drop-in drafts and revisions for Laravel Eloquent models. Create, save, publish, and preview revisions with a simple API, middleware support, and minimal setup—ideal for CMS-style editing workflows without building a custom versioning system.
Installation:
composer require oddvalue/laravel-drafts
php artisan vendor:publish --tag="drafts-config"
Model Integration:
Add HasDrafts trait to your model:
use Oddvalue\LaravelDrafts\Concerns\HasDrafts;
class Post extends Model
{
use HasDrafts;
}
Database Migration: Add draft columns to your table:
Schema::table('posts', function (Blueprint $table) {
$table->drafts();
});
Create a draft post:
$post = Post::createDraft(['title' => 'Draft Post']);
Publish it:
$post->publish();
Draft Creation:
// Create a draft
$post = Post::createDraft(['title' => 'Draft Title']);
// Or update existing as draft
$post->updateAsDraft(['title' => 'Updated Draft']);
Previewing:
// Enable preview mode (shows current draft)
LaravelDrafts::previewMode(true);
$posts = Post::all(); // Shows drafts instead of published
// Disable preview mode
LaravelDrafts::previewMode(false);
Publishing:
$post->publish(); // Publishes the current draft
Post::current() to fetch the latest draft for editing.Post::published() to fetch live content for the frontend.Define draftable relations in your model:
protected $draftableRelations = ['tags', 'author'];
The package automatically syncs relations when publishing.
Restrict draft access to specific routes:
Route::withDrafts(function () {
Route::get('/admin/posts', [PostController::class, 'edit']);
});
Override defaults via model constants:
class Post extends Model
{
use HasDrafts;
public const IS_CURRENT = 'is_editing';
}
Use scopes to filter drafts:
// Get all drafts
$drafts = Post::onlyDrafts()->get();
// Get published posts
$published = Post::published()->get();
Relation Sync Issues:
$draftableRelations includes all relations you want synced.Preview Mode Scope:
previewMode() can cause frontend to show drafts unintentionally.Revision Limits:
config/drafts.php:
'revisions' => ['keep' => 20],
Check Draft Status:
$post->isDraft(); // Returns true if current revision is a draft
$post->isPublished(); // Returns true if published
Inspect Revisions:
$post->revisions()->get(); // List all revisions
Disable Global Scope Temporarily:
Post::withoutGlobalScope(HasDrafts::class)->get(); // Bypasses published-only scope
Custom Revision Logic:
Override shouldCreateRevision() in your model:
public function shouldCreateRevision(): bool
{
return !in_array($this->status, ['archived']);
}
Event Hooks: Listen for draft/publish events:
Post::created(function ($post) {
if ($post->isDraft()) {
event(new DraftCreated($post));
}
});
Soft Deletes:
Revisions respect soft deletes. Use forceDelete() to bypass:
$post->forceDelete(); // Deletes revisions permanently
How can I help you explore Laravel packages today?