gabrielesbaiz/duplicate-toolkit
Laravel package for duplicating Eloquent models and their relationships. Clone records with customizable column overrides, relationship handling, and events via a simple API, making it easy to create reusable copies of templates, content, or configuration records.
max_depth defaults to 1 (matching 1.x behavior, deliberately conservative), and preview() / dry run show the row cost before committing. A PM can scope “duplicate this account” without fear of silently exploding a 43-row copy into thousands — an operational risk that previously argued for restricting the feature to internal tools.DuplicationService or interface. 2.0 is a full rewrite that shares no code with 1.x — a concrete demonstration that the package’s internals can move. The wrapper is what made (and makes) that survivable.Traits\HasDuplicates → Concerns\HasDuplicates, Options\DuplicateOptions → DuplicateOptions, saveAsDuplicate() → duplicate(), getDuplicateOptions() → duplicateOptions(), disableDeepDuplication() → shallow(), saveQuietly() → quietly(), and DuplicateOptions is now immutable and fully typed with __get() removed. Old names still work but are deprecated. A migration command exists, but two cases need human review — notably, if a request duplicated two different model classes under 1.x, treat the result as suspect (1.x leaked relations between model classes via an unreset static cache). Any product that already shipped 1.x duplication should budget a verification pass, not just a dependency bump.Consider it when:
morphToMany no longer fatals, media-library rows are handled by a dedicated pass rather than the relation walk, and unique-column suffixing is resolved in a single query excluding the source row.suffix, prefix, replace, withAttributes, mutate) before insert, so a rename is one write and does not fire the events quietly() suppressed.relations Artisan command draws a model’s relation tree, preview()/dry run reports and rolls back, and duplicate runs interactively. The spike is now hours, not days — use it to test two or three real models before committing.php artisan duplicate-toolkit:upgrade, then manually verify any request that duplicated two different model classes. If 1.x duplication touched important data, treat those copies as suspect until checked.Look elsewhere or build in-house when:
max_depth default; if you need long-term API stability guarantees, pin 1.x behavior deliberately or wrap it and own the interface.For executives: This is a low-cost, MIT-licensed accelerator for a common product capability — duplicating records and their related data. It can help ship “clone,” “save as template,” and tenant-onboarding features faster across multiple parts of the product. The 2.0 release is a full rewrite with real engineering discipline behind it: static analysis at maximum strictness, 105 automated tests, a CI matrix across supported PHP and Laravel versions, published documentation, and an upgrade guide. It also adds the things that make the capability safe to put in front of users: per-relation copy/reference/skip rules, a dry run that previews how many rows a duplication will create, background job support, and traceability from originals to copies. Two caveats to fund accordingly. First, it requires PHP 8.3+ and Laravel 12/13, so it only applies to apps on a current platform. Second, adoption is still near zero, so there is no production track record — and 2.0 is a rewrite, meaning 1.x experience does not transfer. Recommendation: approve the evaluation spike (now genuinely cheap) plus a thin internal wrapper, ship on low-risk workflows first, and treat any upgrade of existing 1.x duplication as a small migration project with data verification, not a version bump.
For engineering: Treat 2.0 as a new dependency, not an upgrade — it shares no code with 1.x. Read UPGRADE.md first: a 1.x app will still boot after updating, and php artisan duplicate-toolkit:upgrade rewrites most call sites, but two cases need a human. Check them explicitly, especially any request that duplicated two different model classes under 1.x (the static relation cache leaked between model classes; those results are suspect). API renames: Traits\HasDuplicates → Concerns\HasDuplicates, Options\DuplicateOptions → DuplicateOptions, saveAsDuplicate() → duplicate(), getDuplicateOptions() → duplicateOptions(), disableDeepDuplication() → shallow(), saveQuietly() → quietly(). Old names still resolve but are deprecated, so do not let them linger. DuplicateOptions is now immutable and fully typed, and the __get() accessor is gone — code that read options dynamically will break loudly, which is preferable to silent drift.
Operationally, max_depth now defaults to 1, matching 1.x behavior. This is the single most important thing to understand before enabling deeper recursion: a previously “43-row” copy can become thousands if you raise depth on a real schema. Use preview() / the dry run to measure cost on a staging dataset before shipping any deep-copy configuration. Prefer shallow() explicitly at call sites where intent is ambiguous. Relation strategies (copy/reference/skip) should be declared on the model where they are domain truth, and overridden at the call site only for genuinely contextual cases; dot notation is available for multi-level exceptions but should be used sparingly, since it makes the copy graph harder to reason about. Use quietly() deliberately when you do not want observer side effects, and apply attribute overrides (suffix, prefix, replace, withAttributes, mutate) before insert so renames are one write and do not trigger events you just suppressed. For larger graphs, move duplication to the queued/batchable job and consume the four events for progress and notification rather than blocking a request.
Finally, wrap the package behind an internal service or interface from day one. 2.0 proves the internals can be replaced wholesale; the wrapper is what makes that a refactor instead of a regression. Pin the version, keep the dependency out of domain models where practical, and add your own tests around the duplication rules that matter to the business — the package’s 105 tests validate the toolkit, not your product’s copy semantics.
How can I help you explore Laravel packages today?