Pagination with Paginator

Pagination is the technique of splitting a long list of database rows into numbered pages so a visitor sees a manageable slice at a time instead of one enormous scroll.

Learn Pagination with Paginator in our free Django course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…

Part of the free Django course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll use Django's Paginator to slice a QuerySet, read the requested page from the ?page= query parameter, build next and previous links from a page object, handle bad page numbers safely, and let a ListView paginate for you with a single attribute.

You create a Paginator by handing it two things: the list (or QuerySet) you want to split and how many items belong on each page. The Paginator then computes the total count , the number of pages ( num_pages ), and a page_range you can loop over to render page-number links.

paginator.page(number) returns a Page object. In a template you iterate it directly to render the rows, and you ask it questions to build navigation: page_obj.has_next , page_obj.has_previous , and the neighbouring numbers next_page_number and previous_page_number .

The ?page= value comes from the URL, so a user (or a crawler) can send anything: ?page=abc , ?page=999 , or nothing at all. Django gives you two exceptions to catch — PageNotAnInteger for non-numeric input and EmptyPage for out-of-range numbers — and the convention is to fall back to the first or last page.

A class-based ListView makes this even shorter. Set paginate_by and Django builds the Paginator, reads ?page= , and adds page_obj and is_paginated to the context automatically.

A ListView with paginate_by = 10 splits 95 items into pages of ten. Fill in the blank with the division operator so the count rounds up correctly.

You passed a page number larger than num_pages (often from ?page=999 ) straight to paginator.page() .

✅ Fix: wrap the call in try/except EmptyPage and fall back to paginator.page(paginator.num_pages) .

❌ PageNotAnInteger when ?page= is missing or text

request.GET.get("page") returns None or a string like "abc" , which paginator.page() can't use.

✅ Fix: catch PageNotAnInteger and fall back to paginator.page(1) .

❌ Next/Prev links break the filters or sort order

A link of just ?page=2 drops any other query parameters like ?q=django .

✅ Fix: preserve the rest of the query string, e.g. build links that keep request.GET and only swap the page value.

Write a helper that prints a full pagination bar — a Prev link, every page number with the current one highlighted, and a Next link — for any total, page size, and current page.

Lesson complete — your lists scale to any size!

You can build a Paginator , pull a single Page with paginator.page() , read ?page= from the URL, render Next/Prev links from has_next and has_previous , survive bad page numbers with EmptyPage and PageNotAnInteger , and let a ListView do it all with paginate_by .

🚀 Up next: The Sessions Framework — remember data about a visitor across requests using request.session .

Practice quiz

Which class splits a list or QuerySet into numbered pages?

  • Paginator
  • PageSplitter
  • QuerySet
  • ListView

Answer: Paginator. django.core.paginator.Paginator takes an object list and a per-page count.

How do you create a Paginator with 5 posts per page?

  • Paginator(5, posts)
  • Paginator(posts, 5)
  • Paginator(posts).limit(5)
  • Paginator(posts, page=5)

Answer: Paginator(posts, 5). The second positional argument is the number of items per page, e.g. Paginator(posts, 5).

Which attribute holds the total number of pages?

  • paginator.count
  • paginator.page_count
  • paginator.total
  • paginator.num_pages

Answer: paginator.num_pages. paginator.num_pages is the total number of pages; paginator.count is the total number of objects.

Which method returns a Page object for a given page number?

  • paginator.get(n)
  • paginator.page(n)
  • paginator.at(n)
  • paginator.fetch(n)

Answer: paginator.page(n). paginator.page(n) returns the Page object for page number n.

Which exception is raised when the requested page number is out of range?

  • PageNotAnInteger
  • IndexError
  • EmptyPage
  • DoesNotExist

Answer: EmptyPage. paginator.page() raises EmptyPage when the page number is too large or too small.

Which exception is raised when ?page= is not a number?

  • EmptyPage
  • PageNotAnInteger
  • ValueError
  • TypeError

Answer: PageNotAnInteger. paginator.page() raises PageNotAnInteger when the value cannot be read as an integer.

Which Page attribute is True when a following page exists?

  • page_obj.has_next
  • page_obj.more
  • page_obj.next_exists
  • page_obj.is_last

Answer: page_obj.has_next. page_obj.has_next is True when there is a page after the current one.

Where does the requested page number usually come from in a view?

  • page

The page number arrives in the query string ?page=, read via request.GET.get('page').

Which ListView attribute turns pagination on?

  • page_size
  • per_page
  • pagination
  • paginate_by

Answer: paginate_by. Setting paginate_by on a ListView makes Django build the Paginator automatically.

How is num_pages computed from count and per_page?

  • count divided down (floor)
  • count minus per_page
  • count divided and rounded up (ceil)
  • count times per_page

Answer: count divided and rounded up (ceil). The page count is the total divided by per-page size, rounded up: ceil(count / per_page).