pushery/sqlens-for-laravel
SQLens for Laravel gives you a clear lens on the SQL your application runs, helping you inspect, analyze, and debug queries during development. Lightweight and easy to drop into any Laravel project, it surfaces the database activity that matters so you can spot problems faster.
SQLens sits between your migrations and your database: instead of eyeballing ALTER TABLE statements, you let it lint the SQL your migrations actually emit, then gate the deploy on the result. Minimum path:
composer require pushery/sqlens-for-laravel --dev
php artisan vendor:publish --tag="sqlens-config"
Then run the linter against a migration before merging:
php artisan sqlens:lint
# or target a specific migration / connection
php artisan sqlens:lint --path=database/migrations/2026_05_01_add_index.php --connection=pgsql
The package exposes three families of checks, and it's worth learning which one you need first:
ALTER TABLE, adding a non-concurrent index, backfilling a column with a default on a large table, type changes that rewrite the table).SECURITY DEFINER functions, unencrypted or wide-open columns).First useful case: add a step to your pre-deploy routine that lints the migrations in the release diff. That single habit catches most of the "deploy took the site down for 40 seconds" incidents.
Confirm the exact command names and config keys against the package README — with a young package (11 stars, first release dated 2026) the surface can shift between minors.
The migration authoring loop. Run sqlens:lint locally as you write migrations, not only at the end. Treat a lock-risk finding like a failing test: either rewrite the migration (concurrent index, NOT VALID constraint, two-phase backfill, online schema change tool) or record an explicit acknowledgement with a reason. Silent suppression is the failure mode to avoid.
CI gate with tri-state handling. The "undetermined" result matters more than pass/fail. Undetermined usually means SQLens couldn't parse the SQL, couldn't reach the DB, or lacks table-size context to judge lock risk. In CI, decide explicitly:
# conceptual — check actual exit codes in the README
- run: php artisan sqlens:guard --format=json > sqlens.json
continue-on-error: true
- run: |
# 0 = pass, 1 = fail, other = undetermined
# fail the job on 1; on undetermined, fail unless an allowlist covers it
Many teams start by failing only on fail and logging undetermined; that quietly lets unparseable raw-SQL migrations through, so graduate to failing on undetermined once you've cleared the backlog.
Pre-deploy classification. Wire the risk classifier into your release job so a release is tagged low / medium / high before it ships. That tag can drive approval rules (high-risk needs a reviewer), maintenance-window scheduling, or a Slack notice. Because the classification is deterministic, the same diff always produces the same verdict — good for audit trails.
Drift detection on a schedule. Run sqlens:audit (or the drift command) nightly against staging and production with a read-only reporting role. Treat drift as a signal, not a hard failure: hotfixes applied directly to prod are legitimate but should be captured into a migration within a day. A scheduled job that posts the diff to a channel keeps the schema honest without blocking anyone at 2 a.m.
Multi-connection setups. Configure per-connection settings — the lint rules can be identical across MySQL and Postgres, but lock semantics differ substantially. Postgres index creation is the classic blocking case (CREATE INDEX vs CONCURRENTLY); MySQL has its own online-DDL caveats. Keep both connections defined so CI lints against the driver you actually use per environment.
Baselines and allowlists. For legacy schemas, establish a baseline of known-good findings and fail only on new ones. Re-baseline deliberately, with a commit message explaining why, so the allowlist doesn't become a graveyard.
undetermined as pass. It's the most common way the guard becomes decorative. Make the exit code for undetermined distinct from success in your pipeline and log the reason.DB::statement() and DB::unprepared() bypass Laravel's schema grammar, so SQLens only sees the string. That's fine — but complex dynamic SQL (string concatenation, conditional DDL) may come back undetermined. Prefer parameterised, static DDL in migrations where possible.SELECT on information_schema / pg_catalog and the relevant tables. Don't reuse your migration credentials in CI; an audit command that can drop a table is a bad default.ALTER TABLE on a 500-row table is harmless; SQLens needs row estimates to say so. Point it at production-like data or it will (correctly) hedge. Use staging with realistic volume, or feed it table-size hints if the config supports them.pg_partman, and migration tools that touch schema_migrations can all look like drift. Scope the audit to the schemas and tables you own; exclude extension-managed objects.php artisan config:cache will freeze SQLens config at build time. If you inject thresholds via env vars at deploy time, either skip config caching for the guard step or ensure the cache is rebuilt after env changes.composer.lock and upgrade the guard deliberately.--format=json and emit your own annotations; don't scrape the human-readable table, which is the most likely thing to change between releases.How can I help you explore Laravel packages today?