Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add xonack/wp-coding-standards-claude-skill --skill "wp-coding-standards"
Install specific skill from multi-skill repository
# Description
>
# SKILL.md
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.
// WRONG β variable on left risks accidental assignment
if ( $type === 'post' ) { ... }
if ( $count == 0 ) { ... }
// RIGHT β literal on left (Yoda style)
if ( 'post' === $type ) { ... }
if ( 0 === $count ) { ... }
Array Syntax
Always use array(). Short array syntax [] is not permitted by WordPress-Core.
// WRONG
$items = [];
$config = ['key' => 'value'];
// RIGHT
$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.
// WRONG
if ($foo) {
my_function($bar, $baz);
}
// RIGHT
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).
// WRONG
function getData() { ... }
$firstName = 'Jen';
class myWidget { ... }
// RIGHT
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.
// WRONG β spaces, brace on new line
function get_items()
{
if ($condition)
{
return true;
}
}
// RIGHT β tabs, braces on same line
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.
// WRONG β double quotes with no variables
$msg = "No items found";
// RIGHT
$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 !==.
// WRONG β loose comparison, type coercion bugs
if ( $value == false ) { ... }
if ( $id != 0 ) { ... }
// RIGHT β strict comparison
if ( false === $value ) { ... }
if ( 0 !== $id ) { ... }
Assignment in Conditions
Do not assign variables inside if, while, or for conditions.
// WRONG β assignment in condition
if ( $result = get_post( $id ) ) { ... }
// RIGHT β assign first, then check
$result = get_post( $id );
if ( $result ) { ... }
Complex Expressions in Function Calls
Do not nest complex operations inside function call arguments.
// WRONG β complex expression inside call
update_option( 'count', absint( $_POST['count'] ) + get_option( 'offset' ) );
// RIGHT β compute first, then call
$count = absint( $_POST['count'] ) + get_option( 'offset' );
update_option( 'count', $count );
Default Switch Case
Every switch statement must include a default case.
// WRONG β missing default
switch ( $status ) {
case 'publish':
break;
case 'draft':
break;
}
// RIGHT
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().
// WRONG β direct query, SQL injection risk
$wpdb->query( "DELETE FROM {$wpdb->posts} WHERE ID = {$id}" );
// RIGHT β prepared statement
$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().
// WRONG β direct file operation
$data = file_get_contents( 'https://api.example.com/data' );
// RIGHT β WordPress HTTP API
$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.
// WRONG β uncached query on every page load
function get_featured_ids() {
return get_posts( array( 'fields' => 'ids', 'meta_key' => 'featured' ) );
}
// RIGHT β cached with expiration
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 ) |
// WRONG β raw superglobal used directly
$name = $_POST['name'];
$page = $_GET['page'];
// RIGHT β sanitized immediately on access
$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() |
// WRONG β unescaped output
echo '<a href="' . $url . '">' . $title . '</a>';
echo '<input value="' . $value . '">';
_e( $user_string, 'textdomain' );
// RIGHT β escaped at point of output
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.
// RIGHT β escape inside sprintf 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
// In form output:
wp_nonce_field( 'zentratec_save_settings', 'zentratec_nonce' );
// In form handler:
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
// Enqueue with nonce:
wp_localize_script( 'zentratec-app', 'zentratecData', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'zentratec_ajax' ),
) );
// In AJAX handler:
function zentratec_ajax_handler() {
check_ajax_referer( 'zentratec_ajax', 'nonce' );
// ... process request
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' );
},
) );
// REST API uses built-in nonce via X-WP-Nonce header (wp.apiFetch handles it).
7. PHPDoc Requirements
File Header
Every PHP file must begin with a file-level docblock.
/**
* Zentratec custom post type registration.
*
* @package Zentratec
* @since 1.0.0
*/
Function Documentation
Every function must have a docblock with description, @since, @param, and
@return tags.
/**
* Retrieve featured post IDs with caching.
*
* @since 1.2.0
*
* @param int $count Number of posts to return.
* @param string $status Post status to query. Default 'publish'.
* @return int[] Array of post IDs, empty array if none found.
*/
function zentratec_get_featured_ids( $count = 10, $status = 'publish' ) { ... }
Hook Documentation
Document do_action() and apply_filters() calls with @since and @param.
/**
* Fires after a zentratec item is saved.
*
* @since 1.3.0
*
* @param int $item_id The saved item ID.
* @param array $item_data The item data array.
*/
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().
// WRONG β anonymous closure, cannot be removed
add_action( 'init', function () {
register_post_type( 'zt_item', array() );
} );
// RIGHT β named function
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.
// Late priority to run after other plugins register their types.
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().
// WRONG β direct interpolation, SQL injection
$results = $wpdb->get_results(
"SELECT * FROM {$wpdb->postmeta} WHERE meta_key = '{$key}' AND meta_value = '{$value}'"
);
// RIGHT β prepared with typed placeholders
$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.
// WRONG β raw SQL for something WP_Query handles
$wpdb->get_results( "SELECT ID FROM wp_posts WHERE post_type = 'page' LIMIT 10" );
// RIGHT β WordPress API
$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
# Project-level installation with Composer
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
# Verify standards are registered
vendor/bin/phpcs -i
# Should list: WordPress, WordPress-Core, WordPress-Extra, WordPress-Docs, WordPress-VIP-Go
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
# Check for violations
vendor/bin/phpcs
# Auto-fix what can be fixed
vendor/bin/phpcbf
# Check a single file
vendor/bin/phpcs wp-content/themes/oshin_child/functions.php
# Check with specific standard
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
phpcsreports zero errors on changed files
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.