Subdomain routing and dynamic domain routing
Concept
Subdomain routing allows routing based on the HTTP Host header's subdomain. This enables tenant-based systems, API versioning by subdomain, or splitting your application across functional subdomains (api., admin., app.).
Route::domain(): Takes a domain pattern. {subdomain}.example.com captures the subdomain as a route parameter. Can be combined with any other route methods.
Dynamic domain routing ({account}.example.com): The captured {account} parameter is injected into controller methods or can be bound to an Eloquent model via route model binding.
Scoping route groups: Route::domain('api.{version}.example.com')->group(...) matches api.v1.example.com and api.v2.example.com and captures version.
Subdomain + prefix combination: You can combine domain() with prefix() and middleware() on the same group.
Local development: Test subdomain routing by adding entries to /etc/hosts: 127.0.0.1 tenant1.laravel.test. Alternatively use Valet's --secure with wildcard DNS, or a service like lvh.me (which resolves all subdomains to 127.0.0.1).
Multi-tenancy use case: Each tenant gets their own subdomain (acme.app.com, globex.app.com). The {tenant} parameter identifies the tenant and middleware sets up tenant context (database connection, config, etc.).
Code Example
<?php
use Illuminate\Support\Facades\Route;
// Static subdomain
Route::domain('api.example.com')->group(function() {
Route::get('/users', [ApiController::class, 'users']);
Route::post('/orders', [ApiController::class, 'createOrder']);
});
// Dynamic subdomain — capture subdomain as parameter
Route::domain('{account}.example.com')->group(function() {
Route::get('/', [AccountController::class, 'dashboard']);
Route::get('/settings', [AccountController::class, 'settings']);
});
// Controller receives subdomain parameter
class AccountController extends Controller
{
public function dashboard(string $account): View
{
$tenant = Tenant::where('subdomain', $account)->firstOrFail();
return view('dashboard', compact('tenant'));
}
}
// Route model binding with subdomain
Route::domain('{tenant}.app.example.com')->group(function() {
Route::get('/dashboard', [TenantController::class, 'dashboard']);
});
// Implicit binding (automatically finds Tenant by subdomain)
// In RouteServiceProvider:
Route::bind('tenant', function(string $value) {
return Tenant::where('subdomain', $value)->firstOrFail();
});
// TenantController receives the model directly
class TenantController extends Controller
{
public function dashboard(Tenant $tenant): View
{
return view('tenant.dashboard', compact('tenant'));
}
}
// Generating URLs with subdomain
route('account.dashboard', ['account' => 'acme']);
// → https://acme.example.com/
// Subdomain + path prefix
Route::domain('{account}.example.com')
->prefix('admin')
->middleware(['auth', 'tenant'])
->group(function() {
Route::resource('/users', AccountUserController::class);
});