Skip to main content

Overview

Django’s pagination system allows you to split large result sets into discrete pages. The Paginator class handles the logic of dividing items across pages.

Paginator

The main pagination class for splitting querysets or lists into pages.
from django.core.paginator import Paginator

paginator = Paginator(
    object_list,
    per_page,
    orphans=0,
    allow_empty_first_page=True,
    error_messages=None
)
object_list
QuerySet | list
required
The list or QuerySet to paginate. Should be ordered for consistent results.
per_page
int
required
Number of items to display per page
orphans
int
default:"0"
Minimum number of items allowed on the last page. If the last page would have fewer than this, items are added to the previous page instead.
allow_empty_first_page
bool
default:"True"
Whether the first page is allowed to be empty
error_messages
dict
default:"None"
Custom error messages for ‘invalid_page’, ‘min_page’, and ‘no_results’

Properties

count
int
Total number of objects across all pages
num_pages
int
Total number of pages
page_range
range
1-based range of page numbers for iteration

Methods

page()

Returns a Page object for the given page number.
page = paginator.page(number)
number
int
required
1-based page number to retrieve
return
Page
Page object for the specified page number
Raises:
  • PageNotAnInteger - If the page number is not an integer
  • EmptyPage - If the page number is out of range

get_page()

Returns a valid page even if the page argument isn’t valid. Returns page 1 for invalid page numbers and the last page for out-of-range numbers.
page = paginator.get_page(number)
number
int | str
required
Page number to retrieve (can be invalid)
return
Page
Valid Page object (never raises exceptions)

validate_number()

Validates that a page number is valid.
valid_number = paginator.validate_number(number)
number
int
required
Page number to validate
return
int
The validated page number

get_elided_page_range()

Returns a 1-based range of pages with some values elided for large page ranges.
page_range = paginator.get_elided_page_range(
    number=1,
    on_each_side=3,
    on_ends=2
)
number
int
default:"1"
Current page number
on_each_side
int
default:"3"
Number of pages to show on each side of current page
on_ends
int
default:"2"
Number of pages to show at the beginning and end
return
generator
Generator yielding page numbers and ellipsis markers
Example output: 1, 2, '…', 40, 41, 42, 43, 44, 45, 46, '…', 49, 50

Usage Example

from django.core.paginator import Paginator
from myapp.models import Article

# Get all articles
articles = Article.objects.all()

# Create a paginator with 25 items per page
paginator = Paginator(articles, 25)

# Get total pages
print(paginator.num_pages)  # e.g., 4

# Get total count
print(paginator.count)  # e.g., 100

# Get a specific page
page = paginator.page(1)
for article in page:
    print(article.title)

AsyncPaginator

Async version of Paginator for use with async views and querysets.
from django.core.paginator import AsyncPaginator

paginator = AsyncPaginator(
    object_list,
    per_page,
    orphans=0,
    allow_empty_first_page=True,
    error_messages=None
)
Parameters are the same as Paginator. All methods are async versions with a prefix:
  • await paginator.acount()
  • await paginator.anum_pages()
  • await paginator.apage(number)
  • await paginator.aget_page(number)
  • await paginator.avalidate_number(number)
  • await paginator.aget_elided_page_range()
Usage:
async def my_view(request):
    articles = Article.objects.all()
    paginator = AsyncPaginator(articles, 25)
    
    page_number = request.GET.get('page', 1)
    page = await paginator.aget_page(page_number)
    
    return render(request, 'articles.html', {'page': page})

Page

Represents a single page of results.
class Page(object_list, number, paginator)
object_list
list
required
List of objects on this page
number
int
required
1-based page number
paginator
Paginator
required
The parent Paginator instance

Properties

object_list
list
The list of objects on this page
number
int
The 1-based page number
paginator
Paginator
The parent Paginator instance

Methods

has_next()

Returns whether there’s a next page.
if page.has_next():
    next_page = paginator.page(page.next_page_number())
return
bool
True if there is a next page

has_previous()

Returns whether there’s a previous page.
if page.has_previous():
    prev_page = paginator.page(page.previous_page_number())
return
bool
True if there is a previous page

has_other_pages()

Returns whether there are any other pages.
if page.has_other_pages():
    # Show pagination controls
    pass
return
bool
True if there are other pages (either previous or next)

next_page_number()

Returns the next page number.
next_num = page.next_page_number()
return
int
The next page number
Raises: EmptyPage if there is no next page

previous_page_number()

Returns the previous page number.
prev_num = page.previous_page_number()
return
int
The previous page number
Raises: EmptyPage if there is no previous page

start_index()

Returns the 1-based index of the first item on this page.
start = page.start_index()  # e.g., 26 for page 2 with 25 items per page
return
int
1-based index of the first item on this page

end_index()

Returns the 1-based index of the last item on this page.
end = page.end_index()  # e.g., 50 for page 2 with 25 items per page
return
int
1-based index of the last item on this page

Usage Example

paginator = Paginator(Article.objects.all(), 25)
page = paginator.page(2)

print(f"Page {page.number} of {page.paginator.num_pages}")
print(f"Showing items {page.start_index()}-{page.end_index()}")

for article in page:
    print(article.title)

if page.has_previous():
    print(f"Previous page: {page.previous_page_number()}")

if page.has_next():
    print(f"Next page: {page.next_page_number()}")

AsyncPage

Async version of Page for use with AsyncPaginator. Supports async iteration and has async versions of methods:
  • await page.ahas_next()
  • await page.ahas_previous()
  • await page.ahas_other_pages()
  • await page.anext_page_number()
  • await page.aprevious_page_number()
  • await page.astart_index()
  • await page.aend_index()
  • await page.aget_object_list()

Exceptions

InvalidPage

Base exception class for pagination errors.
from django.core.paginator import InvalidPage

PageNotAnInteger

Raised when page() is given a value that isn’t an integer.
from django.core.paginator import PageNotAnInteger

try:
    page = paginator.page('invalid')
except PageNotAnInteger:
    page = paginator.page(1)

EmptyPage

Raised when page() is given a valid value but no objects exist on that page.
from django.core.paginator import EmptyPage

try:
    page = paginator.page(999)
except EmptyPage:
    page = paginator.page(paginator.num_pages)

Template Usage

In your view:
def article_list(request):
    articles = Article.objects.all()
    paginator = Paginator(articles, 25)
    
    page_number = request.GET.get('page')
    page = paginator.get_page(page_number)
    
    return render(request, 'articles.html', {'page': page})
In your template:
{% for article in page %}
    <h2>{{ article.title }}</h2>
{% endfor %}

<div class="pagination">
    <span class="step-links">
        {% if page.has_previous %}
            <a href="?page=1">&laquo; first</a>
            <a href="?page={{ page.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ page.number }} of {{ page.paginator.num_pages }}.
        </span>

        {% if page.has_next %}
            <a href="?page={{ page.next_page_number }}">next</a>
            <a href="?page={{ page.paginator.num_pages }}">last &raquo;</a>
        {% endif %}
    </span>
</div>

See Also

  • ListView - Generic view with built-in pagination
  • QuerySets - Commonly paginated objects

Build docs developers (and LLMs) love