claudiodekker/laravel-guardrails
Add configurable guardrails to your Laravel application to enforce rules, catch unsafe or invalid operations early, and keep behavior predictable across your codebase.
Laravel Guardrails is a dev-only quality tool: it runs deterministic checks against only the lines you changed, so it's designed to be dropped into an existing codebase without a mass-cleanup phase. Install it as a dev dependency and it slots in next to Pint/PHPStan rather than replacing them.
Minimal path:
composer require --dev claudiodekker/laravel-guardrails
Then run the guardrails command at the repo root (check the README for the exact Artisan signature and available flags — the v1.0.0 notes defer to it, and flag names are the one thing worth confirming before you wire it into CI). If the package ships a config file, publish it early:
php artisan vendor:publish --tag=guardrails-config
Where to look first, in order:
First real use case: run it on your current feature branch before opening a PR. Because scope is the diff, the first run on a mature app should be near-silent — a handful of findings on the lines you touched. If you instead get a wall of output, that's a signal the diff base isn't resolving correctly (see Gotchas).
Pre-PR / pre-push gate. The lowest-friction adoption is a Composer script so it's part of the loop you already run:
"scripts": {
"guardrails": "php artisan guardrails"
}
Wire it into a Husky/pre-push hook or a composer check aggregate alongside Pint and PHPStan. Developers get feedback before review, not after.
CI on pull requests, diff-scoped. This is where the changed-lines model pays off. On a PR job, check out with full history so the diff base is resolvable, then run the check as a required step. Legacy debt in untouched files never blocks anyone, so you can enable it on day one instead of after a rewrite.
- uses: actions/checkout@v4
with:
fetch-depth: 0 # required for diff resolution
- run: composer install --no-interaction --prefer-dist
- run: php artisan guardrails
Agent-in-the-loop workflow. The "messages a developer or agent can act on" framing is the differentiator. The pattern that works:
Because the checks are deterministic, the same diff produces the same findings every time — no flaky feedback loop, and the agent can't "argue" its way past a finding. Pair this with the diff scoping: the agent only sees findings for lines it actually wrote, which keeps the correction loop short and stops it wandering into unrelated refactors.
Package development. Guardrails explicitly covers packages, so run it in your package repo too (alongside Testbench/Orchestra). This is useful when a package is consumed by apps with different conventions — the checks keep contribution PRs from drifting.
Staged adoption via severity. Rather than disabling checks, start with warnings enabled and promote to errors per-check as the codebase absorbs them. This is the same ratchet pattern as PHPStan levels, and it's the most durable way to introduce a new gate to an existing team.
Compose, don't consolidate. Treat it as a convention/structural linter. Keep Pint for formatting and PHPStan for types. Trying to make Guardrails do type analysis will produce a config nobody understands.
Diff base is the #1 failure mode. Changed-lines tooling depends on git history. Shallow clones (the CI default) make the base commit unreachable, so the tool either sees everything or nothing. Set fetch-depth: 0 and verify behaviour on a throwaway branch before trusting the gate. Locally, confirm whether it compares against the default branch, HEAD~1, or staged changes — the answer changes what "I changed" means.
Whole-file problems can slip through. Line-scoped checks are deliberately blind to issues that only manifest at file or class level (dead code, orphaned imports, a class that no longer satisfies a structural rule). Don't assume "guardrails passed" means the file is clean — keep your other analysers in place.
Deterministic ≠ semantic. These are pattern/structure checks. They will not catch a logic bug or a wrong return type. Framing matters: teams that expect a second PHPStan will be disappointed; teams that expect a fast, zero-noise convention gate will be delighted.
Exit codes and CI wiring. Confirm the failure exit code and that findings fail the build rather than merely printing. A quality gate that always exits 0 is worse than no gate, because it manufactures false confidence.
Config quirks to check on install. Look specifically at: how ignores are expressed (path globs vs. line-level vs. rule IDs), whether there's a baseline concept, and whether the config is cached. If the package respects Laravel's config cache, a stale cache after editing config/guardrails.php will silently ignore your changes — clear it when debugging "why isn't my ignore working."
Message output is a contract. Since the messages are designed for agent consumption, they're likely stable and structured. If you post-process or reformat them (e.g., into GitHub annotations), pin to the format and expect it to be the most likely thing to shift in a minor release. Prefer consuming the raw output.
Extension points. The natural customisation path is a custom check class registered in config. Before writing one, check whether an existing check already covers it with different thresholds — duplicating a check with slightly different config is the most common self-inflicted noise source in tools like this.
Tune severity, don't disable. When a check is noisy on legacy code, downgrade it rather than turning it off. Disabled checks are invisible and never come back; warnings get promoted once the codebase catches up.
How can I help you explore Laravel packages today?