Skip to content

Database And Migrations

Packages own their database tables. Core tables should not grow package-specific columns unless the column is part of a stable extension contract.

Place normal migrations in database/migrations.

Use Spatie Package Tools from the package provider:

$package
->name(self::$name)
->hasMigrations([
'create_example_items_table',
]);

For dynamic discovery, packages can scan migration filenames and pass them to Package Tools.

Composer presence makes a package available. The capell_extensions row controls whether it can affect runtime.

Statuses:

  • installing: install has started and runtime is inactive.
  • enabled: runtime providers may load.
  • disabled: installed but runtime providers must not load.
  • failed: install failed; runtime remains inactive and error details live in metadata.install_error.

Package install actions should mark a package installing before running package install commands, enabled only after success, and failed when an install command throws or exits unsuccessfully.

Place settings migrations in database/settings and make them idempotent:

if (! $this->migration-assistant->exists('example.enabled')) {
$this->migration-assistant->add('example.enabled', true);
}

Register settings migrations from the package install command.

Register package models with Capell when they should appear in package metadata, protected-table checks, exports, or diagnostics:

CapellCore::registerModels([ExampleModel::class]);

Use morph maps for polymorphic package models.

If a package table should not be removed by cleanup tools, register it:

CapellCore::registerProtectedTable(fn (): string => 'example_items');

Use extension points and Capell installed-state checks instead of hard dependencies when another package is optional.

Do not use class_exists() alone to decide whether a Capell package’s tables or models are available. Composer can autoload a package class before capell:extension-install has marked the package installed or before its migrations have created the tables.

if (CapellCore::isPackageInstalled('capell-app/blog') && class_exists(Article::class)) {
ExampleRegistry::register(Article::class);
}

If code may run during install, upgrade, diagnostics, or other partial database states, also guard the table before querying:

if (
CapellCore::isPackageInstalled('capell-app/blog')
&& Schema::hasTable('articles')
&& class_exists(Article::class)
) {
Article::query()->latest()->first();
}