Custom Pagination Classes in Django Rest Framework

Last updated 1 month, 1 week ago | 115 views 75     5

Tags:- Python Django DRF

Pagination helps keep API responses fast, small, and user-friendly. While DRF provides built-in classes like PageNumberPagination, LimitOffsetPagination, and CursorPagination, there are times when you need custom behavior—and that’s where Custom Pagination Classes come in.

This article covers:

  • Why you may need a custom pagination class

  • How pagination works in DRF

  • How to create your own pagination class

  • Full example implementation

  • Tips and common pitfalls


Why Create a Custom Pagination Class?

You might need custom pagination when:

  • You want a different response format

  • You need to add additional metadata

  • You want to paginate based on unusual rules

  • You need to support multiple strategies (e.g., dynamic PageNumber and LimitOffset)

  • You want to include extra links or stats in the response


⚙️ How DRF Pagination Works

All pagination classes in DRF must inherit from BasePagination or a subclass of it (PageNumberPagination, LimitOffsetPagination, etc.). You customize how:

  • The queryset is sliced

  • The response is formatted

  • The parameters are handled


Step-by-Step: Creating a Custom Pagination Class

Let’s walk through creating a custom pagination class that:

  • Uses page numbers

  • Allows dynamic page size via query param

  • Returns a customized JSON response


Step 1: Create the Custom Pagination Class

# pagination.py
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response

class MyCustomPagination(PageNumberPagination):
    page_size = 5  # default page size
    page_size_query_param = 'size'  # client can override with ?size=
    max_page_size = 50

    def get_paginated_response(self, data):
        return Response({
            'status': 'success',
            'total_items': self.page.paginator.count,
            'total_pages': self.page.paginator.num_pages,
            'current_page': self.page.number,
            'next': self.get_next_link(),
            'previous': self.get_previous_link(),
            'results': data
        })

Step 2: Use It in Your View

from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
from .pagination import MyCustomPagination

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    pagination_class = MyCustomPagination

Step 3: Try It in Your API

Example request:

GET /api/posts/?page=2&size=10

Example response:

{
  "status": "success",
  "total_items": 132,
  "total_pages": 14,
  "current_page": 2,
  "next": "http://localhost:8000/api/posts/?page=3&size=10",
  "previous": "http://localhost:8000/api/posts/?page=1&size=10",
  "results": [
    {
      "id": 11,
      "title": "Post Title",
      "content": "Post body..."
    },
    ...
  ]
}

✅ Full Example (All Files)

models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

serializers.py

from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'title', 'content']

views.py

from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
from .pagination import MyCustomPagination

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    pagination_class = MyCustomPagination

urls.py

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

router = DefaultRouter()
router.register('posts', PostViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]

Tips & Best Practices

Tip Why It Helps
✅ Override get_paginated_response() To control the structure of the JSON response
✅ Set page_size_query_param Allows clients to control how much data they get
✅ Enforce max_page_size Prevents abuse (e.g., ?size=10000)
✅ Add metadata like total pages or timestamps Useful for clients building UIs

⚠️ Common Pitfalls

Pitfall Solution
❌ Not returning Response() in get_paginated_response Always wrap your response in Response()
❌ Using the wrong pagination class Make sure the class inherits from the correct base (PageNumberPagination, etc.)
❌ Forgetting to set pagination_class in view DRF won't paginate unless the view is configured correctly
❌ Clients sending excessive size values Use max_page_size to restrict this

Conclusion

Custom pagination classes in Django Rest Framework give you the flexibility to design APIs that meet your frontend and performance needs. Whether you're building a RESTful app, infinite scroll UI, or data-heavy dashboard, fine-tuning pagination can lead to better UX and API performance.