wp-guidelines

SKILL.md

WordPress Coding Standards & Conventions

Quick-reference for WordPress PHP development. Rules are distilled from the official WordPress Coding Standards (WPCS) sniffs and WordPress core documentation.


1. Naming Conventions

All names use snake_case for functions, variables, and properties. Classes use Pascal_Case with underscores (My_Plugin_Admin). Hook names are all-lowercase with underscores, prefixed with your plugin slug.

Element Convention Example
Functions snake_case acme_get_settings()
Variables snake_case $post_title
Classes Pascal_Case (underscored) Acme_Plugin_Admin
Constants UPPER_SNAKE_CASE ACME_VERSION
Files lowercase-hyphens class-acme-admin.php
Hook names lowercase_underscores acme_after_init
Post type slugs lowercase, a-z0-9_-, max 20 chars acme_book

File naming rules: All lowercase, hyphens as separators, class- prefix for single-class files.

Global prefix rule: ALL plugin/theme globals (functions, classes, constants, hooks, namespaces) must start with a unique prefix (min 4 chars). Blocked prefixes: wordpress, wp, _, php.

Post type slugs: Max 20 chars, no reserved names (post, page, attachment, etc.), no wp_ prefix.


2. PHP Best Practices (WordPress-Specific)

Yoda conditions: Place literals on the LEFT side of ==, !=, ===, !== comparisons.

Strict comparisons: Always pass true as third arg to in_array(), array_search(), array_keys().

Forbidden functions:

Function Why Alternative
extract() Pollutes local scope unpredictably Destructure manually
@ (error suppression) Hides errors Check return values; use wp_safe_*
ini_set() Breaks interoperability Use WP constants (WP_DEBUG, etc.)

Development-only functions (remove before shipping): var_dump, var_export, print_r, error_log, trigger_error, phpinfo, and related debug functions.

Use WordPress functions over PHP natives:

PHP Native WordPress Alternative
json_encode() wp_json_encode()
parse_url() wp_parse_url()
curl_*() wp_remote_get() / wp_remote_post()
file_get_contents() (remote) wp_remote_get()
unlink() wp_delete_file()
strip_tags() wp_strip_all_tags()
rand() / mt_rand() wp_rand()
file_put_contents(), fopen() WP_Filesystem methods

Note: file_get_contents() for local files (using ABSPATH, WP_CONTENT_DIR, plugin_dir_path()) is acceptable.

Type casts: Use short forms: (int), (float), (bool), (string). Never (integer), (real), or (unset).


3. Hooks & Actions

3.1 Registration Patterns

// Actions: side effects (send email, save data, enqueue assets)
add_action( 'init', 'acme_register_post_types' );
add_action( 'wp_enqueue_scripts', 'acme_enqueue_assets' );
add_action( 'admin_menu', 'acme_add_admin_page' );

// Filters: modify and return a value
add_filter( 'the_content', 'acme_append_cta' );
add_filter( 'excerpt_length', 'acme_custom_excerpt_length' );

3.2 Priority and Argument Count

// Default priority is 10, default accepted args is 1
add_filter( 'the_title', 'acme_modify_title', 10, 2 );

function acme_modify_title( $title, $post_id ) {
    // Always declare the correct number of parameters
    return $title;
}

3.3 Removing Hooks

Must match the exact callback, priority, and (for objects) the same instance:

// Remove a function callback
remove_action( 'wp_head', 'wp_generator' );

// Remove with matching priority
remove_filter( 'the_content', 'acme_append_cta', 10 );

// Remove an object method (must be same instance)
remove_action( 'init', array( $instance, 'init' ) );

3.4 Hook Naming Conventions

// Plugin hooks should be prefixed and use underscores
do_action( 'acme_before_render', $context );
$value = apply_filters( 'acme_output_format', $default, $post );

// Dynamic hooks: prefix the static part
do_action( "acme_process_{$type}", $data );

4. Internationalization (i18n)

4.1 Core Functions

Function Purpose
__( $text, $domain ) Return translated string
_e( $text, $domain ) Echo translated string
_x( $text, $context, $domain ) Return with disambiguation context
_ex( $text, $context, $domain ) Echo with disambiguation context
_n( $single, $plural, $number, $domain ) Pluralization
_nx( $single, $plural, $number, $context, $domain ) Plural + context
esc_html__( $text, $domain ) Return translated + HTML-escaped
esc_html_e( $text, $domain ) Echo translated + HTML-escaped
esc_attr__( $text, $domain ) Return translated + attribute-escaped
esc_attr_e( $text, $domain ) Echo translated + attribute-escaped
esc_html_x( $text, $context, $domain ) Return translated + escaped + context
esc_attr_x( $text, $context, $domain ) Return translated + escaped + context

4.2 Rules

  • Text domain must match your plugin/theme slug exactly.
  • All user-facing strings must be wrapped in a translation function.
  • Prefer combined escape+translate over separate calls:
// BAD - separate escape and translate
echo esc_html( __( 'Hello', 'acme-plugin' ) );

// GOOD - combined function
echo esc_html__( 'Hello', 'acme-plugin' );

If you pass two parameters to esc_html() or esc_attr(), you probably meant esc_html__() / esc_attr__().

4.3 Translator Comments

Add translator comments for ambiguous strings, sprintf placeholders, or context:

// BAD
printf( __( '%s: %s', 'acme-plugin' ), $label, $value );

// GOOD
/* translators: 1: field label, 2: field value */
printf( __( '%1$s: %2$s', 'acme-plugin' ), $label, $value );

4.4 sprintf Placeholder Rules

  • With 2+ placeholders, use ordered placeholders: %1$s, %2$s (not %s, %s).
  • Do NOT use %1$s if there is only one placeholder (use %s).
  • The number of placeholders must match the number of arguments.

5. Enqueuing Assets

5.1 Never Use Direct Tags

// BAD - direct output
echo '<script src="my-script.js"></script>';
echo '<link rel="stylesheet" href="my-style.css">';

// GOOD - proper enqueue
function acme_enqueue_assets() {
    wp_enqueue_script(
        'acme-main',
        plugin_dir_url( __FILE__ ) . 'js/main.js',
        array( 'jquery' ),
        '1.2.0',
        true  // in_footer
    );
    wp_enqueue_style(
        'acme-styles',
        plugin_dir_url( __FILE__ ) . 'css/styles.css',
        array(),
        '1.2.0'
    );
}
add_action( 'wp_enqueue_scripts', 'acme_enqueue_assets' );

5.2 Required Parameters

Parameter Required? Notes
$handle Yes Unique identifier
$src Conditional Omit only when registering dependency-only handle
$deps Recommended Array of dependency handles
$ver Yes (when src given) Must be non-false; use explicit version string. false = WP core version (bad for cache busting). null = no version query string (also discouraged).
$in_footer (scripts) Yes Explicitly set true (footer) or false (header). Defaults to header if omitted.
$media (styles) Optional Default 'all'
// BAD - missing version, missing in_footer
wp_enqueue_script( 'acme-main', $url );

// GOOD
wp_enqueue_script( 'acme-main', $url, array(), '1.0.0', true );

5.3 Conditional Loading

Load assets only where needed:

function acme_admin_assets( $hook ) {
    if ( 'toplevel_page_acme-settings' !== $hook ) {
        return;
    }
    wp_enqueue_style( 'acme-admin', ... );
}
add_action( 'admin_enqueue_scripts', 'acme_admin_assets' );

function acme_frontend_assets() {
    if ( ! is_singular( 'acme_portfolio' ) ) {
        return;
    }
    wp_enqueue_script( 'acme-portfolio', ... );
}
add_action( 'wp_enqueue_scripts', 'acme_frontend_assets' );

6. WordPress API Usage

Key rules for WordPress API calls. Use capabilities (not roles) in current_user_can(). Cron intervals must be 15+ minutes. Limit posts_per_page (no -1). Never overwrite WP globals ($post, $wp_query). Always pass $single to get_post_meta(). Avoid current_time( 'timestamp' ) -- use time() for UTC or current_time( 'mysql' ) for formatted local time.

Common capabilities: manage_options, edit_posts, edit_others_posts, publish_posts, delete_posts, upload_files, edit_theme_options, activate_plugins.

get_post_meta() $single applies to: get_post_meta, get_user_meta, get_term_meta, get_comment_meta, get_site_meta, get_metadata, get_metadata_raw, get_metadata_default.


7. Deprecated Functions (Common Replacements)

Common deprecated functions and their replacements. WPCS flags usage as an error if the function was deprecated before your configured minimum WP version, and a warning otherwise.

Deprecated Since Replacement
get_currentuserinfo() 4.5 wp_get_current_user()
get_page_by_title() 6.2 WP_Query
is_taxonomy() 3.0 taxonomy_exists()
is_term() 3.0 term_exists()
get_settings() 2.1 get_option()
get_usermeta() 3.0 get_user_meta()
update_usermeta() 3.0 update_user_meta()
delete_usermeta() 3.0 delete_user_meta()
wp_get_sites() 4.6 get_sites()
like_escape() 4.0 $wpdb->esc_like()
get_all_category_ids() 4.0 get_terms()
post_permalink() 4.4 get_permalink()
force_ssl_login() 4.4 force_ssl_admin()
wp_no_robots() 5.7 wp_robots_no_robots()
wp_make_content_images_responsive() 5.5 wp_filter_content_tags()
add_option_whitelist() 5.5 add_allowed_options()
remove_option_whitelist() 5.5 remove_allowed_options()
wp_get_loading_attr_default() 6.3 wp_get_loading_optimization_attributes()
the_meta() 6.0.2 get_post_meta()
readonly() 5.9 wp_readonly()
attribute_escape() 2.8 esc_attr()
wp_specialchars() 2.8 esc_html()
js_escape() 2.8 esc_js()
clean_url() 3.0 esc_url()
seems_utf8() 6.9 wp_is_valid_utf8()
current_user_can_for_blog() 6.7 current_user_can_for_site()

8. Formatting

Spacing: WordPress uses spaces inside parentheses, brackets, and around operators. Control structures always have spaces after keywords and inside parens: if ( $x ), foreach ( $arr as $v ).

Indentation: Use tabs, not spaces. Multi-line arrays: one item per line, trailing comma.

Operator spacing: Spaces around =, +, ., =>, etc. No spaces around -> or ?->.

Cast spacing: Space after cast, no space inside: (int) $val.


9. Testing

PHP: Use PHPUnit with WP_UnitTestCase. Install via composer require --dev phpunit/phpunit or wp scaffold plugin-tests. Test files in tests/, named test-class-{name}.php.

JavaScript: Use @wordpress/scripts (bundles Jest): npx wp-scripts test-unit-js. E2E via @wordpress/e2e-test-utils.

Linting: vendor/bin/phpcs --standard=WordPress src/ for PHP. npx wp-scripts lint-js and npx wp-scripts lint-style for JS/CSS.


10. Quick Reference Tables

Control Structure Spacing

Pattern BAD GOOD
if if($x) if ( $x )
elseif elseif($x) elseif ( $x )
foreach foreach($a as $b) foreach ( $a as $b )
for for($i=0;$i<10;$i++) for ( $i = 0; $i < 10; $i++ )
switch switch($x) switch ( $x )
while while($x) while ( $x )

Naming Quick Reference

Element Convention Example
Functions snake_case acme_get_settings()
Variables snake_case $post_title
Classes Pascal_Case (underscored) Acme_Plugin_Admin
Constants UPPER_SNAKE_CASE ACME_VERSION
Files lowercase-hyphens class-acme-admin.php
Hook names lowercase_underscores acme_after_init
Post type slugs lowercase, a-z0-9_- acme_book

WordPress i18n Functions at a Glance

Need Function
Return translated string __()
Echo translated string _e()
Return + HTML escape esc_html__()
Echo + HTML escape esc_html_e()
Return + attr escape esc_attr__()
Echo + attr escape esc_attr_e()
With context _x(), esc_html_x(), esc_attr_x()
Singular/plural _n()
Singular/plural + context _nx()
Weekly Installs
4
GitHub Stars
3
First Seen
Feb 5, 2026
Installed on
opencode4
github-copilot4
codex4
kimi-cli4
gemini-cli4
amp4