| name | wp-coding-standards |
| description | Enforces WordPress Coding Standards (WPCS) across PHP code. Covers WordPress-Core, WordPress-Extra, and WordPress-VIP rulesets. Provides definitive reference for data sanitization, output escaping, nonce verification, PHPDoc, hook patterns, database query safety, and auto-enforcement tooling setup.
|
| tools | ["Bash","Read","Edit","Write","Grep","Glob"] |
WordPress Coding Standards Enforcement
This skill is the definitive reference for writing PHP code that passes WordPress
Coding Standards (WPCS) sniffs. Every pattern includes WRONG vs RIGHT examples.
1. WordPress-Core Standard
Yoda Conditions
Place the constant or literal on the LEFT side of comparisons. This prevents
accidental assignment (= instead of ===) from silently succeeding.
if ( $type === 'post' ) { ... }
if ( $count == 0 ) { ... }
if ( 'post' === $type ) { ... }
if ( 0 === $count ) { ... }
Array Syntax
Always use array(). Short array syntax [] is not permitted by WordPress-Core.
$items = [];
$config = ['key' => 'value'];
$items = array();
$config = array( 'key' => 'value' );
Spaces Inside Parentheses
Add a single space after opening and before closing parentheses in control
structures, function declarations, and function calls.
if ($foo) {
my_function($bar, $baz);
}
if ( $foo ) {
my_function( $bar, $baz );
}
Naming Conventions
Functions, variables, and action/filter names use snake_case. Classes use
Upper_Snake_Case. Constants use UPPER_SNAKE_CASE. File names use lowercase
hyphens (class-my-widget.php).
function getData() { ... }
$firstName = 'Jen';
class myWidget { ... }
function get_data() { ... }
$first_name = 'Jen';
class My_Widget { ... }
Indentation and Braces
Use tabs for indentation, never spaces. Opening braces go on the same line for
functions, classes, and control structures. Closing braces go on their own line.
function get_items()
{
if ($condition)
{
return true;
}
}
function get_items() {
if ( $condition ) {
return true;
}
}
String Interpolation
Use single quotes when the string contains no variables. Use double quotes or
concatenation for variable strings.
$msg = "No items found";
$msg = 'No items found';
$msg = "Found {$count} items";
$msg = 'Found ' . $count . ' items';
2. WordPress-Extra Standard
WordPress-Extra extends Core with additional sniffs for code quality.
Loose Comparisons
Never use == or != for comparisons. Always use strict === or !==.
if ( $value == false ) { ... }
if ( $id != 0 ) { ... }
if ( false === $value ) { ... }
if ( 0 !== $id ) { ... }
Assignment in Conditions
Do not assign variables inside if, while, or for conditions.
if ( $result = get_post( $id ) ) { ... }
$result = get_post( $id );
if ( $result ) { ... }
Complex Expressions in Function Calls
Do not nest complex operations inside function call arguments.
update_option( 'count', absint( $_POST['count'] ) + get_option( 'offset' ) );
$count = absint( $_POST['count'] ) + get_option( 'offset' );
update_option( 'count', $count );
Default Switch Case
Every switch statement must include a default case.
switch ( $status ) {
case 'publish':
break;
case 'draft':
break;
}
switch ( $status ) {
case 'publish':
break;
case 'draft':
break;
default:
break;
}
3. WordPress-VIP Standard
VIP sniffs enforce performance and security for high-traffic WordPress sites.
No Direct Database Queries
Never use $wpdb->query() with inline SQL. Always use $wpdb->prepare().
$wpdb->query( "DELETE FROM {$wpdb->posts} WHERE ID = {$id}" );
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->posts} WHERE ID = %d", $id ) );
Restricted Functions
These functions are forbidden on VIP: eval(), extract(), create_function(),
file_put_contents(), file_get_contents() (use wp_remote_get() instead),
fwrite(), fopen() for writing, error_log() (use trigger_error() with
E_USER_WARNING), ini_set(), exec(), shell_exec(), system(),
passthru(), proc_open().
$data = file_get_contents( 'https://api.example.com/data' );
$response = wp_remote_get( 'https://api.example.com/data' );
if ( ! is_wp_error( $response ) ) {
$data = wp_remote_retrieve_body( $response );
}
Caching Requirements
Any function that queries the database must use object caching.
function get_featured_ids() {
return get_posts( array( 'fields' => 'ids', 'meta_key' => 'featured' ) );
}
function get_featured_ids() {
$cache_key = 'featured_post_ids';
$ids = wp_cache_get( $cache_key, 'zentratec' );
if ( false === $ids ) {
$ids = get_posts( array( 'fields' => 'ids', 'meta_key' => 'featured' ) );
wp_cache_set( $cache_key, $ids, 'zentratec', HOUR_IN_SECONDS );
}
return $ids;
}
4. Data Sanitization
Every piece of user input MUST be sanitized before use. Match the sanitizer to
the data type and context.
| Context | Sanitizer |
|---|
| Plain text field | sanitize_text_field() |
| Textarea | sanitize_textarea_field() |
| Email address | sanitize_email() |
| URL | esc_url_raw() (for DB storage) |
| Filename | sanitize_file_name() |
| HTML class | sanitize_html_class() |
| Post slug / key | sanitize_key() / sanitize_title() |
| Integer | absint() or intval() |
| Rich HTML (post) | wp_kses_post() |
| Rich HTML (custom) | wp_kses() with allowed tags array |
| Array of values | array_map( 'sanitize_text_field', $arr ) |
$name = $_POST['name'];
$page = $_GET['page'];
$name = sanitize_text_field( wp_unslash( $_POST['name'] ) );
$page = absint( $_GET['page'] );
Always call wp_unslash() before sanitization on superglobals because WordPress
adds magic quotes via wp_magic_quotes().
5. Data Escaping (Output)
Every value rendered in HTML MUST be escaped at the point of output. This is the
late escaping principle -- escape as late as possible, never at storage.
| Output Context | Escaping Function |
|---|
| HTML element content | esc_html() |
| HTML attribute value | esc_attr() |
| URL (href, src) | esc_url() |
| JavaScript string | esc_js() |
| Rich HTML (post body) | wp_kses_post() |
| Translated + escaped | esc_html__(), esc_attr__(), esc_html_e() |
echo '<a href="' . $url . '">' . $title . '</a>';
echo '<input value="' . $value . '">';
_e( $user_string, 'textdomain' );
echo '<a href="' . esc_url( $url ) . '">' . esc_html( $title ) . '</a>';
echo '<input value="' . esc_attr( $value ) . '">';
echo esc_html( $user_string );
For printf/sprintf patterns, escape within the arguments.
printf(
'<h2 class="%1$s">%2$s</h2>',
esc_attr( $class ),
esc_html( $heading )
);
6. Nonce Verification
Every form submission, AJAX request, and state-changing action MUST include and
verify a nonce to prevent CSRF attacks.
Form Pattern
wp_nonce_field( 'zentratec_save_settings', 'zentratec_nonce' );
if ( ! isset( $_POST['zentratec_nonce'] )
|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['zentratec_nonce'] ) ), 'zentratec_save_settings' )
) {
wp_die( 'Security check failed.' );
}
AJAX Pattern
wp_localize_script( 'zentratec-app', 'zentratecData', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'zentratec_ajax' ),
) );
function zentratec_ajax_handler() {
check_ajax_referer( 'zentratec_ajax', 'nonce' );
wp_send_json_success( $data );
}
add_action( 'wp_ajax_zentratec_action', 'zentratec_ajax_handler' );
REST API Pattern
register_rest_route( 'zentratec/v1', '/items', array(
'methods' => 'POST',
'callback' => 'zentratec_create_item',
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
},
) );
7. PHPDoc Requirements
File Header
Every PHP file must begin with a file-level docblock.
Function Documentation
Every function must have a docblock with description, @since, @param, and
@return tags.
function zentratec_get_featured_ids( $count = 10, $status = 'publish' ) { ... }
Hook Documentation
Document do_action() and apply_filters() calls with @since and @param.
do_action( 'zentratec_item_saved', $item_id, $item_data );
8. Hook Callback Patterns
Named Functions Over Closures
Always use named functions for hook callbacks. Closures cannot be removed with
remove_action() / remove_filter().
add_action( 'init', function () {
register_post_type( 'zt_item', array() );
} );
function zentratec_register_post_types() {
register_post_type( 'zt_item', array() );
}
add_action( 'init', 'zentratec_register_post_types' );
Priority Management
Document non-default priorities. Use constants or comments to explain why.
add_action( 'init', 'zentratec_modify_post_types', 99 );
Removal Pattern
To remove a callback, match the function name and priority exactly.
remove_action( 'wp_head', 'wp_generator' );
remove_filter( 'the_content', 'wpautop', 10 );
9. Database Queries
Always Prepare
Every query containing user-supplied or variable data MUST use $wpdb->prepare().
$results = $wpdb->get_results(
"SELECT * FROM {$wpdb->postmeta} WHERE meta_key = '{$key}' AND meta_value = '{$value}'"
);
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s",
$key,
$value
)
);
Prefer WP Abstractions
Use WP_Query, get_posts(), get_post_meta(), get_option() instead of raw
SQL whenever possible.
$wpdb->get_results( "SELECT ID FROM wp_posts WHERE post_type = 'page' LIMIT 10" );
$pages = get_posts( array(
'post_type' => 'page',
'posts_per_page' => 10,
'fields' => 'ids',
) );
When Raw SQL Is Needed
For complex queries that WP_Query cannot express, use the appropriate $wpdb
method: get_var(), get_row(), get_col(), get_results(), insert(),
update(), delete(), replace().
$total = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts}
WHERE post_type = %s AND post_status = %s AND post_date > %s",
'zt_item',
'publish',
$since_date
)
);
10. Auto-Enforcement Setup
Install phpcs and WordPress-Coding-Standards
composer require --dev squizlabs/php_codesniffer
composer require --dev wp-coding-standards/wpcs
composer require --dev phpcompatibility/phpcompatibility-wp
composer require --dev dealerdirect/phpcodesniffer-composer-installer
vendor/bin/phpcs -i
Configuration File (phpcs.xml.dist)
Place at project root.
<?xml version="1.0"?>
<ruleset name="Zentratec">
<description>WordPress Coding Standards for Zentratec</description>
<file>./wp-content/themes/oshin_child/</file>
<file>./wp-content/plugins/zentratec-*/</file>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern>
<arg name="extensions" value="php"/>
<arg name="colors"/>
<arg value="sp"/>
<config name="minimum_supported_wp_version" value="6.4"/>
<config name="testVersion" value="8.0-"/>
<rule ref="WordPress"/>
<rule ref="WordPress-Docs"/>
<rule ref="PHPCompatibilityWP"/>
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
<properties>
<property name="prefixes" type="array">
<element value="zentratec"/>
<element value="zt_"/>
</property>
</properties>
</rule>
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array">
<element value="zentratec"/>
</property>
</properties>
</rule>
</ruleset>
Run Commands
vendor/bin/phpcs
vendor/bin/phpcbf
vendor/bin/phpcs wp-content/themes/oshin_child/functions.php
vendor/bin/phpcs --standard=WordPress-Extra path/to/file.php
VS Code Integration
Install the wongjn.php-sniffer extension. Add to .vscode/settings.json:
{
"phpSniffer.executablesFolder": "./vendor/bin/",
"phpSniffer.standard": "./phpcs.xml.dist",
"phpSniffer.autoDetect": true,
"phpSniffer.onTypeDelay": 500
}
PhpStorm Integration
- Settings > PHP > Quality Tools > PHP_CodeSniffer
- Set path to
vendor/bin/phpcs
- Inspections > PHP > Quality Tools > PHP_CodeSniffer validation
- Set coding standard to "Custom" and point to
phpcs.xml.dist
CI/CD Integration (GitHub Actions)
name: WPCS
on: [pull_request]
jobs:
phpcs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
- run: composer install --no-interaction
- run: vendor/bin/phpcs --report=checkstyle | cs2pr
Quick Reference: Pre-Commit Checklist
Before committing any PHP file, verify:
- All user input is sanitized with the correct function
- All output is escaped at the point of rendering
- All form and AJAX handlers verify a nonce
- All database queries use
$wpdb->prepare() or WP abstractions
- All functions have PHPDoc with
@since, @param, @return
- All hook callbacks are named functions (not closures)
- Yoda conditions are used for all comparisons
array() syntax is used (not [])
- Spaces inside parentheses are present
- Tabs are used for indentation, not spaces
phpcs reports zero errors on changed files