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
Iteminstead ofItem[] - 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 --validateLook 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
| Scenario | Pattern | Why |
|---|---|---|
| Large lists (users, orders) | pagination_class (default) | Performance, frontend pagination UI |
| Small fixed lists (commands, statuses) | pagination_class=None | No pagination needed |
| Dropdown options | pagination_class=None | All items loaded at once |
| Dashboard cards | pagination_class=None | Fixed number of items |
| Search results | pagination_class (default) | Potentially large result set |
Next Steps
- DRF ViewSets & Serializers — General ViewSet patterns
- Nested Serializers — Handling
allOfand nested types - Pagination — Pagination patterns for ViewSet actions