FastAPI’s OpenAPI schema is generated by default to be functional, not pretty, but you can inject custom branding and details to make your API documentation feel like a first-class citizen of your product.
Let’s say you have a simple FastAPI app:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
When you visit /docs, you see the default Swagger UI. It’s functional, but it screams "generated." We want to change that.
The magic happens through the docs_url and redoc_url parameters when you instantiate FastAPI. These are the endpoints where the OpenAPI schema is served. By default, they point to /docs and /redoc respectively. You can change these, but more importantly, you can intercept the generation of the schema.
The FastAPI constructor accepts arguments like title, description, version, and contact. These directly influence the OpenAPI schema’s info object.
app = FastAPI(
title="My Awesome API",
description="This API provides access to all the cool features of My Awesome Product.",
version="1.2.0",
contact={
"name": "Awesome Support Team",
"url": "https://myawesomeproduct.com/support",
"email": "support@myawesomeproduct.com",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)
After this change, when you visit /docs, the title, description, version, and contact information will be updated. This is the easiest way to inject basic branding.
But what if you want to go deeper? You can modify the OpenAPI schema directly using app.openapi(). This method returns the OpenAPI dictionary. You can override it with your own logic.
Here’s how you can add a custom logo and more detailed terms of service:
import json
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI(
title="My Awesome API",
description="This API provides access to all the cool features of My Awesome Product.",
version="1.2.0",
contact={
"name": "Awesome Support Team",
"url": "https://myawesomeproduct.com/support",
"email": "support@myawesomeproduct.com",
},
)
@app.get("/")
async def read_root():
return {"Hello": "World"}
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="My Awesome API",
version="1.2.0",
description="This API provides access to all the cool features of My Awesome Product.",
routes=app.routes,
tags=app.openapi_tags,
servers=app.servers,
)
# Add custom branding elements
openapi_schema["info"]["x-logo"] = {
"url": "https://myawesomeproduct.com/static/logo.png",
"backgroundColor": "#FFFFFF",
"altText": "My Awesome Product Logo"
}
openapi_schema["info"]["termsOfService"] = "https://myawesomeproduct.com/terms"
openapi_schema["info"]["summary"] = "This is a short summary of the API's purpose."
# You can also add custom extensions (x-*) for internal use or specific tools
openapi_schema["x-custom-feature-flag"] = "enabled"
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
The get_openapi function is the core utility that builds the schema. By calling it and then modifying the resulting dictionary, you gain full control. The x-logo field is a custom extension recognized by some OpenAPI UIs (like Swagger UI) to display a logo. The servers list can also be customized to show different deployment environments.
The servers list in the OpenAPI schema is a powerful way to document where your API can be accessed. You can define multiple environments.
app = FastAPI(
servers=[
{"url": "https://api.myawesomeproduct.com", "description": "Production Server"},
{"url": "https://staging-api.myawesomeproduct.com", "description": "Staging Server"},
]
)
When you visit /docs with the custom_openapi function, you’ll see your logo, terms of service, and the servers listed. This makes the documentation much more tailored to your product.
A common pitfall is forgetting to call get_openapi and then trying to modify a potentially non-existent app.openapi_schema. The pattern is to initialize app.openapi_schema by calling get_openapi once, and then overwrite app.openapi to return this cached schema. This ensures that the schema is generated only when needed and subsequent requests use the cached version for performance.
The app.openapi_tags attribute, which you can pass to get_openapi, lets you define metadata for your API tags. This allows you to add descriptions and external documentation links for each tag, organizing your API endpoints more clearly in the UI.
app.openapi_tags = [
{
"name": "items",
"description": "Operations related to items in the store.",
"externalDocs": {
"description": "Items documentation",
"url": "https://myawesomeproduct.com/docs/items",
},
},
{
"name": "users",
"description": "Operations related to users.",
},
]
By leveraging app.openapi_tags and ensuring these tags are used on your API routes (e.g., using tags=["items"] in your path operations), you can significantly enhance the discoverability and usability of your API documentation.
The next step is to explore customizing the Redoc UI specifically, which has its own configuration options.