Permissions in Django Rest Framework (DRF) control who can access what in your APIs. While DRF provides useful built-in permissions (like IsAuthenticated
, IsAdminUser
, etc.), most real-world applications require custom permission logic.
In this article, you’ll learn:
-
What permissions are in DRF
-
When to create custom permissions
-
How to write them step-by-step
-
Code examples
-
Best practices & common pitfalls
What are Permissions in DRF?
Permissions are checks performed after authentication and before the view is executed. They decide whether the request should proceed.
request.user # already set by authentication
If a permission fails, DRF raises a 403 Forbidden
error by default.
Why Create Custom Permissions?
You need custom permissions when your access control logic goes beyond the default options. For example:
-
Only allow users to edit their own posts
-
Admins can access everything, but regular users have limits
-
Allow read access to everyone, but write access to staff only
-
Restrict deletion to the object owner
How to Write a Custom Permission Class
Step 1: Import Base Class
from rest_framework.permissions import BasePermission
Step 2: Define Your Class
class IsOwner(BasePermission):
"""
Custom permission to only allow owners of an object to edit or delete it.
"""
def has_object_permission(self, request, view, obj):
# Object-level permission
return obj.owner == request.user
You can override two methods:
Method | Description |
---|---|
has_permission(self, request, view) |
Checks general access before object is retrieved |
has_object_permission(self, request, view, obj) |
Checks object-level access (e.g., to a specific instance) |
Example: Owner-Only Post Editing
models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
owner = models.ForeignKey(User, on_delete=models.CASCADE)
permissions.py
from rest_framework.permissions import BasePermission
class IsOwnerOrReadOnly(BasePermission):
"""
Read-only for all, but write access only to owner.
"""
def has_object_permission(self, request, view, obj):
if request.method in ('GET', 'HEAD', 'OPTIONS'):
return True
return obj.owner == request.user
views.py
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
from .permissions import IsOwnerOrReadOnly
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
serializers.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'content', 'owner']
read_only_fields = ['owner']
Combining Permissions
You can use multiple permission classes. DRF treats them like AND
conditions: all must pass.
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
Want custom OR
logic? Write a wrapper:
class IsOwnerOrAdmin(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user or request.user.is_staff
✅ Tips & Best Practices
Tip | Description |
---|---|
✅ Use has_permission() for global checks |
e.g., Only allow logged-in users |
✅ Use has_object_permission() for instance-level checks |
e.g., Only the creator can update/delete |
✅ Return True /False , not HTTP responses |
DRF handles the response if permission fails |
✅ Use read-only method check | To allow safe methods like GET , use request.method in SAFE_METHODS |
✅ Document your permission logic | Makes debugging and team collaboration easier |
⚠️ Common Pitfalls
Pitfall | Why It Happens |
---|---|
❌ Modifying response in permission class | DRF doesn’t expect that; just return True or False |
❌ Forgetting to check request.method |
Can unintentionally block safe operations like GET |
❌ Using has_object_permission without retrieve() /update() /destroy() |
Those views must fetch the object for the method to trigger |
❌ Not using perform_create() in viewsets |
request.user won’t be assigned to the object |
Conclusion
Custom permissions in DRF give you fine-grained control over who can access or modify API resources. By leveraging BasePermission
, you can implement business-specific rules cleanly and securely.