-
-
Notifications
You must be signed in to change notification settings - Fork 4
Version Lifecycle
Learn how to manage the complete lifecycle of your API versions from active to sunset.
Every API version goes through a predictable lifecycle:
┌──────────┐ ┌────────────┐ ┌─────────┐
│ Active │ → │ Deprecated │ → │ Sunset │
└──────────┘ └────────────┘ └─────────┘
↑
┌──────────┐
│ Beta │
└──────────┘
The current stable version. This is the default state for new versions.
ApiRoute::version('v2', function () {
Route::apiResource('users', UserController::class);
})->current();Characteristics:
- Full support and maintenance
- No deprecation headers
- Recommended for production use
Response headers:
X-API-Version: v2
X-API-Version-Status: activeA preview version for testing new features.
ApiRoute::version('v3', function () {
Route::apiResource('users', UserController::class);
})->beta();Characteristics:
- May have breaking changes
- Not recommended for production
- Used for early adopter testing
Response headers:
X-API-Version: v3
X-API-Version-Status: betaA version that is still functional but should be migrated away from.
ApiRoute::version('v1', function () {
Route::apiResource('users', UserController::class);
})->deprecated('2025-06-01');Characteristics:
- Still fully functional
- Deprecation headers included
- Limited or no new features
- Migration encouraged
Response headers:
X-API-Version: v1
X-API-Version-Status: deprecated
Deprecation: Sun, 01 Jun 2025 00:00:00 GMTA version that has reached end-of-life.
ApiRoute::version('v1', function () {
Route::apiResource('users', UserController::class);
})
->deprecated('2025-06-01')
->sunset('2025-12-01'); // Past date triggers sunsetCharacteristics:
- No longer available (if configured to reject)
- Returns error response
- Clients must upgrade
Response (if action = 'reject'):
{
"error": "api_version_sunset",
"message": "API version v1 is no longer available.",
"sunset_date": "2025-12-01T00:00:00+00:00",
"successor": "v2",
"migration_guide": "https://docs.example.com/migration/v1-to-v2"
}2024-01 2025-06-01 2025-12-01
│ │ │
│←───────── Active ─────────────────────────│←─── Deprecated ───→│←── Sunset
│ │ │
v v v
v1 released v1 deprecated v1 removed
v2 in development v2 becomes current v3 released
Using the Artisan command:
php artisan api:deprecate v1 --on=2025-06-01 --sunset=2025-12-01Or programmatically:
ApiRoute::version('v1', function () {
// ...
})
->deprecated('2025-06-01')
->sunset('2025-12-01')
->setSuccessor('v2');Using the Artisan command:
php artisan api:sunset v1Or by setting a past sunset date:
->sunset('2024-01-01') // Past date = immediately sunsetConfigure how sunset versions are handled:
// config/apiroute.php
'sunset' => [
'action' => 'reject', // 'reject', 'warn', 'allow'
'status_code' => 410, // HTTP Gone
'include_migration_url' => true,
],| Action | Behavior |
|---|---|
reject |
Return error response (default) |
warn |
Allow request but add warning headers |
allow |
Allow request without warning |
| Code | Meaning |
|---|---|
410 |
Gone (recommended) |
404 |
Not Found |
426 |
Upgrade Required |
When action = 'reject':
{
"error": "api_version_sunset",
"message": "API version v1 is no longer available.",
"sunset_date": "2025-12-01T00:00:00+00:00",
"successor": "v2",
"migration_guide": "https://docs.example.com/migration/v1-to-v2"
}Configure migration guides:
// config/apiroute.php
'documentation' => [
'migration_guides' => [
'v1' => 'https://docs.example.com/api/migration/v1-to-v2',
'v2' => 'https://docs.example.com/api/migration/v2-to-v3',
],
],use Grazulex\ApiRoute\Facades\ApiRoute;
class UserController extends Controller
{
public function index(Request $request)
{
if ($request->isDeprecatedVersion()) {
// Log usage of deprecated version
Log::info('Deprecated version used', [
'version' => $request->apiVersion(),
'user' => $request->user()?->id,
]);
}
// ...
}
}use Grazulex\ApiRoute\Facades\ApiRoute;
// Check version status
if (ApiRoute::isDeprecated('v1')) {
// Version is deprecated
}
if (ApiRoute::isSunset('v1')) {
// Version is sunset
}
if (ApiRoute::isActive('v2')) {
// Version is active
}$version = ApiRoute::getVersion('v1');
$version->isActive(); // bool
$version->isBeta(); // bool
$version->isDeprecated(); // bool
$version->isSunset(); // bool
$version->isUsable(); // bool (not sunset)Listen to lifecycle events:
use Grazulex\ApiRoute\Events\DeprecatedVersionAccessed;
use Grazulex\ApiRoute\Events\VersionDeprecated;
use Grazulex\ApiRoute\Events\VersionSunset;
// In EventServiceProvider
protected $listen = [
DeprecatedVersionAccessed::class => [
LogDeprecatedVersionUsage::class,
],
VersionDeprecated::class => [
NotifyApiConsumers::class,
],
VersionSunset::class => [
ArchiveVersionResources::class,
],
];See Events for more details.
Announce deprecation well in advance (3-6 months minimum).
->setSuccessor('v2')
->documentation('https://docs.example.com/migration/v1-to-v2')php artisan api:stats --period=30Consider using the warn action before reject:
// Phase 1: Warn users
'sunset' => ['action' => 'warn'],
// Phase 2: Reject requests
'sunset' => ['action' => 'reject'],Encourage migration with lower rate limits:
ApiRoute::version('v1', fn() => ...)
->deprecated('2025-06-01')
->rateLimit(100); // Lower limit
ApiRoute::version('v2', fn() => ...)
->current()
->rateLimit(1000); // Higher limit- Events - Hook into lifecycle events
- Artisan Commands - CLI tools for lifecycle management
- Usage Tracking - Monitor version usage
Laravel ApiRoute - Complete API versioning lifecycle management for Laravel
Home | Getting Started | Examples | Configuration
Made with ❤️ for the Laravel community