x-laravel/bookmarkable
x-laravel/bookmarkable adds bookmarking to Laravel Eloquent models with a simple trait. Let users save favorites or bookmarks on any model through polymorphic relations, with straightforward attach, detach, and toggle methods plus convenient per-user queries.
Architecture fit
The package is a domain-layer Eloquent component: two contracts/traits pairs (CanBookmark + Bookmarker for the owner, HasBookmarks + Bookmarkable for the saved model) implementing the standard polymorphic many-to-many pattern already familiar from favourites/tagging packages. Splitting the two sides is the right call — it lets us attach saving to User/Team without touching content models, and vice versa. Folders with ULIDs, a position + last-activity ordering, and a configurable default folder give us a ready-made data model for a "saved items" feature rather than a bare pivot. Config-driven connection, tables and model classes mean we can extend or relocate without forking. It ships no UI, no routes and no controllers, which is appropriate for a package but means the product surface is entirely ours to build.
Integration feasibility
Low-to-moderate effort for a greenfield feature. The main friction points are: (a) composer constraints are not stated in the release notes — Laravel and PHP minimums must be confirmed against our matrix before we commit; (b) migration collisions if any existing favourites/bookmarks table exists; (c) withBookmarkedBy() is only a win if the supporting indexes ship with the migrations, which is unspecified; (d) the "shared" folder flag has no accompanying membership or permission model, so sharing as a feature is not actually delivered by v1.0.0.
Technical risk
private|shared flag without a membership pivot or gate hook invites an authorization bug that becomes a data-exposure incident. Highest severity item in this release.ordered() exists but no reorder/move API is documented, so position writes may fall to application code.bookmark() is unstated — duplicate rows would corrupt counts and saved-state queries.Key questions
illuminate/* constraints? Test coverage and CI matrix?shared come with a membership pivot, policy/gate hook, or invitation flow — or is enforcement left entirely to us?position, or do we write it ourselves?bookmark() idempotent?withBookmarkedBy() avoid N+1 for folder listings and counts?model:prune delete, and is it safe when FKs are disabled or morph aliases change?tenant_id) to the shipped models via config, or does that require forking?Stack fit
Standard Laravel + Eloquent application, API-only or Blade, MySQL 8/Postgres/SQLite. No JS or queue assumptions. Requires a running scheduler for pruning and working after_commit semantics for events. Fits cleanly alongside existing traits on User and content models; watch for trait method-name collisions (bookmark, hasBookmarked are generic enough to clash with in-house helpers).
Migration path No predecessor package, so this is greenfield. If we already have a favourites table, treat it as a data migration, not a code migration: map legacy rows into the new bookmarks table (and into each user's default folder) rather than adapting the package schema. Publish config and migrations, place the tables on the intended connection up front — moving a high-volume table later is a coordinated deploy.
Compatibility
Verify: model:prune requires the package models to implement Prunable; ULID folders need a char(26) column and index-length room on MySQL; morph aliases must be registered before backfill or rows orphan; transaction wrapping in our HTTP layer determines whether after-commit events fire; DB engine parity for ordered() queries (window functions vs. order-by).
Sequencing
Maintenance Small dependency surface, but a single-maintainer, pre-1.x package: pin the exact version, subscribe to releases, read changelogs before every bump. Insurance cost is low — the code is a handful of traits, contracts, models and one command, so a fork is a day's work rather than a re-platforming.
Support No vendor SLA. Documentation depth is unknown; Turkish/English messages suggest an originating team whose working hours may not overlap ours, so treat source as the primary reference and budget for internal ownership. Name an internal owner for the package before adoption.
Scaling
Bookmarks will be among the highest-row-count tables in the product (users × items). Insist on composite indexes for (bookmarker, folder) and (bookmarkable_type, bookmarkable_id) before launch. withBookmarkedBy() is one query per list page — good — but "all saved items" and folder listings need pagination we implement ourselves. Pruning cost scales with orphan volume; chunk size must be tuned to avoid lock storms. The separate-connection option is attractive for read scaling but breaks cross-database joins with the main app — treat as a last resort.
Failure modes
Ramp-up
One backend engineer: ~1–2 days to wire the default-folder save/unsave flow with tests, +2–3 days for folder CRUD and ordering in the API, +1 day for scheduler/prune plus monitoring, and unbounded time for shared folders until authorization is designed. Product must supply all UI (saved-state toggles, empty states, folder management). Team should be comfortable with polymorphic relations, morph maps, model:prune and after-commit events.
Recommendation: adopt, pinned, behind a Phase 1 default-folder-only scope; treat folders as a Phase 2 deliverable and shared folders as unshipped until the authorization question is answered in writing.
How can I help you explore Laravel packages today?