Apply Middleware to all routes except `setup/*` in Laravel 5.4

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company
# Applying Middleware Selectively in Laravel: Excluding Specific Routes When working with middleware in a framework like Laravel, developers often encounter scenarios where a specific set of routes should be exempt from certain checks or logic applied globally. The problem you are facing—where your `CheckOnboarding` middleware must explicitly check the URI to avoid redirect loops on `/setup` routes—is a classic example of tightly coupled business logic inside middleware. As a senior developer, I recommend moving away from embedding path-specific exclusions directly into general middleware handlers. Instead, we should leverage Laravel's powerful routing and grouping capabilities to manage route permissions cleanly. This approach results in code that is more readable, maintainable, and adheres better to the separation of concerns principle that Laravel encourages. ## The Pitfall of Path-Based Middleware Checks Your current implementation inside `handle($request, Closure $next)`: ```php if ($request->user()->onboarding_complete == false && $request->path() != 'setup') { return redirect('setup'); } else { return $next($request); } ``` While functional for your immediate need, this method couples the middleware logic directly to the URI structure of the application. If you add more exceptions (e.g., `/admin`, `/api/v1`), this conditional logic becomes bloated and difficult to maintain. It forces the middleware to become aware of application-specific routing details rather than focusing purely on its intended task (checking authentication or onboarding status). ## The Recommended Solution: Route Grouping for Exclusion The best practice in Laravel is to define *where* middleware applies, not to attempt to filter routes inside every piece of generic middleware. We can use route grouping and prefixes to achieve your goal cleanly. Instead of applying `auth` and `checkOnboarding` to a broad group and then trying to exclude a subdirectory, we should structure the routes so that the protected routes are clearly separated from the setup routes. Here is how you can restructure your routing to ensure the middleware applies everywhere *except* the `/setup` area: ```php use Illuminate\Support\Facades\Route; Route::middleware(['auth', 'checkOnboarding'])->group(function () { // These routes will run the checkOnboarding logic because they are outside the setup prefix. Route::get('/home', 'HomeController@index'); Route::get('/account', 'AccountController@index'); }); // Routes that should NOT be protected by the middleware, or routes that handle the setup flow. Route::prefix('setup')->group(function () { // These routes are intentionally excluded from the general middleware application. Route::get('/', 'OnboardingController@index')->name('setup'); Route::post('/settings', 'SettingsController@store'); }); ``` ### Explanation of the Improvement By separating your route definitions, you achieve several benefits: 1. **Clarity:** It becomes immediately clear which routes are protected and which are not. The middleware is applied only to the routes defined within the main group (`/home`, `/account`). 2. **Maintainability:** If you later decide that a different section of your application should use a different set of middleware, you simply define new route groups without having