Mastering Custom Pagination in Django Rest Framework

January 08, 2026
20 min read
Lalit Mahato
Mastering Custom Pagination in Django Rest Framework

Mastering Custom Pagination in Django Rest Framework

Default pagination in Django Rest Framework is powerful, but often the frontend needs more metadata than the default implementation provides—specifically, the total number of pages.

In this guide, we will build a robust custom pagination class that includes page counts and standardizes the API response structure. We will then implement this in both a ListAPIView and a ModelViewSet.

Prerequisite: The Data Model

First, let's establish the model we are working with. We are using a Task model that tracks completion status and ownership.

models.py

from django.db import models
from users.models import CustomUser

class Task(models.Model):
    title = models.CharField(max_length=200)
    completed = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    
    # Relationships
    creator = models.ForeignKey(CustomUser, related_name='creator', on_delete=models.SET_NULL, null=True, blank=True)
    modifier = models.ForeignKey(CustomUser, related_name='modifier', on_delete=models.SET_NULL, null=True, blank=True)
    assign_to = models.ForeignKey(CustomUser, related_name='assign_to', on_delete=models.SET_NULL, null=True, blank=True)

    def __str__(self):
        return self.title

Step 1: The Serializer

Before we paginate, we need to define how our data is transformed into JSON.

serializers.py

from rest_framework import serializers
from .models import Task

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = '__all__'

Step 2: Creating the Custom Pagination Class

This is the core of our implementation. We will extend PageNumberPagination to override the get_paginated_response method. This allows us to inject total_pages into the response, which is crucial for building UI pagination components (e.g., "Page 1 of 5").

pagination.py

from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from rest_framework import status

class CustomPagination(PageNumberPagination):
    # Set the default page size
    page_size = 10 
    
    # Allow the client to set the page size via a query param (e.g., ?page_size=20)
    page_size_query_param = 'page_size'
    
    # Set a maximum limit to prevent server overload
    max_page_size = 100

    def get_paginated_response(self, data):
        final_data = {
            "count": self.page.paginator.count,
            "next": self.get_next_link(),
            "previous": self.get_previous_link(),
            "total_pages": self.page.paginator.num_pages, # The custom addition
            "results": data,
        }
        return Response(final_data, status=status.HTTP_200_OK)

Step 3: Implementation in Views

We will now apply this pagination to our views. It is important to explicitly set the pagination_class attribute.

views.py

from rest_framework.generics import ListAPIView
from rest_framework import viewsets
from .models import Task
from .serializers import TaskSerializer
from .pagination import CustomPagination

# Implementation 1: Using ListAPIView
class TaskListAPIView(ListAPIView):
    """
    API endpoint that allows tasks to be listed with custom pagination.
    """
    queryset = Task.objects.all().order_by('-created_at')
    serializer_class = TaskSerializer
    pagination_class = CustomPagination  # Hooking up the custom class


# Implementation 2: Using ModelViewSet
class TaskViewSet(viewsets.ModelViewSet):
    """
    A ViewSet for viewing and editing tasks.
    """
    queryset = Task.objects.all().order_by('-created_at')
    serializer_class = TaskSerializer
    pagination_class = CustomPagination  # Hooking up the custom class

Step 4: URL Configuration

Finally, let's expose these views via URLs to test them.

urls.py

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import TaskListAPIView, TaskViewSet

# Create a router and register the ViewSet
router = DefaultRouter()
router.register(r'tasks-set', TaskViewSet, basename='task-set')

urlpatterns = [
    # Route for the ListAPIView
    path('tasks/', TaskListAPIView.as_view(), name='task-list'),
    
    # Route for the ViewSet
    path('', include(router.urls)),
]

The Result

When you make a GET request to /api/tasks/, your response will now look like this:

{
    "count": 55,
    "next": "http://127.0.0.1:8000/api/tasks/?page=2",
    "previous": null,
    "total_pages": 6,
    "results": [
        {
            "id": 1,
            "title": "Fix pagination bug",
            "completed": false,
            ...
        },
        ...
    ]
}

Pro-Tip: Global Configuration

If you want to apply this pagination style across your entire project without adding it to every view manually, add this to your settings.py:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'path.to.your.pagination.CustomPagination',
}

 

 

Lalit Mahato

Lalit Mahato

Software Engineer | Machine Learning Enthusiast

Innovative and results-driven software developer with 5+ years of experience in designing, developing, and deploying high-quality software solutions. Proficient in various programming languages and technologies, with a keen interest in …

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment

Search

Categories

Related Posts

Mastering Token Authentication with Django Rest Framework & Swagger
Mastering Token Authentication with Django Rest Framework & Swagger

Jan 08, 2026

Read More
Data Validation in Django REST Framework (DRF)
Data Validation in Django REST Framework (DRF)

Jan 07, 2026

Read More
Building an API with Django REST Framework
Building an API with Django REST Framework

Jan 03, 2026

Read More