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()],
    ),
)
Any combination of UIs can be served.#
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()],
    ),
)
Any combination of UIs can be served.#
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 is text/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 the version to construct the URL to retrieve the bundle from unpkg, e.g., https://unpkg.com/rapidoc@<version>/dist/rapidoc-min.js

  • js_url: The URL to the JS bundle. If provided, this will override the version option.

  • css_url: The URL to the CSS bundle. If provided, this will override the version 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 from openapi_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.