Plugins
Build custom plugins to extend the functionality of your API reference.
Using a Plugin
import { MyCustomPlugin } from './my-custom-plugin.ts'
const configuration = {
url: 'https://registry.scalar.com/@scalar/apis/galaxy?format=json',
plugins: [
MyCustomPlugin(),
],
}
Loading a Plugin from a URL
When you use the standalone browser build (Scalar.createApiReference), you can also reference plugins by URL.
Each entry in pluginUrls must point to an ESM module that exports a plugin (the same shape as the plugins
entries) as its default export. The modules are imported before the API reference mounts and their default
exports are registered alongside the plugins passed directly.
const configuration = {
url: 'https://registry.scalar.com/@scalar/apis/galaxy?format=json',
pluginUrls: [
'https://cdn.jsdelivr.net/npm/@example/scalar-plugin/dist/plugin.js',
],
}
Unlike plugins, this option is JSON-serializable. That makes it possible to load plugins from integrations
that pass their configuration as JSON — for example the Docker container (API_REFERENCE_CONFIG) or server-side
integrations that render the configuration into the HTML — without replacing the whole bundle.
Each URL is imported and executed as a JavaScript module in the browser, so it runs with the same privileges
as the page hosting the API reference. Only reference pluginUrls you control or fully trust — treat them like
any other <script> you add to your site, and prefer pinning a specific version rather than a floating tag.
A minimal plugin module looks like this:
// plugin.js — served as an ESM module
export default () => ({
name: 'my-custom-plugin',
extensions: [],
})
Note: When you render the ApiReference component yourself (for example in a Vue app), import the plugin
and pass it via plugins instead.
Creating a Plugin
Specification Extensions
The OpenAPI specification allows you to extend the format.
You can add custom properties. They are always prefixed with a x-, here is an example:
openapi: 3.1.1
+x-defaultPlanet: 'earth'
info:
title: Hello World
version: 1.0
paths: {}
Render with Vue
import type { ApiReferencePlugin } from '@scalar/types/api-reference'
import CustomVueComponent from './components/CustomVueComponent.vue'
export const XCustomExtensionPlugin = (): ApiReferencePlugin => {
return () => {
return {
name: 'my-custom-plugin',
extensions: [
// Vue
{
name: 'x-defaultPlanet',
component: CustomVueComponent,
},
],
}
}
}
Render with React
npm install @scalar/react-renderer react react-dom
import { ReactRenderer } from '@scalar/react-renderer'
import type { ApiReferencePlugin } from '@scalar/types/api-reference'
import { CustomReactComponent } from './components/CustomReactComponent'
export const XCustomExtensionPlugin = (): ApiReferencePlugin => {
return () => {
return {
name: 'my-custom-plugin',
extensions: [
// React
{
name: 'x-defaultPlanet',
/** This is a React component. 🤯 */
component: CustomReactComponent,
/** Pass a custom renderer to make it work. */
renderer: ReactRenderer,
},
],
}
}
}
Lifecycle Hooks
Plugins can hook into the API Reference lifecycle to run code at specific points.
Available Hooks
onInit({ config, auth })— Called when the API Reference is initialized. Receives the resolved configuration and the authentication state.onConfigChange({ config, auth })— Called when the API Reference configuration changes.onDestroy()— Called when the API Reference is destroyed. Use for cleanup.
Example
import type { ApiReferencePlugin } from '@scalar/types/api-reference'
export const AnalyticsPlugin = (): ApiReferencePlugin => {
return () => {
return {
name: 'analytics-plugin',
extensions: [],
hooks: {
onInit({ config }) {
console.log('API Reference initialized', config)
},
onConfigChange({ config }) {
console.log('Configuration changed', config)
},
onDestroy() {
console.log('API Reference destroyed')
},
},
}
}
}
Reading the Authentication State
Plugins can read the global authentication state — the secrets the user has entered (tokens, API keys, OAuth credentials) and the selected security schemes. This is read-only; plugins cannot mutate auth.
The auth accessor exposes three methods:
| Method | Description |
|---|---|
export() |
Returns a snapshot of the entire authentication state, keyed by document name. |
getAuthSecrets(documentName, schemeName) |
Returns the stored secrets for a security scheme within a document, or undefined. |
getAuthSelectedSchemas(payload) |
Returns the selected security for a document ({ type: 'document', documentName }) or operation ({ type: 'operation', documentName, path, method }), or undefined. |
From lifecycle hooks
The auth accessor is passed to onInit and onConfigChange alongside config:
import type { ApiReferencePlugin } from '@scalar/types/api-reference'
export const AuthAwarePlugin = (): ApiReferencePlugin => {
return () => {
return {
name: 'auth-aware-plugin',
extensions: [],
hooks: {
onConfigChange({ auth }) {
// Read the secrets the user entered for a specific scheme
const secrets = auth.getAuthSecrets('my-document', 'bearerAuth')
console.log('Current bearer token', secrets?.token)
// Or grab a full snapshot of every document's auth state
console.log('All auth state', auth.export())
},
},
}
}
}
From a view component
View components can reach the same accessor through the plugin manager:
import { usePluginManager } from '@scalar/api-reference/plugins'
const pluginManager = usePluginManager()
const auth = pluginManager.getAuthState()
const selected = auth.getAuthSelectedSchemas({ type: 'document', documentName: 'my-document' })
Additional Components
Plugins can inject components at specific locations in the API Reference using views.
Available Views
content.start- Before the Introduction/Info sectioncontent.end- After the Models section
Sidebar Visibility
View components can optionally appear in the sidebar. Add a sidebar configuration to control this. Clicking the entry scrolls to the component, and the entry highlights as it scrolls into view, just like the built-in sections.
{
component: CustomComponent,
sidebar: {
show: true, // true = visible in sidebar, false/omitted = hidden
label: 'My Page', // Display text in the sidebar
},
}
Example
import type { ApiReferencePlugin } from '@scalar/types/api-reference'
import CustomComponent from './components/CustomComponent.vue'
export const FeedbackPlugin = (): ApiReferencePlugin => {
return () => {
return {
name: 'feedback-plugin',
extensions: [],
views: {
'content.start': [
{
component: CustomComponent,
// Show in sidebar
sidebar: {
show: true,
label: 'Getting Started',
},
},
],
'content.end': [
{
component: CustomComponent,
// Not shown in sidebar (omitted)
},
],
},
}
}
}
Component Props
View components receive:
options- API Reference configuration options
Using React Components
import { ReactRenderer } from '@scalar/react-renderer'
import { CustomComponent } from './components/CustomComponent'
export const FeedbackPlugin = (): ApiReferencePlugin => {
return () => {
return {
name: 'feedback-plugin',
extensions: [],
views: {
'content.end': [
{
component: CustomComponent,
renderer: ReactRenderer,
},
],
},
}
}
}
With sidebar visibility:
import { ReactRenderer } from '@scalar/react-renderer'
import { CustomComponent } from './components/CustomComponent'
export const FeedbackPlugin = (): ApiReferencePlugin => {
return () => {
return {
name: 'feedback-plugin',
extensions: [],
views: {
'content.start': [
{
component: CustomComponent,
renderer: ReactRenderer,
sidebar: {
show: true,
label: 'Support',
},
},
],
},
}
}
}
API Client Plugins
The API Reference uses the API Client to test requests. The API Client has its own plugin API and you can't pass an API Client plugin directly, to the API Reference, but you can wrap it in an API Reference plugin.
API Client Plugins can add lifecycle hooks, custom UI components, and custom response body handlers for content types the client does not natively support.
Using a Plugin
Pass plugins when creating the API Client:
import { createApiClientApp } from '@scalar/api-client/app'
import type { ClientPlugin } from '@scalar/oas-utils/helpers'
const myPlugin: ClientPlugin = {
// ...
}
createApiClientApp(el, {
layout: 'web',
plugins: [myPlugin],
})
Example: Custom Response Body Handling
When your API returns a content type the client does not natively support (for example, MessagePack), you can register a responseBody handler to decode and display it:
import { createApiReference } from '@scalar/api-reference'
import type { ClientPlugin } from '@scalar/oas-utils/helpers'
import type { ApiReferencePlugin } from '@scalar/types/api-reference'
/** Msgpack API Client Plugin */
const MsgpackApiClientPlugin: ClientPlugin = {
responseBody: [
{
mimeTypes: ['application/msgpack', 'application/x-msgpack'],
decode: async (buffer) => {
const { decode } = await import('@msgpack/msgpack')
const decoded = decode(new Uint8Array(buffer))
return JSON.stringify(decoded, null, 2)
},
language: 'json',
},
],
}
/** Wrapped in an API Reference Plugin */
const MsgpackPlugin = (): ApiReferencePlugin => {
return () => ({
name: 'msgpack',
extensions: [],
apiClientPlugins: [MsgpackApiClientPlugin],
})
}
/** Creating a new API Reference */
createApiReference(el, {
url: 'https://registry.scalar.com/@scalar/apis/galaxy?format=json',
plugins: [MsgpackPlugin],
})
Each handler in responseBody supports:
| Property | Description |
|---|---|
mimeTypes |
MIME type patterns to match (exact or wildcard like application/vnd.*+json). |
decode |
Transforms the raw ArrayBuffer into a string or Blob for display. |
language |
CodeMirror language hint for the built-in raw renderer (for example, json). Mutually exclusive with rawComponent. |
rawComponent |
A custom Vue component for the raw view. Mutually exclusive with language. |
previewComponent |
A custom Vue component for the preview view. |
Lifecycle Hooks
Plugins can hook into the request lifecycle:
onRequestMount
Runs when an operation view mounts, before any request is sent. Use it to warm up resources.
| Field | Type | Description |
|---|---|---|
document |
OpenApiDocument |
The current OpenAPI document. |
operation |
OperationObject |
The current operation. |
beforeRequest
Runs before the fetch Request is built. Mutate requestBuilder to change the outgoing request.
| Field | Type | Description |
|---|---|---|
requestBuilder |
RequestFactory |
The mutable request builder. Change its method, path, query, headers, body, or security before the request is built. |
document |
OpenApiDocument |
The current OpenAPI document. |
operation |
OperationObject |
The current operation. |
variablesStore? |
VariablesStore |
The request variable store. |
server? |
ServerObject | null |
The optional active OpenAPI Server Object selected for the request. Its url can be relative or include server variables, so resolve it for the plugin's runtime before making network calls. |
customFetch? |
typeof fetch |
The optional host-provided fetch implementation configured for the API client. Use it for plugin-owned network calls so they use the same network channel as the request, including the desktop app's IPC-backed fetch. |
requestBuilt
Runs after the fetch Request is built, immediately before it is sent. Outside Electron, mutate request.headers to change the outgoing request; its body matches the bytes sent over the wire. In Electron, Scalar sends the request payload through customFetch instead, so mutations to request do not apply and multipart body bytes can differ.
| Field | Type | Description |
|---|---|---|
request |
Request |
The request built before sending. Outside Electron it is sent as-is; Electron sends the request payload through customFetch instead. |
requestBuilder |
RequestFactory |
The builder used to create request. Mutating it at this stage does not change the outgoing request. |
document |
OpenApiDocument |
The current OpenAPI document. |
operation |
OperationObject |
The current operation. |
variablesStore? |
VariablesStore |
The request variable store. |
responseReceived
Runs after a response is received.
| Field | Type | Description |
|---|---|---|
response |
Response |
The received response. |
request |
Request |
A request rebuilt from the sent request payload. It is not the same instance as the sent request; header mutations from requestBuilt are absent and multipart body bytes can differ. |
requestBuilder |
RequestFactory |
The builder used to create request. Mutating it does not change the already-sent request. |
document |
OpenApiDocument |
The current OpenAPI document. |
operation |
OperationObject |
The current operation. |
variablesStore? |
VariablesStore |
The request variable store. |
Example
const authPlugin: ClientPlugin = {
hooks: {
beforeRequest: async ({ requestBuilder, server, customFetch }) => {
console.log('Active server configuration:', server?.url)
if (customFetch) {
// Resolve an absolute URL appropriate for the plugin's runtime.
await customFetch('https://auth.example.com/session/refresh', {
method: 'POST',
})
}
requestBuilder.headers.set('X-Custom-Header', 'value')
},
responseReceived: ({ response }) => {
console.log('Response status:', response.status)
},
},
}