Aug 20, 2026 · PHP · Symfony · OpenAPI · ~8 min read

Serve a modern API reference in Symfony with Scalar

1. OpenAPI in real projects

Working with PHP, you constantly face the need to keep your documentation up to date. Most modern PHP projects are API-first: the backend is PHP, and the frontend is built separately.

That leaves two paths:

  • Spec-first — first you write a specification of all possible routes with input/output parameters, errors, and so on. Often that's a YAML OpenAPI file;
  • the OpenAPI file is generated from attributes and annotations in the code.

In the first case, you implement what the spec says and can verify that everything matches. In the second, the schema is always current (after an automatic regeneration).

The de-facto standard in Symfony is NelmioApiDocBundle. Up to version 4 it still shipped Swagger UI or Redoc as the UI. If you look for other UI options, you find Scalar — a fresh, modern, open-source API Reference renderer (support is announced in NelmioApiDocBundle 5+). But NelmioApiDocBundle generates OpenAPI from attributes — it's the reference representative of the second type.

A look at the competition — Laravel — shows more variety there: Scribe, Scramble, and an official Scalar integration.

What I wanted was a lightweight, modern UI for rendering a ready-made OpenAPI YAML file — for working with API docs the spec-first way. And Scalar seems perfect for that. Except there is no out-of-the-box integration for Symfony. To get Scalar into a Symfony project, you had to copy an HTML page with a <script> tag from the Scalar docs and wire the configuration yourself.

Scalar's own official integration list covers 30+ frameworks (Express, FastAPI, NestJS, Spring Boot, Laravel) — but no Symfony. And there was not a single package on Packagist that plugged Scalar into Symfony.

In one of my projects I follow the OpenAPI-first approach: spec first, implementation second. I didn't want to drag the heavyweight NelmioApiDocBundle into the project. The choice fell on Scalar. One thing was wrong: no integration. I could have integrated it in-project by hand, but I realized it could be packaged as a bundle and reused in other projects. In one evening I wrote the first version of the Symfony bundle, the tests and the CI matrix. I tested it on my own project — and then published it.

2. A closer look at what Scalar is

Scalar is an open-source API platform for working with OpenAPI documents.

Two parts:

  • API Reference — an interactive renderer for OpenAPI 3.x; loads the spec client-side, with a built-in API client (the ability to run test requests), code snippets in different languages (curl, PHP, Python, Go), various themes and auth schemes;
  • API client — a Postman-class desktop app that works offline and reads the same OpenAPI files.

As of now on GitHub: about 16,000 stars, created in 2023, more than 100 releases — an actively maintained project.

There is an integration for Laravel (scalar/laravel, official, in the scalar org) — and that's the reference to look at when building your own bundle. What does it do? A "thin package" that serves a single page with Scalar, pointed at any OpenAPI document.

3. The bundle: what it does

alex-frolov/scalar-symfony renders the Scalar API Reference in Symfony from any OpenAPI document. One route, no coupling to how the spec was generated: it can be a static openapi.yaml, swagger-php, NelmioApiDocBundle or API Platform.

To install and configure it you edit two files.

Add the bundle:

composer require alex-frolov/scalar-symfony

Edit the settings:

# config/packages/scalar_symfony.yaml
scalar_symfony:
    url: '/openapi.yaml'          # your OpenAPI document (required)

    path: '/scalar'               # route (default: /scalar)
    cdn: 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.65.1'

    configuration:
        theme: 'default'
        metaData:
            title: 'API Reference'

    scalar_options:               # any Scalar option, passed through as is
        darkMode: false
        layout: 'modern'

    access_control:
        mode: public              # or 'attribute' + security attribute
# config/routes.yaml
scalar_symfony:
    resource: '@ScalarSymfonyBundle/config/routes.php'

That's it — the reference is available at /scalar. The page is a small HTML document with the CDN script and Scalar.createApiReference('#scalar-api-reference', {...}), with the config serialized XSS-safely (JSON_HEX_TAG/APOS/AMP/QUOT).

4. A real request, a real 201

The best way to show this is a live example, not a mockup. The bundle serves the API reference of the Tender Platform (Symfony 8.5, highload auction API; the spec is OpenAPI 3.1). Through "Test Request" I opened the POST /auth/register endpoint — "Company registration (creates a pending company + the first admin user)" — filled the JSON body, hit Send, and got:

HTTP/1.1 201 Created  (794 ms)
{
  "company_id": "0c7702c6-9667-4ea3-8caa-df4990522ee7",
  "user_id": "86a40d70-5ef9-44fd-882b-8f703e10df7e",
  "verification_status": "pending"
}

A real response with real data. When testing, there are many more options, more variability than Swagger UI offers — it works as an interactive client.

Scalar API Reference in Symfony: the live 201 response from POST /auth/register in Test Request — real UUIDs, 794 ms
The live 201 response in Scalar API Reference: POST /auth/register on the Tender Platform, real backend

5. Quality bar

The final state:

  • 16 functional tests with 46 assertions;
  • static analysis through PHPStan level max;
  • code style control via PHP-CS-Fixer, the @Symfony rules;
  • CI checks: PHP 8.2/8.3/8.5 with Symfony 6.4/7.2/7.4/8.0, including the --prefer-lowest option (5 jobs), a no-dev smoke test and composer validate --strict;
  • security hardening in the config: compile-time validation added — attribute mode without Symfony Security fails cache:clear with a clear error instead of an HTTP 500 on the first request; empty cdn, a path without a leading /, an empty attribute — everything is now blocked at the config level;
  • security documentation added, including SRI (SHA-384 for the pinned CDN file), CSP/nonce recommendations, and a self-hosting recipe.

Two artifacts found along the way.

First: PHPUnit 11.5 flags tests as risky when Symfony's ErrorHandler outlives a kernel boot — fixed by restoring the exception handler in tearDown().

Second: the container is cached by kernel class and environment, which is why functional tests with different bundle configs must use a unique cache-dir per config — otherwise tests run against a stale container with broken state.

6. CI and problems

After the push to the GitHub repository, CI started. Half of the GitHub Actions jobs began to fail with a random error:

Your github oauth token for github.com contains invalid characters

As it turned out, the cause was setup-php: it writes the Actions GITHUB_TOKEN (prefixed ghs_) into composer's global auth.json, and Composer 2.8 only accepts ghp_/gho_/github_pat_ tokens. The failures were flaky — they depended on whether the rate limit actually forced composer to use the token, so a part of the jobs passed. Even composer config --unset failed with the same error. The fix: delete auth.json on the virtual runner before the composer validate --no-check-publish check, and keep the token for dependency installation, where it protects against the rate limit.

7. Roadmap and the proposal

I opened a proposal in the Scalar organization:

Discussion #9920 — "Proposal: official Symfony integration (scalar/symfony)"
github.com/scalar/scalar/discussions/9920

The gist: adopt the bundle into the scalar org as scalar/symfony, following exactly the scalar/laravel path — transfer or fork, add the _integration: symfony tracking, list Symfony in the official integrations. The bundle is published, tested, and running in production — the adoption effort is almost zero, and Symfony developers get what Laravel already has.

If you are a Symfony developer and want this, react and comment on the discussion. Maintainer attention follows community signal.

8. How to start using it

  1. composer require alex-frolov/scalar-symfony
  2. Point scalar_symfony.url at any OpenAPI document (or generate one with NelmioApiDocBundle / swagger-php)
  3. Import the routes, open /scalar

The README covers everything in detail.

9. Summary

Scalar had no official Symfony integration; there was a need, and the bundle was built.

  • Bundle v0.1.0, alex-frolov/scalar-symfony;
  • Supported versions: PHP >= 8.2, Symfony 6.4 / 7.2+ / 8.x;
  • Used on the Tender Platform.

Code: github.com/alex-frolov/scalar-symfony, MIT. Proposal: discussion #9920.

Discuss your problem

Building an API on Symfony and want modern documentation? I can help set up Scalar API Reference and NelmioApiDocBundle, design an OpenAPI spec, and wire documentation checks into CI. I reply within 24–48 hours.