Open-source project
tighten/duster avatar
tighten/duster

Duster: Tighten's linting and formatting stack for Laravel in one command

Automatic configuration for Laravel apps to apply Tighten's standard linting & code standards.

604 stars27 forksPHPMIT

At a glance

What is it?
Duster is a Composer package that runs four tools against a Laravel codebase with Tighten's presets, TLint, PHP_CodeSniffer, PHP CS Fixer and Pint, and it can publish GitHub Actions and Husky hooks so the same rules run in CI and before a commit. It is a configuration bundle with a command line wrapper, not a linter of its own.
Who is it for?
Adopt Duster if you work on Laravel applications and want the same code style across every project without assembling four tool configurations yourself, and you are happy to adopt Tighten's opinions rather than your own. Do not adopt it if your team already has a settled style across several tools, since Duster's value is precisely that it replaces those configurations with Tighten's presets.
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 20 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One command over four tools, with Tighten's presets

Duster is described as automatic configuration for Laravel apps to apply Tighten's standard linting and code standards, and the project description is more compact still: automatically apply Tighten's default code style for Laravel apps.

What it actually is, is a wrapper and a set of configuration. Four tools run underneath, and each has a specific job and a specific Tighten preset.

TLint lints Laravel and PHP code for issues that other tools do not cover, using the default Tighten preset. PHP_CodeSniffer sniffs issues that cannot be fixed automatically, with a Tighten preset described as mostly PSR1 with some Tighten specific rules. PHP CS Fixer adds custom rules that are not supported by Laravel Pint, namely the CustomOrderedClassElementsFixer, which is a Tighten specific order of class elements. And Pint, which is Laravel's own code style rules, runs on the default Laravel preset with a few Tighten specific customisations.

The split between the four is the part that makes sense of the project. Pint handles the bulk of the automatic formatting, PHP CS Fixer handles the things Pint cannot express, PHP_CodeSniffer catches the problems no tool can fix for you, and TLint covers the Laravel specific issues that a general purpose style tool knows nothing about. Overlap is inevitable and Duster does not try to eliminate it, it just runs them all.

The README also points at a style guide document in the repository that lists the compiled rules with examples of what they do, which is the reference to read before adopting anything.

composer require, then lint or fix

Installation is a dev dependency:

bash
composer require tightenco/duster --dev

Then there are two commands, one to report and one to change. To lint everything at once:

bash
./vendor/bin/duster lint

And to fix everything at once:

bash
./vendor/bin/duster fix

The distinction matters because fix rewrites your files, and for a codebase that has never been through these rules, that is a large diff. The --dirty option limits the run to files with uncommitted changes according to Git:

bash
./vendor/bin/duster lint --dirty

or the same with fix. That is the option to reach for when you are introducing Duster to an existing project, because it lets you adopt the rules incrementally on the code you are already touching, instead of mixing a style migration with a feature change in one commit.

To see the available commands, you can run the binary on its own or ask for the commands list:

bash
./vendor/bin/duster

which is the same as:

bash
./vendor/bin/duster commands

duster.json holds the includes, the excludes and your own scripts

The configuration file is a duster.json in the project root, and it does three things.

First, it scopes the tools. If you need to include or exclude files or directories for each tool, you list them under the include and exclude keys. The include list takes directories such as bin, scripts, src and tests, and the exclude list takes plain paths like tests/fixtures as well as glob patterns, so a pattern can remove a whole directory tree from the run.

Duster picks these up automatically when running either lint or fix, and the default timeout for additional scripts is 60 seconds, which you can overwrite with the processTimeout key.

Third, it selects and orders the tools. The --using flag takes a comma separated list of commands:

bash
./vendor/bin/duster lint --using="phpstan,tlint,pint"

That example is worth reading twice, because it shows the intended extension path. Duster ships with four tools, but the tool set is not fixed, and a project can add PHPStan as an extra participant in the same run. The repository itself carries a phpstan.neon at the root, so the maintainers dogfood the pattern they document.

Publishing the GitHub Action and the Husky hooks

Duster can write the integration files for you, which is the part that turns a local command into an enforced standard.

To publish a GitHub Actions config:

bash
./vendor/bin/duster github-actions

To publish Husky hooks:

bash
./vendor/bin/duster husky-hooks

The Husky route has an option for containerised environments where PHP is not installed locally, the --env flag, which tells Duster how to run the command:

bash
./vendor/bin/duster husky-hooks --env=ddev

The supported environments are ddev, warden, lando and sail.

Sail gets its own usage notes, because it is the most common Laravel container setup. You can run the package binary through the PHP container:

bash
./vendor/bin/sail php ./vendor/bin/duster

Or you can use the Sail bin script, which eases the execution of package binaries, and do the same thing like this:

bash
./vendor/bin/sail bin duster

The GitHub Action is a separate project, duster-action, and the README carries a warning that workflows which commit to your repo will stop any currently running workflows and not trigger another workflow run. The suggested fix is to run your other workflows after Duster has completed, by changing their trigger to workflow_run on the Duster Fix workflow with the completed type:

yml
on:
  # Commits made in Duster Fix will not trigger any workflows
  # This workflow is configured to run after Duster finishes
  workflow_run:
    workflows: ["Duster Fix"]
    types:
      - completed

That warning is real and easy to learn the hard way, because a formatter that commits its own changes will silently disable the rest of your pipeline.

Per tool configuration files, and the Tighten presets they reference

Each of the four tools has its own configuration file in your project root, and the README says exactly which one to create and where to get the starting point.

For TLint, create a tlint.json in the project root. For PHP_CodeSniffer, create a .phpcs.xml.dist file with a ruleset that lists the directories to check and references the Tighten standard:

xml
<?xml version="1.0"?>
<ruleset>
    <file>app</file>
    <file>config</file>
    <file>database</file>
    <file>public</file>
    <file>resources</file>
    <file>routes</file>
    <file>tests</file>

    <rule ref="Tighten"/>
</ruleset>

You can add customisations below the Tighten rule line, or disable the Tighten rule entirely and use your own ruleset.

For PHP CS Fixer, create a .php-cs-fixer.dist.php with the contents from the file Duster ships in standards/.php-cs-fixer.dist.php. For Pint, create a pint.json, using the standards/pint.json file in the repository as a starting point.

The pattern is consistent. Duster ships the configuration templates, and the README links the upstream documentation for each tool so that the file you end up with is one you can reason about rather than an opaque blob.

The repository root also holds the standards directory with those templates, a duster.json for the project itself, a composer.json, a box.json for building a PHAR, a Makefile, a rector.php, a phpunit.xml.dist and the tests directory.

A 3.x line with regular patch releases and an MIT licence

The maintenance picture is steady. The recent tags are v3.4.7 on 2026-09-02, v3.4.6 on 2026-07-16 and v3.4.5 on 2026-06-28, so patch releases arrive every few weeks, and the last push to the repository was on 2026-10-03. The default branch is 3.x, which is the branch name for the current major line.

The licence is the MIT License, with the text in the LICENSE.md file at the root, so there is nothing unusual to negotiate if you want to fork it or to reuse its standards in a project of your own.

The security policy is short and direct. If you discover any security related issues, email [email protected] instead of using the issue tracker. That is a sensible convention for a tool whose hooks run on every commit, because a vulnerability in Duster is a vulnerability in your developer machine.

The project also credits Matt Stauffer and Anthony Clark, and acknowledges that much of the original idea and syntax was inspired by Tom Witkowski's ci-test-tools package. The last credit is the longest standing one, the All Contributors link, which is the usual sign of a project that has been maintained by a company for a long time.

The README ends by pointing at CONTRIBUTING for details, and the repository root has a RELEASE.md and a CHANGELOG-style history in the releases themselves rather than a separate file, so the tag notes are the change record.

Editorial conclusion

Adopt Duster if you work on Laravel applications and want the same code style across every project without assembling four tool configurations yourself, and you are happy to adopt Tighten's opinions rather than your own. Do not adopt it if your team already has a settled style across several tools, since Duster's value is precisely that it replaces those configurations with Tighten's presets. Verify first that the preset changes will not churn your diff, which is why the style guide exists, that the four tools can agree on the same files, since they overlap in what they report, and that the version of the pinned tool set is compatible with your PHP and Laravel versions before you wire it into a pre-commit hook that blocks commits.

Frequently asked questions

What does Duster do?

Duster automatically applies Tighten's default code style for Laravel apps by running four tools with Tighten presets: TLint for Laravel specific issues, PHP_CodeSniffer for problems that cannot be fixed automatically, PHP CS Fixer for rules Laravel Pint does not support, and Pint for Laravel's own style rules.

How do I install Duster in a Laravel project?

Add it as a development dependency with composer require tightenco/duster --dev. Then run ./vendor/bin/duster lint to report issues or ./vendor/bin/duster fix to apply fixes, and add the --dirty option to limit the run to files with uncommitted Git changes.

Can I run Duster in a container without PHP installed locally?

Yes. Use the --env option when publishing the Husky hooks, for example ./vendor/bin/duster husky-hooks --env=ddev. The supported environments are ddev, warden, lando and sail, and Sail can also run the binary directly with ./vendor/bin/sail php ./vendor/bin/duster.

How do I add my own tools to a Duster run?

Add them under the scripts key in duster.json, split into lint and fix, where the key is the command name and the value is an array of arguments passed to the Symfony Process component. You can also choose which tools run and in what order with the --using flag, for example --using="phpstan,tlint,pint".

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. tighten/duster 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-duster.svg)](https://hysenlabs.com/projects/tighten-duster)

Was this useful?