> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Extending database connections in packages

> Examine Laravel 13's DB::extend and Connection::resolverFor to understand the precedence of connection names and driver names, retained connections, recreation, and package testing.

To extend database connections from a package, separate **registering how a connection is created** from **choosing which connection to use**. Design your package so that installing it alone does not replace the application's default connection or every MySQL connection.

This page is based on the official source of Laravel Framework `v13.40.0`. For connection configuration basics, see [Database configuration](https://kawax.biz/en/database.md). For provider loading, see [Package discovery](https://kawax.biz/en/advanced/package-discovery.md).

## Choose an extension point

Database connection creation offers extension points with different scopes.

| Extension point | Scope | Callback arguments |
| - | - | - |
| `DB::extend('connection name', $resolver)` | The connection with the given name | Configuration array, connection name |
| `DB::extend('driver name', $resolver)` | Connections that use that driver. Connection name extensions take precedence | Configuration array, connection name |
| `Connection::resolverFor('sqlite', $resolver)` | Connections for that driver created by the standard ConnectionFactory | PDO or a creation Closure, database name, table prefix, configuration array |

The callback passed to `DB::extend()` returns an `Illuminate\Database\Connection`, not a PDO instance or a Query Builder. `DatabaseManager::configure()` requires this class.

> [!WARNING]
>
> The database `extend()` does not follow the same calling convention as the shared `Illuminate\Support\Manager`. The callback's first argument is **the connection configuration array**, not the application container. Do not copy examples from other driver extensions as-is.

## Connection name extensions take precedence over driver names

`DatabaseManager::makeConnection()` retrieves the configuration and then chooses how to create the connection in the following order.

```mermaid theme={null}
flowchart TD
    A["Specify a connection name with DB::connection"] --> B{"Is there an already created connection?"}
    B -->|Yes| C["Return the retained Connection"]
    B -->|No| D["Get the connection configuration and parse the URL"]
    D --> E{"Is there a connection name extension?"}
    E -->|Yes| F["Run the connection name resolver"]
    E -->|No| G{"Is there a driver extension?"}
    G -->|Yes| H["Run the driver resolver"]
    G -->|No| I["Create with the standard ConnectionFactory"]
    F --> J["Configure events, reconnection, etc. and retain it"]
    H --> J
    I --> J
```

* `DB::extend('reporting', ...)` applies to `database.connections.reporting`.
* `DB::extend('mysql', ...)` applies to connections whose `driver` is `mysql` and that have no connection name extension.
* Both use the same extension registry. Use package-specific names, keeping collisions between connection names and driver names in mind.
* `extend()` does not create a configuration array. If you only register an extension and no connection configuration exists for the given name, an exception is thrown.

## Explicitly select a custom driver name

The following is a minimal example that bridges the custom name `acme-sqlite` to standard SQLite connection creation. **It is not an example of implementing a new database protocol or SQL dialect**. It shows the boundary between registration and selection when distributing a custom adapter.

It assumes `Acme\DatabaseBridge\` maps to `src/` via Composer PSR-4, and the class is placed in `src/DatabaseBridgeServiceProvider.php`.

```php theme={null}
<?php

namespace Acme\DatabaseBridge;

use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

final class DatabaseBridgeServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $app = $this->app;

        DB::extend('acme-sqlite', function (array $config, string $name) use ($app): Connection {
            $config['driver'] = 'sqlite';

            return $app->make('db.factory')->make($config, $name);
        });
    }
}
```

`db.factory` is the binding for the `ConnectionFactory` shared by Laravel. Because it calls the standard factory directly, it does not go back through the DatabaseManager's extension selection and can reuse SQLite's lazy PDO creation and Connection creation.

This callback runs when the connection is resolved for the first time. The provider's `boot()` only registers the creation logic; it does not test the connection or run migrations. Load it as a normal, non-deferred provider so it is registered before the target connection is first resolved.

The consuming application adds the connection to the existing `connections` array in `config/database.php`. It does not change the `default`.

```php theme={null}
'acme_reports' => [
    'driver' => 'acme-sqlite',
    'database' => database_path('reports.sqlite'),
    'prefix' => '',
    'foreign_key_constraints' => true,
],
```

Prepare the SQLite file in advance and confirm write permissions before using it. Do not design the package to create the file just by being loaded.

```php theme={null}
use Illuminate\Support\Facades\DB;

$connection = DB::connection('acme_reports');
$rows = $connection->select('select 1 as value');
```

What the user passes is the connection name `acme_reports`, not the driver name `acme-sqlite`. Because the example converts `driver` to `sqlite` before passing it to the factory, `getDriverName()` on the created connection also returns `sqlite`.

> [!TIP]
>
> Calling `DB::connection($name)` again from inside the resolver goes back into creating the same connection, which is not yet retained. To avoid recursion, create the Connection with the factory or your own adapter.

## Replacing with a Connection subclass is a different scope

To replace the Connection class itself for an existing driver, use `Connection::resolverFor()`. The callback may receive **a Closure that lazily creates the PDO** rather than an already connected PDO.

However, this registration is stored in a static property of the Connection class. If you register it for `sqlite`, it affects every SQLite connection newly created by the standard factory, including the `acme-sqlite` example above. It cannot be treated as a replacement for a single connection name.

* Return a Connection subclass for the target database from the callback. The base Connection alone cannot preserve database-specific Grammar, Processor, and so on.
* The factory separates `createConnector()` and `createConnection()`. Registering with `resolverFor()` alone does not provide a PDO connector for an unknown driver.
* If your custom `DB::extend()` resolver does not use the standard factory, the `resolverFor()` check inside the factory is never reached.
* Registering again for the same driver overwrites the previous registration. Check for conflicts with other packages and for state carried over between tests in the same PHP process.

Packages that want to limit their impact should first consider `DB::extend()` with a package-specific connection name or driver name.

## Distinguish reconnect from purge

The DatabaseManager retains created Connections per connection name. Changing the configuration or resolver is not automatically reflected in connections that have already been created.

| Operation | Effect on retained Connections |
| - | - |
| `DB::extend()` | Registers or overwrites the creation logic for the next creation. Existing objects remain as-is |
| `DB::disconnect($name)` | Disconnects the PDO but keeps the Connection retained |
| `DB::reconnect($name)` | If retained, moves the PDO and related objects from a newly created connection. Returns the original Connection |
| `DB::purge($name)` | Disconnects and removes it from the Manager's retained connections. The next `connection()` recreates it |
| `DB::forgetExtension($key)` | Removes the resolver registration. Does not remove the configuration or created connections |

In particular, `reconnect()` is not an API that wholesale replaces the class or configuration array of a retained Connection. To apply changes to an extension class or connection configuration, `purge()` at a safe time when no transaction is in progress, then get a new connection.

> [!WARNING]
>
> `purge()` does not swap Connection references held by other services or variables for the new one. Also, the `::read`, `::write`, and `::direct` connection names are separate retention keys, so do not assume that purging the base name removes them all at once.
>
> Do not casually rewrite shared connection configuration at runtime, for example to switch tenants. For long-lived workers, check not only updating the configuration cache but also restarting the process and the impact on retained services.

## Test package registration and creation

With [Orchestra Testbench](https://kawax.biz/en/advanced/package-testing.md), load the provider and actually select the custom driver. The following is an example of `tests/DatabaseBridgeTest.php`. The test environment requires PDO and the SQLite driver.

```php theme={null}
<?php

namespace Acme\DatabaseBridge\Tests;

use Acme\DatabaseBridge\DatabaseBridgeServiceProvider;
use Illuminate\Database\SQLiteConnection;
use Orchestra\Testbench\TestCase;

final class DatabaseBridgeTest extends TestCase
{
    protected function getPackageProviders($app): array
    {
        return [DatabaseBridgeServiceProvider::class];
    }

    protected function defineEnvironment($app): void
    {
        $app['config']->set('database.default', 'testing');
        $app['config']->set('database.connections.acme_reports', [
            'driver' => 'acme-sqlite',
            'database' => ':memory:',
            'prefix' => '',
        ]);
    }

    public function test_registration_does_not_create_connections(): void
    {
        $manager = $this->app->make('db');

        $this->assertSame('testing', $manager->getDefaultConnection());
        $this->assertArrayNotHasKey('acme_reports', $manager->getConnections());
    }

    public function test_connection_is_reused_and_purge_creates_a_new_one(): void
    {
        $manager = $this->app->make('db');
        $first = $manager->connection('acme_reports');

        $this->assertInstanceOf(SQLiteConnection::class, $first);
        $this->assertSame('acme_reports', $first->getName());
        $this->assertSame('sqlite', $first->getDriverName());
        $this->assertSame(1, (int) $first->selectOne('select 1 as value')->value);
        $this->assertSame($first, $manager->connection('acme_reports'));
        $this->assertSame($first, $manager->reconnect('acme_reports'));

        $manager->purge('acme_reports');

        $second = $manager->connection('acme_reports');

        $this->assertNotSame($first, $second);
        $this->assertSame(1, (int) $second->selectOne('select 1 as value')->value);
    }
}
```

Because `:memory:` loses its contents when the connection is closed, this test does not expect tables or data to persist. In a real package, also add tests for the following:

* The connection name resolver takes precedence over the driver name resolver.
* Re-registering the same name and `forgetExtension()` do not replace created Connections.
* Exceptions when required configuration is missing, and behavior when the connection fails.
* The features you promise to provide on supported databases, such as migrations, transactions, and read/write splitting.
* Registrations and connection state do not get mixed up in long-lived workers or when used alongside multiple packages.

## Maintenance checklist

* Does the package opt in with a custom name, without silently overwriting the default connection or built-in drivers?
* Do you keep Closures and connection objects out of the config cache and place the creation logic in the provider?
* Are connection targets and credentials managed as trusted application configuration?
* Have you rechecked the `extend()` arguments, the factory's creation order, and what `reconnect()` transfers on the supported Laravel versions?
* Do you guide users through the recreation steps and worker restarts after configuration changes?

## Related pages

* [Package development](https://kawax.biz/en/advanced/package-development.md)
* [Designing package service lifetimes](https://kawax.biz/en/advanced/package-service-lifetimes.md)
* [Merging and caching package configuration](https://kawax.biz/en/advanced/package-config-merging.md)
* [Publishing and updating package migrations](https://kawax.biz/en/advanced/package-migrations.md)

## Primary sources

* [Laravel official: Database and multiple connections](https://github.com/laravel/docs/blob/13.x/database.md#using-multiple-database-connections)
* [Laravel official: Package service providers](https://github.com/laravel/docs/blob/13.x/packages.md#service-providers)
* [DatabaseManager: Creation order, extension registration, connection retention and reconnection](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/DatabaseManager.php)
* [ConnectionFactory: Lazy PDO creation and the Connection resolver](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/Connectors/ConnectionFactory.php)
* [Connection: resolverFor and getResolver](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/Connection.php)
* [DatabaseServiceProvider: Registering db and db.factory](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/DatabaseServiceProvider.php)


## Related topics

- [Extending and re-resolving log channels in packages](https://kawax.biz/en/advanced/package-logging.md)
- [Database configuration](https://kawax.biz/en/database.md)
- [Custom session drivers in packages](https://kawax.biz/en/advanced/package-session-drivers.md)
- [Laravel Boost](https://kawax.biz/en/boost.md)
- [Queues](https://kawax.biz/en/queues.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.