Symfony OpenAPI: From Contract-First Design to a Tested, Documented API
Most Symfony teams write the API first and the documentation later, usually as an afterthought once someone on the frontend asks what a field actually returns. A Symfony OpenAPI workflow flips that order. You write the OpenAPI spec before you write a single controller, and the spec becomes the thing both sides of the project argue about and agree on, rather than a PDF nobody opens after the kickoff meeting.
This post covers that full workflow: designing the contract first, generating Symfony scaffolding from it, and setting up the checks that stop the implementation from drifting away from what the spec promises. If your team already has a working API and wants to retrofit documentation onto it, the second half covers that path too.
Why contract-first instead of code-first
Code-first API documentation, where you annotate PHP classes and generate a spec from them, is the more common approach in the Symfony ecosystem, and there is nothing wrong with it for small projects. The problem shows up once more than one team depends on the API. A frontend team, a mobile team, and a partner integration all need to know what the API looks like before it is built, not after. If the spec is generated from code that does not exist yet, there is nothing for them to build against.
Contract-first design solves this by making the OpenAPI document the source of truth from day one. The backend team designs the spec, gets sign-off from consumers, and only then starts implementing. Anyone downstream can build against the contract using mock servers while the real endpoints are still in progress.
Designing the spec before you touch Symfony
Start in a dedicated design tool rather than a text editor. Stoplight and Redocly both give you a visual editor for OpenAPI 3.1 documents, inline validation as you type, and a way to preview the generated documentation immediately. Either one catches structural mistakes, like a missing required field or a response schema that does not match the request schema, well before they reach a pull request.
During this phase, treat the spec like a database migration: something other people need to review before it merges. Circulate the draft to whichever teams will consume the API. A mobile team that discovers a pagination scheme they cannot work with is much cheaper to fix in a design tool than in a shipped endpoint.
Once the spec stabilizes, export the OpenAPI 3.1 YAML or JSON and commit it to the repository. This file now has to survive contact with Symfony without silently going stale, which is where the rest of the workflow comes in.
Generating Symfony route stubs from the spec
With a validated spec in hand, the openapi-generator toolchain can produce PHP scaffolding: controller stubs, route definitions, and request and response DTOs that match the schema exactly. This saves the tedious part of translating a spec into Symfony conventions by hand, and it means the generated code and the spec start out in agreement.
Generated stubs are a starting point, not a finished feature. You will still write the business logic, wire up the Doctrine repositories, and handle authentication. Keep the generated layer thin: a controller that validates the incoming request against the schema and hands off to a service class the generator never touches. That separation matters later, because regenerating stubs after a spec change should not risk overwriting logic you wrote by hand.
Keeping the spec and implementation honest with openapi-psr7-validator
The real risk in a contract-first setup is not the initial generation, it is the slow drift afterward. Someone adds a field to the response and forgets to update the spec. Someone renames a query parameter in the controller but not in the YAML. Six months in, the single source of truth is quietly wrong.
The openapi-psr7-validator library closes this gap by validating real HTTP requests and responses against the OpenAPI schema at runtime, using Symfony's PSR-7 bridge. Wire it into your functional test suite so every test hitting a real endpoint also checks that the request and response match the spec. When a developer adds a new field to a response without updating the schema, the test fails immediately, with a message pointing at the exact property that is out of contract, instead of surfacing as a confused bug report from a consumer three sprints later.
Retrofitting a spec onto existing Symfony code
If you are not starting from scratch, and most teams are not, NelmioApiDocBundle takes the opposite direction: instead of generating code from a spec, it generates an OpenAPI spec from PHP attributes already present on your controllers and DTOs. This is the practical entry point for teams whose API was built without any contract at all.
The bundle reads your existing route definitions, request classes, and response serialization to produce a spec that reflects what the API actually does today, not what it was supposed to do. That first generated spec is rarely clean. Expect to spend time adding missing descriptions, fixing response codes that were never documented correctly, and tightening schemas that were left too loose, an endpoint typed as returning a generic object instead of the actual shape of the data, for example.
Once that first spec is generated and cleaned up, validate it against real consumers before treating it as authoritative. Run the same requests your frontend or partner integrations already make and check them against the new schema with openapi-psr7-validator. Anywhere the validator flags a mismatch, you have found either a bug in the existing API or a gap in the newly generated spec, and either way it needs a decision before the spec becomes the reference document everyone works from.
Serving the documentation
A spec that lives only in a Git repository does not help anyone outside the backend team. Both Swagger UI and Redoc can render an OpenAPI document as a browsable, interactive reference, and both integrate cleanly with a Symfony application: expose the raw spec at a route like /api/docs.json, then point either renderer at it.
Swagger UI leans toward interactive exploration, letting a developer send test requests directly from the browser against a running instance. Redoc produces a cleaner, reading-oriented layout that works better as a reference for partners who just need to look something up. Some teams run both, Swagger UI for internal development and Redoc for the version shared with external integrators.
Versioning the spec alongside the code
The OpenAPI document should live in the same repository as the Symfony application and move through the same branches and pull requests as any other code change. Treating it as a separate artifact maintained on its own schedule is how specs end up out of date within a few weeks.
When the API introduces a breaking change, version the spec the same way you version the API itself, whether that is a path prefix, a header, or whatever scheme your team already uses. The spec for each supported version should be retrievable on its own, so a consumer still on an older version is not looking at documentation for endpoints they cannot call yet.
The CI check that keeps the system honest
None of the above holds up without enforcement. Add a CI step that runs the openapi-psr7-validator checks against your functional test suite on every pull request, and fail the build if any request or response does not match the spec. This is the step that turns the idea of a spec as the source of truth into something actually true.
The rule is simple: no pull request merges if the implementation and the spec disagree. That includes pull requests that only touch the spec, since a schema change without a corresponding implementation update is just as much a form of drift as the reverse. Teams that skip this step tend to end up back where they started, with a document that was accurate on the day it was written and increasingly fictional every day after.
Where this fits into a bigger Symfony project
A contract-first OpenAPI setup is one piece of a larger discipline around code quality consulting, the kind of review that catches drift, missing tests, and architectural shortcuts before they become expensive to fix. If you are building this out on a new Symfony application, it also connects to decisions covered under custom software development, particularly around how much API surface to expose and to which consumers.
If your team is dealing with a Symfony API that grew organically and now needs a real contract retrofitted onto it, or you are starting a new API and want the contract-first setup done right the first time, reach out at hello@wolf-tech.io or visit wolf-tech.io. We have done both, and the second one is always less work than it looks from the outside.

