nikolawd/laravel-route-disabling

Temporarily disable Laravel routes without removing them from the codebase. Originally proposed for Laravel core.

Maintainers

Package info

github.com/NikolaWd/laravel-route-disabling

pkg:composer/nikolawd/laravel-route-disabling

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 0

V1.1.0 2025-12-22 19:36 UTC

This package is auto-updated.

Last update: 2026-03-22 20:06:18 UTC


README

Latest Version on Packagist Total Downloads

Temporarily disable Laravel routes without removing them from the codebase. Perfect for maintenance mode, feature flags, A/B testing, and gradual rollouts.

The implementation follows Laravel's internal patterns for route handling and has been thoroughly tested to work with route caching.

Installation

You can install the package via Composer:

composer require nikolawd/laravel-route-disabling

The package will automatically register its service provider.

Usage

Basic Usage - Default Message

Route::get('/users', [UserController::class, 'index'])->disabled();
// Returns Laravel's default 503 error page with message: "This route is temporarily disabled."

Custom Message

Route::get('/users', [UserController::class, 'index'])
    ->disabled('User management is under maintenance');
// Returns Laravel's 503 error page with your custom message

The package uses Laravel's native error handling system (abort(503, $message)), which means:

  • Laravel automatically displays its styled 503 error page
  • You can customize the page by creating resources/views/errors/503.blade.php in your app
  • The $exception->getMessage() variable contains your custom message
  • Full compatibility with Laravel's exception handling

Custom Response with Callback

Route::get('/users', [UserController::class, 'index'])
    ->disabled(function ($request) {
        return response()->json([
            'message' => 'This feature is temporarily unavailable',
            'retry_after' => now()->addHours(2)->timestamp,
        ], 503);
    });

Conditional Disabling

Route::get('/beta-feature', [BetaController::class, 'index'])
    ->disabled(config('features.beta_disabled') ? 'Beta features are disabled' : false);

Dynamic Enabling/Disabling

Return null or false from a callback to allow the route to proceed normally:

Route::get('/premium-feature', [PremiumController::class, 'index'])
    ->disabled(function ($request) {
        if ($request->user()?->isPremium()) {
            return null; // Allow access for premium users
        }

        return response()->json([
            'message' => 'This feature requires a premium subscription',
        ], 403);
    });

Use Cases

1. Selective Maintenance Mode

Disable specific routes without putting the entire application in maintenance mode:

Route::get('/checkout', [CheckoutController::class, 'index'])
    ->disabled('Checkout is temporarily unavailable for system maintenance');

2. Feature Flags

Easily toggle features on/off based on configuration or environment:

Route::get('/new-dashboard', [DashboardController::class, 'new'])
    ->disabled(!config('features.new_dashboard_enabled'));

3. Time-Based Availability

Enable routes only during specific time periods:

Route::get('/christmas-sale', [SaleController::class, 'christmas'])
    ->disabled(function ($request) {
        $now = now();
        if ($now->between('2025-12-24', '2025-12-26')) {
            return null; // Enable during Christmas
        }
        return 'Christmas sale is only available December 24-26';
    });

4. Business Hours

Restrict access to certain routes during business hours:

Route::post('/trading/execute', [TradingController::class, 'execute'])
    ->disabled(function ($request) {
        if (now()->isWeekend() || now()->hour < 9 || now()->hour >= 17) {
            return response()->json([
                'message' => 'Trading is only available 9 AM - 5 PM on weekdays',
            ], 503);
        }
        return null; // Enable during business hours
    });

5. Gradual Rollouts

Enable features for a percentage of users:

Route::get('/new-feature', [FeatureController::class, 'index'])
    ->disabled(function ($request) {
        // Enable for 20% of users based on user ID
        if (($request->user()->id % 5) === 0) {
            return null;
        }
        return 'New feature coming soon!';
    });

6. Emergency Response

Quickly disable problematic endpoints in production:

Route::post('/problematic-endpoint', [ProblematicController::class, 'store'])
    ->disabled('This endpoint is temporarily disabled due to a known issue');

Route Caching Support

This package fully supports Laravel's route caching (php artisan route:cache). Closures are automatically serialized and deserialized using Laravel's SerializableClosure, following the same pattern as Laravel's core routing system.

# Works perfectly with route caching
php artisan route:cache

Testing

composer test

Customizing the 503 Error Page

Since the package uses Laravel's native error handling, you can customize the 503 error page just like any other Laravel error page:

  1. Create resources/views/errors/503.blade.php in your application
  2. Use $exception->getMessage() to display your custom message

Example custom error page:

<!DOCTYPE html>
<html>
<head>
    <title>Service Unavailable</title>
</head>
<body>
    <h1>503 - Service Unavailable</h1>
    <p>{{ $exception->getMessage() }}</p>
    <a href="{{ url('/') }}">Back to Home</a>
</body>
</html>

Laravel will automatically use your custom view instead of the default error page.

How It Works

The package uses Laravel's macro system to add disabled() and getDisabled() methods to the Route class with full type hinting support. When a route is disabled:

  1. The disabled status/message/callback is stored in the route action array
  2. A middleware is automatically registered to the route
  3. The middleware checks if the route is disabled
  4. For simple messages, it calls abort(503, $message) which triggers Laravel's error handling
  5. If a callback is provided, it's executed and the response is returned
  6. If the callback returns null or false, the request proceeds normally

For route caching:

  • Closures are serialized using SerializableClosure::unsigned()
  • Deserialization happens automatically when accessing getDisabled()
  • String and boolean values are not affected by serialization

Type Hints

All methods include proper PHP 8.2+ type hints:

  • disabled(string|bool|Closure $messageOrCallback = true): Route
  • getDisabled(): string|bool|Closure|null
  • Full PHPDoc annotations for IDE support

License

The MIT License (MIT). Please see License File for more information.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Changelog

Please see CHANGELOG.md for more information on what has changed recently.