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

Fast Paginate Laravel Package

hammerstone/fast-paginate

Laravel pagination macro for large tables: swap paginate() for fastPaginate() and it fetches just the IDs for the current page via a subquery, then joins them back—avoiding slow deep offsets. Drop-in replacement for Eloquent builders, no config or view changes needed.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer; Laravel auto-discovers the service provider and registers the query-builder macros, so there is no manual wiring required:

composer require hammerstone/fast-paginate

First place to look is the README's "how it works" explanation. It boils the query down to an inner subquery that selects only the primary key with your LIMIT/OFFSET, joined back to the full row set — so the slow part becomes an index-only scan instead of a wide sequential scan. Once you understand that, the API is obvious: it is a drop-in replacement for paginate().

Minimal first use case — swap in fastPaginate() on any query that already has an orderBy:

User::where('active', true)
    ->orderBy('id')
    ->fastPaginate(25);

That is it. It returns a normal LengthAwarePaginator, so Blade pagination links, ->withQueryString(), ->onEachSide(), and ->appends() all behave exactly as they do with paginate(). If you only need next/previous links, use simpleFastPaginate() to skip the count(*) query entirely.

Implementation Patterns

Drop-in swap, scoped to large tables. Do not replace paginate() everywhere — only where deep offsets hurt. A repository method is a good containment boundary:

public function activeUsers(int $perPage): LengthAwarePaginator
{
    return User::query()
        ->where('active', true)
        ->orderBy('id')
        ->fastPaginate($perPage);
}

Simple (next/prev only) pagination. For "Load more" or API cursors that don't need a total count, simpleFastPaginate() avoids the count(*) round trip, which is often the real bottleneck:

$posts = Post::where('published', true)
    ->orderBy('id')
    ->simpleFastPaginate(50);

With eager loading and relationships. Eloquent-level concerns still work; the subquery is built around the model's qualified primary key, and with() relationships are hydrated normally:

Post::with(['author', 'tags'])
    ->where('status', 'published')
    ->orderBy('id')
    ->fastPaginate();

Force an index (MySQL/MariaDB). When the optimizer picks the wrong plan, useIndex() pushes your choice into the subquery:

User::query()
    ->useIndex('users_active_created_idx')
    ->where('active', true)
    ->orderBy('id')
    ->fastPaginate();

Wrap it in a base trait. If you use this across many models, centralize the ordering rule so callers can't forget it:

trait FastPaginates
{
    public function paginateFast(int $perPage = 25): LengthAwarePaginator
    {
        return $this->newQuery()
            ->orderBy($this->getQualifiedKeyName())
            ->fastPaginate($perPage);
    }
}

Combine with filters/scopes. Local scopes compose normally; just make sure the terminal call is fastPaginate()/simpleFastPaginate() and the order column is the primary key (or at least unique and indexed).

Gotchas and Tips

  • You must order by a unique, indexed column. The package assumes ordering is deterministic. Ordering by created_at where duplicates exist can produce rows appearing on two pages or disappearing across pages. Default to orderBy('id') (or the model's qualified key) and only deviate when the column is guaranteed unique.

  • Forgetting orderBy breaks the assumption. The subquery relies on the ordering being present and stable; without it you get inconsistent results and often a worse plan than plain paginate().

  • Composite primary keys are not supported. The join is built around a single key column.

  • fastPaginate() still runs the count query. If the reason you switch is a slow count(*) on a huge table, use simpleFastPaginate() or accept the tradeoff. The speedup is in the row-fetch portion, not the count.

  • Don't over-narrow select(). Picking specific columns without the primary key, or aliasing it, breaks the join. If you must alias, alias other columns, not the key.

  • groupBy, having, and distinct are fragile. These reshape the inner query and can cause the subquery's id column to be missing or ambiguous. Test before shipping; if needed, rewrite as a base query plus a whereIn on the paginated IDs.

  • useIndex() is MySQL/MariaDB-specific. The syntax does not translate to Postgres. On Postgres, rely on proper indexing rather than hints.

  • Debugging: log the actual SQL. Since the paginator fires two queries internally, use the query log rather than toSql() on the builder alone:

    DB::enableQueryLog();
    $users = User::orderBy('id')->fastPaginate(25);
    dd(DB::getQueryLog());
    

    You should see your inner select id ... limit ? offset ? joined to the outer select * .... If the inner subquery contains wide columns, you know the package didn't recognize your key — check for aliased selects or a groupBy.

  • cursorPaginate() is still preferable for infinite scroll. Fast paginate fixes offset cost; it does not make deep offsets scale-free. If you can live without direct page-number jumps, cursor pagination is the real answer.

  • Extension points are minimal by design. There is no plugin surface; the customization you get is useIndex() plus the ability to compose on top of the returned paginator (->withQueryString(), ->through(), Illuminate\Http\Resources\Json\ResourceCollection, etc.). If you need fundamentally different behavior, wrap the package in your own trait so you can swap it out later.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect