# Pagy: agnostic pagination in plain Ruby, rebuilt in version 43

> Pagy is an MIT-licensed Ruby pagination gem that works outside Rails and supports offset, keyset, countless and search-server techniques. Version 43 is a full redesign of the API, and the upgrade path is the part worth planning for.

**ddnexus/pagy** — Agnostic pagination in plain ruby

- Repository: https://github.com/ddnexus/pagy
- Website: https://ddnexus.github.io/pagy/
- Stars: 4,993 · Forks: 444
- Language: Ruby
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/ddnexus-pagy

## What Pagy solves, and who ends up needing it

Pagination looks like a solved problem until the collection stops being an ActiveRecord relation. Pagy's stated scope is agnostic pagination in plain Ruby, and the README lists compatibility with all environments and collection types. That matters for Sinatra, Padrino and Hanami applications, for JSON:API endpoints, and for anything that pages results coming back from Elasticsearch, Meilisearch, Searchkick or Typesense rather than from a SQL query.

The gem is for Ruby developers who want the page arithmetic and the navigation markup separated from the framework. The repository topics include rails, sinatra, hanami and padrino alongside bootstrap and bulma, which reflects that split. If your pagination needs are one ActiveRecord model and one Bootstrap nav bar, Pagy is more machinery than the job requires. If your application pages four different backends through four different code paths, the single pagy method is the argument for adopting it.

## One pagy method, many paginators

Version 43 collapsed the API around a single call. The README says you solely need the pagy method and the @pagy instance to paginate any collection and use any navigation tag and helper. The technique is chosen by a symbol passed as the first argument: :offset, :keyset, :countish, :countless, :keynav, :search, :calendar and others.

Under the hood the paginators differ in how they learn the total. Offset pagination runs a count query and then takes a slice. Keyset pagination uses an ordered column and a cursor instead of an offset, which the README calls the fastest technique. Countish is described as faster than OFFSET while still supporting the full UI. Countless skips the count entirely, which means the interface cannot show a total or a last page.

Search pagination is a separate path. You extend a model with Pagy::Search, call pagy_search, and pass the result to a search paginator. The available ones are :elasticsearch_rails, :meilisearch, :searchkick and :typesense_rails. The README also documents JSON:API output through the jsonapi: true option, which switches the query string to the nested page[number] and page[size] form, and a JSON-client form that renders @pagy.data_hash rather than urls_hash.

One design claim is worth repeating because it affects load time rather than correctness: methods are autoloaded only if used, and consume no memory otherwise. That is a decision about process footprint, not about pagination logic.

## Installing Pagy and paginating a first collection

The project is distributed as the pagy gem on RubyGems and the README points at the documentation site for the quick start. Installation is the ordinary Ruby path: add the gem, or install it directly.

```bash
gem install pagy
```

Inside a Rails application the include goes in the controller, usually application_controller.rb, and then a single call returns both the pagination object and the records.

```rb
# Include pagy in your code (usually application_controller.rb)
include Pagy::Method

# Offset-based pagination
@pagy, @records = pagy(:offset, Product.all)

# Keyset-based pagination (fastest technique)
@pagy, @records = pagy(:keyset, Product.order(my_order).all)
```

The README gives the first line as the offset example and the second as the keyset example. After the call, @pagy holds the pagination state and @records the slice you iterate in the view. If you are serving JSON:API, the same call takes an option and the response is built from urls_hash.

```ruby
# JSON:API nested query string. E.g.: ?page[number]=2&page[size]=100
@pagy, @records = pagy(:offset, Product.all, jsonapi: true)
@pagy, @records = pagy(:keyset, Product.order(my_order).all, jsonapi: true)
render json: { links: @pagy.urls_hash, data: @records }
```

For a plain JSON client, the README renders @pagy.data_hash instead. For search backends, you extend the model first and then paginate the search object rather than a relation:

```rb
# Extend your models (e.g. application_record.rb)
extend Pagy::Search

# Paginate with pagy:
search           = Product.pagy_search(params[:q])
@pagy, @response = pagy(:a_search_paginator, search)
```

The symbol in that last call is a placeholder in the README text; the concrete names are :elasticsearch_rails, :meilisearch, :searchkick and :typesense_rails. Version 43 also claims configuration requirements reduced by 99% and automatic I18n loading, so the initializer is smaller than it was in earlier majors.

## The version 43 upgrade is the real cost

The README is blunt about this: version 43 is a complete redesign of the legacy code at all levels, usage and API included. The version number was chosen to signal that it is not an ordinary major. That means an application on Pagy 5 or 6 cannot be moved by bumping the Gemfile constraint.

Methods now have narrower scopes and can be overridden without deep knowledge, and the helper surface changed. Anything that called the old helper methods in views has to be rewritten against the @pagy instance. The upgrade guide at ddnexus.github.io/pagy/guides/upgrade-guide is the document that governs the work, and the README does not summarise it.

The second constraint is the count. Choosing :countless removes the total, and with it the last-page link and any "page X of Y" text. Choosing :keyset removes random access to page numbers, because a cursor cannot jump to page 47. Both are correct trade-offs for large tables and both break interfaces that assume numbered pages. Pagy lets you pick the technique per call, which is the point, but it does not make the trade-off disappear.

## Pagy against will_paginate and Kaminari

will_paginate and Kaminari are the two gems most Ruby teams reach for first, and the difference is architectural rather than cosmetic. Both grew up inside Rails and expect ActiveRecord or a similar collection interface, and both ship view helpers that render markup for you. Kaminari in particular is built around a configurable theme system and per-model pagination settings.

Pagy keeps the page arithmetic in plain Ruby and treats rendering as a separate concern. The README describes support for server-side rendering or faster client-side rendering for popular CSS frameworks and APIs, which is a different division of labour: the gem gives you the numbers and the URLs, and the markup is yours. The cost is that migrating from Kaminari or will_paginate is not a find-and-replace. The benefit is that a Sinatra route or a JSON:API endpoint gets the same pagination object as a Rails controller, and search-server results can be paged without pretending they are a relation. If your entire pagination surface is one ActiveRecord model rendered with Bootstrap, Kaminari will ask less of you.

## Maintenance, licence and what an upgrade actually involves

The repository is not archived and the last push was on 2026-09-22, one day before this article's reference point. Recent releases are 43.6.0 on 2026-07-09, 43.6.1 on 2026-07-21 and 43.6.2 on 2026-08-24, so patch releases have been arriving roughly monthly within the 43.x line. That pattern suggests 43.x is the line to be on, and that staying on an older major means diverging from where fixes land.

The licence is MIT, declared in LICENSE.txt and shown in the README badge. In practical terms MIT permits commercial and closed-source use with attribution and no warranty; it is not a copyleft licence, so it does not impose source-disclosure obligations on your application. That is a description of the licence text, not legal advice, and anything unusual about your distribution model belongs with a lawyer.

The upgrade cost is the maintenance cost. Version 43 is a redesign, so the budget item is not the gem bump, it is the view and controller edits plus whatever your test suite catches. The README states 100% test coverage for Ruby, HTML and JavaScript end-to-end, and the repository carries API test and E2E test workflows, which is reassuring for the gem and irrelevant to whether your own call sites still work.

## Conclusion

Adopt Pagy if you want pagination that does not assume ActionView, ActiveRecord or Rails, and you are willing to read the version 43 upgrade guide before touching an existing codebase. Skip it if you need a drop-in replacement for will_paginate or Kaminari view helpers, because version 43 replaced the legacy API wholesale. Verify first that every paginator you use exists in the version 43 docs, that your Ruby version is still supported, and that your views render the new @pagy instance rather than the old helper calls.

## FAQ

### Does Pagy work outside Rails?

Yes. The README describes it as agnostic pagination in plain ruby and lists compatibility with all environments and collection types, and the repository topics include sinatra, hanami and padrino alongside rails.

### How do I install Pagy?

It ships as the pagy gem on RubyGems, so gem install pagy works, and the README points at the quick start guide on the documentation site for setup inside an application.

### Which paginators does Pagy provide?

The README lists OFFSET, COUNTISH, COUNTLESS, KEYSET, KEYNAV, SEARCH and CALENDAR techniques, plus the search-server paginators :elasticsearch_rails, :meilisearch, :searchkick and :typesense_rails.

### Can Pagy return JSON:API formatted links?

Yes. Passing jsonapi: true to the pagy call switches to the nested page[number] and page[size] query string, and the README renders the response from @pagy.urls_hash.

### Is upgrading to Pagy version 43 a simple version bump?

No. The README states version 43 is a complete redesign of the legacy code at all levels, usage and API included, and directs readers to a separate upgrade guide.

## Sources

- [ddnexus/pagy on GitHub](https://github.com/ddnexus/pagy)
- [License: MIT](https://github.com/ddnexus/pagy/blob/master/LICENSE)
- [Project website](https://ddnexus.github.io/pagy/)
- [README](https://github.com/ddnexus/pagy/blob/master/README.md)
- [Releases](https://github.com/ddnexus/pagy/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ddnexus-pagy
