Running Laravel Pint Automatically with Git Pre-Commit Hooks

3 min read

I've lost count of how many PRs I've reviewed where the only changes in half the files were code style fixes. Someone pushed without running Pint, CI caught it, they fixed it, and now the diff is full of noise. It's a solved problem — you just need to automate it at the right point.

Laravel Pint is a zero-configuration code style fixer built on top of PHP-CS-Fixer. Every Laravel project ships with it. What most teams don't do is enforce it before the commit lands — they leave it for CI to catch instead. Here's how to fix that with a Git pre-commit hook.

Why automate code style with Pint?

Pint replaces PHP-CS-Fixer with sensible defaults baked in. Run ./vendor/bin/pint and it reformats your PHP files to match Laravel's opinionated style — trailing commas, import ordering, spacing, all of it.

The problem with relying on CI is feedback latency. You push, wait for a pipeline, see a style failure, fix it, push again. A pre-commit hook cuts that loop to zero: the formatting happens on your machine, on only the files you're about to commit, before the commit is even created.

Creating a pre-commit hook manually

Git hooks live in .git/hooks/. Create a pre-commit file there and make it executable:

touch .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

Then add this script:

#!/bin/bash

# Get staged PHP files only
STAGED_PHP_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep "\.php$")

if [ -z "$STAGED_PHP_FILES" ]; then
  exit 0
fi

echo "Running Laravel Pint on staged files..."

# Run Pint on staged PHP files, then re-stage the formatted versions
./vendor/bin/pint $STAGED_PHP_FILES

# Re-add the files Pint may have reformatted
git add $STAGED_PHP_FILES

The --diff-filter=ACMR flag limits to Added, Copied, Modified, and Renamed files — it skips deletions. The --cached flag means we only look at what's staged, not everything in the working tree. After Pint runs, git add re-stages any files it reformatted so the commit includes the fixed versions.

One edge case: if you have a large monorepo with many PHP files touched in a single commit, this can be slow. In that case, use ./vendor/bin/pint --parallel $STAGED_PHP_FILES to spread the work across CPU cores.

Sharing hooks across the team with a Makefile

.git/hooks/ isn't committed to the repo, which means every developer has to set this up manually. A Makefile target solves that:

.PHONY: install-hooks

install-hooks:
	@echo "Installing Git hooks..."
	@cp scripts/pre-commit .git/hooks/pre-commit
	@chmod +x .git/hooks/pre-commit
	@echo "Done."

Move the pre-commit script to scripts/pre-commit in your repo root (committed alongside your code), then document in your README that new team members should run:

make install-hooks

I typically add a note to the project README and also mention it in onboarding. You can also trigger this automatically via Composer's post-autoload-dump hook in composer.json:

{
    "scripts": {
        "post-autoload-dump": [
            "@php artisan package:discover --ansi",
            "[ -d .git ] && cp scripts/pre-commit .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit || true"
        ]
    }
}

The [ -d .git ] guard prevents it from failing in CI environments where there's no .git directory.

Alternative: lint-staged with Husky

If your project already has a package.json (common with Vite and Inertia setups), lint-staged with Husky is a cleaner cross-platform option. It handles the staged-files filtering for you:

npm install --save-dev husky lint-staged
npx husky init

Then update .husky/pre-commit:

npx lint-staged

And add the lint-staged config to package.json:

{
    "lint-staged": {
        "**/*.php": "./vendor/bin/pint"
    }
}

The trade-off: this adds Node.js tooling as a dependency for a PHP workflow. I use the pure bash approach on PHP-only projects and the Husky approach on anything with a frontend build step already.

Either way, the result is the same — code style gets fixed before it ever leaves your machine, CI stays green, and your PRs contain only meaningful changes.

laravel
tooling
git
php
pint
Steven Richardson

Steven is a software engineer with a passion for building scalable web applications. He enjoys sharing his knowledge through articles and tutorials.