Django REST API – Filtering, Search and Ordering
In this post, we will make our Django REST API more flexible by adding filtering, search, and ordering.
These features allow clients to request specific data instead of always receiving the full list of results.
Why filtering matters
At this point, our API can list projects and tasks, but it always returns all available results for the authenticated user.
For small APIs this may be enough, but in real applications clients usually need more control over the data they receive.
Show only completed tasks
Show only tasks from one project
Search tasks by title
Order projects by creation date
Order tasks alphabetically
Instead of creating a different endpoint for every case, we can use query parameters.
/api/tasks/?completed=true
/api/tasks/?search=homepage
/api/tasks/?ordering=-created_at
What are query parameters?
Query parameters are values added to the end of a URL after a question mark.
/api/tasks/?completed=true
In this example, completed is the parameter name and true is the value.
/api/tasks/?parameter=value
Multiple query parameters can be combined using &.
/api/tasks/?completed=false&ordering=-created_at
This allows the client to ask for more specific results without changing the main endpoint structure.
Installing django-filter
Django REST Framework includes search and ordering filters, but for field-based filtering we will use django-filter.
pip install django-filter
This package allows us to filter API results using model fields.
Adding django_filters to settings.py
Open config/config/settings.py and add django_filters to INSTALLED_APPS.
INSTALLED_APPS = [
...
'rest_framework',
'django_filters',
'projects',
]
Now Django knows that the filtering package is part of the project.
Adding global filter settings
We can configure Django REST Framework to use the filter backends globally.
Open config/config/settings.py and update the REST_FRAMEWORK configuration.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
}
This enables field filtering, search, and ordering as default filter backends.
DjangoFilterBackend → Filter by specific fields
SearchFilter → Search text fields
OrderingFilter → Order results by selected fields
Even with these backends enabled globally, each ViewSet still needs to define which fields can be filtered, searched, or ordered.
Current ViewSets
At the moment, our config/projects/views.py file should have ViewSets similar to this:
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import PermissionDenied
from .models import Project, Task
from .serializers import ProjectSerializer, TaskSerializer
class ProjectViewSet(viewsets.ModelViewSet):
serializer_class = ProjectSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Project.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class TaskViewSet(viewsets.ModelViewSet):
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Task.objects.filter(project__owner=self.request.user)
def perform_create(self, serializer):
project = serializer.validated_data['project']
if project.owner != self.request.user:
raise PermissionDenied('You cannot create tasks for this project.')
serializer.save()
We will now add filtering, search, and ordering to these ViewSets.
Adding filtering to projects
For projects, we may want to filter by creation date or search by name and description.
Update the ProjectViewSet like this:
class ProjectViewSet(viewsets.ModelViewSet):
serializer_class = ProjectSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['created_at']
search_fields = ['name', 'description']
ordering_fields = ['name', 'created_at']
ordering = ['-created_at']
def get_queryset(self):
return Project.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
These new attributes define what the API allows.
filterset_fields → Fields available for exact filtering
search_fields → Fields available for text search
ordering_fields → Fields available for ordering
ordering → Default ordering
The default ordering ['-created_at'] means the newest projects will appear first.
Testing project search
To search projects by name or description, use the search query parameter.
/api/projects/?search=website
This will return projects where the word website appears in the configured search fields.
search_fields = ['name', 'description']
Example URLs:
Search Projects: http://localhost:8000/api/projects/?search=website
Order Projects: http://localhost:8000/api/projects/?ordering=name
Ordering projects
To order projects by name, use:
/api/projects/?ordering=name
To reverse the order, add a minus sign before the field name.
/api/projects/?ordering=-name
The same works with creation date:
/api/projects/?ordering=created_at
/api/projects/?ordering=-created_at
Only fields listed in ordering_fields can be used for ordering.
ordering_fields = ['name', 'created_at']
Adding filtering to tasks
Tasks are a good example for filtering, because each task has fields such as completed, project, and created_at.
Update the TaskViewSet like this:
class TaskViewSet(viewsets.ModelViewSet):
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['project', 'completed', 'created_at']
search_fields = ['title', 'description']
ordering_fields = ['title', 'completed', 'created_at']
ordering = ['-created_at']
def get_queryset(self):
return Task.objects.filter(project__owner=self.request.user)
def perform_create(self, serializer):
project = serializer.validated_data['project']
if project.owner != self.request.user:
raise PermissionDenied('You cannot create tasks for this project.')
serializer.save()
Now the API can filter tasks by project, completion status, and creation date.
/api/tasks/?project=1
/api/tasks/?completed=true
/api/tasks/?completed=false
/api/tasks/?created_at=2026-05-05
Because our queryset is already filtered by project__owner=self.request.user, users still only receive tasks from their own projects.
Filtering completed tasks
To list only completed tasks, use:
/api/tasks/?completed=true
To list only incomplete tasks, use:
/api/tasks/?completed=false
Example links:
Completed Tasks: http://localhost:8000/api/tasks/?completed=true
Incomplete Tasks: http://localhost:8000/api/tasks/?completed=false
This is useful when a frontend wants to display separate task lists.
Completed tasks
Pending tasks
Tasks from a specific project
Filtering tasks by project
To list tasks from a specific project, use the project ID.
/api/tasks/?project=1
This will return only tasks connected to project with ID 1, as long as that project belongs to the authenticated user.
Project Tasks: http://localhost:8000/api/tasks/?project=1
This can be very useful when building a project detail page in a frontend application.
Open project page
Load project details
Load tasks where project=project_id
Searching tasks
To search tasks by title or description, use the search parameter.
/api/tasks/?search=homepage
This searches inside the fields defined in search_fields.
search_fields = ['title', 'description']
Example link:
Search Tasks: http://localhost:8000/api/tasks/?search=homepage
If a task title or description contains the search term, it should appear in the response.
Ordering tasks
To order tasks by title, use:
/api/tasks/?ordering=title
To order by newest first, use:
/api/tasks/?ordering=-created_at
To order incomplete and completed tasks, use:
/api/tasks/?ordering=completed
Only fields listed in ordering_fields are allowed.
ordering_fields = ['title', 'completed', 'created_at']
Combining filters, search, and ordering
Query parameters can be combined to create more specific requests.
For example, we can search incomplete tasks and order them by creation date.
/api/tasks/?completed=false&search=homepage&ordering=-created_at
This request means:
Return tasks where completed is false
Search for the word homepage
Order results by newest first
This is much more flexible than creating separate endpoints for every possible case.
Complete views.py file
At this point, your config/projects/views.py file should look like this:
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import PermissionDenied
from .models import Project, Task
from .serializers import ProjectSerializer, TaskSerializer
class ProjectViewSet(viewsets.ModelViewSet):
serializer_class = ProjectSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['created_at']
search_fields = ['name', 'description']
ordering_fields = ['name', 'created_at']
ordering = ['-created_at']
def get_queryset(self):
return Project.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class TaskViewSet(viewsets.ModelViewSet):
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['project', 'completed', 'created_at']
search_fields = ['title', 'description']
ordering_fields = ['title', 'completed', 'created_at']
ordering = ['-created_at']
def get_queryset(self):
return Task.objects.filter(project__owner=self.request.user)
def perform_create(self, serializer):
project = serializer.validated_data['project']
if project.owner != self.request.user:
raise PermissionDenied('You cannot create tasks for this project.')
serializer.save()
This gives our API more flexibility while keeping the ownership protection from the previous post.
Testing in the browser
Run the development server:
python manage.py runserver
Then test the endpoints in the browser:
Projects: http://localhost:8000/api/projects/
Tasks: http://localhost:8000/api/tasks/
Try adding query parameters to the URL:
/api/projects/?search=website
/api/projects/?ordering=name
/api/tasks/?completed=false
/api/tasks/?project=1
/api/tasks/?search=homepage
/api/tasks/?ordering=-created_at
The browsable API may also display filter controls depending on your configuration and installed packages.
Filtering vs search vs ordering
Although these features are related, they are used for different purposes.
Filtering → Select exact values
Search → Look for text inside fields
Ordering → Sort the result list
Examples:
Filtering:
/api/tasks/?completed=true
Search:
/api/tasks/?search=homepage
Ordering:
/api/tasks/?ordering=-created_at
When used together, they make the API much more useful for real frontend applications.
Recommended approach
A good approach is to only expose filters and ordering fields that are useful to the client.
Do not expose every model field automatically.
Choose fields that make sense for the API user.
Keep filtering predictable and easy to document.
For this project, the following fields are enough for now:
Projects:
- Search by name and description
- Order by name and created_at
Tasks:
- Filter by project and completed
- Search by title and description
- Order by title, completed, and created_at
This keeps the API simple while still giving clients useful options.
What we have learned
In this post, we added filtering, search, and ordering to our Django REST API.
Query parameters allow clients to customize API results
django-filter enables field-based filtering
SearchFilter enables text search
OrderingFilter enables result sorting
filterset_fields controls exact filtering
search_fields controls searchable text fields
ordering_fields controls allowed ordering fields
Filters can be combined in the same request
Our API is now easier to use and more suitable for real frontend applications.
What comes next?
In the next post, we will add pagination to our API.
Pagination helps prevent the API from returning too much data at once and improves performance as the database grows.
Django REST API: Pagination