Skip to main content

Migration Guide

Switching to @apiboost/omnispec from another renderer. Each section covers one tool — jump to yours.


Why Switch

Capability@apiboost/omnispecRedoc CESwagger UIStoplight ElementsScalar
OpenAPI 2.0 / 3.0 / 3.1FreeFreeFreeFreeFree
AsyncAPI 2.x / 3.xFree
GraphQL / SOAP / gRPCPro
Try-It panelFreePro onlyFreeFreeFree
6-language code samplesFreePro onlyFree
Custom sidebar navFree
Compact + reference display modesFreeReference onlyCompact onlyCompact onlyReference only
Grouped + segmented navigationFreeGrouped onlyGrouped onlySegmented onlyGrouped only
Light / dark / auto theme + CSS-variable overridesFreeLimited (Pro)CSS overridesCSS overridesLimited
Full white-label theming (theme.overrides, 70+ tokens)ProLimited (Pro)
Auto theme (system preference)FreeFree
Built-in CORS proxy (SSRF-safe)Free
x-tagGroups, x-codeSamples, etc.FreeProFree

@apiboost/omnispec renders OpenAPI 2.0, 3.0.x, and 3.1, plus AsyncAPI 2.x and 3.x, in a single component — all free, including the documentation vendor extensions (x-logo, x-codeSamples, x-tagGroups, x-displayName, x-badges, x-internal, x-enumDescriptions). GraphQL, SOAP/WSDL, and gRPC renderers, full white-label theming, and the interactive-OAuth extensions are part of Apiboost OmniSpec Pro.


From Redocly / Redoc

Installation

# Remove
npm uninstall redoc redoc-cli @redocly/cli

# Install
npm install @apiboost/omnispec @emotion/css

Component Swap

Before (Redoc CE)

import { RedocStandalone } from 'redoc'

<RedocStandalone
specUrl="https://api.example.com/openapi.json"
options={{
theme: { colors: { primary: { main: '#8B5CF6' } } },
scrollYOffset: 60,
hideDownloadButton: true,
expandResponses: '200,201',
nativeScrollbars: false,
}}
/>

After (@apiboost/omnispec)

import { OmniSpecRenderer } from '@apiboost/omnispec'

<OmniSpecRenderer
spec="https://api.example.com/openapi.json"
theme={{
base: 'light',
overrides: { '--omnispec-color-primary': '#8B5CF6' },
}}
downloadLink={false}
defaultExpandOperations={false}
/>
/* In your app stylesheet — set sticky offset */
.omnispec-root {
--omnispec-offset-top: 60px;
}

Prop Mapping

Redoc option@apiboost/omnispec equivalentNotes
specUrlspecAlso accepts raw strings and objects
theme.colors.primary.maintheme.overrides['--omnispec-color-primary']See Theming for all 70+ tokens
scrollYOffset--omnispec-offset-top CSS variable on .omnispec-rootCSS-only, not in theme.overrides
hideDownloadButton: truedownloadLink={false}
expandResponsesdefaultExpandOperationsExpands all operations on load
nativeScrollbarsNo equivalentCustom scrollbar styling is built-in via --omnispec-scrollbar-* tokens
disableTryItFromallowTryIt={false}
showExtensionsNo equivalentAll supported x- extensions render automatically
sortPropsAlphabeticallyNot supported
requiredPropsFirstNot supported

Theme Migration

Redoc uses a nested theme object. @apiboost/omnispec uses flat CSS custom properties, which gives you more granular control.

Pro

Passing the token map through the theme.overrides prop (shown in the "After" examples below) requires Apiboost OmniSpec Pro. In the free core, set the identical --omnispec-* tokens with a plain CSS rule on .omnispec-root — the visual result is the same. See Theming for the free CSS-variable path.

Before

options={{
theme: {
colors: {
primary: { main: '#8B5CF6' },
text: { primary: '#1a1a2e' },
},
typography: {
fontSize: '15px',
fontFamily: '"Inter", sans-serif',
},
sidebar: {
width: '300px',
backgroundColor: '#f6f8fa',
},
},
}}

After

theme={{
base: 'light',
overrides: {
'--omnispec-color-primary': '#8B5CF6',
'--omnispec-fg-primary': '#1a1a2e',
'--omnispec-font-size-base': '0.9375rem',
'--omnispec-font-sans': '"Inter", sans-serif',
'--omnispec-nav-width': '18.75rem',
'--omnispec-nav-bg': '#f6f8fa',
},
}}

See Theming Guide for all tokens.

Vendor Extensions

Your existing Redoc vendor extensions carry over without spec changes:

ExtensionStatus
x-logoSupported (free)
x-tagGroupsSupported (free)
x-displayNameSupported (free)
x-codeSamples / x-code-samplesSupported (free)
x-enumDescriptionsSupported (free)
x-internalSupported (free)

The free core renders these documentation vendor extensions along with all OpenAPI + AsyncAPI content — the same set you would have used Redoc Pro for — at no cost.

Display Mode

Redocly's classic three-panel layout is available as displayMode="reference":

<OmniSpecRenderer
spec={specUrl}
displayMode="reference"
/>

The default displayMode="compact" uses collapsible operation cards, which works better for narrower viewports and embedded use cases.


From Swagger UI

Installation

# Remove
npm uninstall swagger-ui-react swagger-ui

# Install
npm install @apiboost/omnispec @emotion/css

Component Swap

Before (Swagger UI)

import SwaggerUI from 'swagger-ui-react'
import 'swagger-ui-react/swagger-ui.css'

<SwaggerUI
url="https://api.example.com/openapi.json"
tryItOutEnabled={true}
docExpansion="list"
defaultModelsExpandDepth={1}
/>

After (@apiboost/omnispec)

import { OmniSpecRenderer } from '@apiboost/omnispec'

<OmniSpecRenderer
spec="https://api.example.com/openapi.json"
allowTryIt={true}
navigationMode="grouped"
/>

No separate CSS import required — styles are applied via @emotion/css at runtime.

Prop Mapping

Swagger UI prop@apiboost/omnispec equivalentNotes
urlspecAlso accepts raw strings and objects
specspecSame — pre-parsed object
tryItOutEnabledallowTryItDefault: true
docExpansion: 'none'defaultExpandOperations={false}Default behavior
docExpansion: 'full'defaultExpandOperations={true}Expand all operations on load
docExpansion: 'list'navigationMode="grouped"All operations visible, collapsed
defaultModelsExpandDepthNo direct equivalentSchemas are always accessible via the schema browser
filterNo direct equivalentUse the built-in search bar
requestInterceptoronTryItRequest callbackObserve requests; cannot mutate them
responseInterceptoronTryItResponse callbackObserve responses; cannot mutate them
onCompleteonSpecLoadedReceives { title, version, type }
persistAuthorizationNot supportedAuth values are session-scoped
validatorUrlNot supported
pluginsslots + sidebarNav + theme.overridesSee below

Replacing Plugins

Swagger UI's plugin system lets you inject custom components or override built-in ones. @apiboost/omnispec provides equivalent flexibility through typed props:

Swagger UI plugin use case@apiboost/omnispec equivalent
Custom logo / brandingslots.sidebarHeader or x-logo extension
Custom header / footerslots.header / slots.footer
Additional sidebar linkssidebarNav prop
Custom auth UINot supported (auth panel is built-in)
CSS overridestheme.overrides design tokens
// Swagger UI plugin (before)
const MyPlugin = () => ({
wrapComponents: {
InfoUrl: () => () => <a href="/home">Back to Home</a>,
},
})

// @apiboost/omnispec equivalent (after)
<OmniSpecRenderer
spec={specUrl}
slots={{
sidebarHeader: (
<nav style={{ padding: '12px' }}>
<a href="/home">Back to Home</a>
</nav>
),
}}
/>

Three-Panel Feel

If you want a layout closer to Redocly's reference style (schema left, samples right):

<OmniSpecRenderer
spec={specUrl}
displayMode="reference"
/>

CORS and Try-It

Swagger UI sends Try-It requests directly from the browser. This still works in @apiboost/omnispec. If you were working around CORS with a custom proxy, mount the built-in proxy middleware instead:

// Express
import { createProxyRouter } from '@apiboost/omnispec/server'
app.use('/api/proxy', createProxyRouter())
<OmniSpecRenderer spec={specUrl} proxyUrl="/api/proxy" />

The built-in proxy includes SSRF protection (blocks private IPs and link-local ranges) and rate limiting (60 req/min per IP).


From Stoplight Elements

Installation

# Remove
npm uninstall @stoplight/elements @stoplight/elements-core

# Install
npm install @apiboost/omnispec @emotion/css

Component Swap

Before (Stoplight Elements)

import { API } from '@stoplight/elements'
import '@stoplight/elements/styles.min.css'

<API
apiDescriptionUrl="https://api.example.com/openapi.json"
layout="sidebar"
router="hash"
tryItCredentialsPolicy="same-origin"
/>

After (@apiboost/omnispec)

import { OmniSpecRenderer } from '@apiboost/omnispec'

<OmniSpecRenderer
spec="https://api.example.com/openapi.json"
layout="sidebar"
navigationMode="segmented"
/>

Prop Mapping

Stoplight prop@apiboost/omnispec equivalentNotes
apiDescriptionUrlspecAlso accepts raw strings and objects
apiDescriptionDocumentspecPre-parsed object or raw string
layout="sidebar"layout="sidebar"Same — default
layout="stacked"layout="stacked"Same
router="hash"navigationMode="segmented"Segmented mode shows one operation at a time; closest equivalent to hash routing
router="memory"navigationMode="segmented"Same
router="history"navigationMode="segmented"
router="static"navigationMode="grouped"All operations on one page
tryItCredentialsPolicyonTryItRequest + proxyUrlSee below
hideTryItPanelallowTryIt={false}
hideSchemasNot supportedSchema browser is always shown
hideInternalNot supportedUse x-internal: true in your spec
logoslots.sidebarHeader or x-logo
basePathNot supportedUse spec with the full URL

Credentials Policy

Elements' tryItCredentialsPolicy controls how cookies and auth are sent. The equivalent in @apiboost/omnispec is to route requests through your backend proxy and handle credentials server-side:

Before

<API
apiDescriptionUrl={specUrl}
tryItCredentialsPolicy="include"
/>

After — handle credentials in your proxy:

// In your proxy route handler
const response = await fetch(targetUrl, {
method,
headers,
body,
credentials: 'include', // or handle session cookies here
})
<OmniSpecRenderer spec={specUrl} proxyUrl="/api/proxy" />

Alternatively, use onTryItRequest to observe what is being sent and log requests before they go out:

<OmniSpecRenderer
spec={specUrl}
onTryItRequest={(request) => {
console.log(request.method, request.url, request.headers)
}}
/>

Segmented Navigation

Stoplight Elements is always segmented — one operation per view. @apiboost/omnispec auto-selects segmented for specs with more than 50 operations. You can force it:

// Always segmented, regardless of spec size
<OmniSpecRenderer spec={specUrl} navigationMode="segmented" />

// Always grouped (all operations on one page)
<OmniSpecRenderer spec={specUrl} navigationMode="grouped" />

From Scalar

Installation

# Remove
npm uninstall @scalar/api-reference

# Install
npm install @apiboost/omnispec @emotion/css

Component Swap

Before (Scalar)

import { ApiReference } from '@scalar/api-reference'

<ApiReference
configuration={{
url: 'https://api.example.com/openapi.json',
theme: 'default',
darkMode: true,
hideDownloadButton: false,
}}
/>

After (@apiboost/omnispec)

import { OmniSpecRenderer } from '@apiboost/omnispec'

<OmniSpecRenderer
spec="https://api.example.com/openapi.json"
theme={{ base: 'dark' }}
/>

Prop Mapping

Scalar prop@apiboost/omnispec equivalentNotes
configuration.urlspecAlso accepts raw strings and objects
configuration.spec.contentspecPre-parsed object or raw string
configuration.darkMode: truetheme={{ base: 'dark' }}
configuration.darkMode: falsetheme={{ base: 'light' }}
configuration.themetheme.overridesScalar themes map to design token overrides — see table below
configuration.hideDownloadButtondownloadLink={false}
configuration.hiddenClientsNo equivalentAll 6 language samples are always shown
configuration.defaultHttpClientNo equivalentLanguage selector is user-controlled
configuration.authenticationNo equivalentAuth panel inferred from spec securitySchemes
configuration.generateCodeSamplesAuto-generated by defaultCannot disable per-language
configuration.isEditableNot supported
configuration.showSidebar: falselayout="stacked"
configuration.layout: 'classic'displayMode="compact"
configuration.layout: 'modern'displayMode="reference"Three-panel layout

Theme Migration

Scalar's named themes (default, moon, purple, solarized) do not map directly. Use design tokens to match the visual style instead.

Pro

As with the Redoc migration above, the theme.overrides prop is an Apiboost OmniSpec Pro feature. In the free core, apply the same --omnispec-* tokens with a CSS rule on .omnispec-root — see Theming.

Before — Scalar purple theme

configuration={{
theme: 'purple',
darkMode: true,
}}

After — equivalent token overrides

theme={{
base: 'dark',
overrides: {
'--omnispec-color-primary': '#8B5CF6',
'--omnispec-color-primary-hover': '#7C3AED',
'--omnispec-nav-accent': '#8B5CF6',
'--omnispec-nav-active-bg': 'rgba(139, 92, 246, 0.1)',
},
}}

See Theming Guide for all 70+ tokens.

Auto Theme

Both Scalar and @apiboost/omnispec support system-preference detection:

Before

configuration={{ theme: 'default', darkMode: false }}
// Scalar has no built-in auto mode

After

// Detects system preference, renders a toggle button
<OmniSpecRenderer spec={specUrl} theme={{ base: 'auto' }} />

// Auto-detect without showing the built-in toggle
<OmniSpecRenderer
spec={specUrl}
theme={{
base: 'auto',
themeToggle: false,
onThemeChange: (mode) => {
document.documentElement.setAttribute('data-theme', mode)
},
}}
/>

Vendor Extensions

Specs using Scalar-specific extensions are partially supported. Standard extensions work without modification:

Scalar extensionStatus in @apiboost/omnispec
x-codeSamplesSupported (free)
x-tagGroupsSupported (free)
x-displayNameSupported (free)
x-enumDescriptionsSupported (free)
x-internalSupported (free)
x-scalar-ignoreNot rendered (safely ignored)
x-scalar-environmentsNot rendered (safely ignored)
x-scalar-stabilityNot rendered (safely ignored)

Unknown x- extensions are safely ignored — they do not cause parse errors.

Custom Sidebar

Scalar does not offer a prop API for adding custom sidebar items. @apiboost/omnispec does:

import type { SidebarNavConfig } from '@apiboost/omnispec'

const nav: SidebarNavConfig = {
placement: 'before',
items: [
{ id: 'changelog', label: 'Changelog', href: '/changelog' },
{ id: 'status', label: 'API Status', href: 'https://status.example.com', target: '_blank' },
],
}

<OmniSpecRenderer spec={specUrl} sidebarNav={nav} />

See Sidebar Navigation for groups, icons, separators, and nested menus.


Common Post-Migration Tasks

Sticky Header Offset

If your app has a sticky navigation bar, tell the renderer to clear it:

.omnispec-root {
--omnispec-offset-top: 64px; /* height of your sticky nav */
}

This adjusts the sidebar height, Try-It panel position, and scroll target offsets in one variable.

Embedding in Existing App Chrome

Use slots to keep your app header/footer around the renderer:

<OmniSpecRenderer
spec={specUrl}
slots={{
header: <YourAppHeader />,
footer: <YourAppFooter />,
sidebarHeader: <BackLink />,
}}
/>

AsyncAPI Without Spec Changes

If you are also documenting event-driven APIs, @apiboost/omnispec handles AsyncAPI 2.x and 3.x natively in the same component — no separate package or integration:

// OpenAPI spec
<OmniSpecRenderer spec="/openapi.json" />

// AsyncAPI spec — same component, auto-detected
<OmniSpecRenderer spec="/asyncapi.yaml" />

Next Steps