3neti/contact
3neti/contact is a Laravel package for streamlined contact form handling: it validates submissions, stores enquiries in the database, sends email notifications, and provides configurable routes and views for quick, minimal-setup integration.
The package ships its own database migrations and auto-loads them from its service provider via loadMigrationsFrom() — meaning there is no publish step and no vendor:publish dance for schema.
Minimal steps:
composer require <vendor>/<package>
php artisan migrate
That's it for a fresh install. Laravel's package auto-discovery registers the service provider, loadMigrationsFrom() registers the migration path, and migrate creates the tables/columns the package needs.
Where to look first (in this order):
README.md — added in v1.1.0 and now the canonical source for installation, usage, and architecture notes. Read the architecture section before touching the schema.boot() — confirm loadMigrationsFrom() and check whether it is guarded by runningInConsole() (it should be).database/migrations/ — the package runtime migrations. These are what actually run against your app's database.tests/database/migrations/ — test-only migrations. Useful as documentation of edge cases, but they are not applied to your app.First use case: install, run php artisan migrate, then confirm the tables with php artisan db:table <table> or Schema::hasTable(). Because the migrations are auto-loaded rather than published, the fastest sanity check is php artisan migrate:status — the package's migration filenames should appear in the list alongside your app's own.
Treat package migrations as part of your app's migration history. They run in the same migration repository, in filename order, in the same batch as your app migrations on a fresh install. That is usually what you want, but it means a php artisan migrate after a composer update can change your schema — build that into your deploy runbook rather than treating it as a surprise.
Deployment flow:
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache # and route/view cache as usual
Run migrate after composer install — the package migration files must be on disk before Artisan can see them. Never run config:cache before Composer has installed the provider.
Testing your application against the package. Use RefreshDatabase as normal; Testbench/the package's own migrations are irrelevant to your suite unless you're testing the package itself. If you need the package schema in tests, RefreshDatabase will run all registered migrations, including the package's, so no extra setup is needed. Don't hand-roll $this->artisan('migrate') calls — that's exactly the pattern v1.1.0 removed from the package's own test suite.
Local development. php artisan migrate:fresh --seed is safe and is the fastest way to pick up a package schema change after composer update. If you're iterating on the package itself, use a path repository in composer.json:
"repositories": [{ "type": "path", "url": "../package" }]
Symlinked path installs mean migration file edits are picked up immediately — no re-install between migrate:fresh runs.
Integrating with your models. The package owns the schema; your models should own the behaviour. Prefer extending the package's models or referencing its tables from your own models rather than reaching into the migration files. If you need extra columns, add your own migration in database/migrations/ — do not edit the package's migrations, and do not copy them into your app.
Upgrading between package versions. Pin the version in composer.json ("^1.1" is reasonable now), read the release notes, run php artisan migrate, then php artisan migrate:status to confirm the expected new migrations were applied and no old ones were rolled back unexpectedly.
No publish step means no override. Because migrations are auto-loaded with loadMigrationsFrom(), you cannot customise the package's schema by publishing and editing. If you need different columns, add a follow-up migration of your own that Schema::table()-alters the result. Be aware that your alteration now depends on the package's column definitions staying stable — an upstream change can break your follow-up migration silently (a ->change() on a column that no longer exists fails at runtime, not at install).
The old database/migrations/test convention is gone. v1.1.0 removed the legacy structure and moved test-only migrations to tests/database/migrations. If you had anything referencing the old test migration path — a custom test base class, a CI script, a docker entrypoint — it will now fail. This is flagged as non-breaking upstream, but it is breaking for anyone who reached into that path.
Test migrations are not runtime migrations. Don't be confused by seeing two migration directories in the repo. Only database/migrations/ is loaded into your application. The tests/database/migrations/ directory exists solely for the package's Testbench suite; copying those into your app will produce tables the package doesn't expect.
migrate:rollback semantics. Package migrations share your app's batch table. A migrate:rollback --step=1 can roll back package migrations together with app migrations from the same batch, dropping tables your app now depends on. Prefer forward-only migrations (migrate, not rollback) in production, and use migrate:fresh in local/test environments.
Debugging "table not found" after install. Check, in order: (1) the provider is auto-discovered (php artisan about or php artisan package:discover), (2) php artisan migrate:status lists the package's migrations, (3) config:clear hasn't left a stale cached config from before the provider existed. The most common cause is running migrate before composer install finished on a deploy.
runningInConsole() guard. loadMigrationsFrom() should be registered unconditionally in boot() — Laravel only uses the paths during console commands — but if you see migration behaviour diverge between web and CLI contexts, that guard is the first place to look.
No runtime schema changes in v1.1.0. Upgrading from v1.0.x requires no migrate run for schema reasons, and no code changes. The release is documentation plus internal test restructuring; the only externally visible artefact is the README, which is worth re-reading since it now documents the architecture that was previously tribal knowledge.
Extension points. The supported extension surface is: your own migrations layered on top, your own models, and configuration published by the provider (if any — check the README's architecture notes). Editing vendor files, copying migrations, or subclassing internal Testbench classes is unsupported and will break on the next release.
How can I help you explore Laravel packages today?