A shareable PHP_CodeSniffer ruleset for WordPress plugin development, composing four upstream standards into one opinionated layer:
- PSR-12 — the primary style guide (naming, formatting, braces, indentation).
- WordPress (WPCS) —
WordPress-Extra+WordPress-Docsfor WordPress security, database, PHP, API, and documentation best practices. - WordPress VIP (VIPCS) — a small, curated, platform-agnostic
subset of
WordPressVIPMinimumsecurity/correctness sniffs. - PHPCompatibilityWP — PHP cross-version compatibility aware of WordPress's own polyfills.
PSR-12 → WordPress → WordPress VIP
PSR-12 wins wherever rules overlap — especially naming and code formatting.
WordPress best-practice sniffs are layered on top, but every WordPress or VIP
sniff that conflicts with PSR-12 (tabs vs spaces, brace placement, snake_case
function/variable names, file naming, array alignment, …) is explicitly
excluded in WPTechnixWordPress/ruleset.xml.
The result: PSR-12 code style with WordPress-grade security/correctness checks.
VIPCS (automattic/vipwpcs) is not a style guide — it is additive policy
for the managed WordPress VIP hosting platform, layered on top of WPCS. Most
of its sniffs assume the VIP runtime and become false positives on a
self-hosted plugin because they:
- recommend VIP-only wrapper functions that don't exist elsewhere
(
wpcom_vip_file_get_contents, "uncached"get_posts/attachment_url_to_postid); - encode VIP's Varnish/Batcache edge cache (ban
setcookie, reading$_COOKIE/HTTP_USER_AGENT); - encode VIP's filesystem & ops policy (no
fwrite/unlink, 15-minute minimum cron,posts_per_page ≤ 100, no admin-bar removal).
The standard does not reference the entire WordPressVIPMinimum ruleset.
Only sniffs with universal value are included.
| Sniff | Catches |
|---|---|
Security.ProperEscapingFunction |
Wrong esc_* for the output context |
Security.ExitAfterRedirect |
wp_redirect() not followed by exit |
Security.EscapingVoidReturnFunctions |
Escaping a function that echoes and returns void |
Security.PHPFilterFunctions |
filter_var() with FILTER_DEFAULT (no sanitization) |
Security.StaticStrreplace |
str_replace() misused as escaping |
Hooks.AlwaysReturnInFilter |
Filter callback with a missing return |
Hooks.PreGetPosts |
pre_get_posts modified without an is_admin()/is_main_query() guard |
Classes.DeclarationCompatibility |
Incompatible method override (PHP fatal) |
Classes.RestrictedExtendClasses |
Extending private/non-API core classes (e.g. WP_List_Table) |
Functions.CheckReturnValue |
Using a WP_Error/false result unchecked |
Functions.StripTags |
strip_tags() where wp_strip_all_tags() is safer |
Constants.ConstantString |
define() first arg not a string literal |
Performance.RegexpCompare |
'compare' => 'REGEXP' unindexed query scan |
File operations, cookie/$_SERVER restrictions, the "uncached function"
family, remote-data caching, low cache-TTL, admin-bar removal, restricted
hooks, VIP path-layout include rules, and VIP severity overrides for cron /
posts_per_page. See the inline documentation in
ruleset.xml for the complete list and the
reason for each exclusion.
On top of the four upstream standards, the ruleset enables a small set of
PSR-12-compatible correctness/clarity sniffs from PHPCSExtra
(Universal/Modernize) and core PHPCS (Generic) — no extra dependency, and
none of them affect whitespace or naming:
| Sniff | Catches |
|---|---|
Universal.CodeAnalysis.ForeachUniqueAssignment |
foreach ( $a as $k => $k ) key/value clobber |
Universal.CodeAnalysis.ConstructorDestructorReturn |
Return type / value on a constructor or destructor |
Universal.CodeAnalysis.NoDoubleNegative |
!!$x double-negation |
Generic.CodeAnalysis.UnnecessaryFinalModifier |
final method inside a final class |
Universal.CodeAnalysis.NoEchoSprintf |
echo sprintf( … ) → printf( … ) |
Modernize.FunctionCalls.Dirname |
dirname( dirname( $f ) ) → dirname( $f, 2 ) |
Universal.CodeAnalysis.StaticInFinalClass |
static:: where self:: suffices in a final class |
Universal.Classes.RequireAnonClassParentheses |
new class {} missing constructor parentheses |
Universal.Constants.UppercaseMagicConstants |
Lowercase magic constants (__line__) |
Universal.NamingConventions.NoReservedKeywordParameterNames |
Parameter named after a reserved keyword |
Generic.PHP.DiscourageGoto |
goto |
Generic.PHP.BacktickOperator |
Backtick shell-exec operator |
A few more opinionated sniffs (e.g. DisallowShortTernary, DisallowLonelyIf,
error-level strict comparisons) are included commented-out in the ruleset
for opt-in from your own phpcs.xml.
composer require --dev wptechnix/wp-coding-standardsThis installs PHP_CodeSniffer, WPCS, VIPCS, PHPCompatibilityWP, PHPCSUtils,
PHPCSExtra, and the
dealerdirect/phpcodesniffer-composer-installer
plugin, which auto-registers all standards.
Downstream requirement: Composer must be allowed to run the installer plugin. In your plugin's
composer.json:{ "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } }
Create a phpcs.xml.dist in your plugin. You only need to set the two
project-specific values (prefixes and text_domain) — everything else is
inherited.
<?xml version="1.0" ?>
<ruleset name="MyPlugin">
<description>My WordPress plugin coding standards.</description>
<!-- Option A (recommended): reference by registered standard name. -->
<rule ref="WPTechnixWordPress" />
<!-- Option B (equivalent): reference by path. -->
<!-- <rule ref="./vendor/wptechnix/wp-coding-standards/WPTechnixWordPress/ruleset.xml" /> -->
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
<properties>
<property name="prefixes" type="array">
<element value="myplugin_" />
</property>
</properties>
</rule>
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array">
<element value="my-plugin" />
</property>
</properties>
</rule>
</ruleset>Then run:
vendor/bin/phpcs --standard=phpcs.xml.dist
# or, using the registered standard name:
vendor/bin/phpcs --standard=WPTechnixWordPressCommit messages follow Conventional Commits
and are validated by commitlint via a Husky
commit-msg hook. After cloning:
composer install # PHP toolchain + standards
npm install # commitlint + husky hooksSee CONTRIBUTING.md for details.
MIT © WPTechnix