automattic/vipwpcs
PHP_CodeSniffer sniffs and rulesets for validating WordPress VIP code. Includes WordPressVIPMinimum for the older WordPress.com VIP platform and WordPress-VIP-Go for VIP Go, flagging errors and warnings per VIP documentation.
VIPCS is a developer-only Composer package that registers two PHP_CodeSniffer standards (WordPressVIPMinimum for legacy WP.com VIP, WordPress-VIP-Go for VIP Go). It is not a runtime library — treat it like PHPUnit: install to require-dev, wire into CI, and never ship it.
1. Install (Composer 2.2+ requires explicit plugin permission):
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev automattic/vipwpcs
That single require pulls PHPCS 3.13.2+, PHPCSUtils, PHPCSExtra, WPCS 3.1.0+ and VariableAnalysis 2.12.0+, and the Composer installer plugin registers the standards with PHPCS automatically.
2. Add a phpcs.xml.dist at the project root (the file is the contract — commit it):
<?xml version="1.0"?>
<ruleset name="Acme VIP">
<description>VIP Go platform rules for custom plugin/theme code.</description>
<file>./wp-content/plugins/acme-api</file>
<file>./wp-content/themes/acme</file>
<arg name="extensions" value="php"/>
<arg name="parallel" value="8"/>
<arg name="cache" value=".phpcs.cache"/>
<arg name="colors"/>
<arg value="p"/>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern>
<exclude-pattern>*/build/*</exclude-pattern>
<rule ref="WordPress-VIP-Go"/>
</ruleset>
3. Verify registration, then run:
vendor/bin/phpcs -i # should list WordPress-VIP-Go and WordPressVIPMinimum
vendor/bin/phpcs -s # -s => append sniff codes, essential for triage
Where to look first:
vendor/automattic/vipwpcs/WordPress-VIP-Go/ruleset.xml and WordPressVIPMinimum/ruleset.xml. There is no per-sniff docs directory in the repo — the ruleset XML is the authoritative list of what is enabled and at what severity. Read it before arguing about a violation.vendor/bin/phpcs -e --standard=WordPress-VIP-Go to enumerate every sniff in the standard.docs.wpvip.com/php_codesniffer/errors/, /warnings/, and the Code Analysis Bot report page) for the error-vs-warning semantics. That distinction is a platform policy, not a PHPCS detail.First use case: point it at the one custom plugin that talks to a VIP-only API before your first deploy. The Code Analysis Bot on VIP runs on push/PR and will surface the same sniff codes, so a clean local run is the fastest way to avoid a red deploy gate. Expect the first run on a legacy codebase to be brutal — hundreds of hits, dominated by Performance/WPQueryParams (post__not_in, suppress_filters), Performance/NoPaging, Performance/OrderByRand, and escaping sniffs. That is normal; see triage below.
Triage loop (the actual day-to-day workflow). Never read the full report top-to-bottom. Aggregate by sniff, fix the highest-count / lowest-risk category, re-run:
vendor/bin/phpcs -q --report=source # sniff => count, sorted
vendor/bin/phpcs -s --sniffs=WordPressVIPMinimum.Performance.WPQueryParams
vendor/bin/phpcs -s --report=full wp-content/plugins/acme-api/includes/query.php
--report=source first, --report=full -s last. Fixing post__not_in and suppress_filters is usually a genuine performance win, not lint noise, so it doubles as real work.
Mirroring the VIP Code Analysis Bot in CI. The bot fails builds on errors only; warnings are advisory. Reproduce that so your pipeline and the platform agree:
vendor/bin/phpcs -q --report=checkstyle --runtime-set ignore_warnings_on_exit 1 | cs2pr
Keep ignore_warnings_on_exit 1 in CI but omit it in the local/pre-commit run, so warnings are visible to authors without blocking merges. Also pass --cache in CI when you can persist .phpcs.cache between runs — full scans of a large theme are slow even with --parallel.
Scope a Laravel hybrid codebase. If you are running WordPress embedded in a Laravel app (headless content API, johnpbloch/wordpress + Symfony-ish front end, Bedrock-style layout), do not point VIPCS at app/, config/, database/, routes/ or resources/views/. Blade templates parse as PHP and produce pure noise, and Classes/DeclarationCompatibility will fire on Laravel classes that have nothing to do with WP core signatures. Use per-path <file> entries as in the sample above, or run two jobs:
vendor/bin/phpcs --standard=WordPress-VIP-Go wp-content/ # VIP surface
vendor/bin/pint --test app/ # Laravel surface
VIPCS and Pint (PHP-CS-Fixer) do not conflict at the engine level; they conflict at the policy level. Keep them on disjoint paths and you avoid the "which style wins" argument entirely.
Coexistence with WPCS. VIPCS deliberately contains only the rules VIP classifies as errors/warnings. If you also want formatting/whitespace enforcement, add <rule ref="WordPress-Extra"/> alongside WordPress-VIP-Go — but keep them in separate rulesets if you intend to waive style findings on legacy code while still gating on platform risk.
Suppression with an audit trail. Suppressions survive code review only when they carry a reason:
// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- small curated ID list, cache-safe.
$query = new WP_Query( [ 'post__not_in' => $featured_ids ] );
Prefer phpcs:ignore on the specific line over phpcs:disable blocks, and never a file-wide disable at the top of a file — the bot still reports it and reviewers will bounce it. When a whole category is genuinely inapplicable (e.g. escaping sniffs on a CLI-only importer), scope it in a separate ruleset for that directory rather than sprinkling ignores.
Extension points. VIPCS is just a standard, so it composes: <rule ref="WordPress-VIP-Go"/> inside your own custom standard, plus your own sniffs registered in the same ruleset. For team-specific bans (a forbidden helper, a required wrapper around remote requests), write one sniff in an internal standard and ref VIPCS beneath it — this is far cheaper than trying to patch VIPCS (GPL-2.0-or-later; forking for local policy is allowed but creates a versioning burden you do not want).
Editor + pre-commit. Wire phpcs into PHPStorm's Quality Tools or the VS Code phpcs extension pointing at phpcs.xml.dist, and add a GrumPHP or CaptainHook task before commit. The feedback latency difference between "pre-commit" and "CI" is the single biggest determinant of whether VIPCS gets adopted or resented.
Version discipline. Pin "automattic/vipwpcs": "^3.0", commit composer.lock, and on upgrade run composer update automattic/vipwpcs --with-dependencies — the 3.0.1 notes are explicit about needing the transitive PHPCS bump to get PHP 8.2/8.3 support. Treat a VIPCS minor bump as a code change: new sniffs can turn a green branch red with no edits on your side.
WordPress-VIP-Go standard is not installed" almost always means allow-plugins.dealerdirect/phpcodesniffer-composer-installer was never granted, so the plugin never ran and installed_paths was never set. Fix the config, then composer install again — requiring the package alone is not enough on Composer 2.2+.WordPressVIPMinimum is for the older WP.com VIP platform; WordPress-VIP-Go is for VIP Go. Running both doubles your findings and confuses the gate. Confirm which platform the site is on before you write the ruleset.Performance.WPQueryParams.PostNotIn became ...PostNotIn_post__not_in in 3.0.0, and SuppressFiltersTrue became SuppressFilters_suppress_filters. Any phpcs:ignore referencing a retired code silently stops suppressing (and may surface an invalid-annotation warning), so grep your phpcs:ignore comments as part of every major upgrade rather than trusting CI to tell you.ignore_warnings_on_exit, or --warning-severity=0 to mute warnings entirely for a first-pass cleanup)..phpcs.cache is keyed on file mtime; change the ruleset or bump the package and you can get pre-change results. Delete the cache file when results look wrong, and add it to .gitignore.--parallel needs pcntl. Fine on Linux CI and macOS; unavailable/limited on Windows and some minimal containers. If you see ignored parallel args, that is why. In Laravel Sail/Docker, confirm the PHP image has pcntl compiled in.VariableAnalysis is bundled and configured by the standard. VIPCS removed its own Variables/VariableAnalysis sniff in 3.0.0 and now relies on sirbrillig/phpcs-variable-analysis (config moved into the WordPressVIPMinimum ruleset in 2.3.4). Unused-variable and undefined-variable findings therefore come from a third-party sniff — check its docs, not VIP's, when the message is cryptic.Classes/DeclarationCompatibility tracks WordPress core signatures. It will flag method signatures that diverge from the WP version it was synced against. On a project targeting an older or newer WP core than VIPCS assumes, this produces findings you cannot fix. Scope-exclude it for those files and note the WP version in a comment — do not "fix" the signature to satisfy the sniff.exclude-pattern is a trap. Excluding vendor/ and build output is correct; excluding includes/legacy/ to get to green is how a VIP deploy gate surprises you later. If legacy code cannot be fixed, use scoped rulesets with a tracked TODO rather than a global exclusion.ruleset.xml and the sniff class under vendor/automattic/vipwpcs/WordPressVIPMinimum/Sniffs/ — phpcs -e plus --sniffs= to isolate one check is faster than guessing.<rule ref="WordPress-VIP-Go"><exclude name="..."/></rule>) is the supported extension point; patching files under vendor/ is not and will vanish on the next composer install.How can I help you explore Laravel packages today?