Skip to Content
FeaturesDRF GuideArray Responses

Array Responses

When a @action returns a list of items without pagination, the generated TypeScript client must know it’s an array — not a paginated object. This requires specific drf-spectacular configuration.

The Problem

By default, drf-spectacular inherits pagination_class from the ViewSet. When it sees many=True, it generates a paginated response schema instead of a simple array:

# ❌ What you get without proper config responses: '200': schema: $ref: '#/components/schemas/PaginatedItemList' # ✅ What you want responses: '200': schema: type: array items: $ref: '#/components/schemas/Item'

Frontend symptoms:

  • "expected object, received array" runtime error
  • TypeScript type Item instead of Item[]
  • Missing fetchers/hooks for the endpoint

The Solution

Three things must be correct:

1. Disable pagination on the action

@action( detail=False, methods=['get'], pagination_class=None # ← Prevents paginated schema ) def available_items(self, request): ...

2. Use dict format for responses

@extend_schema( responses={200: ItemSerializer(many=True)} # ← Dict format + many=True )

3. Put @extend_schema before @action

# ✅ Correct order @extend_schema(...) @action(...) def my_action(self, request): ...

Complete Pattern

from drf_spectacular.utils import extend_schema from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response class CommandViewSet(viewsets.GenericViewSet): serializer_class = CommandSerializer # Required for drf-spectacular @extend_schema( summary="List available commands", responses={200: CommandAvailableSerializer(many=True)}, tags=["commands"], ) @action( detail=False, methods=['get'], pagination_class=None, # Simple array, no pagination ) def available(self, request): commands = [ {"name": "migrate", "title": "Database Migration"}, {"name": "collectstatic", "title": "Collect Static Files"}, ] serializer = CommandAvailableSerializer(commands, many=True) return Response(serializer.data)

Common Mistakes

Missing pagination_class=None

# ❌ Pagination inherited from ViewSet @extend_schema(responses={200: ItemSerializer(many=True)}) @action(detail=False, methods=['get']) # No pagination_class=None! def items(self, request): ...

Result: Schema wraps the array in {count, next, previous, results}.

Bare serializer format

# ❌ Not using dict format @extend_schema( responses=ItemSerializer(many=True) # Should be {200: ItemSerializer(many=True)} )

Result: TypeScript generator creates Promise<any> instead of Promise<Item[]>.

Wrong decorator order

# ❌ @action before @extend_schema @action(detail=False, methods=['get'], pagination_class=None) @extend_schema(responses={200: ItemSerializer(many=True)}) def items(self, request): ...

Result: Wrong operationId, wrong tags, possibly wrong serializer reference.

Verification

Check the OpenAPI schema

python manage.py spectacular --file schema.yaml --validate

Look for correct array format:

/api/commands/available/: get: responses: '200': content: application/json: schema: type: array # ✅ Simple array items: $ref: '#/components/schemas/CommandAvailable'

Check generated TypeScript

After generation, the fetcher should return an array type:

// ✅ Correct export async function getCommandsAvailable(): Promise<CommandAvailable[]> { ... } export function useCommandsAvailable(): SWRResponse<CommandAvailable[]> { ... } // ❌ Wrong (paginated wrapper) export async function getCommandsAvailable(): Promise<PaginatedCommandAvailableList> { ... }

Test API response

curl http://localhost:8000/api/commands/available/

Should return a bare array:

[ {"name": "migrate", "title": "Database Migration"}, {"name": "collectstatic", "title": "Collect Static Files"} ]

When to Use Pagination vs Array

ScenarioPatternWhy
Large lists (users, orders)pagination_class (default)Performance, frontend pagination UI
Small fixed lists (commands, statuses)pagination_class=NoneNo pagination needed
Dropdown optionspagination_class=NoneAll items loaded at once
Dashboard cardspagination_class=NoneFixed number of items
Search resultspagination_class (default)Potentially large result set

Next Steps

Last updated on