tightenco/tlint
Tlint is Tighten's opinionated command-line linter for Laravel projects. It checks PHP code against Laravel conventions and best practices, helping teams enforce consistent style, catch common issues early, and keep codebases clean and maintainable.
TLint is a Laravel-convention linter and formatter. Treat it first as a targeted audit tool, not as a replacement for PHPStan, Larastan, Pint, or PHP_CodeSniffer.
Minimal adoption path:
Install as a dev dependency:
composer require tightenco/tlint --dev
Run a read-only lint pass against a narrow, high-value directory first:
./vendor/bin/tlint lint app/Http/Controllers
./vendor/bin/tlint lint routes
Pick one rule you care about and run it in isolation before enabling a preset:
./vendor/bin/tlint lint app --only=NoDump
Add a tlint.json at the project root once you know which preset and paths you want. TLint v9+ uses a single tlint.json; older versions also used tformat.json, so avoid copying old blog-post configs into a current project.
Wire a non-mutating lint command into CI after the local audit is clean:
./vendor/bin/tlint lint app routes tests
Where to look first:
./vendor/bin/tlint command help in your installed version.Best first use case: run TLint against app/Http/Controllers, app/Models, routes, and Blade views to find Laravel-specific convention issues such as debug calls, facade imports, route path slashes, request helper opportunities, and Blade spacing. Then use formatters only on a clean branch for small, reviewable codemods.
Use TLint on changed areas rather than always running the whole repository:
./vendor/bin/tlint lint app/Http/Controllers/UserController.php
./vendor/bin/tlint lint app/Models
./vendor/bin/tlint lint resources/views
Use --only for focused work:
./vendor/bin/tlint lint app --only=NoDump
./vendor/bin/tlint format app --only=FullyQualifiedFacades
This is especially useful when onboarding the team: start with low-risk rules, then expand to a preset.
Configure a project preset once the initial audit is clean. A typical tlint.json will select a preset and scope paths:
{
"preset": "laravel",
"paths": ["app", "routes", "tests", "resources/views"]
}
Use the Tighten preset when you want Tighten’s house conventions; use Laravel when you want a broader Laravel-community baseline. Check the exact linters enabled by your installed version, because presets change between majors. v7 added FullyQualifiedFacades to both presets.
TLint’s formatters are useful for mechanical fixes, but treat them like refactoring tools:
Example workflow:
git switch -c chore/tlint-formatting
./vendor/bin/tlint format app --only=UseAuthHelperOverFacade
git diff
Do not run formatters blindly across vendor, generated code, or large untouched areas unless you are prepared to review a large diff.
Use linting in CI, not formatting:
./vendor/bin/tlint lint app routes tests
Keep formatting as a local or scheduled maintenance task. If you want CI annotations, v9.1+ added checkstyle reporting; check the installed version’s command help for the exact flag rather than assuming a generic --report syntax.
A practical Composer script:
{
"scripts": {
"lint:tlint": "tlint lint app routes tests",
"format:tlint": "tlint format app routes tests"
}
}
Then developers run:
composer lint:tlint
TLint works well in a pre-commit hook if scoped to staged PHP/Blade files. Avoid running the full preset on every commit unless the repository is already clean; otherwise the hook becomes slow and noisy.
A reasonable pattern:
TLint is convention-focused and Laravel-specific. Pint handles general PHP style. Duster may wrap TLint alongside other tools. Avoid duplicate or conflicting auto-fixes by deciding which tool owns which concern:
Run formatters in a defined order and review diffs if both Pint and TLint touch the same files.
For team-specific conventions, TLint is extensible:
BaseFormatter, using helpers such as replaceCodeLine and the code methods added in v8.Keep custom rules few and well-documented. A custom TLint rule that nobody understands becomes permanent tech debt.
Pin the major version. TLint has had breaking changes across majors. v7 removed many linters/formatters, v8 added formatter APIs, and v9 changed configuration to a single tlint.json. Read the changelog before upgrading.
Do not treat TLint as a full static analyzer. It enforces Laravel conventions. It will not catch type errors, missing return types, or most runtime bugs.
Use --only for debugging. If a preset produces surprising output, isolate the rule:
./vendor/bin/tlint lint app --only=RuleName
Since v6.2, --only works with linters/formatters even when they are not in the active preset.
Formatter output needs review. Formatters rewrite code. Run them on a clean branch, one rule at a time, and check for edge cases around comments, anonymous classes, dynamic calls, and mailable constructors. Several changelog entries are fixes for exactly these kinds of formatting bugs.
Update for known edge-case fixes. Examples from the changelog:
SpacesAroundBladeRenderContent.NoRequestAll to allow other request methods.
If you hit a false positive, first check whether a newer patch release fixes it.Blade rules need special care. Blade is not PHP AST in the same way as class files. Escaped Blade, directives, and render-content spacing are common false-positive areas. Scope Blade linting to resources/views and review formatter diffs closely.
Preset behavior is not universal. Laravel and Tighten presets differ, and their contents change. Do not assume a rule is active just because it exists in the package. Inspect the preset or run --only explicitly.
bootstrap directory behavior has changed. Recent releases adjusted whether TLint ignores or allows the bootstrap directory. If your CI suddenly lints or skips bootstrap files, check the installed version and changelog.
Older versions had Windows and EOL issues. v6.2.1 fixed DIRECTORY_SEPARATOR and PHP_EOL handling. If you are on an older version and see path or newline problems, upgrade rather than patching around it.
Config migration is a real upgrade task. Do not copy a tlint.json and tformat.json pair into a v9+ project. Consolidate into tlint.json and verify paths, preset, and rule configuration against the current README.
Keep the lint surface intentional. Linting vendor, storage, compiled assets, or generated files creates noise. Scope paths to application code, routes, tests, and views.
Use it as a teaching tool. TLint’s value is highest when it surfaces Laravel idioms during code review and onboarding. Pair violations with short team notes rather than expecting developers to infer the convention from a rule name alone.
How can I help you explore Laravel packages today?