In Laravel 13's bootstrap/app.php, how do you register an extra route file, and how do withRouting's then and using arguments differ?
answer
- application builder, not RouteServiceProvider
- web: and api: accept arrays
- then: runs after the default files
- using: you register every HTTP route
- cached routes skip both closures
basics
~20 sPass extra files to withRouting's web: or api: arrays, or register them in a then: closure that runs after the default files; a using: closure replaces the framework's registration entirely, so web:, api: and health: are no longer registered for you.
solid answer
~40 sIn Laravel 13, route files are registered by `withRouting()` in `bootstrap/app.php`, not by an app `RouteServiceProvider`. The simplest extra file goes into the `web:` or `api:` array and inherits that group. For a file with its own middleware or prefix, add a `then:` closure: the builder registers the API files, the health route and the web files first, then calls your closure, where you write `Route::middleware('api')->prefix('feeds')->name('feeds.')->group(base_path('routes/feeds.php'))`. A `using:` closure is different: when it is given (without `then:`), the builder skips its own registration, so you must register every HTTP route yourself, including anything `web:`, `api:` or `health:` would have added. When routes are cached, neither closure runs; routes load from the cache file.
code
php · 16 lines<?php
// bootstrap/app.php with using: - the builder registers no HTTP routes itself
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
commands: __DIR__.'/../routes/console.php',
using: function () {
Route::middleware('api')->prefix('api/v1')->group(base_path('routes/api_v1.php'));
Route::middleware('api')->prefix('api/v2')->group(base_path('routes/api_v2.php'));
Route::middleware('web')->group(base_path('routes/web.php'));
},
)
->create();go deeper
Know that route files are registered in bootstrap/app.php through withRouting, and that web: and api: can take several files.
Explain the order the builder registers api, health, web and then:, and why a then: file needs an explicit middleware group.
Choose between arrays, then: and using: deliberately, remember what using: stops registering, and keep non-routing logic out of closures that caching skips.
Define how route files are split across teams and versions so the layout stays within then:, and reserve using: for layouts that truly need it.
## Where route registration lives now Before Laravel 11, an application's `App\Providers\RouteServiceProvider` loaded `routes/web.php` and `routes/api.php` in its `boot()` method, and teams added files there. The slim skeleton removed that class from new apps. In Laravel 13 the **application builder** in `bootstrap/app.php` owns route registration through one method: ```php withRouting( ?Closure $using = null, array|string|null $web = null, array|string|null $api = null, ?string $commands = null, ?string $channels = null, ?string $pages = null, ?string $health = null, string $apiPrefix = 'api', ?callable $then = null, ) ``` The framework still has a routing service provider internally; it simply calls the closure the builder hands it. ## Option 1: more files in web: or api: `web:` and `api:` accept **arrays**. Each file in the array gets the same treatment as the default one: web files are wrapped in the `web` middleware group, API files in the `api` group plus `apiPrefix`. This is the right tool when the new file is just "more web routes" or "more API routes". ## Option 2: a then: closure for files with their own attributes When a file needs a different prefix, name prefix or middleware group, use `then:`. The builder's own registration callback runs in a fixed order: 1. API files, each as `Route::middleware('api')->prefix($apiPrefix)->group(...)`. 2. The health route, when `health:` is set. 3. Web files, each as `Route::middleware('web')->group(...)`. 4. Any additional routing callbacks collected by the builder, then Folio pages when `pages:` is set and Folio is installed. 5. Your **`then:` closure**, which receives the application instance, always last. A file registered in `then:` gets **no middleware group by default**; you choose one explicitly. For a recipe site exposing a public JSON feed for partner sites: ```php ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', then: function () { Route::middleware('api') ->prefix('feeds') ->name('feeds.') ->group(base_path('routes/feeds.php')); }, ) ``` `Route::get('/recipes', ...)->name('recipes')` inside `routes/feeds.php` now answers `/feeds/recipes` and is named `feeds.recipes`. ## Option 3: a using: closure takes over completely `using:` is for apps whose route layout does not fit the builder's model: several API versions with different groups, tenant-specific files, or a package-style layout. When `using:` is passed and `then:` is not, the builder **does not build its own callback**. Consequences: - Files passed to `web:` and `api:` are **not** registered; register them yourself in the closure. - The `health:` route is **not** registered, and it is not exempted from maintenance mode for you. - `commands:` and `channels:` are still processed, because they are handled outside the HTTP routing callback. | Argument | Default web/api/health registration | Your closure runs | Typical use | |---|---|---|---| | none | yes | no | the skeleton | | `then:` | yes | after them | an extra file with its own prefix or group | | `using:` | no | instead of them | full control over every HTTP route | ## Route caching and the closures When routes are cached, the framework's routing provider loads the compiled cache file and **never calls** the `then:` or `using:` closure. Two consequences: - Anything besides route registration placed in those closures (binding a service, reading config) silently stops running in production. - A route added to `routes/feeds.php` does not exist until the route cache is rebuilt. Keep the closures limited to registering routes, and put other boot logic in a service provider. ## Mistakes seen in reviews - **Forgetting the group in `then:`.** A file registered as `Route::prefix('feeds')->group(...)` gets neither `web` nor `api`, so it has no session and no API middleware; say which one you want. - **Expecting `then:` to receive the router.** The builder calls it with the application instance; register routes through the `Route` facade inside it. - **Moving the web file into `using:` but leaving `web:` in place.** With `using:` alone the `web:` argument is ignored, which reads as if the file were registered twice when it is registered once, by your closure. - **Re-creating an app RouteServiceProvider out of habit.** It still works as a provider, but it hides route files from the place newcomers look first, `bootstrap/app.php`. ## Choosing - More routes of an existing kind: add a file to `web:` or `api:`. - A file with its own prefix, name prefix or group: `then:`. - A layout the builder cannot express: `using:`, accepting that you now register everything, health route included. - Always confirm with `php artisan route:list --path=feeds -v`, which shows the URI, name and middleware the file actually received.
- An app switched withRouting to using: and its uptime monitor on /up now fails. Why?The builder only registers the `health:` route inside its own callback, and passing `using:` alone skips that callback. The `/up` route no longer exists, and the builder no longer exempts it from maintenance mode either. Register an equivalent route in the `using:` closure or go back to `then:` if you only needed an extra file.
- Why can logic placed in a then: closure work locally and vanish in production?Locally, routes are usually not cached, so the framework calls the closure on every boot. In production the route cache is typically built during deployment, and when routes are cached the provider loads the cache file instead of calling `then:` or `using:`. Anything besides route registration in the closure stops running.
saying these in an interview costs you the question
- then: replaces the registration of web.php and api.php
- A new Laravel 13 app needs a RouteServiceProvider to add route files
- Passing using: keeps the /up health route registered
- A file registered in then: automatically gets the web middleware group
- The then: closure runs on every request even when routes are cached