Published Sep 16, 2025 ⦁ Updated Oct 11, 2026 ⦁ 14 min read
Django Swagger: Add OpenAPI Docs with drf-spectacular

Django Swagger: Add OpenAPI Docs with drf-spectacular

To add Swagger to a Django project in 2026, install drf-spectacular if you use Django REST Framework (DRF). It generates an OpenAPI 3 schema from your views and serializers and serves Swagger UI and ReDoc pages, in about ten lines of settings and URLs. DRF's own docs recommend it: the built-in OpenAPI generator is deprecated "in favor of third-party packages", with drf-spectacular named as the replacement. If you are not on DRF, Django Ninja gives you Swagger UI out of the box.

Swagger, OpenAPI, and the Django packages

"Swagger" is the old name of the specification; since version 3 it is called the OpenAPI Specification. Swagger UI is the interactive web page that renders an OpenAPI file and lets you click "Try it out" to send requests. A Django "swagger" setup therefore has two parts: something that generates the OpenAPI schema from your code, and a page that renders it.

Package Schema format Status (October 2026) Use it when
drf-spectacular OpenAPI 3.0.3 / 3.1 / 3.2 Actively maintained, 0.30.0 (July 2026), recommended by DRF Any new or existing DRF project
drf-yasg Swagger / OpenAPI 2.0 Maintained (1.21.18, October 2026), no OpenAPI 3 planned Legacy projects already built on it
django-rest-swagger Swagger 2.0 Abandoned years ago Never; migrate off it
Django Ninja OpenAPI, from type hints Actively maintained, 1.7.1 New APIs not built on DRF

drf-yasg's own README says it is "unlikely to soon, if ever, get support for OpenAPI 3.0" and points new projects to drf-spectacular. OpenAPI 2.0 lacks things like oneOf, multiple servers, and proper request body components, and many code generators now expect 3.x. So drf-spectacular vs drf-yasg is not a close call for new code.

Set up Swagger UI in Django REST Framework

These steps follow the drf-spectacular README.

  1. Install it:
pip install drf-spectacular
  1. Register the app and the schema class in settings.py:
INSTALLED_APPS = [
    # ...
    "rest_framework",
    "drf_spectacular",
]

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

SPECTACULAR_SETTINGS = {
    "TITLE": "Shop API",
    "DESCRIPTION": "Products, carts and orders",
    "VERSION": "1.0.0",
    "SERVE_INCLUDE_SCHEMA": False,  # hide the schema endpoint from the docs themselves
}
  1. Add the schema and UI routes to urls.py:
from django.urls import path
from drf_spectacular.views import (
    SpectacularAPIView,
    SpectacularRedocView,
    SpectacularSwaggerView,
)

urlpatterns = [
    # ... your API routes
    path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
    path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
    path("api/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
]
  1. Run python manage.py runserver and open http://127.0.0.1:8000/api/docs/. That is your Django Swagger URL. The raw schema is at /api/schema/ (YAML by default; add ?format=json for JSON).

  2. Optionally export the schema to a file in CI and fail the build on warnings:

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

Committing schema.yml makes API changes visible in code review, and the file can feed client generators (openapi-generator, orval, openapi-typescript).

By default the Swagger UI and ReDoc assets load from a CDN. For air-gapped or strict-CSP environments, install drf-spectacular[sidecar], add drf_spectacular_sidecar to INSTALLED_APPS, and set "SWAGGER_UI_DIST": "SIDECAR", "SWAGGER_UI_FAVICON_HREF": "SIDECAR" and "REDOC_DIST": "SIDECAR" so the files are served by Django's static files.

A worked example: documenting a product endpoint

drf-spectacular reads ModelViewSets and GenericAPIViews with a serializer_class automatically. You only annotate what it cannot infer: query parameters you read by hand, custom responses, and plain APIViews.

# shop/serializers.py
from rest_framework import serializers
from .models import Product

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ["id", "name", "price", "in_stock"]
# shop/views.py
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer

@extend_schema_view(
    list=extend_schema(
        summary="List products",
        parameters=[
            OpenApiParameter("in_stock", bool, description="Only products in stock"),
        ],
    ),
    retrieve=extend_schema(summary="Get one product"),
)
class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        if self.request.query_params.get("in_stock") == "true":
            qs = qs.filter(in_stock=True)
        return qs
# urls.py
from rest_framework.routers import DefaultRouter
from shop.views import ProductViewSet

router = DefaultRouter()
router.register("products", ProductViewSet)
urlpatterns += router.urls

Reload /api/docs/: you get GET/POST /products/ and GET/PUT/PATCH/DELETE /products/{id}/, the Product schema with field types, the in_stock query parameter, and a working "Try it out" button.

For a plain APIView or @api_view function, declare the shapes yourself, otherwise drf-spectacular warns that it cannot guess the serializer:

from drf_spectacular.utils import extend_schema
from rest_framework.views import APIView
from rest_framework.response import Response

class StatsView(APIView):
    @extend_schema(responses={200: StatsSerializer}, summary="Shop statistics")
    def get(self, request):
        return Response(StatsSerializer(compute_stats()).data)

Authentication and who can see the docs

  • Authorize button: drf-spectacular reads your DEFAULT_AUTHENTICATION_CLASSES (session, token, basic, and common JWT packages) and adds the matching security schemes, so Swagger UI shows an "Authorize" button. Add "SWAGGER_UI_SETTINGS": {"persistAuthorization": True} to SPECTACULAR_SETTINGS to keep the token across reloads.
  • Restrict access in production: by default anyone who can reach /api/schema/ can read your full API surface. Set "SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"], or only add the docs routes when DEBUG is true. Public APIs can leave them open on purpose.

Django Ninja: Swagger without DRF

If you are starting fresh and like FastAPI-style type hints, Django Ninja builds the schema from function signatures and serves Swagger UI at /api/docs with no extra package:

from ninja import NinjaAPI

api = NinjaAPI()

@api.get("/add")
def add(request, a: int, b: int):
    return {"result": a + b}

# urls.py: path("api/", api.urls)

The trade-off: Ninja is not DRF, so DRF serializers, permissions, and third-party DRF packages don't plug in. For an existing DRF codebase, stay with drf-spectacular.

Common problems

  • "could not resolve serializer" warnings: the view has no serializer_class or overrides get_serializer_class() in a way the generator can't follow. Add @extend_schema(request=..., responses=...).
  • Blank Swagger page: usually a Content Security Policy blocking the CDN. Use the sidecar package.
  • Migrating from drf-yasg: replace swagger_auto_schema with extend_schema (the argument names differ: responses stays, manual_parameters becomes parameters with OpenApiParameter), remove the get_schema_view routes, and compare the old and new schemas before deleting drf-yasg.

Good generated docs still need human-written summaries and examples; see our REST API documentation best practices. For more tools in this space, browse API tools and documentation tools.

FAQ

Can I use Swagger with Django?

Yes. With Django REST Framework, install drf-spectacular and add SpectacularSwaggerView to your URLs; without DRF, Django Ninja serves Swagger UI automatically. Both generate an OpenAPI schema from your code, so the docs stay in sync with the endpoints.

What is the difference between Django REST Framework and Swagger?

DRF is the library you build the API with (serializers, views, auth). Swagger, now OpenAPI, is a format for describing an API, and Swagger UI is a page that renders that description. drf-spectacular is the bridge that turns a DRF API into an OpenAPI document.

Should I use drf-yasg or drf-spectacular?

drf-spectacular for anything new. It produces OpenAPI 3, is the option DRF's docs recommend, and drf-yasg says it does not plan OpenAPI 3 support. Keep drf-yasg only on a legacy project where migrating isn't worth it yet.

What is the Swagger URL in Django?

There is no fixed one; it is whatever path you map SpectacularSwaggerView to. Common choices are /api/docs/ or /api/schema/swagger-ui/ (the drf-spectacular README uses the latter). In Django Ninja it is /api/docs by default.

How do I generate a swagger.json or schema.yml file from Django?

Run python manage.py spectacular --file schema.yml (or --format openapi-json --file schema.json). Add --validate to check the output against the OpenAPI specification.