Scalar & Modern Docs Integration
Scalar is a modern OpenAPI reference renderer built around a first-class HTTP client: developers read an endpoint and execute it from the same page, with credential inputs derived from your spec. It ships dark mode, a fast client-side search, and a self-contained static build with no server runtime. This guide is part of Developer Portal Frameworks & UI Setup, and it walks through configuring Scalar via the @scalar/api-reference package, theming it from your design tokens, wiring a CI build, and routing multiple API versions cleanly.
Scalar suits teams who want an interactive “try it” experience without standing up a backend. Where a static renderer like Redoc produces a read-only reference, Scalar puts a request runner inline — useful for onboarding, support, and exploratory testing. If you are weighing the two renderers, the Redoc vs Scalar comparison breaks down the trade-offs, and teams arriving from the classic Swagger UI should read Migrating from Swagger to Scalar for better DX for a step-by-step path.
Prerequisites & Environment Setup
You need Node.js 18 or newer, an OpenAPI 3.0 or 3.1 description, and a static host (Vercel, Netlify, S3 + CloudFront, or an internal cluster). Scalar offers two integration surfaces: a CDN-delivered standalone script for plain HTML pages, and the @scalar/api-reference package for embedding inside a JavaScript app. Choose based on where the reference lives — a standalone marketing-style docs page favors the script tag; a React or Vue portal favors the package.
Pin your versions. Scalar releases frequently, and an unpinned dependency can shift layout or tokens between builds. For the package approach, install it as a regular dependency and commit the lockfile:
npm install @scalar/api-reference@latest
npm ls @scalar/api-reference # record the resolved version
For the CDN approach, pin a specific version in the script URL rather than using a floating @latest tag in production:
<script src="https://cdn.jsdelivr.net/npm/@scalar/[email protected]/dist/browser/standalone.js"></script>
Before any build, make sure your spec is valid. Scalar will render a malformed spec into broken or incomplete docs rather than erroring loudly, so run a linter first. @redocly/cli works well as a pre-build gate — see Redocly & OpenAPI UI Configuration for its rule set — or use Spectral as covered in Spec Linting & Governance.
Core Configuration
The standalone script tag reads its settings from a data-configuration attribute containing a JSON object. The minimal version points at a spec and turns on dark mode:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>API Reference</title>
<link rel="stylesheet" href="/scalar-theme.css" />
</head>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/@scalar/[email protected]/dist/browser/standalone.js"></script>
<script>
Scalar.createApiReference('#app', {
url: '/openapi.yaml', // spec location, fetched at load
theme: 'default', // built-in theme name
darkMode: true, // start in dark mode
hideModels: false, // show the Models section
hiddenClients: ['unirest'], // remove noisy client snippets
defaultHttpClient: { // pre-select a code-sample language
targetKey: 'shell',
clientKey: 'curl'
}
})
</script>
</body>
</html>
For an app, import the same factory and configure it in code. This is the path to use inside a React or Vue portal so the reference shares the host bundle and routing:
// reference.js — bundled with your portal
import { createApiReference } from '@scalar/api-reference'
import '@scalar/api-reference/style.css'
createApiReference('#app', {
url: '/openapi.yaml',
darkMode: true,
// metaData drives <title>/<meta> for SEO when server-rendered
metaData: { title: 'Payments API Reference' },
// authentication can be pre-filled from the spec's securitySchemes
authentication: { preferredSecurityScheme: 'bearerAuth' },
})
The two configuration keys teams most often miss are defaultHttpClient, which sets the code-sample language shown by default, and hiddenClients, which trims the client list to the languages you actually support. Authentication needs no special wiring beyond declaring securitySchemes in the spec — Scalar renders the matching inputs and feeds them to the built-in HTTP client, keeping credentials in the browser.
Integration Pattern
Build Scalar’s static output in CI, gating on a lint pass so a broken spec never ships. The workflow below validates, builds, and deploys; it triggers only when the spec changes:
# .github/workflows/scalar-build.yml
name: Build Scalar Docs
on:
push:
branches: [main]
paths: ['openapi.yaml']
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Lint spec (gate before build)
run: npx @redocly/cli lint openapi.yaml --format=github
- name: Build Scalar static docs
run: npx @scalar/cli document bundle openapi.yaml --output dist/openapi.json
- name: Assemble static site
run: npm run build:docs # copies index.html + pinned standalone.js + dist/openapi.json
- name: Deploy
run: npx vercel deploy dist/ --prod --token "$VERCEL_TOKEN"
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
The lint step uses @redocly/cli because it returns a non-zero exit on error-severity violations, which fails the job cleanly. The @scalar/cli document bundle step resolves all $refs into a single openapi.json that the page loads with no further network round-trips. Keep the pinned standalone.js in your repo or vendored assets so the deployed page never silently pulls a newer renderer.
Advanced Options
Theme from design tokens, not hardcoded values. Override Scalar’s CSS custom properties in an external stylesheet and map them to your design system, so one token file drives both the portal shell and the reference:
/* scalar-theme.css */
.scalar-app {
--scalar-font: 'Inter', sans-serif;
--scalar-color-1: #16295d; /* primary text */
--scalar-color-2: #334155; /* secondary text */
--scalar-background-1: #ffffff;
}
[data-theme='dark'] .scalar-app {
--scalar-color-1: #e2e8f0;
--scalar-background-1: #0f172a;
}
Use a CSS @layer to isolate these rules below a framework reset; otherwise Tailwind’s preflight or Bootstrap’s resets can override Scalar’s scoped variables. The shared light/dark token approach is detailed in Multi-Theme & Dark Mode Support.
Version routing. Build one output directory per version and serve each from a distinct path, then add a landing page that links to all of them:
npx @scalar/cli document bundle openapi-v1.yaml --output dist/v1/openapi.json
npx @scalar/cli document bundle openapi-v2.yaml --output dist/v2/openapi.json
For deprecated versions, keep the static bundle live but add a visible deprecation banner and a server-side redirect to the current path once the grace period in your API lifecycle policy expires.
Embed inside a component tree. In a React or Vue portal, render the reference as a component via @scalar/api-reference instead of an iframe, so it shares routing, theme toggling, and the host bundle. This is the cleaner path when the reference is one tab among many in a larger developer portal.
Two ways to ship the same reference
Scalar can be mounted two ways, and the choice determines what can fail. A script tag pointing at a specification URL renders in the browser: nothing is built, the page is a few lines, and both the script and the specification are fetched at load. A CLI build produces a static artifact ahead of time: there is a build step, and at page load there is nothing left to fetch or resolve.
Neither is wrong, and the deciding question is where you want failures to appear. The script-tag mount is excellent for embedding a reference into an existing site quickly, and it moves two failure modes — an unreachable CDN and an unresolvable specification — into the reader’s browser, where you find out about them from a support ticket. The CLI build moves both into CI, where a broken build is a red check on a branch. For a public portal the second is almost always the right trade; for an internal page that must exist by Friday, the first is a perfectly reasonable choice made deliberately.
Bundling is not optional either way
Whichever mount you use, the specification must be resolved into a single document before the renderer sees it. A multi-file specification is good authoring practice and unusable in a browser, because a relative reference to a sibling file that was never published cannot be resolved — and the failure is silent. The operations list correctly, the navigation works, and every schema table is empty, because from the renderer’s point of view an unresolvable reference is simply an unknown schema rather than an error.
Because the failure produces no error, the only reliable defence is an explicit assertion after bundling: count the external references remaining in the output and fail the build when it is not zero. It costs one line and converts the most common Scalar support question into a red check.
Verification & Testing
Run the build locally before pushing and confirm each artifact exists and is non-empty:
npx @redocly/cli lint openapi.yaml
echo "lint exit: $?" # 0 = pass; 1 = errors block the build
npx @scalar/cli document bundle openapi.yaml --output dist/openapi.json
test -s dist/openapi.json && echo "bundle OK"
Serve the assembled directory and load it in a browser to confirm three things: the reference renders, dark mode toggles, and the built-in HTTP client can execute a request against a test server. A quick static check confirms the page references your bundled spec rather than a remote one:
npx http-server dist -p 8080 &
curl -s http://localhost:8080/ | grep -q "openapi.json" \
&& echo "page references bundled spec" || echo "spec reference MISSING"
After deploy, fetch the live page and grep for your API title to confirm the deploy replaced the previous build rather than failing silently.
The console is the part that needs decisions
Everything else about mounting Scalar is configuration; the request console is where real choices have to be made, because it is the only component on the page that talks to a live API. Three of those choices should be settled before the reference ships, and all three are cheap now and awkward later.
The first is which environment the console targets by default. The renderer builds its environment selector from the specification’s servers array and selects the first entry, so the order of that array is a safety control rather than a stylistic detail. A sandbox first means a reader who presses Send without reading the selector does something harmless; production first means a documentation page can mutate real data with one click.
The second is where credentials live. A console needs one to do anything interesting, and the convenient option — letting the component persist whatever the reader types — writes a token to browser storage where it survives the tab, is readable by any script on the origin, and reliably ends up in screenshots attached to support tickets. Holding it in memory for the session costs the reader one paste per visit and removes an entire category of incident.
The third is how requests reach the API at all. A browser calling a different origin needs that API’s CORS policy to name your documentation domain exactly, which is often not something you control and is never something a reader can fix. Routing console requests through a same-origin path removes the question entirely and gives you somewhere to attach a credential server-side.
Keeping the reference honest over time
A reference that renders is not the same as a reference that is right, and the gap opens quietly. The specification changes, the build keeps succeeding, and nobody notices that a schema table has been empty since a refactor moved a components file. Two habits close it.
Assert properties rather than eyeballing pages. After each build, check the things that fail silently: that no external references survived bundling, that the first server entry is a sandbox, and that at least some operations carry response examples. Each is a single command, each catches a failure that produces a page which looks fine, and together they are worth more than any amount of manual review.
Exercise the console in CI as well as the page. The reference and the console fail independently — a page can render perfectly while every request is blocked by a policy that changed on the API side — so a headless check that presses Send and asserts a real status code is the only thing that notices. Run it against the deployed preview rather than a local build, because CORS, redirects and cache headers only exist once something is actually served.
Troubleshooting
Blank reference, spinner never resolves. The page can fetch standalone.js but cannot fetch the spec, usually a CORS failure or a wrong url. Bundle the spec into the same origin as the page (the @scalar/cli document bundle step does this) so there is no cross-origin fetch, and confirm the url path resolves with curl.
Spec loads but operations are missing. The spec is OpenAPI 2.0 or has invalid $refs that Scalar skipped silently. Convert 2.0 with npx swagger2openapi swagger.yaml -o openapi.yaml, then re-run the linter to catch unresolved references before building.
Theme overrides have no effect. Your CSS framework’s reset is winning the cascade over Scalar’s scoped variables. Wrap Scalar’s overrides in @layer placed after the framework reset, and target .scalar-app rather than deep internal selectors that are not a stable API.
Layout regression after a deploy. The page pulled a newer renderer because the script tag used a floating @latest. Pin the version in the script URL (or vendor the file) and treat renderer upgrades as reviewed changes with a visual diff.
Examples do more for the reference than any setting
Once the mount and the console are configured, the largest remaining improvement is not in the renderer at all — it is in the specification. Scalar prefills the console’s request body from the examples in the document, and it renders those same examples in the reference. An operation with a realistic example gives a reader a request they can send unmodified; an operation without one gives them a skeleton of placeholder values they must fill in before anything happens, which is the difference between a successful first call in ten seconds and an abandoned page.
That investment pays into three places at once. The rendered reference improves, the console becomes usable rather than merely present, and any mock server generated from the same document starts returning realistic data instead of type-correct noise. There is no configuration change with a comparable return, which makes “add examples to the specification” the correct answer to a surprising share of the questions that arrive phrased as renderer configuration problems.
FAQ
Can Scalar render OpenAPI 2.0 (Swagger) specs directly?
No. Scalar requires OpenAPI 3.0 or 3.1. Convert legacy Swagger 2.0 with npx swagger2openapi swagger.yaml -o openapi.yaml, then point Scalar at the converted file.
How does Scalar handle authentication in the reference UI?
Scalar reads securitySchemes from your OpenAPI spec and renders the matching credential inputs for API keys, HTTP Basic, and OAuth2 flows. Credentials entered in the UI stay in the browser and drive the built-in HTTP client; they are never persisted server-side.
Do I configure Scalar through a script tag or a package?
Both are supported. Use the data-configuration attribute on the standalone script tag for a static page, or import createApiReference from @scalar/api-reference to embed and configure it inside a React or Vue app.
Is self-hosting required for data residency?
Deploy the static build output to your own CDN or internal cluster to keep everything in your network. The build has no external runtime dependency once the assets are in place, so it satisfies typical data-residency requirements.
Related
- Developer Portal Frameworks & UI Setup — parent overview of renderer and portal choices.
- Migrating from Swagger to Scalar for better DX — step-by-step migration path.
- Redoc vs Scalar comparison — choosing between the two renderers.
- Redocly & OpenAPI UI Configuration — the static-generation alternative.
- Swagger UI Customization — the renderer many teams migrate away from.
Keeping that in mind turns most reference-quality work into specification work, which is where it belongs.