Switching Database Connections on the Fly with Laravel's connection() Method
Most Laravel apps run their entire lifetime against whatever DB_CONNECTION is set in .env, and that's fine until it isn't — a reporting query that needs a read replica, a model that lives on a legacy database, or a SaaS product that needs a different database per tenant. Laravel doesn't require you to restart the app or juggle multiple .env files for any of these. Connections can be selected, and even registered, at runtime.
Querying a specific connection directly
The simplest case: run a one-off query builder call against a named connection instead of the default.
use Illuminate\Support\Facades\DB;
$orders = DB::connection('reporting')->table('orders')->get();
reporting here just needs to exist as a key under connections in config/database.php — it doesn't have to be your default connection, and you don't need to switch DB_CONNECTION to use it for this one query.
Pointing an Eloquent model at another connection
For a single query, call ->on() on the model:
$archivedInvoices = Invoice::on('archive')->where('status', 'closed')->get();
If a model should always live on a non-default connection, skip the runtime call and declare it on the class instead:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LegacySubscription extends Model
{
protected $connection = 'legacy_mysql';
}
LegacySubscription will now use legacy_mysql for every query, migration-independent of whatever the app's default connection is, without needing ->on() sprinkled through the codebase.
Registering a brand-new connection at runtime
The more interesting case: a multi-tenant PhpArchitect-style SaaS where each tenant's database credentials live in a lookup table (or a central "landlord" database), and the actual connection details aren't known until you know which tenant is making the request. config/database.php can't hardcode a connection for a tenant that doesn't exist yet.
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
function connectToTenant(string $tenantId): void
{
$tenant = DB::connection('landlord')
->table('tenants')
->where('id', $tenantId)
->first();
Config::set('database.connections.tenant', [
'driver' => 'mysql',
'host' => $tenant->db_host,
'database' => $tenant->db_database,
'username' => $tenant->db_username,
'password' => decrypt($tenant->db_password),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
]);
// Force Laravel to drop any cached PDO connection under this name
// and rebuild it next time it's used, picking up the config we just set.
DB::purge('tenant');
}
Once that's done, DB::connection('tenant') (or a model with protected $connection = 'tenant') will resolve against the credentials you just registered. The DB::purge() call matters: Laravel caches an active PDO connection instance per connection name once it's been resolved. If a connection named tenant was already open from a previous request in the same process (common with long-running workers, Octane, or queue workers that don't restart between jobs), skipping purge() means you'd keep querying the previous tenant's database under the new tenant's request. Purging forces a fresh connection attempt using whatever config is currently registered.
Two things worth being careful about
Connection pooling and persistent connections. If you're running under Octane, a long-lived queue worker, or anything else that keeps the same PHP process alive across multiple requests, dynamically-registered connections and their underlying PDO instances can persist longer than you intend. Always purge before reconnecting, and be conservative about how many distinct tenant connections you allow to accumulate in memory within one process — each one holds an open database handle.
Reset back to default after a request-scoped override. If you swap a model's connection, or set up a tenant connection for one request, make sure something at the end of that request's lifecycle resets things back to the default. Otherwise, in a worker or persistent process, tenant A's connection context can leak into the handling of tenant B's request. A middleware that sets the tenant connection at the start of a request, paired with terminating() or a finally block that purges and resets Config::set('database.default', 'mysql'), keeps that boundary clean. Don't rely on the request just "ending" to clean this up for you — in anything other than classic PHP-FPM with a fresh process per request, it won't.