elipzis/laravel-pastable-model
Quickly duplicate Eloquent models in Laravel. Paste attribute values from an existing record into a new model while controlling which fields carry over, making record cloning and repeat data entry simple and configurable.
The v0.2.0 release is a compatibility-only release: the only substantive change is the bump to PHP 8.2+ / Laravel 11, with the remaining entries being Dependabot chores on GitHub Actions (actions/checkout, laravel-pint-action, composer-install, git-auto-commit-action, fetch-metadata). No public API, config, or behaviour changes are announced, so the day-to-day workflow below is unchanged from v0.1.0 — you simply cannot install it on Laravel 10.
Install
composer require elipzis/laravel-pastable-model
Confirm the constraint resolves to ^0.2.0; on a Laravel 10 app Composer will hold you at 0.1.x, which is the main practical consequence of this release (see Gotchas).
Where to look first
There is no published config file or service provider surface worth exploring. This is a single-purpose trait package, so the highest-value five minutes you can spend is reading vendor/elipzis/laravel-pastable-model/src end to end. The trait is small, and the exact method names, whether attributes are copied by whitelist or by getAttributes(), and whether model events fire are all details you need to know before wiring it into a controller. Treat the source as the documentation until a stable API ships.
First use case
An admin "Duplicate" action on a resource record — clone an existing row into a new draft, resetting the primary key and any "published" style flags. Apply the trait to the model, fetch the source record, produce the pasted copy inside a transaction, then redirect to the edit screen for the new instance. That single flow exercises every quirk you are likely to hit later (key handling, unique constraints, casts), so build it first before generalising.
Trait on the model, not a base class. Apply the package trait to each model you want copyable rather than introducing an abstract base model. Copying is a rare capability; putting it on a shared base class spreads it across models that have no business being duplicated.
Wrap the copy in a transaction and an explicit authorization check. A paste is a write, so it deserves the same policy check as create:
public function duplicate(Post $post)
{
$this->authorize('create', Post::class);
$copy = DB::transaction(fn () => $post->paste()); // exact method name per trait source
return redirect()->route('posts.edit', $copy);
}
Whitelist the fields you actually want carried over. Blindly copying every attribute is the single most common source of surprises: primary keys, created_at/updated_at, soft-delete timestamps, and any status column get dragged along. In practice you either reset them immediately after the paste or override the trait's attribute selection in the model. Prefer explicit whitelists — they survive schema changes without silently copying newly added internal columns.
Distinguish "paste" from Laravel's replicate(). replicate() returns an unsaved instance you must save() yourself and handles nothing but attributes. If the package trait saves the copy for you, be deliberate about which one you reach for; mixing both idioms in one codebase produces inconsistent event behaviour. Pick one and standardise.
Extend via method overrides, not by forking. The natural extension points are the trait methods themselves: override the attribute selection, the post-paste hook, or the event-suppression flag in each model. Record those overrides in a short docblock so the next developer knows why the model deviates from the package default.
Use it in seeders and factories for demo data. Copying a realistic existing record is far cheaper than describing forty attributes in a factory state. This is the lowest-risk place to get familiar with the trait.
Test the copy, not the trait. Assert on outcomes with assertDatabaseHas plus a count check — that exactly one new row exists, that its key differs from the source, and that the fields you reset are correctly reset.
Laravel 11 is a hard floor. v0.2.0 drops Laravel 10 support. If you maintain packages or a monorepo spanning both 10 and 11, your dependency constraint must allow ^0.1|^0.2 and your code must not assume any API difference between them — there isn't one, but CI matrices will fail loudly until the constraint is widened. Pin ^0.2 on Laravel 11 apps and ^0.1 on Laravel 10 apps.
Pre-1.0 means the API can move. A 0.x package on its second release has made no stability promise. Wrap the call in a thin application-level method (e.g. PostDuplicator) rather than scattering $model->paste() through controllers, so a future rename is a one-file change.
Unique constraints bite before anything else. A copied record with a unique slug or email throws a QueryException inside your transaction. Decide the strategy up front — suffix with a counter, null out and prompt the user, or generate a fresh value — and implement it before the paste, not in an error handler.
Primary and foreign keys. Confirm from the trait source whether the auto-increment key is stripped and whether foreign keys are retained. Retaining user_id/tenant_id is usually right for a duplicate but wrong for a template-style copy; the difference is a per-model decision, which is another argument for the wrapper class.
Relations are almost certainly not copied. A duplicated post will have no comments, tags, or media unless you explicitly loop and paste them too. Plan for that deliberately: either exclude children from the feature's promise or copy them inside the same transaction with their own key resets.
Casts and dates are your friend here. Because the copy goes through the model layer rather than a raw SQL INSERT ... SELECT, attributes are read through Eloquent and cast correctly. Do not "optimise" a paste into a DB::statement clone — you lose casts, mutators, and accessors, which is precisely the value the package adds.
Events and observers. Determine whether the pasted model fires creating/created/saving. If it does, observers that assume a genuine user submission (sending notifications, writing audit logs, dispatching jobs) will fire on duplicates. Guard those observers, or suppress events around the paste if the trait exposes a flag.
Soft deletes. Pasting a soft-deleted row typically produces an active one — decide whether that is intended, and make sure deleted_at is reset rather than copied.
Debugging a failed paste. The trait is small enough that ray()/dd() inside it is faster than searching the web. Log the attribute array immediately before the save; nine times out of ten the culprit is a unique index or a non-nullable column you forgot to reset.
How can I help you explore Laravel packages today?