Download the PHP package jayanta/laravel-api-versionist without Composer
On this page you can find all versions of the php package jayanta/laravel-api-versionist. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download jayanta/laravel-api-versionist
More information about jayanta/laravel-api-versionist
Files in jayanta/laravel-api-versionist
Package laravel-api-versionist
Short Description Elegant API versioning for Laravel — transform requests and responses across versions automatically.
License MIT
Informations about the package laravel-api-versionist
Laravel API Versionist
Your API changes. Your controllers don't.
Inspired by Stripe's API versioning architecture — you write small transformer classes that describe what changed between versions. The package upgrades old requests and downgrades new responses automatically. Your controllers always speak the latest version.
Supports Laravel 10, 11, 12 & 13 · PHP 8.1–8.5
The problem, in short
You shipped v1. A mobile app depends on it. Now you need v2 with different field names. Your options are: duplicate every controller per version (bug fixes applied N times), scatter if/else version checks everywhere, or use this package — one controller, one version, transformers handle the rest.
A v1 client hits this endpoint and gets back username, role — automatically downgraded by the transformer you wrote once.
How it works
Old request comes in → upgraded to latest → controller processes it → response downgraded back to the client's version. Your controller never changes.
Installation
Laravel auto-discovers the service provider. No manual registration.
Supported versions
| Laravel | PHP | Tested against |
|---|---|---|
| 13.x | 8.3 – 8.5 | testbench 11, PHPUnit 12 |
| 12.x | 8.3 – 8.5 | testbench 10, PHPUnit 11 |
| 11.x | 8.2 – 8.4 | testbench 9, PHPUnit 11 |
| 10.x | 8.1 – 8.4 | testbench 8, PHPUnit 10 |
Quick start
1. Generate a transformer:
2. Define what changed:
3. Register it:
If you leave latest_version as 'v1' after adding a V2Transformer, no transformers will run. Always update this value.
4. Add middleware:
5. Test it:
Transformers in depth
Each transformer describes one version transition. A V2Transformer handles v1→v2. A V3Transformer handles v2→v3. The package chains them automatically.
Fields you don't touch pass through unchanged. A request with age, city, custom_field keeps all of them — transformers only modify what they explicitly reference.
Attribute-based metadata
Instead of overriding version(), description(), and releasedAt(), you can declare the same metadata with the #[ApiVersion] class attribute:
Method overrides always win over the attribute, so existing transformers are unaffected.
Constructor injection
Transformers are resolved through Laravel's service container, so DI works:
Keep DB access in transformers read-only and lightweight. If a transformer needs heavy queries, the version gap is probably too wide for this pattern.
When NOT to use transformers
Transformers handle data shape changes — field renames, restructures, type conversions.
For behavior changes, use the request macros the package provides:
Transformers = structure. Application code = behavior. If your breaking change is behavioral, separate controllers are probably cleaner.
Multi-version example
Three versions, showing the full upgrade/downgrade chain:
| Version | Fields |
|---|---|
| v1 | username, role, is_active |
| v2 | handle, role, is_active |
| v3 | handle, roles[], status |
A v1 client sends: { "username": "janedoe", "role": "admin", "is_active": true }
Upgrade chain:
- V2Transformer:
username→handle - V3Transformer:
role→roles[],is_active→status - Controller receives:
{ "handle": "janedoe", "roles": ["admin"], "status": "active" }
Downgrade chain (response goes back through V3 then V2):
- V3Transformer:
roles[]→role,status→is_active - V2Transformer:
handle→username - v1 client receives:
{ "username": "janedoe", "role": "admin", "is_active": true }
Version detection
Four strategies, tried in config order. First match wins.
| Strategy | Example |
|---|---|
url_prefix |
GET /api/v2/users |
header |
X-Api-Version: v2 |
query_param |
GET /api/users?version=v2 |
accept_header |
Accept: application/vnd.api+json;version=2 |
No version detected → falls back to default_version (usually 'v1'). Unknown version in strict mode → HTTP 400.
Configuration reference
| Key | Default | Description |
|---|---|---|
default_version |
'v1' |
Fallback when no version detected |
latest_version |
'v1' |
Must match your highest transformer |
transformers |
[] |
Array of transformer class names |
deprecated_versions |
[] |
Version → sunset date map ('v1' => '2025-12-31' or 'v1' => ['deprecated_at' => ..., 'sunset' => ...]) |
rfc_compliant_headers |
false |
Emit Sunset as RFC 8594 HTTP-date and Deprecation as RFC 9745 timestamp |
strict_mode |
false |
true = unknown versions throw HTTP 400 |
response_data_key |
'data' |
Key to transform in responses. null = entire body |
request_data_key |
null |
Key to transform in requests. null = entire body |
add_version_headers |
true |
Adds X-Api-Version and X-Api-Latest-Version headers |
detection_strategies |
[...] |
Ordered list of detection strategies |
header_name |
'X-Api-Version' |
Header name for header strategy |
query_param |
'version' |
Param name for query_param strategy |
changelog.enabled |
false |
Expose version metadata as JSON endpoint |
changelog.endpoint |
'/api/versions' |
Changelog URL path |
Important: The default response_data_key is 'data'. If your controller returns flat JSON (not wrapped in {"data": {...}}), set this to null or transformers won't touch the response.
Response headers
With add_version_headers enabled:
For deprecated versions:
Mark versions as deprecated in config:
Headers follow RFC 8594. Deprecated versions still work normally.
Standards-compliant wire formats
By default the package emits the widely-used legacy formats shown above (Deprecation: true, Sunset as the raw configured date). Enable rfc_compliant_headers to switch to the standards-track formats:
The plain string form of deprecated_versions keeps working in both modes; without a deprecated_at date, Deprecation falls back to true.
Changelog endpoint
Enable changelog.enabled to register a JSON endpoint (route name api-versionist.changelog) that serves the same version metadata as php artisan api:changelog --format=json — minus internal transformer class names:
This is the endpoint the Link: rel="successor-version" deprecation header points clients to.
Request macros
After the middleware runs, every Request gets these:
Useful for version-specific behavior that doesn't belong in transformers:
Artisan commands
Envelope mode
If your API wraps responses in {"data": {...}, "meta": {...}}, set response_data_key to 'data' so transformers only touch the data portion:
Only "data" gets transformed. "meta", "links", pagination — all untouched.
If your controller returns flat JSON, set both to null.
Known limitations
Flat array responses — If you return [{...}, {...}] (a list of objects), the transformer sees the array, not individual items. Wrap lists in {"data": [...]} and use response_data_key => 'data'.
Nested keys — The package transforms one location (top-level or response_data_key). It won't recursively walk {"user": {...}, "company": {...}}. Keep transformable data at one level.
Lossy transforms — Some field changes can't be perfectly reversed. Converting an array to a scalar (e.g. roles[] → role) drops extra elements. Design transforms that round-trip cleanly, or accept the data loss and document it.
When separate controllers are the better choice
This pattern works best for structural changes — field renames, payload reshaping, type conversions.
Consider separate controllers when:
- Changes are behavioral, not structural (different auth, pricing, business rules)
- Version differences are so large that transformers become hard to follow
- You're maintaining very old legacy systems where scattered logic makes debugging harder
No universally correct approach. Transformers reduce duplication for structural versioning. Separate controllers give clearer isolation when behavior diverges.
Testing
Transformers are plain PHP — test them directly:
For transformers with constructor injection, mock the dependency:
Or validate everything at once: php artisan api:audit
FAQ
Do I need to change my controllers for every version? No. Controllers always return the latest version. You write one transformer per version step.
What if v1 and v3 are completely different? Write one transformer per step. V2Transformer handles v1→v2, V3Transformer handles v2→v3. The package chains them. You never write a combined v1→v3 transformer.
Can I use date-based versions like 2024-01-15?
Yes. The parser accepts both numeric (v1, v2.1) and date-based (2024-01-15) formats. Dates are compared chronologically. You can even mix them — date versions sort higher than numeric ones.
Prior art
This pattern was publicly documented by Stripe in 2017 (Brandur Leach), adopted by Intercom in 2018, and open-sourced for Ruby by Keygen. This package brings the same idea to Laravel.
How it compares
| Versionist | request-migrations | api-evolution | vermillion | |
|---|---|---|---|---|
| Laravel 10–13 / PHP 8.5 | ✅ | ❌ (dormant, Laravel ≤6 era) | partial | partial |
| Detection strategies (URL/header/Accept/query) | ✅ 4 | header only | header only | ✅ |
| RFC 8594 + RFC 9745 deprecation headers | ✅ | ❌ | ❌ | ❌ |
| JSON changelog endpoint | ✅ | ❌ | ❌ | ❌ |
| Attribute-based registration | ✅ | ❌ | ❌ | ❌ |
Transformer audit CLI (api:audit) |
✅ | ❌ | ❌ | ❌ |
| Date-based versions (Stripe-style) | ✅ | ❌ | ❌ | ✅ |
Coming from request-migrations? See the migration guide.
Credits
Contributing
Contributions welcome:
- Fork the repository
- Create a feature branch
- Write tests — all PRs must include tests
- Run
composer test - Submit a pull request
License
MIT. See LICENSE for details.
All versions of laravel-api-versionist with dependencies
illuminate/console Version ^10.0|^11.0|^12.0|^13.0
illuminate/contracts Version ^10.0|^11.0|^12.0|^13.0
illuminate/filesystem Version ^10.0|^11.0|^12.0|^13.0
illuminate/http Version ^10.0|^11.0|^12.0|^13.0
illuminate/routing Version ^10.0|^11.0|^12.0|^13.0
illuminate/support Version ^10.0|^11.0|^12.0|^13.0
symfony/http-foundation Version ^6.2|^7.0|^8.0