Render modern Scalar API References in Symfony from any OpenAPI document.
Scalar is an open-source, beautiful and interactive API reference renderer — a modern replacement for the classic Swagger UI. This bundle does one thing: serve the Scalar reference page from your Symfony app in a single route, pointing at your OpenAPI spec.
No OpenAPI generation included (and none required) — bring your spec from any source:
a static openapi.yaml, swagger-php, NelmioApiDocBundle, API Platform, or an
external URL.
| Scenario | NelmioApiDocBundle 5 (+Scalar UI) | scalar-symfony |
|---|---|---|
| I already use Nelmio | ✅ built-in Scalar renderer | ✅ compatible (just point url at your nelmio spec route) |
| I use swagger-php / static spec / API Platform | ❌ would need Nelmio as a dependency | ✅ standalone, no Nelmio required |
| Lightweight docs page, one route | — | ✅ one bundle, one route |
| Symfony 6.4+ | needs upgrade to v5 | ✅ works on 6.4 / 7.2+ / 8.x |
The bundle powers the API reference of the Tender Platform (Symfony 8.1, highload auction platform) — a real OpenAPI document served through a single route, no other documentation tooling involved:

/openapi.yaml)composer require alex-frolov/scalar-symfony
Create config/packages/scalar_symfony.yaml:
scalar_symfony:
# Public URL of your OpenAPI document (required)
url: '/openapi.yaml'
# Route path (default: /scalar)
path: '/scalar'
# CDN of the Scalar standalone bundle (default: jsdelivr)
cdn: 'https://cdn.jsdelivr.net/npm/@scalar/[email protected]'
# Well-known options (theme, metadata, integration identifier)
configuration:
theme: 'default' # default | alternate | none ...
metaData:
title: 'API Reference'
description: 'Public API documentation'
# Any other Scalar option, passed through verbatim
scalar_options:
darkMode: false
layout: 'modern'
# Access control
access_control:
mode: public # 'public' | 'attribute'
attribute: ~ # e.g. ROLE_ADMIN (required when mode: attribute)
Import the bundle routes (add to config/routes.yaml):
scalar_symfony:
resource: '@ScalarSymfonyBundle/config/routes.php'
That's it. Your API reference is now served at /scalar (route name
scalar_symfony_reference).
public (default) — the reference is available to everyone (recommended for
public API docs; matches the default Laravel integration behaviour).attribute — the page requires a security attribute:scalar_symfony:
url: '/openapi.yaml'
access_control:
mode: attribute
attribute: ROLE_ADMIN
The attribute mode requires symfony/security-core
and an enabled security.authorization_checker service (i.e. Symfony Security configured).
Requests without the attribute get a 403.
Misconfiguration is detected at container compile time: enabling attribute without
Symfony Security makes bin/console cache:clear (and any container build) fail with a clear
message instead of surfacing an HTTP 500 on the first request.
The attribute mode protects only the HTML reference page (/scalar). The
OpenAPI document itself (the url you configured, e.g. /openapi.yaml) is fetched
client-side by the Scalar bundle in the user's browser — it is never served
through this bundle and the bundle does not proxy it.
Practical consequences:
attribute.Treat attribute as protection of the page, not of the spec. If the spec must
stay confidential, protect the document with the same rule (or use a protected
proxy endpoint — planned).
The bundle serves a small HTML page that loads the Scalar standalone bundle from the
CDN and calls Scalar.createApiReference('#scalar-api-reference', { ... }) with your
configuration and the url of the OpenAPI document. The document itself is loaded
client-side by Scalar — the bundle never parses or proxies it.
The reference page loads the Scalar standalone bundle from a CDN and runs an
inline Scalar.createApiReference(...) call. If the CDN is compromised (or the
cdn value is replaced), arbitrary JavaScript executes with your origin's
privileges. This section documents what you can do today. Built-in sri /
nonce configuration is a planned improvement (see the project TODO).
Download the standalone bundle once and serve it from your own origin — no third-party request at page load, no CDN trust at all:
# Pin the version and download the original package file (not the jsDelivr
# minifier output — its hash is not stable, see SRI below):
curl -fLo public/scalar/api-reference.standalone.js \
https://cdn.jsdelivr.net/npm/@scalar/[email protected]/dist/browser/standalone.js
shasum -a 384 public/scalar/api-reference.standalone.js
# config/packages/scalar_symfony.yaml
scalar_symfony:
url: '/openapi.yaml'
cdn: '/scalar/api-reference.standalone.js'
The file is served by your own web server with no extra dependency.
When keeping a third-party CDN, pin the expected content hash so a compromised or swapped file is rejected by the browser.
The root jsDelivr URL (…/@scalar/[email protected]) is minified on
the fly by jsDelivr — the response carries the warning "Do NOT use SRI
with dynamically generated files!". Its hash can change without a package
release, so it is not SRI-safe.
The explicit file URL (…/dist/browser/standalone.js) is served as-is from
the immutable npm tarball. Its SHA-384 for 1.65.1 is:
sha384-G6dkutu2k5IYVyNESLoFIpgaHx38IJTZ/HhrwN0fecTle9te75y8Kru3rJEJ0ZJV
The bundle does not render integrity yet. To apply SRI today, override the
template (templates/bundles/ScalarSymfonyBundle/reference.html.twig) and add
the attributes to the CDN script tag:
<script src="{{ cdn }}"
integrity="sha384-G6dkutu2k5IYVyNESLoFIpgaHx38IJTZ/HhrwN0fecTle9te75y8Kru3rJEJ0ZJV"
crossorigin="anonymous"></script>
crossorigin="anonymous" is required for cross-origin SRI (CORS-enabled
request so the integrity check works).
If your app sends a Content-Security-Policy with a restrictive script-src,
the inline Scalar.createApiReference(...) script must be allowlisted —
prefer a per-request nonce over 'unsafe-inline'. A nonce-aware config option
is a planned improvement; until then, override the template and add
nonce="{{ csp_nonce }}" to both <script> tags.
composer install
composer test # PHPUnit functional tests
composer analyse # PHPStan level max
composer fix # PHP-CS-Fixer
For the reproducible Docker validation pipeline from the repository root:
./validate.sh
The pipeline runs the regular checks in a pinned Composer image and then tests PHP 8.2 with Symfony 6.4 and lowest dependencies in a disposable copy. The images can be overridden when validating a security-patched base image:
COMPOSER_IMAGE=composer:2.8.12 \
PHP82_BASE_IMAGE=php:8.2-cli-bookworm \
./validate.sh
MIT — see LICENSE.
Heavily inspired by scalar/laravel (Scalar OpenAPI References in Laravel) and the Scalar project (open-source API platform, MIT).
How can I help you explore Laravel packages today?