Scalar UI
openapi.SetupDocsEndpoints serves a Scalar reference page that reads your generated spec. Readers get a searchable endpoint list, request and response schemas, and a client for trying calls.
Serve it
openapi.SetupDocsEndpoints(app)
| Path | Contents |
|---|---|
/docs | The Scalar reference page |
/openapi.json | The generated document |
/docs/ | A permanent redirect to /docs |
All three are registered Public(), so they work without a token even when the rest of the app requires one.
Configure it
openapi.SetupDocsEndpoints(app, openapi.ScalarConfig{
Title: "Orders API",
Theme: "deepSpace",
Layout: "modern",
DarkMode: true,
ShowSidebar: true,
HideModels: false,
FavIcon: "/favicon.ico",
})
| Field | Values | Default |
|---|---|---|
Title | Any string, used as the page title | API Documentation |
Theme | default, alternate, moon, purple, solarized, bluePlanet, saturn, kepler, mars, deepSpace | default |
Layout | modern, classic | modern |
DarkMode | bool | false |
ShowSidebar | bool | true in the default config |
HideModels | bool, hides the schema list | false |
CustomCSS | CSS injected into the page | empty |
FavIcon | URL of a favicon | empty |
openapi.DefaultScalarConfig() returns the defaults if you want to start from them and change one field.
Use custom paths
openapi.ServeScalar(app, "/openapi.json", "/reference", openapi.ScalarConfig{
Title: "Orders API",
})
The first path serves the spec, the second serves the UI. The UI fetches the spec path from the browser, so it has to be reachable by whoever opens the page.
Restrict who can read the docs
Public docs are convenient internally and often unwanted in production. Two straightforward options.
Serve them only outside production:
if os.Getenv("APP_ENV") != "production" {
openapi.SetupDocsEndpoints(app, cfg)
}
Or gate them with middleware on a group, keeping the paths but requiring a credential:
docs := app.Group("/internal").Use(basicAuth(os.Getenv("DOCS_USER"), os.Getenv("DOCS_PASS")))
docs.GET("/openapi.json", specHandler).Public()
docs.GET("/docs", uiHandler).Public()
Because ServeScalar registers on the app rather than a group, this variant means writing the two small handlers yourself, as shown in OpenAPI.
Style it
CustomCSS is injected into a <style> tag, which is enough to match a brand:
openapi.SetupDocsEndpoints(app, openapi.ScalarConfig{
Title: "Orders API",
DarkMode: true,
CustomCSS: `
:root {
--scalar-color-accent: #00add8;
--scalar-font: "Inter", system-ui, sans-serif;
}
`,
})
Helper functions exist for building a config in steps:
cfg := openapi.DefaultScalarConfig()
openapi.ApplyScalarOptions(&cfg,
openapi.WithCustomTheme("moon"),
openapi.WithDarkMode(true),
openapi.WithLayout("classic"),
)
openapi.SetupDocsEndpoints(app, cfg)
Know the constraints
cdn.jsdelivr.net. A machine with no internet access, or a strict Content-Security-Policy, will render an empty page. If either applies, serve the spec with ezz and host the viewer yourself, or vendor the bundle and serve it from your own static assets.The spec is built once, on the first request to the spec path, and served from cache after that. Responses carry an ETag, so a client sending If-None-Match gets a 304 Not Modified instead of the full document.
Because the document is cached, routes registered after that first request will not appear in it. Register every route before the server starts serving, which is what route setup normally does anyway.
Generator.SpecJSON is the entry point the endpoint uses: it builds the document once, caches the bytes, and is safe to call from any number of goroutines. Generator.GenerateFromApp returns the *Spec itself, for a build step that writes a spec file or a test that asserts on the document. It rebuilds on every call, but the spec it hands back owns its components, so you can hold it, marshal it, or edit it without the generator changing underneath you.