As your datasets grow and users scroll endlessly through results, pagination becomes critical—not just for performance but for a seamless experience. CursorPagination
is DRF’s most stable and secure pagination class, ideal for large or frequently changing datasets.
This article covers:
-
What
CursorPagination
is -
How it works
-
How to configure and implement it
-
A complete example
-
Tips & best practices
-
Common pitfalls
What is CursorPagination
?
CursorPagination
is a position-based pagination system. Unlike offset-based or page-based methods, it doesn’t rely on arbitrary numbers. Instead, it uses an encoded cursor that includes a position and ordering.
This makes it:
✅ Stable
✅ Efficient
✅ Tamper-proof
It’s perfect for APIs with infinite scrolling, real-time updates, or frequent insertions/deletions.
How CursorPagination Works
-
Each response includes a
next
andprevious
link with a cursor value. -
The cursor is a base64-encoded reference to the current position in the result set.
-
It uses an ordering field (e.g.,
created_at
,id
) to navigate through the records.
Example API Response
{
"next": "http://localhost:8000/api/posts/?cursor=cD0yMDI0LTEwLTI4...",
"previous": null,
"results": [
{
"id": 1,
"title": "Latest Post",
"created_at": "2024-10-28T10:00:00Z"
},
...
]
}
Implementing CursorPagination
Step 1: Define the Pagination Class
# pagination.py
from rest_framework.pagination import CursorPagination
class CustomCursorPagination(CursorPagination):
page_size = 5
ordering = '-created_at' # Required: determines navigation order
Step 2: Apply It Globally (optional)
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'yourapp.pagination.CustomCursorPagination',
'PAGE_SIZE': 5,
}
Step 3: Use It in a View or ViewSet
# views.py
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
from .pagination import CustomCursorPagination
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
pagination_class = CustomCursorPagination # Optional if set globally
Step 4: Ensure You Have a Proper Ordering Field
You must have a unique, ordered field—like created_at
, updated_at
, or id
.
# models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
✅ Full Working Example
serializers.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'content', 'created_at']
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)),
]
When Should You Use CursorPagination?
Use it when:
-
You need stable pagination despite frequent data changes
-
You want secure cursors (can’t be manipulated like
?offset=9999
) -
You’re implementing infinite scroll or "load more" buttons
-
You want better performance at scale
✅ Tips & Best Practices
Tip | Why It Matters |
---|---|
✅ Use a unique, indexed field for ordering |
Ensures stable and performant pagination |
✅ Use descending order (-created_at ) |
Shows latest records first |
✅ Set a page_size |
Keeps results manageable |
✅ Always expose next and previous |
Enables full navigation |
✅ Use in infinite scroll UIs | Cursor-based APIs pair well with frontend load-more logic |
⚠️ Common Pitfalls
Pitfall | Fix |
---|---|
❌ Using non-unique fields for ordering | Can result in incorrect or duplicate records |
❌ Not including an ordering field |
DRF will raise an error |
❌ Clients manually editing cursors | Always treat cursors as opaque values |
❌ Expecting page numbers | CursorPagination does not support traditional page numbers |
❌ Using it with filters without stable ordering | Combine filtering with stable ordering to avoid unexpected results |
Comparison with Other Pagination Classes
Feature | PageNumberPagination |
LimitOffsetPagination |
CursorPagination |
---|---|---|---|
Simple to use | ✅ | ✅ | ❌ Slightly complex |
Stable under data changes | ❌ | ❌ | ✅ |
Supports infinite scroll | ⚠️ Limited | ✅ | ✅ Best |
Exposes total count | ✅ | ✅ | ❌ No count |
Query tamper-proof | ❌ | ❌ | ✅ |
Fast at scale | ❌ | ❌ | ✅ |
Conclusion
CursorPagination
is a robust, secure, and high-performance pagination solution for APIs. It’s ideal for large datasets and dynamic applications where content is constantly updated.
If you’re building:
-
A mobile app feed
-
A social media timeline
-
An infinite scroll UI
-
A highly dynamic API
...then CursorPagination
is your best choice.