Custom Actions in Django REST Framework ViewSets with @action

Last updated 2 weeks, 6 days ago | 95 views 75     5

Tags:- Python Django DRF

Django REST Framework’s ViewSet classes handle the standard CRUD operations well. But what if you need to implement custom logic—like publishing a blog post, marking an item as featured, or resetting a password?

That's where custom actions using the @action decorator come in.

This article covers:

  • What the @action decorator is

  • How to add custom routes to a ViewSet

  • GET vs POST custom actions

  • Full example with code

  • Tips and common pitfalls


What is the @action Decorator?

The @action decorator (from rest_framework.decorators) allows you to define custom routes inside a ViewSet that aren't tied to the standard CRUD operations (list, retrieve, create, etc.).

You can create extra routes like:

GET /books/<pk>/publish/
POST /users/<pk>/reset_password/

When to Use Custom Actions

Use the @action decorator when:

  • You need an endpoint that performs a specific operation on a resource

  • It doesn’t fit the standard CRUD structure

  • You want to keep the functionality grouped within the ViewSet


Step-by-Step Example: Add a publish Action to a Book

1. Model

# models.py
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=100)
    is_published = models.BooleanField(default=False)

2. Serializer

# serializers.py
from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

3. ViewSet with Custom Action

# views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
from .models import Book
from .serializers import BookSerializer

class BookViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        book = self.get_object()
        book.is_published = True
        book.save()
        return Response({'status': 'book published'})

✅ This creates a new endpoint: POST /books/<pk>/publish/


4. Add to urls.py via Router

# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import BookViewSet

router = DefaultRouter()
router.register(r'books', BookViewSet)

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

GET vs POST Actions

  • Use methods=['get'] for safe, read-only actions

  • Use methods=['post'] for modifying state

Example GET Action

@action(detail=True, methods=['get'])
def summary(self, request, pk=None):
    book = self.get_object()
    return Response({'title': book.title, 'author': book.author})

Access it via:

GET /books/3/summary/

Permissions on Custom Actions

You can apply permissions per action:

@action(detail=True, methods=['post'], permission_classes=[IsAdminUser])
def publish(self, request, pk=None):
    ...

Summary

Feature Description
@action Adds custom method-based endpoints to a ViewSet
detail=True Applies to individual instance (/books/<pk>/...)
detail=False Applies to the entire collection (/books/custom/)
methods=['post'] Use for actions that change state
methods=['get'] Use for read-only info

⚠️ Common Pitfalls

Problem Fix
URL not found Make sure the action is correctly decorated with @action
detail=True vs False confusion Use detail=True for instance actions; False for collection actions
Wrong HTTP method Match methods=['get'] or ['post'] to how you call the endpoint
Permissions not enforced Use permission_classes=[] on the custom action

Best Practices

  • ✅ Keep your custom action names RESTful (publish, approve, reset_password)

  • ✅ Document the custom routes clearly for front-end teams

  • ✅ Use detail=False for collection-level actions (e.g., /books/archive_all/)

  • ✅ Don’t abuse @action to replace good model/view logic


✅ What’s Next?

You can explore:

  • @action(detail=False) for collection-level actions

  • Using throttle_classes for rate-limiting custom actions

  • Testing custom actions with APIClient in DRF tests