Search code examples
pythonfastapiswagger-ui

Add a custom javascript to the FastAPI Swagger UI docs webpage in Python


I want to load my custom javascript file or code to the FastAPI Swagger UI webpage, to add some dynamic interaction when I create a FastAPI object.

For example, in Swagger UI on docs webpage I would like to

<script src="custom_script.js"></script> 

or

<script> alert('worked!') </script>

I tried:

api = FastAPI(docs_url=None)

api.mount("/static", StaticFiles(directory="static"), name="static")

@api.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=api.openapi_url,
        title=api.title + " - Swagger UI",
        oauth2_redirect_url=api.swagger_ui_oauth2_redirect_url,
        swagger_js_url="/static/sample.js",
        swagger_css_url="/static/sample.css",
    )

but it is not working. Is there a way just to insert my custom javascript code on docs webpage of FastAPI Swagger UI with Python ?


Solution

  • If you take a look at the get_swagger_ui_html function that is imported from fastapi.openapi.docs, you will see that the HTML for the docs page is constructed manually via string interpolation/concatenation. It would be trivial to modify this function to include an additional script element, as shown below:

    # custom_swagger.py
    
    import json
    from typing import Any, Dict, Optional
    
    from fastapi.encoders import jsonable_encoder
    from fastapi.openapi.docs import swagger_ui_default_parameters
    from starlette.responses import HTMLResponse
    
    def get_swagger_ui_html(
        *,
        openapi_url: str,
        title: str,
        swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js",
        swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css",
        swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
        oauth2_redirect_url: Optional[str] = None,
        init_oauth: Optional[Dict[str, Any]] = None,
        swagger_ui_parameters: Optional[Dict[str, Any]] = None,
        custom_js_url: Optional[str] = None,
    ) -> HTMLResponse:
        current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
        if swagger_ui_parameters:
            current_swagger_ui_parameters.update(swagger_ui_parameters)
    
        html = f"""
        <!DOCTYPE html>
        <html>
        <head>
        <link type="text/css" rel="stylesheet" href="{swagger_css_url}">
        <link rel="shortcut icon" href="{swagger_favicon_url}">
        <title>{title}</title>
        </head>
        <body>
        <div id="swagger-ui">
        </div>
        """
        
        if custom_js_url:
            html += f"""
            <script src="{custom_js_url}"></script>
            """
    
        html += f"""
        <script src="{swagger_js_url}"></script>
        <!-- `SwaggerUIBundle` is now available on the page -->
        <script>
        const ui = SwaggerUIBundle({{
            url: '{openapi_url}',
        """
    
        for key, value in current_swagger_ui_parameters.items():
            html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n"
    
        if oauth2_redirect_url:
            html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
    
        html += """
        presets: [
            SwaggerUIBundle.presets.apis,
            SwaggerUIBundle.SwaggerUIStandalonePreset
            ],
        })"""
    
        if init_oauth:
            html += f"""
            ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))})
            """
    
        html += """
        </script>
        </body>
        </html>
        """
        return HTMLResponse(html)
    

    A new, optional parameter named custom_js_url is added:

        custom_js_url: Optional[str] = None,
    

    If a value is provided for this parameter, a script element is inserted into the DOM directly before the script element for swagger_js_url (this is an arbitrary choice, you can change the location of the custom script element based on your needs).

        if custom_js_url:
            html += f"""
            <script src="{custom_js_url}"></script>
            """
    

    If no value is provided, the HTML produced is the same as the original function.

    Remember to update your import statements for get_swagger_ui_html and update your function for the /docs endpoint as shown below:

    from fastapi import FastAPI
    from fastapi.staticfiles import StaticFiles
    from fastapi.openapi.docs import (
        get_redoc_html,
        get_swagger_ui_oauth2_redirect_html,
    )
    from custom_swagger import get_swagger_ui_html
    import os
    
    app = FastAPI(docs_url=None) 
    path_to_static = os.path.join(os.path.dirname(__file__), 'static')
    app.mount("/static", StaticFiles(directory=path_to_static), name="static")
    
    @app.get("/docs", include_in_schema=False)
    async def custom_swagger_ui_html():
        return get_swagger_ui_html(
            openapi_url=app.openapi_url,
            title="My API",
            oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
            swagger_js_url="/static/swagger-ui-bundle.js",
            swagger_css_url="/static/swagger-ui.css",
            # swagger_favicon_url="/static/favicon-32x32.png",
            custom_js_url="/static/custom_script.js",
        )
    

    This is still a pretty hacky solution, but I think it is much cleaner and more maintainable than putting a bunch of custom javascript inside the swagger-ui-bundle.js file.