Skip to main content

FastAPI Documentation Customization

Introduction

FastAPI automatically generates interactive documentation for your APIs, but the default presentation might not always suit your project's needs. Customizing your API documentation makes it more professional, easier to navigate, and more useful for developers consuming your API. In this guide, we'll explore various ways to customize FastAPI's documentation to enhance its appearance and functionality.

Basic Documentation Customization

Customizing the API Title and Description

By default, FastAPI uses a generic title and description for your API. Let's make this more specific to your application:

python
from fastapi import FastAPI

app = FastAPI(
title="Recipe Manager API",
description="API for managing cooking recipes with ingredients, instructions, and categories",
version="1.0.0",
)

@app.get("/")
def read_root():
return {"Hello": "World"}

This will update the title and description in both the Swagger UI (/docs) and ReDoc (/redoc) interfaces.

Adding API Version Information

Adding version information helps API consumers know which version they're working with:

python
app = FastAPI(
title="Recipe Manager API",
description="API for managing cooking recipes with ingredients, instructions, and categories",
version="1.0.0",
terms_of_service="http://example.com/terms/",
contact={
"name": "API Support Team",
"url": "http://example.com/support",
"email": "[email protected]",
},
license_info={
"name": "MIT License",
"url": "https://opensource.org/licenses/MIT",
},
)

Organizing API Endpoints with Tags

Tags help organize related endpoints into groups, making the documentation easier to navigate.

Basic Tagging

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/", tags=["Users"])
def get_users():
return [{"name": "John"}, {"name": "Jane"}]

@app.post("/users/", tags=["Users"])
def create_user(name: str):
return {"message": f"User {name} created"}

@app.get("/recipes/", tags=["Recipes"])
def get_recipes():
return [{"title": "Pancakes"}, {"title": "Pasta"}]

This organizes your API endpoints into "Users" and "Recipes" sections in the documentation.

Tag Metadata

You can add descriptions and additional metadata to your tags:

python
from fastapi import FastAPI

app = FastAPI()

tags_metadata = [
{
"name": "Users",
"description": "Operations related to user management",
"externalDocs": {
"description": "User management docs",
"url": "https://example.com/users",
},
},
{
"name": "Recipes",
"description": "Operations related to recipes",
},
]

app = FastAPI(openapi_tags=tags_metadata)

@app.get("/users/", tags=["Users"])
def get_users():
return [{"name": "John"}, {"name": "Jane"}]

@app.get("/recipes/", tags=["Recipes"])
def get_recipes():
return [{"title": "Pancakes"}, {"title": "Pasta"}]

Customizing Operation Descriptions

Adding Documentation to Endpoints

You can add detailed documentation to your endpoints using docstrings:

python
@app.get("/recipes/", tags=["Recipes"])
def get_recipes():
"""
Retrieve all recipes.

Returns a list of all available recipes with their titles.
"""
return [{"title": "Pancakes"}, {"title": "Pasta"}]

Using the summary and description Parameters

For more control, use the summary and description parameters:

python
@app.get(
"/recipes/{recipe_id}",
tags=["Recipes"],
summary="Get a specific recipe",
description="Retrieve a single recipe by its ID, including all ingredients and preparation steps",
)
def get_recipe(recipe_id: int):
return {"title": "Pancakes", "id": recipe_id}

Response Descriptions and Examples

Documenting Response Models

python
from pydantic import BaseModel
from typing import List
from fastapi import FastAPI, status

app = FastAPI()

class Recipe(BaseModel):
id: int
title: str
ingredients: List[str]
instructions: str

class Config:
schema_extra = {
"example": {
"id": 1,
"title": "Chocolate Cake",
"ingredients": ["Flour", "Sugar", "Cocoa Powder", "Eggs"],
"instructions": "Mix dry ingredients. Add wet ingredients. Bake at 350°F for 30 minutes."
}
}

@app.get(
"/recipes/{recipe_id}",
response_model=Recipe,
responses={
200: {
"description": "Recipe retrieved successfully",
},
404: {
"description": "Recipe not found",
"content": {
"application/json": {
"example": {"detail": "Recipe with ID 123 not found"}
}
}
}
}
)
def get_recipe(recipe_id: int):
# Logic to retrieve recipe
return {
"id": recipe_id,
"title": "Chocolate Cake",
"ingredients": ["Flour", "Sugar", "Cocoa Powder", "Eggs"],
"instructions": "Mix dry ingredients. Add wet ingredients. Bake at 350°F for 30 minutes."
}

Customizing the Documentation UI

Changing the Documentation URL

By default, the Swagger UI is available at /docs and ReDoc at /redoc. You can customize these URLs:

python
from fastapi import FastAPI

app = FastAPI(
docs_url="/api/documentation",
redoc_url="/api/redoc",
)

Disabling Documentation

If you want to disable Swagger UI or ReDoc in production:

python
app = FastAPI(
docs_url=None, # Disable Swagger UI
redoc_url=None # Disable ReDoc
)

Customizing Swagger UI

You can customize the Swagger UI appearance with additional JavaScript and CSS:

python
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse

app = FastAPI(docs_url=None) # Disable automatic docs

@app.get("/custom-docs", include_in_schema=False)
async def custom_swagger_ui_html(req: Request):
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=app.title + " - API Documentation",
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
swagger_js_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js",
swagger_css_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css",
swagger_favicon_url="/static/favicon.png",
swagger_ui_parameters={"defaultModelsExpandDepth": -1}, # Hide schemas section by default
)

Real-World Application Example

Let's create a more comprehensive example that demonstrates how documentation customization makes a real API more usable:

python
from fastapi import FastAPI, HTTPException, Query, Path, Body, status
from pydantic import BaseModel, Field
from typing import List, Optional
from enum import Enum

class DifficultyLevel(str, Enum):
easy = "easy"
medium = "medium"
hard = "hard"

class RecipeBase(BaseModel):
title: str = Field(..., example="Spaghetti Carbonara")
description: str = Field(..., example="A classic Italian pasta dish")
difficulty: DifficultyLevel = Field(default=DifficultyLevel.medium,
example="medium")
cooking_time_minutes: int = Field(..., example=30, gt=0)

class RecipeInput(RecipeBase):
ingredients: List[str] = Field(...,
example=["Pasta", "Eggs", "Pancetta", "Parmesan"])
instructions: str = Field(...,
example="Cook pasta. Mix eggs and cheese. Combine.")

class RecipeOutput(RecipeInput):
id: int = Field(..., example=1)
rating: Optional[float] = Field(None, example=4.5)

tags_metadata = [
{
"name": "Recipes",
"description": "Manage cooking recipes",
},
{
"name": "Users",
"description": "User operations",
},
]

app = FastAPI(
title="Culinary API",
description="An API for managing recipes and cooking instructions",
version="2.0.0",
openapi_tags=tags_metadata,
contact={
"name": "Culinary API Team",
"email": "[email protected]",
"url": "http://culinaryapi.com/contact",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)

# In-memory database for demo
recipes_db = [
{
"id": 1,
"title": "Spaghetti Carbonara",
"description": "A classic Italian pasta dish",
"difficulty": "medium",
"cooking_time_minutes": 30,
"ingredients": ["Pasta", "Eggs", "Pancetta", "Parmesan"],
"instructions": "Cook pasta. Mix eggs and cheese. Combine.",
"rating": 4.7
}
]

@app.get(
"/recipes/",
response_model=List[RecipeOutput],
tags=["Recipes"],
summary="Get all recipes",
description="Retrieve a list of all available recipes with their details"
)
def get_all_recipes(
skip: int = Query(0, description="Number of recipes to skip"),
limit: int = Query(10, description="Maximum number of recipes to return")
):
"""
Fetch all recipes in the database with pagination support.

- **skip**: Number of recipes to skip (for pagination)
- **limit**: Maximum number of recipes to return
"""
return recipes_db[skip:skip+limit]

@app.get(
"/recipes/{recipe_id}",
response_model=RecipeOutput,
tags=["Recipes"],
summary="Get a recipe by ID",
responses={
200: {
"description": "Recipe found",
"content": {
"application/json": {
"example": {
"id": 1,
"title": "Spaghetti Carbonara",
"description": "A classic Italian pasta dish",
"difficulty": "medium",
"cooking_time_minutes": 30,
"ingredients": ["Pasta", "Eggs", "Pancetta", "Parmesan"],
"instructions": "Cook pasta. Mix eggs and cheese. Combine.",
"rating": 4.7
}
}
}
},
404: {
"description": "Recipe not found",
"content": {
"application/json": {
"example": {"detail": "Recipe with ID 999 not found"}
}
}
}
}
)
def get_recipe(
recipe_id: int = Path(..., description="The ID of the recipe to retrieve", gt=0)
):
"""
Get a specific recipe by its unique ID.

If the recipe doesn't exist, a 404 error is returned.
"""
for recipe in recipes_db:
if recipe["id"] == recipe_id:
return recipe
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Recipe with ID {recipe_id} not found"
)

@app.post(
"/recipes/",
response_model=RecipeOutput,
status_code=status.HTTP_201_CREATED,
tags=["Recipes"],
summary="Create a new recipe",
description="Add a new recipe to the database with all details"
)
def create_recipe(recipe: RecipeInput = Body(..., description="Recipe data to add")):
"""
Create a new recipe with the provided details.

Returns the created recipe with its assigned ID.
"""
new_id = max([r["id"] for r in recipes_db], default=0) + 1
new_recipe = recipe.dict()
new_recipe["id"] = new_id
new_recipe["rating"] = None
recipes_db.append(new_recipe)
return new_recipe

This example shows:

  1. Well-structured documentation with tags
  2. Detailed response examples and status codes
  3. Field-level examples through Pydantic models
  4. Query parameter documentation
  5. Comprehensive docstrings and descriptions

Summary

Customizing FastAPI documentation transforms a basic API into one that's more professional and easier to use. We've covered:

  • Basic FastAPI documentation customization with titles and descriptions
  • Organizing endpoints using tags and metadata
  • Documenting endpoint operations with detailed descriptions
  • Enhancing response models with examples
  • Customizing the documentation UI itself
  • A comprehensive real-world example showing all these techniques in action

By implementing these customization techniques, you make your API much more developer-friendly and ensure that users can quickly understand how to interact with your endpoints.

Additional Resources

Exercises

  1. Take an existing FastAPI application and add comprehensive documentation with at least 3 different tags and custom descriptions.
  2. Create a custom Swagger UI page with a different theme or styling.
  3. Document a complex API endpoint with multiple possible response codes and examples for each.
  4. Create a Pydantic model with detailed field descriptions and examples for use in your API.
  5. Implement a versioned API with different documentation for each version.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)