Automate WordPress Code Quality Checks with Git Pre-Commit Hooks and PHP_CodeSniffer

Git hooks are shell scripts that run automatically at specific points in the Git workflow — before a commit is saved, before a push is sent, or after a merge completes — making them the ideal place to enforce code quality standards before bad code ever reaches the repository. PHP_CodeSniffer (PHPCS) with the WordPress Coding Standards ruleset checks WordPress PHP code for PSR-12 violations, security anti-patterns, and WordPress-specific best practices such as missing nonces, unsafe direct database queries, and incorrect text domain usage. A pre-commit hook that runs PHPCS on the staged PHP files fails the commit when violations are found, preventing the developer from bypassing the check and pushing non-compliant code. Checking only staged files — using git diff --cached --name-only filtered to .php — makes the hook fast even in large repositories by scanning only the files changed in the current commit. phpcbf (PHP Code Beautifier and Fixer), bundled with PHPCS, auto-fixes the majority of whitespace and formatting violations — running it before PHPCS in the hook reduces false failures caused by style issues the developer did not intend to introduce. ESLint can be added to the same pre-commit hook for JavaScript files in the theme or plugin, enforcing consistent JS style alongside PHP checks. The commit-msg hook validates the commit message format — enforcing a pattern like type(scope): message helps generate changelogs and makes git bisect searches easier. git commit --no-verify bypasses all hooks — document clearly in the project README that this flag should never be used on commits destined for the main branch. The interactive rebase post describes how clean commit history — enforced partly by these hooks — makes subsequent rebases and code reviews significantly faster. The deployment hooks post covers post-receive hooks on the server side that complement the pre-commit quality gates described here. Store hooks in a .githooks/ directory in the repository root and configure Git to use that directory with git config core.hooksPath .githooks so hooks are versioned and shared with the whole team.

Problem: WordPress PHP code in a shared repository drifts from coding standards when developers commit without running PHPCS locally — violations accumulate silently until a CI pipeline fails on a pull request or security review reveals ignored best-practice warnings.

Solution: Install PHPCS with the WordPress Coding Standards ruleset and configure a Git pre-commit hook stored in .githooks/pre-commit that runs PHPCS on all staged PHP files and blocks the commit when violations are found.

# 1. Install PHPCS and WordPress Coding Standards (project-local)
composer require --dev squizlabs/php_codesniffer dealerdirect/phpcodesniffer-composer-installer
composer require --dev wp-coding-standards/wpcs automattic/vipwpcs

# 2. Create .phpcs.xml in the repository root
cat > .phpcs.xml << 'XML'


    WordPress Coding Standards for this project
    
    
    
    
    .
    */vendor/*
    */node_modules/*

XML

# 3. Create the pre-commit hook
mkdir -p .githooks
cat > .githooks/pre-commit << 'HOOK'
#!/bin/sh
# Run PHPCS on staged PHP files only
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$')
if [ -z "$STAGED" ]; then exit 0; fi

echo "Running PHP_CodeSniffer on staged files..."
echo "$STAGED" | xargs ./vendor/bin/phpcs --standard=.phpcs.xml
STATUS=$?

if [ $STATUS -ne 0 ]; then
    echo "PHPCS found violations. Run: ./vendor/bin/phpcbf to auto-fix."
    exit 1
fi
exit 0
HOOK

chmod +x .githooks/pre-commit

# 4. Tell Git to use the project-local hooks directory (run once per clone)
git config core.hooksPath .githooks

# 5. Add setup instructions to Makefile for team onboarding
# make setup  -> composer install && git config core.hooksPath .githooks

NOTE: Add git config core.hooksPath .githooks to a make setup or npm run prepare script so new team members activate the hooks automatically when setting up the project — without this step, the hook file exists in the repo but Git does not execute it.