Category guides

Pagination in Rails: Pagy, Kaminari and will_paginate

Category guides · · Published 2026-10-24

An old book lying open with one page lifting in mid-turn, a magnifier beside it
An old book with a page in mid-turn.

Pagination is one of the oldest jobs in the Rails plugin world: will_paginate was already a staple in the Rails 2 days. The three main gems are all still released, but they take different approaches, and the bigger decision is often offset versus keyset pagination.

Release figures are from RubyGems.org, October 2026.

Offset pagination and its cost

Classic pagination translates "page 7, 25 per page" into SQL with LIMIT 25 OFFSET 150, plus a COUNT(*) query to know how many pages exist. It is simple, lets users jump to any page, and works with any ordering. Its weaknesses show on large tables:

Keyset (cursor) pagination

Keyset pagination remembers the last row seen and asks for the next rows after it: WHERE (created_at, id) < (?, ?) ORDER BY created_at DESC, id DESC LIMIT 25. With a matching index, every page costs the same, and concurrent inserts do not shift results. The trade-off is that users cannot jump to an arbitrary page number, which is fine for feeds, infinite scrolling, APIs and exports, and less fine for search results where people expect numbered pages.

The ordering must be unique and stable, which is why the primary key is added as a tiebreaker.

Pagy

Pagy (43.8.1, October 2026) was created with speed and memory use in mind. It does not add methods to your models; instead you call a helper in the controller that returns a pagination object and the paginated collection, and render navigation with its helpers. It supports offset pagination as well as keyset and countless modes, provides helpers for JSON APIs and JavaScript front ends, and ships navigation styles for several CSS frameworks.

Pagy's version numbers jumped to 43 with a major redesign, so the API in older blog posts may not match the current release. Read the changelog and upgrade guide before moving between major versions, and pin the major version in your Gemfile.

Choose it when you are starting fresh and want performance, flexibility and no model-level patches.

Kaminari

Kaminari (1.3.0, October 2026) adds scopes to Active Record and other ORMs:

@articles = Article.order(published_at: :desc).page(params[:page]).per(25)
<%= paginate @articles %>

Its views are generated as templates you can customise with rails g kaminari:views, and themes exist for common CSS frameworks. Kaminari went almost five years without a release between 1.2.2 (December 2021) and 1.3.0, which is worth knowing when you evaluate it, but it is used in a very large number of applications and works with current Rails.

Choose it when you like chainable scopes and template-based views, or your application already uses it.

will_paginate

will_paginate (4.0.1, June 2024) is the veteran:

@articles = Article.order(published_at: :desc).paginate(page: params[:page], per_page: 25)
<%= will_paginate @articles %>

Its README describes it as being in maintenance mode, without new features, while the gem still receives occasional compatibility releases (4.0.0 in 2023, 4.0.1 in 2024). It is stable and predictable in existing applications; there is little reason to rip it out, and little reason to add it to a new one.

Do you need a gem?

For an API that returns "next page" links, keyset pagination is a few lines of Active Record: order by a unique key, take one more row than the page size to know whether a next page exists, and encode the last row's key in the link. The gems earn their place mostly through view helpers, URL handling and edge cases such as invalid page parameters. Whatever you use, cap the page size a client can request.

The records on each page still have to be turned into JSON; JSON serializers for Rails APIs compares the gems that do it.

Results sorted by distance, as in a radius search over property listings, are a case where keyset pagination needs care: the sort key is computed per request, so offset pagination over a bounded result set is often the simpler choice there.

Performance tips

Summary

GemStyleKeysetNotes
PagyController helper, no model patchesYesFast; check the API for your major version
KaminariModel scopes, view templatesNo (offset)Ubiquitous, customisable views
will_paginateModel method, view helperNo (offset)Stable veteran for existing apps