Plug points

Swagger UI exposes most of its internal logic through the plugin system.

Often, it is beneficial to override the core internals to achieve custom behavior.

Note: Semantic Versioning

Swagger UI's internal APIs are not part of our public contract, which means that they can change without the major version change.

If your custom plugins wrap, extend, override, or consume any internal core APIs, we recommend specifying a specific minor version of Swagger UI to use in your application, because they will not change between patch versions.

If you're installing Swagger UI via NPM, for example, you can do this by using a tilde:

{
  "dependencies": {
    "swagger-ui": "~3.11.0"
  }
}

fn.opsFilter

When using the filter option, tag names will be filtered by the user-provided value. If you'd like to customize this behavior, you can override the default opsFilter function.

For example, you can implement a multiple-phrase filter:

const MultiplePhraseFilterPlugin = function() {
  return {
    fn: {
      opsFilter: (taggedOps, phrase) => {
        const phrases = phrase.split(", ")

        return taggedOps.filter((val, key) => {
          return phrases.some(item => key.indexOf(item) > -1)
        })
      }
    }
  }
}

JSON Schema components

In swagger there are so called JSON Schema components. These are used to render inputs for parameters and components of request bodies with application/x-www-form-urlencoded or multipart/* media-type.

Internally swagger uses following mapping to find the JSON Schema component from OpenAPI Specification schema information:

For each schema’s type(eg. string, array, …) and if defined schema’s format (eg. ‘date’, ‘uuid’, …) there is a corresponding component mapping:

If format defined:

`JsonSchema_${type}_${format}`

Fallback if JsonSchema_${type}_${format} component does not exist or format not defined:

`JsonSchema_${type}`

Default:

`JsonSchema_string`

With this, one can define custom input components or override existing.

Example Date-Picker plugin

If one would like to input date values you could provide a custom plugin to integrate react-datepicker into swagger-ui. All you need to do is to create a component to wrap react-datepicker accordingly to the format.

There are two cases:

  • type: string
    format: date
    
    The resulting name for mapping to succeed: JsonSchema_string_date
  • type: string
    format: date-time
    
    The resulting name for mapping to succeed: JsonSchema_string_date-time

This creates the need for two components and simple logic to strip any time input in case the format is date:

import React from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";

const JsonSchema_string_date = (props) => {
  const dateNumber = Date.parse(props.value);
  const date = dateNumber
    ? new Date(dateNumber)
    : new Date();

  return (
    <DatePicker
      selected={date}
      onChange={d => props.onChange(d.toISOString().substring(0, 10))}
    />
  );
}

const JsonSchema_string_date_time = (props) => {
  const dateNumber = Date.parse(props.value);
  const date = dateNumber
    ? new Date(dateNumber)
    : new Date();

  return (
    <DatePicker
      selected={date}
      onChange={d => props.onChange(d.toISOString())}
      showTimeSelect
      timeFormat="p"
      dateFormat="Pp"
    />
  );
}


export const DateTimeSwaggerPlugin = {
  components: {
    JsonSchema_string_date: JsonSchema_string_date,
    "JsonSchema_string_date-time": JsonSchema_string_date_time
  }
};

Request Snippets

SwaggerUI can be configured with the requestSnippetsEnabled: true option to activate Request Snippets.
Instead of the generic curl that is generated upon doing a request. It gives you more granular options:

  • curl for bash
  • curl for cmd
  • curl for powershell

There might be the case where you want to provide your own snipped generator. This can be done by using the plugin api.
A Request Snipped generator consists of the configuration and a fn,
which takes the internal request object and transforms it to the desired snippet.

// Add config to Request Snippets Configuration with an unique key like "node_native" 
const snippetConfig = {
  requestSnippetsEnabled: true,
  requestSnippets: {
    generators: {
      "node_native": {
        title: "NodeJs Native",
        syntax: "javascript"
      }
    }
  }
}

const SnippedGeneratorNodeJsPlugin = {
  fn: {
    // use `requestSnippetGenerator_` + key from config (node_native) for generator fn
    requestSnippetGenerator_node_native: (request) => {
      const url = new Url(request.get("url"))
      let isMultipartFormDataRequest = false
      const headers = request.get("headers")
      if(headers && headers.size) {
        request.get("headers").map((val, key) => {
          isMultipartFormDataRequest = isMultipartFormDataRequest || /^content-type$/i.test(key) && /^multipart\/form-data$/i.test(val)
        })
      }
      const packageStr = url.protocol === "https:" ? "https" : "http"
      let reqBody = request.get("body")
      if (request.get("body")) {
        if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH"].includes(request.get("method"))) {
          return "throw new Error(\"Currently unsupported content-type: /^multipart\\/form-data$/i\");"
        } else {
          if (!Map.isMap(reqBody)) {
            if (typeof reqBody !== "string") {
              reqBody = JSON.stringify(reqBody)
            }
          } else {
            reqBody = getStringBodyOfMap(request)
          }
        }
      } else if (!request.get("body") && request.get("method") === "POST") {
        reqBody = ""
      }

      const stringBody = "`" + (reqBody || "")
          .replace(/\\n/g, "\n")
          .replace(/`/g, "\\`")
        + "`"

      return `const http = require("${packageStr}");
const options = {
  "method": "${request.get("method")}",
  "hostname": "${url.host}",
  "port": ${url.port || "null"},
  "path": "${url.pathname}"${headers && headers.size ? `,
  "headers": {
    ${request.get("headers").map((val, key) => `"${key}": "${val}"`).valueSeq().join(",\n    ")}
  }` : ""}
};
const req = http.request(options, function (res) {
  const chunks = [];
  res.on("data", function (chunk) {
    chunks.push(chunk);
  });
  res.on("end", function () {
    const body = Buffer.concat(chunks);
    console.log(body.toString());
  });
});
${reqBody ? `\nreq.write(${stringBody});` : ""}
req.end();`
    }
  }
}

const ui = SwaggerUIBundle({
  "dom_id": "#swagger-ui",
  deepLinking: true,
  presets: [
    SwaggerUIBundle.presets.apis,
    SwaggerUIStandalonePreset
  ],
  plugins: [
    SwaggerUIBundle.plugins.DownloadUrl,
    SnippedGeneratorNodeJsPlugin
  ],
  layout: "StandaloneLayout",
  validatorUrl: "https://validator.swagger.io/validator",
  url: "https://petstore.swagger.io/v2/swagger.json",
  ...snippetConfig,
})

Error handling

SwaggerUI comes with a safe-render plugin that handles error handling allows plugging into error handling system and modify it.

The plugin accepts a list of component names that should be protected by error boundaries.

Its public API looks like this:

{
  fn: {
    componentDidCatch,
    withErrorBoundary: withErrorBoundary(getSystem),
  },
  components: {
    ErrorBoundary,
    Fallback,
  },
}

safe-render plugin is automatically utilized by base and standalone SwaggerUI presets and should always be used as the last plugin, after all the components are already known to the SwaggerUI. The plugin defines a default list of components that should be protected by error boundaries:

[
  "App",
  "BaseLayout",
  "VersionPragmaFilter",
  "InfoContainer",
  "ServersContainer",
  "SchemesContainer",
  "AuthorizeBtnContainer",
  "FilterContainer",
  "Operations",
  "OperationContainer",
  "parameters",
  "responses",
  "OperationServers",
  "Models",
  "ModelWrapper",
  "Topbar",
  "StandaloneLayout",
  "onlineValidatorBadge"
]

As demonstrated below, additional components can be protected by utilizing the safe-render plugin with configuration options. This gets really handy if you are a SwaggerUI integrator and you maintain a number of plugins with additional custom components.

const swaggerUI = SwaggerUI({
  url: "https://petstore.swagger.io/v2/swagger.json",
  dom_id: '#swagger-ui',
  plugins: [
    () => ({
      components: {
        MyCustomComponent1: () => 'my custom component',
      },
    }),
    SwaggerUI.plugins.SafeRender({
      fullOverride: true, // only the component list defined here will apply (not the default list)
      componentList: [
        "MyCustomComponent1",
      ],
    }),
  ],
});
componentDidCatch

This static function is invoked after a component has thrown an error.
It receives two parameters:

  1. error - The error that was thrown.
  2. info - An object with a componentStack key containing information about which component threw the error.

It has precisely the same signature as error boundaries componentDidCatch lifecycle method, except it's a static function and not a class method.

Default implement of componentDidCatch uses console.error to display the received error:

export const componentDidCatch = console.error;

To utilize your own error handling logic (e.g. bugsnag), create new SwaggerUI plugin that overrides componentDidCatch:

{% highlight js linenos %} const BugsnagErrorHandlerPlugin = () => { // init bugsnag

return { fn: { componentDidCatch = (error, info) => { Bugsnag.notify(error); Bugsnag.notify(info); }, }, }; }; {% endhighlight %}

withErrorBoundary

This function is HOC (Higher Order Component). It wraps a particular component into the ErrorBoundary component. It can be overridden via a plugin system to control how components are wrapped by the ErrorBoundary component. In 99.9% of situations, you won't need to override this function, but if you do, please read the source code of this function first.

Fallback

The component is displayed when the error boundary catches an error. It can be overridden via a plugin system. Its default implementation is trivial:

import React from "react"
import PropTypes from "prop-types"

const Fallback = ({ name }) => (
  <div className="fallback">
    😱 <i>Could not render { name === "t" ? "this component" : name }, see the console.</i>
  </div>
)
Fallback.propTypes = {
  name: PropTypes.string.isRequired,
}
export default Fallback

Feel free to override it to match your look & feel:

const CustomFallbackPlugin = () => ({
  components: {
    Fallback: ({ name } ) => `This is my custom fallback. ${name} failed to render`,
  },
});

const swaggerUI = SwaggerUI({
  url: "https://petstore.swagger.io/v2/swagger.json",
  dom_id: '#swagger-ui',
  plugins: [
    CustomFallbackPlugin,
  ]  
});
ErrorBoundary

This is the component that implements React error boundaries. Uses componentDidCatch and Fallback under the hood. In 99.9% of situations, you won't need to override this component, but if you do, please read the source code of this component first.

Change in behavior

In prior releases of SwaggerUI (before v4.3.0), almost all components have been protected, and when thrown error, Fallback component was displayed. This changes with SwaggerUI v4.3.0. Only components defined by the safe-render plugin are now protected and display fallback. If a small component somewhere within SwaggerUI React component tree fails to render and throws an error. The error bubbles up to the closest error boundary, and that error boundary displays the Fallback component and invokes componentDidCatch.