Skip to content

Exceptions

Exceptions allow error handling to be organized cleanly in a central place within the program structure. In DRF, views automatically catch specific exceptions and convert them into standard HTTP error responses.

Built-in Exception Handling

By default, DRF handles the following exceptions natively inside your views:

  • Subclasses of APIException raised inside DRF.
  • Django's Http404 exception.
  • Django's PermissionDenied exception.

In each case, DRF intercepts the exception and returns a response with the appropriate status code, content type, and an error body containing details.

Default Error Responses

Most error responses will include a detail key in the JSON body. For example, if a client tries to use a DELETE method where it's not allowed, the response body will look like this:

json
{
  "detail": "Method 'DELETE' not allowed."
}

Validation errors (e.g., from serializers) are handled slightly differently. They return the specific field names as keys in the response. If the validation error isn't tied to a specific field, it uses the non_field_errors key (or the value set in your NON_FIELD_ERRORS_KEY setting).

json
{
  "amount": ["A valid integer is required."],
  "description": ["This field may not be blank."]
}

Custom Exception Handling

While DRF's defaults are great for most applications, you may want to centralize how errors are formatted across your entire API. For instance, you might want every single error response to include the HTTP status code, or you might need to structure errors to match a specific frontend requirement.

By defining a custom exception handler, you keep your code DRY. Instead of writing redundant try/except blocks in every single view, you handle it in one central location.

Writing a Custom Handler

A custom handler function takes two arguments:

  • exc: The exception being handled.
  • context: A dictionary containing extra context, such as the view that raised the error.

The function must return a Response object if it successfully handles the error, or None if it cannot. If it returns None, Django takes over and returns a standard HTTP 500 Server Error.

A common pattern is to call DRF's default exception handler first to get the standard response, and then modify it to suit your needs.

python
from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):
    # Call DRF's default exception handler first
    # to get the standard error response.
    response = exception_handler(exc, context)

    # If the response is not None, an error was caught.
    # We can now add extra information, like the status code.
    if response is not None:
        response.data['status_code'] = response.status_code

    return response

NOTE

The context argument is extremely useful if you need to access context['view'] to see exactly which view threw the error, or context['request'] to get request data.

Setting the Custom Handler

Once you've written your handler, you need to tell DRF to use it globally. By default, DRF uses its own built-in rest_framework.views.exception_handler. You can override this in your settings.py:

python
REST_FRAMEWORK = {
    # Default is 'rest_framework.views.exception_handler'
    'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}

TIP

Custom exception handlers are the perfect place to integrate error monitoring tools (like Sentry) or to unify error response structures so your frontend teams always know exactly what format to expect.