Versioning and upgrades
Semantic versioning answers one question: “will this upgrade break me?” For a normal library that means the public API, the functions and classes you call. For a code generator the question is subtler, because the thing that can break you is not the generator’s own API: it is the code the generator writes. A patch to the emitter can change every Data class in your repo without changing a single method signature you call.
This page states how openapi-laravel defines a breaking change, what it promises to keep stable, and the upgrade flow that follows from those promises.
The two surfaces
Section titled “The two surfaces”A code generator has two distinct compatibility surfaces, and they version differently.
- The tool surface. The CLI commands and flags (
openapi:generate,openapi:check,--spec,--controllers, …) and the config file keys. This is the classic library API. The frozen surface includes the intentional flag asymmetry between the artisan commands (config-file-first) and the standalone binary (which carries extra flags such as--suffixand--max-depthbecause it has no framework config); adding those flags to the artisan commands later would be additive and non-breaking. (Today the support classes the generated code imports also sit here, because they ship from the generator package; once #40 lands in 0.9.0 they move into the output surface instead, see below.) - The output surface. The actual PHP the generator writes for a given spec: class and property names, the shape of
rules(), the type hints, the file set. You do not call this as an API, but you commit it, and your application depends on its behavior.
A normal library only has surface 1. A generator’s stability promise has to cover surface 2 as well, because a silent change there is exactly what consumers cannot see coming.
One thing the tool surface deliberately does not include: the PHP classes under CodeWithAgents\OpenApiLaravel\*. They are internal implementation, marked @internal (issue #69), and may change in any release without notice. The BC promise covers the CLI commands and flags, the config keys, the exit codes, and the generated output, per the two surfaces above; consumers interact with the artisan commands, the standalone binary, the config file, and the generated code, never with the generator classes directly.
What counts as a breaking change
Section titled “What counts as a breaking change”Under this policy, a major version bump (post-1.0.0) is required for any of:
Output-surface breaks (the generator-specific part):
- A change to the generated output for an unchanged spec that alters its shape: different class or property names, a changed
rules()result, a different type hint, a moved or renamed file, a changed#[MapName]or attribute. Pure formatting churn counts too, because the drift check compares byte-for-byte. - A change to validation behavior: a payload that the generated
rules()previously accepted is now rejected, or vice versa. This is the most consequential break, because it can start rejecting real production traffic. - A change to the support classes (
MultipleOfRule,Rfc3339DateTimeRule,Rfc3339TimeRule,Iso8601DurationRule,HostnameRule,MapObjectTransformer, andNoUnknownPropertiesRule) that changes their validation or serialization behavior. Where these classes live, and therefore which surface governs them, is settled by #40 (runtime coupling): Option B is implemented, the classes are inlined into the consumer’s own output (the Data namespace plus a\Supportsuffix). They are now part of the output surface, owned, committed, and drift-checked, so a change to their behavior is an output-shape change under the same major-bump rule above. The previous silent-change surface (acomposer updateswapping their behavior under committed code) is closed: the support classes live in your repo, and a rule changes only when you regenerate and review the diff.
Tool-surface breaks (the classic part):
- Removing or repurposing a CLI flag or a config key, or changing its default in a way that changes output.
- A floor bump: raising the minimum supported PHP, Laravel, or
spatie/laravel-dataversion.
What is explicitly not breaking (a minor or patch, no major bump needed):
- A new opt-in flag or config key that defaults to the existing behavior.
- A new stderr diagnostic or warning (the generated code is unchanged).
- Documentation changes.
- A bug fix in the generator that changes output to be more correct is still technically an output change, see the honest caveat below.
What if laravel-data ships a v5
Section titled “What if laravel-data ships a v5”The generated code extends spatie/laravel-data, and the package pins ^4.23. A laravel-data v5
release therefore does not reach you through this package: Composer keeps resolving 4.x until
openapi-laravel itself widens or raises the constraint. When a v5 exists, it gets evaluated like
any other floor change under the rules above. If supporting it changes the generated output or the
behavior of the generated code in any way (different base-class semantics, changed validation or
serialization), the bump ships as a major release of this package, with a changelog entry and a
regeneration note, exactly like the floor-bump bullet. If v5 can be supported with byte-identical
output, the constraint could instead be widened (^4.23 || ^5.0) in a minor, since nothing
observable changes for existing consumers. Either way, nothing is silent: the
drift check and the upgrade flow below apply unchanged.
The upgrade flow (regeneration is the contract)
Section titled “The upgrade flow (regeneration is the contract)”Because you own the generated code, an upgrade is not “install and trust”. It is install, regenerate, review, commit:
- Upgrade the package:
composer update codewithagents/openapi-laravel. - Regenerate: re-run
openapi:generate(with the same flags you use in CI). - Review the diff. Git shows exactly what changed in your committed Data classes, controllers, and routes. A major upgrade’s changelog tells you what to expect; the diff confirms it.
- Commit the regenerated output.
This is the same loop you run when the spec changes. An upgrade of the generator is just the other input changing. The review step is where you catch anything the changelog did not prepare you for, and it is non-negotiable: never composer update the generator without regenerating and reviewing.
Interaction with the drift check
Section titled “Interaction with the drift check”The drift check (openapi:check) compares your committed code byte-for-byte against what the installed generator would produce. That has a direct, by-design consequence for upgrades:
Practically: pin the generator version (Composer does this), and treat a generator bump as a deliberate, reviewed change set, a PR that bumps the version and commits the regenerated output together, so the drift check passes on that PR and your history shows exactly what the upgrade did to your code.
0.x versus 1.0.0
Section titled “0.x versus 1.0.0”The project is deliberately in 0.x while the output format is still settling (decision 8).
- Pre-1.0.0 (0.x): the output format is not frozen. Minor releases (
0.5.0,0.6.0,0.7.0) have changed generated output as correctness work landed (the silent-validation sweep, the differential oracle fixes, hostname enforcement). The 0.x contract is “regenerate on every upgrade and review the diff”, with no promise that the diff is empty. Correctness beats stability while the format evolves. - 1.0.0 and after: 1.0.0 is the output-format freeze. From then on, the breaking-change definition above governs: an intentional change to output shape or validation behavior requires a major version, with a changelog entry and a regeneration note. The upgrade flow stays the same; the difference is that a minor or patch upgrade is now promised not to change output shape (correctness-fix caveat aside).
In short: 0.x says “expect output churn, always regenerate”; 1.0.0 says “output is stable across minors and patches, output changes are a major release you opt into”.
For a consolidated overview of the stability story, including the criterion for when 1.0.0 is tagged and what the freeze covers, see Stability & the 1.0 promise.