Filtering is a crucial feature for building dynamic and user-friendly APIs. While DjangoFilterBackend
and django-filter
handle basic filtering out of the box, sometimes you need more power. That’s where Custom Filters come in.
In this article, you’ll learn:
-
What custom filters are
-
When to use them
-
How to build them step-by-step
-
A complete working example
-
Tips and common pitfalls
What Are Custom Filters?
Custom filters allow you to go beyond simple field lookups, enabling:
-
Custom business logic in filters
-
Querying related or computed fields
-
Accepting special query parameters (e.g.
?has_discount=true
) -
Filtering with custom methods
Custom filters are created by extending django_filters.Filter
and writing your own logic or using the method=
parameter.
When Should You Use Custom Filters?
Use a custom filter when:
-
Built-in field lookups (like
lte
,icontains
) aren't enough -
You need complex conditions across multiple fields
-
You want to expose user-friendly filter parameters
Step-by-Step: Creating a Custom Filter
1. Install django-filter
(if not already)
pip install django-filter
2. Define Your Models
# models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
discount_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
in_stock = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
3. Create a Custom FilterSet
# filters.py
import django_filters
from .models import Product
class ProductFilter(django_filters.FilterSet):
has_discount = django_filters.BooleanFilter(method='filter_has_discount')
def filter_has_discount(self, queryset, name, value):
if value:
return queryset.exclude(discount_price__isnull=True)
return queryset
class Meta:
model = Product
fields = ['in_stock', 'has_discount']
Explanation:
-
has_discount
is a virtual filter (not a model field) -
method='filter_has_discount'
tells the filter to call that method -
The method returns a filtered queryset based on the condition
4. Use the FilterSet in Your ViewSet
# views.py
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from .models import Product
from .serializers import ProductSerializer
from .filters import ProductFilter
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = ProductFilter
5. Test the API
Example API requests:
GET /api/products/?has_discount=true
GET /api/products/?in_stock=false&has_discount=false
✅ Advanced Custom Filter Example
Suppose you want to filter products created in the last N days.
# filters.py
from django.utils import timezone
import django_filters
class ProductFilter(django_filters.FilterSet):
recent_days = django_filters.NumberFilter(method='filter_recent_days')
def filter_recent_days(self, queryset, name, value):
cutoff_date = timezone.now() - timezone.timedelta(days=value)
return queryset.filter(created_at__gte=cutoff_date)
class Meta:
model = Product
fields = ['recent_days']
Use like:
GET /api/products/?recent_days=7
✅ Full Working Example (Code Summary)
-
Model:
Product
withdiscount_price
andcreated_at
-
FilterSet: Filters by custom logic (
has_discount
,recent_days
) -
ViewSet: Uses
DjangoFilterBackend
with customfilterset_class
Tips & Best Practices
Tip | Why It Helps |
---|---|
✅ Use descriptive filter names (has_discount , created_after ) |
Improves API clarity |
✅ Always validate input in custom methods | Prevents edge-case bugs |
✅ Combine multiple custom filters in one FilterSet |
Keeps code clean and organized |
✅ Write unit tests for your filter methods | Ensures long-term maintainability |
✅ Document available filters in your API docs | Helps frontend/dev users |
⚠️ Common Pitfalls
Pitfall | Solution |
---|---|
❌ Filter field not working | Ensure it's listed in fields or defined with method |
❌ Filtering by calculated or unrelated data fails | Use custom method filters and query annotation if needed |
❌ Performance issues | Index frequently filtered fields and profile queries |
❌ Confusing parameter names | Choose intuitive names like price_min , has_stock |
Conclusion
Custom filters in DRF using django-filter
give you the flexibility to handle real-world business logic in your APIs. With just a few lines of code, you can expose powerful filtering options that go beyond simple field lookups.
They're essential when you want your API to be expressive, performant, and easy to consume.