OpenAPI UI Plugins#
New in version 2.8.0.
OpenAPI UI Plugins are designed to allow easy integration with your OpenAPI UI framework of choice. These plugins facilitate the creation of interactive, user-friendly API documentation, making it easier for developers and end-users to understand and interact with your API.
Litestar maintains and ships with UI plugins for a range of popular popular OpenAPI documentation tools:
Each plugin is easily configurable, allowing developers to customize aspects like version, paths, CSS and JavaScript resources.
Using OpenAPI UI Plugins#
Using OpenAPI UI Plugins is as simple as importing the plugin, instantiating it, and adding it to the OpenAPIConfig.
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import ScalarRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of Litestar with Scalar OpenAPI docs",
version="0.0.1",
render_plugins=[ScalarRenderPlugin()],
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import ScalarRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of Litestar with Scalar OpenAPI docs",
version="0.0.1",
render_plugins=[ScalarRenderPlugin()],
),
)
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import RapidocRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[RapidocRenderPlugin()],
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import RapidocRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[RapidocRenderPlugin()],
),
)
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import RedocRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[RedocRenderPlugin()],
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import RedocRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[RedocRenderPlugin()],
),
)
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import StoplightRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[StoplightRenderPlugin()],
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import StoplightRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[StoplightRenderPlugin()],
),
)
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import SwaggerRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[SwaggerRenderPlugin()],
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import SwaggerRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[SwaggerRenderPlugin()],
),
)
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import YamlRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[YamlRenderPlugin()],
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import YamlRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[YamlRenderPlugin()],
),
)
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import RapidocRenderPlugin, SwaggerRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[RapidocRenderPlugin(), SwaggerRenderPlugin()],
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import RapidocRenderPlugin, SwaggerRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[RapidocRenderPlugin(), SwaggerRenderPlugin()],
),
)
Configuring OpenAPI UI Plugins#
Each plugin can be tailored to meet your unique requirements by passing options at instantiation. For full details on each plugin’s options, see the API Reference.
All plugins support:
path
: Each plugin has its own default, e.g.,/rapidoc
for RapiDoc. This can be overridden to serve the UI at a different path.media_type
: The default media type for the plugin, typically the default istext/html
.favicon
: A string that should be a valid<link>
tag, e.g.,<link rel="icon" href="https://example.com/favicon.ico">
.style
: A string that should be a valid<style>
tag, e.g.,<style>body { margin: 0; padding: 0; }``</style>.
Most plugins support the following additional options:
version
: The version of the UIs JS and (in some cases) CSS bundle to use. We use theversion
to construct the URL to retrieve the bundle fromunpkg
, e.g.,https://unpkg.com/rapidoc@<version>/dist/rapidoc-min.js
js_url
: The URL to the JS bundle. If provided, this will override theversion
option.css_url
: The URL to the CSS bundle. If provided, this will override theversion
option.
Here’s some example plugin configurations:
from litestar.openapi.plugins import ScalarRenderPlugin
scalar_plugin = ScalarRenderPlugin(version="1.19.5", path="/scalar")
from litestar.openapi.plugins import RapidocRenderPlugin
rapidoc_plugin = RapidocRenderPlugin(version="9.3.4", path="/rapidoc")
from litestar.openapi.plugins import RedocRenderPlugin
redoc_plugin = RedocRenderPlugin(version="next", google_fonts=True, path="/redoc")
from litestar.openapi.plugins import StoplightRenderPlugin
stoplight_plugin = StoplightRenderPlugin(version="7.7.18", path="/elements")
from litestar.openapi.plugins import SwaggerRenderPlugin
swagger_plugin = SwaggerRenderPlugin(version="5.1.3", path="/swagger")
Configuring the OpenAPI Root Path#
The OpenAPI root path is the path at which the OpenAPI representations are served. By default, this is /schema
.
This can be changed by setting the OpenAPIConfig.path
attribute.
In the following example, we configure the OpenAPI root path to be /docs
:
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
@get("/")
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="My API",
description="This is the description of my API",
version="0.1.0",
path="/docs",
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
@get("/")
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="My API",
description="This is the description of my API",
version="0.1.0",
path="/docs",
),
)
This will result in any of the OpenAPI endpoints being served at /docs
instead of /schema
, e.g.,
/docs/openapi.json
.
Backward Compatibility#
OpenAPI UI plugins are a new feature introduced in v2.8.0
.
Providing a subclass of OpenAPIController#
Deprecated since version v2.8.0.
The previous method of configuring elements such as the root path and styling was to subclass
OpenAPIController
, and set it on the OpenAPIConfig.openapi_controller
attribute. This approach is now
deprecated and slated for removal in v3.0.0
, but if you are using it, there should be no change in behavior.
To maintain backward compatibility with the previous approach, if neither the OpenAPIConfig.openapi_controller
or OpenAPIConfig.render_plugins
attributes are set, we will automatically add the plugins to respect the also
deprecated OpenAPIConfig.enabled_endpoints
attribute. By default, this will result in the following endpoints
being enabled:
/schema/openapi.json
/schema/redoc
/schema/rapidoc
/schema/elements
/schema/swagger
/schema/openapi.yml
/schema/openapi.yaml
In v3.0.0
, the OpenAPIConfig.enabled_endpoints
attribute will be removed, and only a single UI plugin will be
enabled by default, in addition to the openapi.json
endpoint which will always be enabled. Scalar
will also
become the default UI plugin in v3.0.0
.
To adopt the future behavior, explicitly set the OpenAPIConfig.render_plugins
field to an instance of
ScalarRenderPlugin
:
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of Litestar with Scalar OpenAPI docs",
version="0.0.1",
render_plugins=[ScalarRenderPlugin()],
),
)
Backward compatibility with root_schema_site
#
Litestar has always supported a root_schema_site
attribute on the OpenAPIConfig
class. This attribute
allows you to elect to serve a UI at the OpenAPI root path, e.g., by default redoc
would be served at both
/schema
and /schema/redoc
.
In v3.0.0
, the root_schema_site
attribute will be removed, and the first OpenAPIRenderPlugin
in the
OpenAPIConfig.render_plugins
list will be assigned to the /schema
endpoint.
As of v2.8.0
, if you explicitly use the new OpenAPIConfig.render_plugins
attribute, you will be
automatically opted in to the new behavior, and the root_schema_site
attribute will be ignored.
Building your own OpenAPI UI Plugin#
If Litestar does not have built-in support for your OpenAPI UI framework of choice, you can easily create your own
plugin by subclassing OpenAPIRenderPlugin
and implementing the OpenAPIRenderPlugin.render()
method.
To demonstrate building a custom plugin, we’ll look at a plugin very similar to the ScalarRenderPlugin
that is
maintained by Litestar. Here’s the finished product:
from __future__ import annotations
from collections.abc import Sequence
from typing import Any
from litestar.connection import Request
from litestar.openapi.plugins import OpenAPIRenderPlugin
class ScalarRenderPlugin(OpenAPIRenderPlugin):
def __init__(
self,
*,
version: str = "1.19.5",
js_url: str | None = None,
css_url: str | None = None,
path: str | Sequence[str] = "/scalar",
**kwargs: Any,
) -> None:
self.js_url = js_url or f"https://cdn.jsdelivr.net/npm/@scalar/api-reference@{version}"
self.css_url = css_url
super().__init__(path=path, **kwargs)
def render(self, request: Request, openapi_schema: dict[str, Any]) -> bytes:
style_sheet_link = f'<link rel="stylesheet" type="text/css" href="{self.css_url}">' if self.css_url else ""
head = f"""
<head>
<title>{openapi_schema["info"]["title"]}</title>
{self.style}
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
{self.favicon}
{style_sheet_link}
</head>
"""
body = f"""
<script
id="api-reference"
data-url="openapi.json">
</script>
<script src="{self.js_url}" crossorigin></script>
"""
return f"""
<!DOCTYPE html>
<html>
{head}
{body}
</html>
""".encode()
Class definition#
The class ScalarRenderPlugin
inherits from OpenAPIRenderPlugin
:
class ScalarRenderPlugin(OpenAPIRenderPlugin):
__init__
Constructor#
def __init__(
self,
*,
version: str = "1.19.5",
js_url: str | None = None,
css_url: str | None = None,
path: str | Sequence[str] = "/scalar",
**kwargs: Any,
) -> None:
self.js_url = js_url or f"https://cdn.jsdelivr.net/npm/@scalar/api-reference@{version}"
self.css_url = css_url
super().__init__(path=path, **kwargs)
We support configuration via the following arguments:
version
: Specifies the version of RapiDoc to use.js_url
: Custom URL to the RapiDoc JavaScript bundle.css_url
: Custom URL to the RapiDoc CSS bundle.path
: The URL path where the RapiDoc UI will be served.**kwargs
: Captures additional arguments to pass to the superclass.
And we construct a url for the Scalar JavaScript bundle if one is not provided:
self.js_url = js_url or f"https://cdn.jsdelivr.net/npm/@scalar/api-reference@{version}"
render()
#
def render(self, request: Request, openapi_schema: dict[str, Any]) -> bytes:
Finally we define the render
method, which is called by Litestar to render the UI. It receives the a
Request
object and the openapi_schema
as a dictionary.
Inside the render
method, we construct the HTML to render the UI, and return it as a string.
head
: Defines the HTML<head>
section, including the title fromopenapi_schema
, any additional styles (self.style
), the favicon and custom style sheet if one is provided:style_sheet_link = f'<link rel="stylesheet" type="text/css" href="{self.css_url}">' if self.css_url else "" head = f""" <head> <title>{openapi_schema["info"]["title"]}</title> {self.style} <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"> {self.favicon} {style_sheet_link} </head> """
body
: Constructs the HTML<body>
, including a link to the OpenAPI JSON, and the JavaScript bundle:body = f""" <script id="api-reference" data-url="openapi.json"> </script> <script src="{self.js_url}" crossorigin></script> """
Finally, returns a complete HTML document (as a byte string), combining head and body.
return f""" <!DOCTYPE html> <html> {head} {body} </html> """.encode()
Interacting with the Router
#
An instance of Router
is used to serve the OpenAPI endpoints and is made available to plugins via the
OpenAPIRenderPlugin.receive_router()
method.
This can be used for a variety of purposes, including adding additional routes to the Router
.
from __future__ import annotations
from typing import TYPE_CHECKING
from litestar import get
from litestar.enums import MediaType
from litestar.openapi.plugins import OpenAPIRenderPlugin
if TYPE_CHECKING:
from litestar.connection import Request
from litestar.router import Router
class MyOpenAPIPlugin(OpenAPIRenderPlugin):
def render(self, request: Request, openapi_schema: dict[str, str]) -> bytes:
return b"<html>My UI of Choice!</html>"
def receive_router(self, router: Router) -> None:
@get("/something", media_type=MediaType.TEXT)
def something() -> str:
return "Something"
router.register(something)
OAuth2 in Swagger UI#
When using Swagger, OAuth2 settings can be configured via
swagger_ui_init_oauth
, which can be set to
a dictionary containing the parameters described in the Swagger UI documentation for OAuth2
here.
With that, you can preset your clientId or enable PKCE support.
from typing import Dict
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import SwaggerRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> Dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[
SwaggerRenderPlugin(
init_oauth={
"clientId": "your-client-id",
"appName": "your-app-name",
"scopeSeparator": " ",
"scopes": "openid profile",
"useBasicAuthenticationWithAccessCodeGrant": True,
"usePkceWithAuthorizationCodeGrant": True,
}
)
],
),
)
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import SwaggerRenderPlugin
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello World"}
app = Litestar(
route_handlers=[hello_world],
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of litestar",
version="0.0.1",
render_plugins=[
SwaggerRenderPlugin(
init_oauth={
"clientId": "your-client-id",
"appName": "your-app-name",
"scopeSeparator": " ",
"scopes": "openid profile",
"useBasicAuthenticationWithAccessCodeGrant": True,
"usePkceWithAuthorizationCodeGrant": True,
}
)
],
),
)
Customizing the OpenAPI UI#
Style and behavior of the OpenAPI UI can be customized by overriding the default css_url
and js_url
attributes
on the render plugin class, for example:
from litestar.openapi.plugins import ScalarRenderPlugin
scalar_plugin = ScalarRenderPlugin(
js_url="https://example.com/my-custom-scalar.js",
css_url="https://example.com/my-custom-scalar.css",
path="/scalar",
)
To learn more about customizing the Scalar
UI, see the Scalar documentation.
CDN and offline file support#
Each plugin supports js_url
and css_url
attributes, which can be used to specify a custom URL to the JavaScript.
These can be used to serve the JavaScript and CSS from a CDN, or to serve the files from a local directory.