Backend / Web frameworks / Django / DRF / 16_schema_openapi.md

DRF Schema and OpenAPI

Updated 5 interview angles 4 min read source
On this page13
  1. The landscape
  2. Setup
  3. What it picks up automatically
  4. Decorating views for accuracy
  5. Documenting query params
  6. Multiple serializers per action
  7. Generating a static schema (for CI / SDK gen)
  8. Auth in the schema
  9. Common warnings and how to fix them
  10. Versioning + schemas
  11. Why bother with a schema at all?
  12. Pitfalls
  13. Interview angle

DRF Schema and OpenAPI

Schemas describe your API in a machine-readable format (OpenAPI 3 / Swagger). They power: client SDK generation, interactive docs (Swagger UI / Redoc / Stoplight), contract testing, and frontend type generation.

DRF has built-in schema generation, but it’s limited. The standard for production is drf-spectacular.

The landscape

Option Status Use
coreapi (built into DRF) Deprecated as of DRF 3.10+ Don’t use for new work
Built-in AutoSchema (OpenAPI 3) Maintained but minimal Quick docs, no fine control
drf-yasg Mature, OpenAPI 2 (Swagger) — slower release pace Legacy projects
drf-spectacular OpenAPI 3, actively maintained Default choice for new DRF projects

The rest of this file uses drf-spectacular.

Setup

bash
pip install drf-spectacular
python
# settings.py
INSTALLED_APPS += ["drf_spectacular"]

REST_FRAMEWORK = {
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}

SPECTACULAR_SETTINGS = {
    "TITLE": "My API",
    "DESCRIPTION": "...",
    "VERSION": "1.0.0",
    # don't expose the raw schema endpoint inside docs
    "SERVE_INCLUDE_SCHEMA": False,
}
python
# urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView

urlpatterns += [
    path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
    path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema")),
    path("api/redoc/", SpectacularRedocView.as_view(url_name="schema")),
]

Now /api/schema/ returns OpenAPI YAML/JSON, /api/docs/ is interactive Swagger UI, /api/redoc/ is Redoc.

What it picks up automatically

For a ModelViewSet:

  • All standard CRUD operations with their HTTP methods, paths, path parameters.
  • Request/response schemas from serializer_class.
  • Field types, required/optional, defaults, descriptions from serializer fields’ help_text.
  • Authentication classes from authentication_classes.
  • Pagination wrapper from the configured pagination class.

What it can’t infer:

  • Custom @action request/response shape if you don’t return a serializer.
  • to_representation overrides that change the shape.
  • Conditional response based on permissions/version.
  • Examples.

Decorating views for accuracy

python
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiExample, OpenApiResponse

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

    @extend_schema(
        summary="Publish a book",
        description="Marks the book as published and emits a `book.published` event.",
        request=PublishRequestSerializer,
        responses={
            200: BookSerializer,
            409: OpenApiResponse(description="Already published"),
        },
        examples=[OpenApiExample("Default", value={"send_notification": True})],
    )
    @action(detail=True, methods=["post"])
    def publish(self, request, pk=None):
        ...

@extend_schema is the main lever. Use it on:

  • Every @action (the auto-detection often guesses wrong).
  • Endpoints whose response shape isn’t a serializer (raw dicts, file downloads).
  • Endpoints with multiple possible status codes you want documented (404, 409, 422).

Documenting query params

For viewset filters that aren’t picked up:

python
@extend_schema(
    parameters=[
        OpenApiParameter("q", str, description="Search query"),
        OpenApiParameter("category", int, OpenApiParameter.QUERY, required=False),
    ],
)
def list(self, request, *args, **kwargs):
    ...

Often unnecessary if you use DjangoFilterBackend + SearchFilter + OrderingFilterdrf-spectacular introspects them.

Multiple serializers per action

python
@extend_schema_view(
    list=extend_schema(responses={200: BookListSerializer(many=True)}),
    create=extend_schema(request=BookCreateSerializer, responses={201: BookDetailSerializer}),
    retrieve=extend_schema(responses={200: BookDetailSerializer}),
)
class BookViewSet(viewsets.ModelViewSet):
    ...

extend_schema_view wraps an entire viewset; extend_schema decorates a single method.

Generating a static schema (for CI / SDK gen)

bash
python manage.py spectacular --file schema.yml --validate

--validate runs OpenAPI 3 validation. Check the file into git or publish as a build artifact. Frontend teams can run openapi-typescript schema.yml -o api.ts to get typed clients.

Auth in the schema

python
SPECTACULAR_SETTINGS["SECURITY"] = [{"jwtAuth": []}]
SPECTACULAR_SETTINGS["COMPONENTS"] = {
    "securitySchemes": {
        "jwtAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"},
    },
}

This adds the “Authorize” button in Swagger UI so testers can paste a JWT.

Common warnings and how to fix them

drf-spectacular prints warnings on schema generation. Common ones:

Warning Fix
unable to guess serializer for an @action Add @extend_schema(request=..., responses=...)
enum field has no choices Use serializers.ChoiceField with explicit choices
unhandled lookup_field type for non-int pks Set lookup_field and OpenApiParameter accordingly
view is not a subclass of GenericAPIView Add @extend_schema per method on plain APIView

Run python manage.py spectacular --fail-on-warn in CI to block warning-introducing PRs.

Versioning + schemas

python
path("api/v1/schema/", SpectacularAPIView.as_view(api_version="v1")),
path("api/v2/schema/", SpectacularAPIView.as_view(api_version="v2")),

See DRF Versioning.

Why bother with a schema at all?

  • Frontend types — generate TypeScript interfaces; refactors break compile, not runtime.
  • Contract tests — Schemathesis / Dredd hammer your API against the schema and catch drift.
  • Docs — Swagger/Redoc are free, always-up-to-date, interactive (try-it-out works against the live API).
  • SDK generationopenapi-generator produces clients in 30+ languages.
  • API gateway integration — AWS API Gateway / Kong import OpenAPI directly.

Pitfalls

  • Trusting auto-detection blindly. It’s wrong on @actions and to_representation overrides; always spot-check the rendered schema.
  • Generating schema in dev only. If schema generation throws warnings/errors only in prod settings (e.g. due to env-conditional URL config), schema CI fails surprisingly. Run spectacular in CI with prod-like settings.
  • coreapi and drf-yasg and drf-spectacular all in the same project. Pick one. Mixing them wastes setup time and produces conflicting docs.
  • Auto-generated schema for serializers with SerializerMethodField — type defaults to str. Use @extend_schema_field(int) to fix.

Interview angle 5

  • “How would you generate API docs for a DRF project today?”drf-spectacular, expose /api/schema/ + /api/docs/. Skip coreapi (deprecated) and drf-yasg (OpenAPI 2 only).
  • “Auto-generation works for ModelViewSet — when does it break?”@actions without explicit request/responses, plain APIView without @extend_schema, to_representation reshaping, SerializerMethodField (types as str), endpoints returning raw dicts.
  • “How do you keep the schema honest?”python manage.py spectacular --fail-on-warn in CI; contract testing with Schemathesis; check the schema file into git so PRs reveal API surface changes.
  • “Why generate a schema at all?” — typed frontend clients, contract testing, always-up-to-date docs, SDK generation, API gateway import.
  • “How do you document JWT/OAuth in the schema?”SPECTACULAR_SETTINGS["SECURITY"] and a securitySchemes component; renders an “Authorize” button in Swagger UI.