Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Union Paginator Laravel Package

austinw/laravel-union-paginator

Laravel Union Paginator lets you paginate the combined results of multiple Eloquent queries as a single dataset. Merge different models or filtered queries with UNION, keep consistent ordering and page sizes, and return a standard Laravel paginator for Blade or API output.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package solves one specific problem: presenting heterogeneous Eloquent models (e.g. Posts, Videos, Photos) as a single, consistently-ordered, paginated feed. v3.0.0 re-architects the core around a derived table over the union — where*/orderBy* are forwarded and applied once, over the union, at paginate time, in any call order. That is a real correctness improvement: per-model filter/order application was the class of bug where a filter silently applied to some models and not others. The new type/id tie-breakers give deterministic page boundaries, which is the minimum bar for any feed surface.

Fit is good where you have 3+ tables that must share one paginator, one search, one sort. Fit is poor where you have a single table (use native pagination), or where the union spans different database connections — that now throws explicitly, which rules out read/write-split and sharded topologies for the unioned models.

Integration feasibility. Small API surface, and v3 is a call-site-shaped migration rather than a data migration. The work is an inventory exercise: every use of the removed members ($unionQuery, $scopes, $transformers, prepareUnionQuery(), hasScope(), getScopesFor()), every Eloquent-only filter (whereHas() and friends) that must convert to applyScope(), every setSelectedColumns() call that must stop including type, and every model that must now be explicitly registered. A published MIGRATION.md is a meaningful de-risking signal — it suggests the maintainer expects and supports upgrades rather than treating majors as disposable.

Technical risk.

  • Framework floor is the dominant risk. Laravel 12 and 13 only. This forces your application onto Laravel 12+ as a hard prerequisite and offers no path for 10/11 estates. It also means the package tracks a narrow, fast-moving window: when Laravel 14 lands, you inherit a blocking dependency until the maintainer ships.
  • Silent row skipping. Rows whose model can't be loaded are now skipped rather than returned as null. Better ergonomics, but COUNT over the union will no longer equal rendered rows. Pagination metadata becomes advisory — total, page counts, and "page N of M" UI must not be trusted for analytics or for loop-termination logic.
  • Fail-loud at runtime. Eloquent-only methods throwing instead of silently misbehaving is the right call, but it converts a class of bug from wrong-results to 500s. That's discoverable only by exercising every filter path.
  • Public API removal breaks subclasses, decorators, and test doubles built on 2.x.
  • Bus factor. Single-maintainer GitHub project doing breaking majors on a 2.3 → 3.0 cadence. Counterweight: PHPStan level 8, PHP 8.2–8.5, and CI across SQLite, MySQL 8.4 and Postgres 17 is a stronger quality posture than most packages in this niche.

Key questions.

  1. Is Laravel 13 actually available to us, or does adopting v3.0.0 pin us to Laravel 12 today with an unvalidated forward claim?
  2. Do all models in any proposed union live on one connection — including replicas?
  3. Which unioned models have non-id primary keys, and are those keys unique across types? The type/id tie-breaker only guarantees stable pages if the pair is unique.
  4. Which models have CREATED_AT = null or non-standard timestamp columns? (Fixed here, but verify on our Postgres version.)
  5. What proportion of our list surfaces are deep-scrolling enough that offset pagination is unacceptable — i.e. how much do we actually gain from cursorPaginate()?
  6. Does any consumer of these endpoints read total or page counts?
  7. Is production MySQL 8.4 / Postgres 17, or are we on MariaDB / MySQL 5.7 / SQL Server (outside the tested matrix)?
  8. Who owns the fork if the maintainer goes quiet? The codebase is small and statically analysed, which makes this answerable.

Integration Approach

Stack fit. Laravel 12+, PHP 8.2+, single connection per union, MySQL 8.4 or Postgres 17. All three tested drivers are now in CI, so the class of driver-specific escaping regression fixed here (MySQL NO_BACKSLASH_ESCAPES) is now covered rather than discovered. Anything outside that matrix — MariaDB, MySQL 5.7, SQL Server — should be treated as unsupported regardless of what Composer resolves.

Migration path.

  1. Branch; freeze feature work on the affected feed surfaces.
  2. Grep for the removed API surface and build a call-site inventory before touching Composer.
  3. Upgrade Laravel to 12 first, as its own change. It is a prerequisite and a larger blast radius than the package bump; landing them together makes attribution impossible if pagination breaks.
  4. Convert Eloquent-only filters (whereHas, whereRelation) to applyScope().
  5. Strip type from every setSelectedColumns() argument and confirm every unioned model is registered.
  6. Add tests covering: transformers, non-id keys, null-timestamp models, skipped-model behaviour, and ordering stability across pages.

Compatibility. The realistic blocker is Composer, not code: a package pinning illuminate/* to 12/13 will conflict with anything else in the tree still on 11. Resolve the framework upgrade in isolation first so the dependency graph failure is unambiguous.

Sequencing. Laravel 12 upgrade → package upgrade on branch → canary on a low-traffic feed with real cardinality → production. Adopt simplePaginate() by default for anything user-facing and reserve offset pagination for admin surfaces where page counts are actually displayed.

Operational Impact

Maintenance. Net simpler internally — the derived-table design consolidates ordering and filtering into one place, and the empty service provider is gone. But any in-house wrapper built on the removed public members needs rework, and the project's cadence says to expect another breaking major. Budget for it as recurring cost, not a one-off.

Support. No vendor; GitHub issues and a single maintainer. Mitigate by keeping the upgrade contained behind your own thin adapter so a fork is a contained change rather than a hunt through call sites. The codebase's enforced PHPStan 8 and multi-driver CI make a fork genuinely tractable.

Scaling. simplePaginate() removes the COUNT over the union — that is the single biggest lever, since the count forces a full union evaluation on every request. cursorPaginate() is the right answer for deep feeds and infinite scroll: keyset pagination sourced from raw union rows avoids the O(offset) scan inherent to a derived table. Offset pagination should be reserved for shallow or internal surfaces.

Failure modes to instrument.

  • Skipped rows — a page renders short or empty mid-feed with no error. Alert on rendered-count vs. requested-page-size mismatch.
  • Unregistered type or cross-connection model — throws at paginate time, i.e. a 500 in production. Validate registration at boot or in a test, not per request.
  • Surviving whereHas() call — BadMethodCallException under load. Cover every filter path in tests; the throw is only useful if it fires in CI.
  • type included in selected columns — ambiguity or SQL error. Enforce with a test on every paginator construction.
  • Duplicate id across types — silently unstable pages. Verify composite uniqueness rather than assuming it.
  • Raw SQL in transformers — the bound-parameter fix covers type only; hand-written SQL in a transformer still carries its own escaping risk.

Ramp-up. Low for a Laravel-fluent engineer: the API is a day's reading plus MIGRATION.md. End-to-end, budget one to two sprints including the Laravel upgrade and test hardening. Two durable artifacts worth writing on day one: a documented convention that applyScope() is the sanctioned escape hatch for anything Eloquent-only, and a lint or test rule that fails on whereHas() called against a paginator instance.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core