Open-source project
tighten/tlint avatar
tighten/tlint

TLint lints the Laravel conventions a formatter cannot see

Tighten linter for Laravel conventions.

530 stars34 forksPHPMIT

At a glance

What is it?
TLint is an opinionated linter and auto-formatter for Tighten flavoured Laravel and PHP conventions, shipped as the tightenco/tlint Composer package. It runs two presets, reads a tlint.json file, and covers the awkward choices in application code rather than whitespace and imports.
Who is it for?
TLint suits a Laravel team that has already agreed on house conventions and wants a review bot to enforce them, and it does not suit a team that treats framework files as freely editable or that wants a general PHP style checker. Before adopting it, run it once in report mode on an existing branch and read every message against the linter list in src, because the README's own example output and its linter table disagree about which view style is preferred.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 52 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A linter for the choices, not the whitespace

TLint calls itself an opinionated code linter with growing support for auto-formatting, aimed at Tighten flavoured conventions for Laravel and PHP. The distinction it draws is with the category of tools that check formatting, and its own history says so: the 7.x release removed lints and formatters covered by tools in Duster, on the grounds that TLint should focus on linting and formatting issues other tools are not able to catch.

The canonical example in the README makes the idea concrete. Laravel offers several ways to hand a variable to a view, and the README shows three. The first assigns a variable and passes it with compact.

php
$value = 'Hello, World!';

return view('view', compact('value'));

The second passes the array inline.

php
return view('view', ['value' => 'Hello, World!']);

The third chains a with call onto the view. The README calls this a meta layer of code linting, whose purpose is to let teams avoid higher level sticking points in code review.

That is the whole pitch, and it is either compelling or annoying depending on who you ask. For a team that has had the same three-way argument three times, a tool that settles it in a config file is a real saving. For a team that has never had that argument, it is a rule imposed from outside. The linters are named individually, and the README shows the one that covers this case, ArrayParametersOverViewWith, described as preferring the array form of view arguments over the chained with form.

Two presets, and a config file that can narrow them

TLint ships with two preset styles. The Laravel preset is intended to match the conventions agreed upon by the Laravel framework contributors, and the Tighten preset is intended to match those agreed upon by the Tighten team. The default is the Tighten one, and you switch by putting a tlint.json file in the project root.

The schema gives you four levers, and the first three are the ones most teams actually use.

json
{
    "preset": "laravel",
    "disabled": ["ArrayParametersOverViewWith"],
    "excluded": ["tests/"],
    "paths": [
        {
            "controllers": ["app/Domain/Http/Controllers"]
        }
    ]
}

The disabled list takes individual lint names, the excluded list takes directories to skip, and the paths list lets you point the controller linters at a directory of your own, which is what a project that does not use the default app/Http/Controllers layout needs. The preset is also the extension point: you can supply a fully qualified class name that implements PresetInterface, and the class returns the linters and formatters it wants, which is how you add rules of your own or modify the existing ones. A minimal custom preset looks like this.

php
namespace App\Support\Linting;

use Tighten\TLint\Presets\PresetInterface;

class Preset implements PresetInterface
{
  public function getLinters() : array
  {
    return [
      CustomLinter::class,
    ];
  }

The configuration then points at the class by name, and everything the preset returns is what runs. The practical takeaway is that you are not locked into the shipped rules, provided you are willing to write a small class.

The command line is small and scriptable

Running TLint is a single command, and the README notes that you must pass the lint command to use the other options. Bare tlint checks the whole project. Individual files and directories are linted by naming them.

bash
tlint lint index.php
tlint lint app

Two options make it usable in a review workflow. The diff option restricts the run to unstaged git changes, which is what you want on a branch with unrelated work in it.

bash
tlint lint --diff
tlint lint src --diff

The json option emits the findings as JSON, which the README says is primarily for editor plugins.

bash
tlint lint test.php --json

The checkstyle option emits a checkstyle XML report instead, and the README points at reviewdog and cs2pr as the consumers of that format in CI pipelines.

bash
tlint lint test.php --checkstyle

And the only flag lets you run a single linter, which is the fastest way to find out whether a rule is the one bothering you.

bash
tlint lint --only=ArrayParametersOverViewWith

The format command applies the subset of rules that can be fixed automatically, using the same conventions as the linter.

bash
tlint format

There is also a PHPStorm plugin, a Sublime Text package and a VS Code extension, so the same rules can run inside the editor rather than only in CI.

Duster is the intended home for the whole style toolchain

The README is explicit that TLint is intended to work with the tools included in Duster, and that using Duster to install and configure TLint gives the best coverage. That is a design decision with consequences, because the division of labour between the two tools has moved across major versions.

In 7.x, TLint dropped the lints and formatters that Duster already covers, on the principle that TLint should concentrate on what other tools cannot catch. The README tells you that if you need those rules back you can take them from an earlier version of TLint and wire them up as custom configuration. In 8.x, a significant number of formatters were added between the releases, and the escape hatch is the disabled setting in tlint.json if you want to roll them out gradually or turn them off.

So the practical advice is to install through Duster, and then to know which half of the style toolchain a given rule lives in. A team that adopted TLint on its own in the 6.x era and never revisited the config can find itself with duplicate or conflicting rules after upgrading, because the 7.x cleanup and the 8.x formatter additions moved the boundary in opposite directions.

The repository reflects that pairing too: a duster.json file sits at the root next to composer.json, tlint.json and phpunit.xml, which suggests the two are configured side by side rather than TLint being a standalone tool.

The 9.x upgrade changed which files get linted

Upgrading from 8.x to 9.x changes more than a version number, because the set of linted paths changed. TLint 9 requires PHP 8.1 or newer, and the tformat.json file was dropped in favour of a single tlint.json configuration file.

The more disruptive change is that TLint now lints files and directories that most Laravel applications treat as their own, whether they want that or not: the public directory, the bootstrap directory, server.php, the RedirectIfAuthenticated middleware, the Exceptions handler, the Auth controllers directory and the HTTP kernel.

Those are exactly the files a team is most likely to have customised, and the consequence is a wave of findings in code the team did not write and may not own. The escape hatch is documented and simple: add those paths to the excluded list in tlint.json, and the upgrade proceeds on your terms rather than the tool's.

The same file also carries the 7.x and 8.x guidance, which is a good sign for a tool whose breaking changes are the interesting part. The repository keeps a CHANGELOG.md, a CONTRIBUTING.md and a LICENSE.md at the root, so the history of these changes is meant to be read rather than rediscovered through failed builds.

Where a house style linter stops being useful

The honest limitation is that TLint encodes one organisation's taste. The Tighten preset is the default, the Laravel preset is the fallback, and neither is derived from your codebase. If your team resolves the view argument differently, the rule is still there, and the only supported answers are to disable it or to write a custom preset class. That is a workable answer and a recurring tax, and it grows with the number of rules that disagree with you.

The second limitation is the auto-formatting. The README says the format command can auto-fix some lints, and the upgrade notes from 8.x talk about a significant number of formatters being added, which means the format command can rewrite code beyond the single concern you asked it to fix. On a branch with unrelated work, running the formatter produces a diff that mixes mechanical rewrites with your actual change, and the diff option is not available to the formatter in the way it is for the linter.

The third is the alternative. Duster, which TLint is designed to sit inside, covers the style level work through the tools it includes, and PHP-CS-Fixer is the underlying fixer in that stack. The difference in approach is exactly the division TLint describes: a general fixer decides how code is laid out, and TLint decides which of several valid ways of expressing something your team prefers. If your disagreements with your colleagues are about layout, TLint will not help you, and if they are about which of three equivalent APIs to call, it is the tool for you.

Editorial conclusion

TLint suits a Laravel team that has already agreed on house conventions and wants a review bot to enforce them, and it does not suit a team that treats framework files as freely editable or that wants a general PHP style checker. Before adopting it, run it once in report mode on an existing branch and read every message against the linter list in src, because the README's own example output and its linter table disagree about which view style is preferred. The last push was on 2026-09-01, the newest release is v9.7.0 from the same day, and the licence is MIT.

Frequently asked questions

How do I install TLint in a Laravel project?

TLint requires PHP 8.1 or newer and is published as the tightenco/tlint Composer package, so you add it as a development dependency with composer require tightenco/tlint --dev, or install it globally with composer global require tightenco/tlint. The README recommends using Duster to install and configure it for the best coverage.

How do I configure TLint with a tlint.json file?

The file goes in the project root and accepts a preset key, either laravel or tighten, plus disabled, excluded and paths lists. The disabled list turns off individual lints by name, excluded skips directories, and paths points the controller linters at your own directories.

What changed when upgrading TLint from 8.x to 9.x?

TLint 9 requires PHP 8.1 or newer, tformat.json was dropped in favour of a single tlint.json file, and the set of linted paths grew to include public/, bootstrap/, server.php, RedirectIfAuthenticated.php, Exceptions/Handler.php, app/Http/Controllers/Auth/ and app/Http/Kernel.php. You can add those paths to the excluded list to keep the old behaviour.

Can TLint output JSON or a checkstyle report for CI?

Yes. tlint lint test.php --json produces JSON output, which the README says is mainly for editor plugins, and tlint lint test.php --checkstyle produces a checkstyle XML report for CI tools such as reviewdog and cs2pr. There is also tlint lint --diff to restrict the run to unstaged git changes.

Does TLint work with IDEs?

The README lists a PHPStorm plugin, a Sublime Text package and a VS Code extension, so the same rules can run inside the editor. The JSON output option is described as primarily intended for integration with those editor plugins.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. tighten/tlint on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tighten-tlint.svg)](https://hysenlabs.com/projects/tighten-tlint)

Was this useful?