Skip to content

Fix: Scalar Not Working — API Docs Not Rendering, Try-It Not Sending Requests, or Theme Broken

FixDevs ·

Quick Answer

How to fix Scalar API documentation issues — OpenAPI spec loading, interactive Try-It panel, authentication configuration, custom themes, CDN and React integration, and self-hosting.

The Problem

Scalar renders a blank page or shows a loading spinner forever:

<script id="api-reference" data-url="./openapi.json"></script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
<!-- Blank page — no documentation visible -->

Or the “Try It” panel sends requests but they fail:

CORS error: Access to fetch blocked by CORS policy

Or the authentication section doesn’t pre-fill tokens:

Every request returns 401 even after entering the API key

Why This Happens

Scalar is an API documentation tool that renders interactive docs from OpenAPI specifications. It displays endpoints, schemas, and provides a “Try It” panel for live API testing:

  • The OpenAPI spec must be accessible — Scalar fetches the spec file via HTTP. If the URL is wrong, the file returns 404, or CORS blocks the request, Scalar can’t load the spec and shows a blank page.
  • Try-It requests go directly from the browser — the browser sends API requests to your server. If the API doesn’t include CORS headers (Access-Control-Allow-Origin), the browser blocks the response. This isn’t a Scalar issue — it’s a server configuration issue.
  • Authentication values must be configured — Scalar reads securitySchemes from the OpenAPI spec to show auth fields, but it doesn’t automatically fill in values. You must configure authentication through the Scalar options or have the user enter credentials manually.
  • The spec must be valid OpenAPI 3.x — Scalar supports OpenAPI 3.0 and 3.1. Swagger 2.0 specs need conversion. Invalid specs cause partial renders or missing endpoints.

Fix 1: Basic Setup (CDN)

<!-- Simplest setup — single HTML file -->
<!DOCTYPE html>
<html>
<head>
  <title>API Documentation</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
  <!-- Reference your OpenAPI spec -->
  <script
    id="api-reference"
    data-url="https://api.myapp.com/openapi.json"
    data-proxy-url="https://proxy.scalar.com"
  ></script>

  <!-- Load Scalar -->
  <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
<!-- Inline spec instead of URL -->
<script id="api-reference" type="application/json">
{
  "openapi": "3.1.0",
  "info": { "title": "My API", "version": "1.0.0" },
  "paths": {
    "/api/users": {
      "get": {
        "summary": "List users",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/User" }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "email": { "type": "string", "format": "email" }
        }
      }
    }
  }
}
</script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>

Fix 2: React Integration

npm install @scalar/api-reference-react
// app/docs/page.tsx — Next.js App Router
'use client';

import { ApiReferenceReact } from '@scalar/api-reference-react';
import '@scalar/api-reference-react/style.css';

export default function ApiDocs() {
  return (
    <ApiReferenceReact
      configuration={{
        spec: {
          url: '/api/openapi',  // URL to your OpenAPI spec
          // Or inline content:
          // content: openApiSpec,
        },
        // Theme
        theme: 'kepler',  // 'default' | 'alternate' | 'moon' | 'purple' | 'solarized' | 'kepler' | 'saturn' | 'bluePlanet' | 'deepSpace' | 'mars'

        // Hide certain sections
        hideModels: false,
        hideDownloadButton: false,

        // Pre-configured authentication
        authentication: {
          preferredSecurityScheme: 'bearerAuth',
          http: {
            bearer: {
              token: '',  // Pre-fill token
            },
          },
          // Or API key
          apiKey: {
            token: '',
          },
        },

        // Customize base URL for Try It
        servers: [
          { url: 'https://api.myapp.com', description: 'Production' },
          { url: 'http://localhost:3000', description: 'Local' },
        ],
      }}
    />
  );
}

Fix 3: Serve OpenAPI Spec from Next.js

// app/api/openapi/route.ts — dynamic OpenAPI spec
export async function GET() {
  const spec = {
    openapi: '3.1.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'API documentation for My App',
    },
    servers: [
      { url: 'https://api.myapp.com', description: 'Production' },
      { url: 'http://localhost:3000', description: 'Development' },
    ],
    paths: {
      '/api/users': {
        get: {
          tags: ['Users'],
          summary: 'List all users',
          parameters: [
            {
              name: 'page',
              in: 'query',
              schema: { type: 'integer', default: 1 },
            },
            {
              name: 'limit',
              in: 'query',
              schema: { type: 'integer', default: 20 },
            },
          ],
          responses: {
            '200': {
              description: 'List of users',
              content: {
                'application/json': {
                  schema: {
                    type: 'object',
                    properties: {
                      users: { type: 'array', items: { $ref: '#/components/schemas/User' } },
                      total: { type: 'integer' },
                    },
                  },
                },
              },
            },
          },
        },
        post: {
          tags: ['Users'],
          summary: 'Create a user',
          security: [{ bearerAuth: [] }],
          requestBody: {
            required: true,
            content: {
              'application/json': {
                schema: { $ref: '#/components/schemas/CreateUser' },
              },
            },
          },
          responses: {
            '201': {
              description: 'User created',
              content: {
                'application/json': {
                  schema: { $ref: '#/components/schemas/User' },
                },
              },
            },
          },
        },
      },
    },
    components: {
      schemas: {
        User: {
          type: 'object',
          properties: {
            id: { type: 'string', format: 'uuid' },
            name: { type: 'string' },
            email: { type: 'string', format: 'email' },
            createdAt: { type: 'string', format: 'date-time' },
          },
          required: ['id', 'name', 'email'],
        },
        CreateUser: {
          type: 'object',
          properties: {
            name: { type: 'string', minLength: 1, maxLength: 100 },
            email: { type: 'string', format: 'email' },
          },
          required: ['name', 'email'],
        },
      },
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
        },
        apiKey: {
          type: 'apiKey',
          in: 'header',
          name: 'X-API-Key',
        },
      },
    },
  };

  return Response.json(spec, {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Cache-Control': 'public, max-age=3600',
    },
  });
}

Fix 4: Hono Integration

npm install @scalar/hono-api-reference
import { Hono } from 'hono';
import { apiReference } from '@scalar/hono-api-reference';

const app = new Hono();

// Serve OpenAPI spec
app.get('/openapi.json', (c) => {
  return c.json(openApiSpec);
});

// Serve Scalar docs
app.get(
  '/docs',
  apiReference({
    spec: { url: '/openapi.json' },
    theme: 'kepler',
  }),
);

// Your API routes
app.get('/api/users', (c) => { /* ... */ });

Fix 5: Fix CORS for Try-It

The Try-It panel sends requests from the browser. Your API needs CORS headers:

// Next.js middleware — add CORS headers
// middleware.ts
import { NextResponse, type NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const response = NextResponse.next();

    response.headers.set('Access-Control-Allow-Origin', '*');
    response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');

    if (request.method === 'OPTIONS') {
      return new Response(null, { status: 204, headers: response.headers });
    }

    return response;
  }
}

// Or use Scalar's proxy for production APIs
// data-proxy-url="https://proxy.scalar.com"

Fix 6: Generate OpenAPI from Code

# From Zod schemas
npm install zod-to-openapi

# From ts-rest contracts
npm install @ts-rest/open-api
// Generate from Zod schemas
import { extendZodWithOpenApi, OpenAPIRegistry, OpenApiGeneratorV31 } from 'zod-to-openapi';
import { z } from 'zod';

extendZodWithOpenApi(z);

const registry = new OpenAPIRegistry();

// Register schemas
const UserSchema = registry.register('User', z.object({
  id: z.string().uuid().openapi({ example: '123e4567-e89b-12d3-a456-426614174000' }),
  name: z.string().openapi({ example: 'Alice' }),
  email: z.string().email().openapi({ example: '[email protected]' }),
}));

// Register endpoints
registry.registerPath({
  method: 'get',
  path: '/api/users',
  tags: ['Users'],
  summary: 'List users',
  responses: {
    200: {
      description: 'List of users',
      content: { 'application/json': { schema: z.array(UserSchema) } },
    },
  },
});

// Generate spec
const generator = new OpenApiGeneratorV31(registry.definitions);
const spec = generator.generateDocument({
  openapi: '3.1.0',
  info: { title: 'My API', version: '1.0.0' },
});

Still Not Working?

Blank page with no errors — the OpenAPI spec URL is unreachable. Check the browser’s Network tab for 404 or CORS errors on the spec fetch. If the spec is on a different domain, it needs CORS headers. Use the data-proxy-url attribute to route through Scalar’s proxy.

Try-It returns CORS errors — the API server must send Access-Control-Allow-Origin headers. For local development, add CORS middleware. For production APIs that can’t add CORS, use Scalar’s built-in proxy: add data-proxy-url="https://proxy.scalar.com" to the script tag.

Auth token not sent with requests — verify the OpenAPI spec has securitySchemes defined and that endpoints reference them with security: [{ bearerAuth: [] }]. Without the security declaration, Scalar doesn’t show the auth input or include tokens in requests.

Some endpoints are missing from the docs — Scalar renders every path in the OpenAPI spec. Missing endpoints mean they’re not in the spec. If using code generation (Zod, ts-rest), re-run the generator. Also check for tags filtering — if the spec has tags and you’ve configured tag filtering, some endpoints may be hidden.

For related API documentation issues, see Fix: ts-rest Not Working and Fix: Orval Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles