Every WordPress plugin or theme developer has shipped at least one pull request that mixed tabs with spaces, used deprecated functions, or skipped a closing brace comment. The fixes are trivial once you know about them, but finding them manually in a code review is slow and error-prone. WordPress Coding Standards (WPCS) solves this by automating the checks, and with a one-time setup in your IDE and Git hooks, violations never reach your repository again.
This guide walks through the complete WordPress coding standards workflow: installing the tool via Composer, writing a phpcs.xml config that covers your project, wiring up VS Code and PhpStorm, adding a pre-commit hook so bad code can never be committed, running auto-fixes with phpcbf, and enforcing everything in CI on every pull request.
What WordPress Coding Standards Actually Checks
WPCS is a ruleset for PHP_CodeSniffer (phpcs), which is the underlying engine that reads your PHP files and compares them against a set of rules. The WordPress project ships three primary rulesets:
- WordPress-Core: The base rules covering indentation (tabs, not spaces), brace placement, Yoda conditions, and spacing around operators and parentheses. This is the minimum every WordPress plugin should pass.
- WordPress-Extra: Extends Core with additional best practices, including checks for internationalization functions, safe output escaping, and nonce verification patterns. This is the recommended ruleset for plugins submitted to the official directory.
- WordPress-Docs: Adds documentation requirements: every function, class, and method must have a PHPDoc block with proper
@paramand@returnannotations. - WordPress: An alias that includes Core plus Extra plus Docs. Use this if you want the strictest complete check in one declaration.
Beyond style, WPCS includes security sniffs. It flags direct use of $_GET, $_POST, or $_REQUEST without sanitization, echoing untrusted data without escaping, running SQL queries without prepared statements, and using dynamic code execution constructs. These checks alone catch real vulnerabilities before they reach production.
Installing WPCS via Composer
Composer is the standard package manager for PHP, and it is the cleanest way to install WPCS because it keeps the tool version pinned to your project and makes the setup reproducible for every team member.
If Composer is not installed on your machine, install it globally:
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
From your plugin or theme root directory, require the two packages:
composer require --dev squizlabs/php_codesniffer wp-coding-standards/wpcs dealerdirect/phpcodesniffer-composer-installer
The third package, dealerdirect/phpcodesniffer-composer-installer, is a Composer plugin that automatically registers the WPCS ruleset path with phpcs after installation. Without it, you would need to run phpcs --config-set installed_paths manually each time someone clones the project.
After installation, verify everything is wired up:
./vendor/bin/phpcs -i
You should see WordPress, WordPress-Core, WordPress-Extra, and WordPress-Docs listed among the installed coding standards.
Add these lines to your .gitignore if they are not already there:
/vendor/
composer.lock
Team members who clone the project just run composer install and get the exact same phpcs and WPCS versions.
Writing Your phpcs.xml Configuration File
Running phpcs with command-line flags every time gets old fast. A phpcs.xml file at the project root encodes all your settings so the tool works the same for every developer and in CI.
Here is a practical starting configuration for a WordPress plugin:
<?xml version="1.0"?>
<ruleset name="My Plugin">
<description>WordPress Coding Standards for My Plugin.</description>
<file>.</file>
<exclude-pattern>/vendor/*</exclude-pattern>
<exclude-pattern>/node_modules/*</exclude-pattern>
<exclude-pattern>/tests/*</exclude-pattern>
<exclude-pattern>*.min.js</exclude-pattern>
<arg name="extensions" value="php"/>
<arg name="colors"/>
<arg value="sp"/>
<config name="minimum_supported_wp_version" value="6.0"/>
<rule ref="WordPress-Extra"/>
<rule ref="WordPress-Docs"/>
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array" value="my-plugin-textdomain"/>
</properties>
</rule>
<rule ref="Generic.Arrays.DisallowShortArraySyntax.Found">
<severity>0</severity>
</rule>
</ruleset>
A few things worth noting about this config:
- Setting
minimum_supported_wp_versiontells WPCS which WordPress functions it can safely expect to exist. Set this to match your plugin header’sRequires at leastvalue. - The
text_domainproperty underWordPress.WP.I18nmakes WPCS verify that every__(),esc_html_e(), and related function uses your plugin’s text domain, not an empty string or a typo. - The short array syntax rule shows how to silence individual sniffs you disagree with. Use
<severity>0</severity>rather than<exclude>so the violation still shows in reports but does not cause a non-zero exit code.
Run it to see your first report:
./vendor/bin/phpcs
The first run on a legacy codebase typically produces hundreds of warnings. That is expected. The next section covers how to fix the bulk of them automatically before setting up the IDE checks.
Auto-Fixing Violations with phpcbf
PHP Code Beautifier and Fixer (phpcbf) ships alongside phpcs and can fix a large portion of style violations automatically. It handles whitespace, indentation, brace placement, and simple operator spacing. It does not touch logic-level issues like missing sanitization or incorrect escaping.
Run phpcbf on the entire project:
./vendor/bin/phpcbf
You will see output like:
PHPCBF RESULT SUMMARY
----------------------------------
FILE FIXED REMAINING
----------------------------------
includes/class-main.php 47 12
admin/settings.php 23 5
----------------------------------
A TOTAL OF 70 ERRORS WERE FIXED IN 2 FILES
After phpcbf runs, re-run phpcs to see what is left. The remaining violations are items phpcbf cannot fix automatically: missing doc blocks, unsafe database queries, direct variable output without escaping. These require human decisions.
For large codebases, fix one directory at a time to keep each commit reviewable:
./vendor/bin/phpcbf includes/
git add includes/
git commit -m "Apply WPCS style fixes to includes/"
VS Code Setup: PHPCS Extension and Intelephense
Visual Studio Code needs two things: a PHP language server for IntelliSense and a phpcs integration for inline error display.
Install PHP Intelephense
Open the Extensions panel (Ctrl+Shift+X on Windows/Linux, Cmd+Shift+X on macOS) and search for PHP Intelephense by Ben Mewburn. This extension provides code completion, go-to-definition, and hover documentation for PHP. It is faster and more accurate than the basic PHP extension.
Disable the built-in PHP language features to avoid conflicts. Open the Command Palette (Ctrl+Shift+P), type Open User Settings JSON, and add:
{
"[php]": {
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client"
},
"php.validate.enable": false,
"php.suggest.basic": false
}
Install the PHP Sniffer Extension
Search for PHP Sniffer & Beautifier by Samuel Hilson (extension ID: wongjn.php-sniffer). This extension runs phpcs in the background and shows violations as inline red and yellow underlines as you type.
Configure it in your VS Code settings.json. The key is pointing it to the project-local binary so it uses your Composer-installed version, not a global one that might have different WPCS installed:
{
"phpSniffer.standard": "${workspaceFolder}/phpcs.xml",
"phpSniffer.executablesFolder": "${workspaceFolder}/vendor/bin/",
"phpSniffer.run": "onSave",
"editor.formatOnSave": true
}
Setting executablesFolder to the vendor path means every project uses its own pinned version. If you work on ten plugins with different WPCS configurations, VS Code picks up the right one automatically when you switch folders.
Workspace vs User Settings
Store the phpcs paths in a .vscode/settings.json file in the project root, not in your global user settings. Commit this file to the repository. New team members who open the project in VS Code get the correct configuration automatically without any manual steps.
PhpStorm Setup
PhpStorm has built-in phpcs support since version 2022.1 and it is more straightforward to configure than VS Code.
Configure the PHP CLI Interpreter
Go to Settings > PHP and set your CLI interpreter to the PHP version your plugin targets. If you use Homebrew on macOS, the path is typically /opt/homebrew/bin/php.
Enable Quality Tools
Go to Settings > PHP > Quality Tools > PHP_CodeSniffer. Click the three-dot button next to the PHP_CodeSniffer path and set it to $ProjectFileDir$/vendor/bin/phpcs. PhpStorm resolves the $ProjectFileDir$ variable at runtime, so this configuration is portable across machines without hardcoded paths.
In the same dialog, under Coding standard, choose Custom and point it to your phpcs.xml file. PhpStorm reads the file and applies the ruleset to all PHP files you open in that project.
Enable Inspections
Go to Settings > Editor > Inspections > PHP > Quality Tools > PHP_CodeSniffer validation and enable it. You can set the severity separately for errors and warnings. Violations appear as underlines and in the Problems panel.
PhpStorm also supports a phpcbf external tool for auto-fixing. Go to Settings > Tools > External Tools, add a tool with Program $ProjectFileDir$/vendor/bin/phpcbf and Arguments $FilePathRelativeToProjectRoot$, then assign it a keyboard shortcut for fast access.
Pre-Commit Git Hook: Simple Bash Approach
IDE checks catch problems as you type, but a pre-commit hook prevents bad code from being committed even when someone bypasses the editor (command-line commits, merge commits, automated scripts). The simplest approach requires no additional dependencies.
Create the file at .git/hooks/pre-commit:
#!/bin/bash
STAGED_PHP_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$')
if [ -z "$STAGED_PHP_FILES" ]; then
exit 0
fi
echo "Running WPCS on staged PHP files..."
./vendor/bin/phpcs $STAGED_PHP_FILES
STATUS=$?
if [ $STATUS -ne 0 ]; then
echo ""
echo "WPCS found violations. Run ./vendor/bin/phpcbf to auto-fix what it can."
echo "Commit blocked."
exit 1
fi
exit 0
Make it executable:
chmod +x .git/hooks/pre-commit
This hook only scans files currently staged with git add, not the entire project. That keeps the commit fast on large codebases and makes the feedback specific: the error output tells you exactly which staged file and line has the violation.
The limitation of .git/hooks/ is that it is not tracked by Git, so new team members who clone the project do not get the hook automatically. The next section covers how to fix this with Husky.
Pre-Commit Hook via Husky for Node.js Projects
If your plugin uses npm for build tooling (webpack, Vite, or @wordpress/scripts), you likely have a package.json already. Husky is a small npm package that manages Git hooks as files inside your repository so every developer gets them automatically after npm install.
Install Husky and lint-staged:
npm install --save-dev husky lint-staged
npx husky install
Add a prepare script to package.json so Husky activates for every developer who runs npm install:
"scripts": {
"prepare": "husky install"
}
Create the pre-commit hook file:
npx husky add .husky/pre-commit "npx lint-staged"
Add the lint-staged configuration to package.json:
"lint-staged": {
"*.php": [
"./vendor/bin/phpcs"
]
}
lint-staged passes only the files being committed to phpcs, making the check fast. If phpcs exits with a non-zero code (violations found), lint-staged aborts the commit and prints the errors.
For projects that want auto-fixing before the commit is blocked, you can add phpcbf as the first step:
"lint-staged": {
"*.php": [
"./vendor/bin/phpcbf",
"./vendor/bin/phpcs"
]
}
phpcbf fixes what it can, then phpcs checks again. If any unfixable violations remain, the commit is still blocked with a clear error message.
If your project is pure PHP with no package.json, the bash hook approach in the previous section works well. For sharing it across the team, create a scripts/setup-hooks.sh file that copies hooks to .git/hooks/ and add a note in your README asking developers to run it after cloning.
Integrating WPCS in CI on Every Pull Request
Local hooks prevent violations in individual commits, but CI integration ensures that nothing slips through via force-push, direct commits to the main branch, or a developer who has not run composer install yet.
GitHub Actions
Create .github/workflows/wpcs.yml:
name: WPCS
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main ]
jobs:
phpcs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
tools: composer
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Run WPCS
run: ./vendor/bin/phpcs
The shivammathur/setup-php action handles PHP installation and Composer. It caches the Composer dependencies between runs, so subsequent CI runs complete in around 30 seconds instead of 2 minutes.
When phpcs finds a violation, the action exits with a non-zero code, the GitHub Actions job turns red, and the pull request is blocked from merging if you have branch protection rules enabled.
Making CI Output Readable with Inline Annotations
Add the --report=checkstyle flag to phpcs and pipe it to a GitHub Actions annotation tool so violations appear directly in the pull request diff view rather than buried in the CI log:
- name: Run WPCS
run: ./vendor/bin/phpcs --report=checkstyle | cs2pr
Install cs2pr as a Composer dev dependency to convert the checkstyle output to GitHub annotation format:
composer require --dev staabm/annotate-pull-request-from-checkstyle
With this setup, each violation appears as an inline comment on the exact line in the pull request, making code review faster. Reviewers see the WPCS issue without switching to the CI log tab.
GitLab CI
For teams on GitLab, the equivalent configuration in .gitlab-ci.yml looks like this:
wpcs:
image: php:8.1-cli
before_script:
- curl -sS https://getcomposer.org/installer | php
- mv composer.phar /usr/local/bin/composer
- composer install --no-interaction
script:
- ./vendor/bin/phpcs
only:
- merge_requests
- main
Handling Legacy Code: Incremental Enforcement
Turning WPCS on for a project that has been running for years can produce thousands of violations. Blocking every pull request until all of them are fixed is impractical. A better approach is incremental enforcement.
The Baseline Strategy
A practical manual baseline approach that works on any codebase:
- Run
./vendor/bin/phpcbfto auto-fix everything fixable. - For remaining violations, add
// phpcs:ignore WordPress.Security.NonceVerification.Recommendedinline comments on lines you cannot fix immediately, with a TODO comment explaining the debt. - Commit this state as the baseline.
- From this point, CI will only catch new violations introduced in future code.
Keep a docs/phpcs-debt.md file that tracks the number of remaining ignore comments and which sniffs they cover. Review it quarterly and work down the list.
Custom Ruleset for Gradual Adoption
Another approach is to start with a subset of rules. In your phpcs.xml, reference only the sniffs you are ready to enforce today:
<rule ref="WordPress-Core">
<exclude name="WordPress.Arrays.ArrayIndentation"/>
<exclude name="WordPress.Files.FileName"/>
</rule>
Remove exclusions one by one as you clean up each category. This way, CI stays green while you pay down the debt systematically.
Common WPCS Violations and How to Fix Them
Some sniffs confuse developers seeing WPCS for the first time. Here are the patterns that appear most often and how to address each one.
Direct Database Queries Without Prepare
Violation: WordPress.DB.PreparedSQL.NotPrepared
// Wrong
$results = $wpdb->get_results(
"SELECT * FROM $wpdb->posts WHERE post_status = 'publish'"
);
// Correct
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM $wpdb->posts WHERE post_status = %s",
'publish'
)
);
Output Without Escaping
Violation: WordPress.Security.EscapeOutput.OutputNotEscaped
// Wrong
echo $user_input;
// Correct - pick the right escaping function for the context
echo esc_html( $user_input ); // for text content
echo esc_attr( $user_input ); // for HTML attributes
echo esc_url( $user_input ); // for URLs
Nonce Not Verified on Form Submissions
Violation: WordPress.Security.NonceVerification.Missing
// Wrong
if ( isset( $_POST['my_field'] ) ) {
$value = sanitize_text_field( $_POST['my_field'] );
}
// Correct
if (
isset( $_POST['my_nonce'] )
&& wp_verify_nonce( sanitize_key( $_POST['my_nonce'] ), 'my_action' )
) {
if ( isset( $_POST['my_field'] ) ) {
$value = sanitize_text_field( wp_unslash( $_POST['my_field'] ) );
}
}
Missing Function Documentation
Violation: Squiz.Commenting.FunctionComment.Missing
Every function needs at minimum a one-line description plus @param and @return annotations when the WordPress-Docs ruleset is active:
/**
* Register the plugin settings page.
*
* @since 1.0.0
* @return void
*/
function my_plugin_register_settings_page(): void {
add_options_page( /* ... */ );
}
Testing Your WPCS Setup End to End
Before relying on the setup in production, run a quick smoke test to verify every layer works correctly.
Test the IDE integration
Open any PHP file in your project, add a deliberate violation on a new line (for example, add a direct echo $_GET['test']; without escaping), and save. Within a second or two, you should see a yellow or red underline on that line. Hover over it to confirm the message mentions WordPress.Security.EscapeOutput. Delete the test line and save again.
Test the pre-commit hook
Create a throwaway PHP file with an obvious style violation like a line using spaces for indentation instead of tabs. Stage it with git add and run git commit -m "test". The commit should be blocked with a phpcs error output. Delete the file and verify the hook is not blocking clean commits.
Test CI locally with act
If you have act installed (a tool for running GitHub Actions locally via Docker), you can simulate the full CI run before pushing:
act pull_request --job phpcs
This runs the exact same steps as GitHub Actions on your local machine, confirming the workflow file is valid and the phpcs command exits cleanly.
What to check after onboarding a new developer
When a new team member clones the project, ask them to run through this checklist:
- Run
composer installand verify./vendor/bin/phpcs -ishows WordPress rulesets. - Open the project in VS Code or PhpStorm and confirm the IDE extension is active (a deliberate violation should underline).
- If using the bash hook, run the
scripts/setup-hooks.shscript. If using Husky, confirm.husky/pre-commitexists afternpm install. - Run
./vendor/bin/phpcsfrom the project root and confirm it exits with 0 errors (assuming the baseline is clean).
This onboarding check takes five minutes and prevents the scenario where a developer writes several days of code before discovering their environment was not configured correctly.
Keeping WPCS Updated
WPCS releases new versions alongside major WordPress releases and when the PHP_CodeSniffer API changes. Update regularly to catch newly introduced sniffs. WordPress coding standards enforcement pairs well with other maintenance habits: the WordPress cron jobs guide covers scheduled tasks that can automate periodic dependency updates.
composer update squizlabs/php_codesniffer wp-coding-standards/wpcs
After updating, run phpcs against the full project to see if any new sniffs fire on existing code. Treat a WPCS version bump the same as a dependency upgrade: review the diff, fix or suppress new violations intentionally, and commit the composer.lock.
Pin major versions in composer.json to avoid surprise breakage from sniff removals or severity changes:
"require-dev": {
"squizlabs/php_codesniffer": "^3.9",
"wp-coding-standards/wpcs": "^3.1",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0"
}
Putting It All Together
The full workflow, once set up, is invisible during normal development. You write code, save the file, and VS Code or PhpStorm underlines violations in real time. When you try to commit, the pre-commit hook catches anything you missed. When you open a pull request, GitHub Actions confirms the branch is clean before the review even starts.
The one-time setup takes about 30 minutes for a fresh project:
- 5 minutes:
composer requirethe three packages and verify withphpcs -i. - 10 minutes: Write your
phpcs.xml, run phpcbf to clear auto-fixable violations, handle the rest manually. - 5 minutes: Install and configure the IDE extension (VS Code or PhpStorm).
- 5 minutes: Create and make executable the pre-commit hook, or set up Husky if you have a
package.json. - 5 minutes: Add the GitHub Actions workflow file.
After that, WPCS runs on every save, every commit, and every pull request with no manual effort. Code reviews focus on logic and architecture rather than debating whether a space belongs before a parenthesis.
For teams building custom Gutenberg blocks alongside their plugin PHP, this phpcs workflow pairs well with ESLint and the @wordpress/eslint-plugin package, which enforces JavaScript coding standards in the same pre-commit and CI pipeline. See the guide to building custom Gutenberg blocks with React for how block development fits into a professional plugin workflow.
Developers who want to take code quality further can combine WPCS with PHPStan for static analysis. While WPCS checks style and known security patterns, PHPStan analyzes type correctness and logic paths. The two tools are complementary: run both in CI for the most thorough pre-merge check. The WordPress security audit checklist covers the broader security review process that WPCS enforcement supports.
@wordpress/scripts code quality Git hooks PHP CodeSniffer WordPress coding standards
Last modified: May 8, 2026









