Disclaimer: If you are looking for documentation on the experimental new frontend system support, please go here.
This is an extension for the catalog plugin that provides components to discover and display API entities. APIs define the interface between components, see the system model for details. They are defined in machine readable formats and provide a human readable documentation.
The plugin provides a standalone list of APIs, as well as an integration into the API tab of a catalog entity.
Right now, the following API formats are supported:
Other formats are displayed as plain text, but this can easily be extended.
To fill the catalog with APIs, provide entities of kind API.
To link that a component provides or consumes an API, see the providesApis
and consumesApis
properties on the Component kind.
The plugin is already added when using
npx @backstage/create-app
so you can skip these steps.
- Install the API docs plugin
# From your Backstage root directory
yarn --cwd packages/app add @backstage/plugin-api-docs
- Add the
ApiExplorerPage
extension to the app:
// In packages/app/src/App.tsx
import { ApiExplorerPage } from '@backstage/plugin-api-docs';
<Route path="/api-docs" element={<ApiExplorerPage />} />;
- Add one of the provided widgets to the EntityPage:
// packages/app/src/components/catalog/EntityPage.tsx
import {
EntityAboutCard,
EntityApiDefinitionCard,
EntityConsumingComponentsCard,
EntityProvidingComponentsCard,
} from '@backstage/plugin-api-docs';
const apiPage = (
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EntityAboutCard />
</Grid>
<Grid container>
<Grid item md={12}>
<Grid item xs={12} md={6}>
<EntityProvidingComponentsCard />
</Grid>
<Grid item xs={12} md={6}>
<EntityConsumingComponentsCard />
</Grid>
</Grid>
</Grid>
</Grid>
</EntityLayout.Route>
<EntityLayout.Route path="/definition" title="Definition">
<Grid container spacing={3}>
<Grid item xs={12}>
<EntityApiDefinitionCard />
</Grid>
</Grid>
</EntityLayout.Route>
</EntityLayout>
);
// ...
export const entityPage = (
<EntitySwitch>
// ...
<EntitySwitch.Case if={isKind('api')} children={apiPage} />
// ...
</EntitySwitch>
);
There are other components to discover in ./src/components
that are also added by the default app.
You can add support for additional API types by providing a custom implementation for the apiDocsConfigRef
.
You can also use this to override the rendering of one of the already supported types.
This is an example with a made-up renderer for SQL schemas:
// packages/app/src/apis.tsx
import { ApiEntity } from '@backstage/catalog-model';
import {
ApiDefinitionWidget,
apiDocsConfigRef,
defaultDefinitionWidgets,
} from '@backstage/plugin-api-docs';
import { SqlRenderer } from '...';
// ...
export const apis: AnyApiFactory[] = [
// ...
createApiFactory({
api: apiDocsConfigRef,
deps: {},
factory: () => {
// load the default widgets
const definitionWidgets = defaultDefinitionWidgets();
return {
getApiDefinitionWidget: (apiEntity: ApiEntity) => {
// custom rendering for sql
if (apiEntity.spec.type === 'sql') {
return {
type: 'sql',
title: 'SQL',
component: definition => <SqlRenderer definition={definition} />,
} as ApiDefinitionWidget;
}
// fallback to the defaults
return definitionWidgets.find(d => d.type === apiEntity.spec.type);
},
};
},
}),
];
The Swagger UI package by expects to have a route to /oauth2-redirect.html
which processes
the redirect callback for the OAuth2 Authorization Code flow, however, this file is not installed
by this plugin.
Grab a copy of oauth2-redirect.html
and put it in the app/public/
directory in order to enable Swagger UI to complete this redirection.
This also may require you to adjust Content Security Policy
header settings of your Backstage application, so that the script in oauth2-redirect.html
can be executed. Since the script is static we can add the hash of it directly to our CSP policy, which we do by adding the following to the csp
section of the app configuration:
script-src:
- "'self'"
- "'unsafe-eval'" # this is required for scaffolder usage, and ajv validation.
- "'sha256-GeDavzSZ8O71Jggf/pQkKbt52dfZkrdNMQ3e+Ox+AkI='" # oauth2-redirect.html
You'll need to make sure your OAuth2 client has been registered in your OAuth2 Authentication Server (AS)
with the appropriate redirect_uris
, scopes
and grant_types
. For example, if your AS supports
the OAuth 2.0 Dynamic Client Registration Protocol, an example
POST request body would look like this:
{
"client_name": "Example Backstage api-docs plugin Swagger UI Client",
"redirect_uris": [
"https://www.getpostman.com/oauth2/callback",
"http://localhost:3000/oauth2-redirect.html"
"https://<yourhost>/oauth2-redirect.html"
],
"scope": "read_pets write_pets",
"grant_types": [
"authorization_code"
]
}
The above redirect_uris
are:
- Postman testing:
https://www.getpostman.com/oauth2/callback
- Local Backstage app development:
http://localhost:3000/oauth2-redirect.html
- Backstage app production:
https://<yourhost>/oauth2-redirect.html
To configure OAuth 2 Authorization Code flow in your OpenAPI 3.0 schema you'll need something like this snippet:
components:
securitySchemes:
oauth:
type: oauth2
description: OAuth2 service
flows:
authorizationCode:
authorizationUrl: https://api.example.com/oauth2/authorize
tokenUrl: https://api.example.com/oauth2/token
scopes:
read_pets: read your pets
write_pets: modify pets in your account
security:
oauth:
- [read_pets, write_pets]
To configure a requestInterceptor
for Swagger UI you'll need to add the following to your api.tsx
:
...
import { OpenApiDefinitionWidget, apiDocsConfigRef, defaultDefinitionWidgets } from '@backstage/plugin-api-docs';
import { ApiEntity } from '@backstage/catalog-model';
export const apis: AnyApiFactory[] = [
...
createApiFactory({
api: apiDocsConfigRef,
deps: {},
factory: () => {
// Overriding openapi definition widget to add header
const requestInterceptor = (req: any) => {
req.headers.append('myheader', 'wombats');
return req;
};
const definitionWidgets = defaultDefinitionWidgets().map(obj => {
if (obj.type === 'openapi') {
return {
...obj,
component: (definition) => <OpenApiDefinitionWidget definition={definition} requestInterceptor={requestInterceptor} />,
}
}
return obj;
});
return {
getApiDefinitionWidget: (apiEntity: ApiEntity) => {
return definitionWidgets.find(d => d.type === apiEntity.spec.type);
},
};
},
})
In the same way as the requestInterceptor
you can override any property of Swagger UI
This can be done through utilising the
supportedSubmitMethods prop.
If you wish to limit the HTTP methods available for the Try It Out
feature of an OpenAPI API
component, you will need to add the following to your api.tsx
, listing the permitted methods for
your API in the supportedSubmitMethods
parameter:
...
import {
OpenApiDefinitionWidget,
apiDocsConfigRef,
defaultDefinitionWidgets,
} from '@backstage/plugin-api-docs';
import { ApiEntity } from '@backstage/catalog-model';
export const apis: AnyApiFactory[] = [
...
createApiFactory({
api: apiDocsConfigRef,
deps: {},
factory: () => {
const supportedSubmitMethods = ['get', 'post', 'put', 'delete'];
const definitionWidgets = defaultDefinitionWidgets().map(obj => {
if (obj.type === 'openapi') {
return {
...obj,
component: definition => (
<OpenApiDefinitionWidget
definition={definition}
supportedSubmitMethods={supportedSubmitMethods}
/>
),
};
}
return obj;
});
return {
getApiDefinitionWidget: (apiEntity: ApiEntity) => {
return definitionWidgets.find(d => d.type === apiEntity.spec.type);
}
};
}
})
]
N.B. if you wish to disable the Try It Out
feature for your API, you can provide an empty list to
the supportedSubmitMethods
parameter.
You can override the default http/https resolvers, for example to add authentication to requests to internal schema registries by providing the resolvers
prop to the AsyncApiDefinitionWidget
. This is an example:
...
import {
AsyncApiDefinitionWidget,
apiDocsConfigRef,
defaultDefinitionWidgets,
} from '@backstage/plugin-api-docs';
import { ApiEntity } from '@backstage/catalog-model';
export const apis: AnyApiFactory[] = [
...
createApiFactory({
api: apiDocsConfigRef,
deps: {},
factory: () => {
const myCustomResolver = {
schema: 'https',
order: 1,
canRead: true,
async read(uri: any) {
const response = await fetch(request, {
headers: {
X-Custom: 'Custom',
},
});
return response.text();
},
};
const definitionWidgets = defaultDefinitionWidgets().map(obj => {
if (obj.type === 'asyncapi') {
return {
...obj,
component: (definition) => (
<AsyncApiDefinitionWidget definition={definition} resolvers={[myCustomResolver]} />
),
};
}
return obj;
});
return {
getApiDefinitionWidget: (apiEntity: ApiEntity) => {
return definitionWidgets.find(d => d.type === apiEntity.spec.type);
},
};
}
})
]
You can add pagination support to ApiExplorerPage
extension.
This is an example:
// In packages/app/src/App.tsx
import { ApiExplorerPage } from '@backstage/plugin-api-docs';
<Route
path="/api-docs"
element={<ApiExplorerPage pagination={{ mode: 'offset', limit: 20 }} />}
/>;