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:
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:
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
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:
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:
@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:
@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
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:
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:
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:
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:
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:
- Well-structured documentation with tags
- Detailed response examples and status codes
- Field-level examples through Pydantic models
- Query parameter documentation
- 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
- Take an existing FastAPI application and add comprehensive documentation with at least 3 different tags and custom descriptions.
- Create a custom Swagger UI page with a different theme or styling.
- Document a complex API endpoint with multiple possible response codes and examples for each.
- Create a Pydantic model with detailed field descriptions and examples for use in your API.
- 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! :)