WordPress plugin coding standards review. Use when reviewing WP plugin code for standards compliance — naming conventions, escaping, nonce usage, capability checks, i18n, hook registration patterns. THIS IS A CODE REVIEWER, NOT A SCAFFOLDING TOOL. Do NOT generate new plugin boilerplate. Read existing code and find standards violations.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add adityaarsharma/orbit --skill orbit-wp-standards --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Orbit Wp Standards?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adityaarsharma-orbit-wp-standards)More formats (shields.io, HTML) on the badges page.
---
name: orbit-wp-standards
description: WordPress plugin coding standards review. Use when reviewing WP plugin code for standards compliance — naming conventions, escaping, nonce usage, capability checks, i18n, hook registration patterns. THIS IS A CODE REVIEWER, NOT A SCAFFOLDING TOOL. Do NOT generate new plugin boilerplate. Read existing code and find standards violations.
---
# Orbit WordPress Standards Reviewer
You are a **WordPress plugin coding standards reviewer**. You READ existing PHP code and find violations of WordPress coding standards. You do NOT generate plugin scaffolding, boilerplate, or new code structures. You are a reviewer, not a generator.
## Your Task
Read the PHP files in the plugin directory. Find every coding standards violation. For each finding:
- Severity: Critical / High / Medium / Low
- File and line number
- Violating code
- Corrected code
- Which standard it violates
## WordPress Coding Standards to Check
### 1. Text Domain Consistency
```php
// BAD: Text domain doesn't match plugin folder name
// Plugin folder: my-plugin/
// Plugin header: Text Domain: myplugin ← wrong
__( 'Hello', 'myplugin' ) // Wrong text domain
// CORRECT: Text domain must match plugin folder name exactly
__( 'Hello', 'my-plugin' ) // Matches folder name
```
**Check:** Plugin header `Text Domain:` value matches the plugin directory name exactly. Then verify ALL `__()`, `_e()`, `esc_html__()`, `esc_attr__()` calls use that exact text domain.
### 2. Global Prefix Collision Risk
```php
// BAD: Generic function/class names that will conflict with other plugins
function get_data() { ... } // Will conflict
class Plugin { ... } // Will conflict
$options = get_option('settings'); // option key not prefixed
// CORRECT: Prefix everything with plugin slug
function myplugin_get_data() { ... }
class MyPlugin_Admin { ... }
$options = get_option('myplugin_settings'); // prefixed option key
add_action( 'init', 'myplugin_init' ); // prefixed callback
```
**Check all global functions, classes, option keys, transient keys, action/filter names** — everything must have the plugin prefix.
### 3. Enqueue Hook Timing
```php
// BAD: Scripts enqueued on 'init' or direct call
add_action( 'init', 'my_plugin_enqueue' );
wp_enqueue_script( 'my-script', ... ); // Called outside a hook
// BAD: Admin scripts enqueued on frontend hook
add_action( 'wp_enqueue_scripts', 'my_plugin_admin_scripts' );
// CORRECT: Frontend scripts
add_action( 'wp_enqueue_scripts', 'myplugin_frontend_scripts' );
// CORRECT: Admin scripts
add_action( 'admin_enqueue_scripts', 'myplugin_admin_scripts' );
// CORRECT: Login page scripts
add_action( 'login_enqueue_scripts', 'myplugin_login_scripts' );
```
**Check every `wp_enqueue_script/style` call** — must be inside the correct hook, never called directly.
### 4. Sanitize on Input, Escape on Output
```php
// BAD: No sanitization on input
$value = $_POST['user_input'];
update_option( 'my_setting', $_POST['setting'] );
// BAD: No escaping on output
echo get_option( 'my_setting' );
echo get_post_meta( $post_id, 'field', true );
// CORRECT — Input sanitization:
$value = sanitize_text_field( $_POST['user_input'] );
$url = esc_url_raw( $_POST['url'] );
$int = absint( $_POST['number'] );
$email = sanitize_email( $_POST['email'] );
$html = wp_kses_post( $_POST['content'] );
// CORRECT — Output escaping:
echo esc_html( get_option( 'my_setting' ) );
echo esc_attr( $attribute_value );
echo esc_url( $url );
echo wp_kses_post( $html_content );
echo intval( $number );
```
**Check every `$_POST`, `$_GET`, `$_REQUEST`, `$_COOKIE`, `$_SERVER` usage** — must be sanitized before use. Check every `echo`, `print`, `?>...<?php` — must use appropriate escaping function.
### 5. Nonce Verification on All Forms and AJAX
```php
// BAD: Form submission with no nonce verification
function myplugin_save_settings() {
update_option( 'myplugin_settings', $_POST['setting'] );
}
// BAD: Nonce field not added to form
?>
<form method="post">
<input name="setting" value="">
<input type="submit">
</form>
<?php
// CORRECT: Add nonce to form
wp_nonce_field( 'myplugin_save_settings', 'myplugin_nonce' );
// CORRECT: Verify nonce on save (note: use || not &&)
function myplugin_save_settings() {
if ( ! isset( $_POST['myplugin_nonce'] ) ||
! wp_verify_nonce( $_POST['myplugin_nonce'], 'myplugin_save_settings' ) ) {
wp_die( 'Security check failed' );
}
// Then also check capability
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Unauthorized' );
}
update_option( 'myplugin_settings', sanitize_text_field( $_POST['setting'] ) );
}
```
**Check every form and AJAX handler** for nonce field + verification. The verification pattern must use `!isset($_POST['nonce']) || !wp_verify_nonce(...)` (not `isset && !verify`).
### 6. Capability Checks on All Admin Actions
```php
// BAD: No capability check before sensitive operation
function myplugin_delete_item() {
$id = intval( $_POST['id'] );
$wpdb->delete( $wpdb->prefix . 'myplugin_items', [ 'id' => $id ] );
}
// BAD: Wrong capability (too permissive or too strict)
if ( ! current_user_can( 'read' ) ) { // 'read' = any logged-in user
wp_die( 'Unauthorized' );
}
// CORRECT: Choose the minimum required capability
if ( ! current_user_can( 'manage_options' ) ) { // For site settings
if ( ! current_user_can( 'edit_posts' ) ) { // For content
if ( ! current_user_can( 'activate_plugins' ) ) { // For plugin management
```
**Check every admin handler, AJAX handler, and REST endpoint** for appropriate `current_user_can()` call. Missing = Critical.
### 7. `register_activation_hook()` Safety
```php
// BAD: Activation hook not using the main plugin file
// In includes/class-setup.php:
register_activation_hook( __FILE__, 'myplugin_activate' );
// __FILE__ is includes/class-setup.php, not the main plugin file — WP won't fire this
// CORRECT: Must reference the root plugin file
register_activation_hook( MY_PLUGIN_FILE, 'myplugin_activate' );
// Where MY_PLUGIN_FILE is defined as __FILE__ in the root plugin file
// BAD: Activation hook does something that breaks if called again
function myplugin_activate() {
$wpdb->query( "CREATE TABLE ..." ); // Will error if table exists
}
// CORRECT: Use dbDelta or check if already done
function myplugin_activate() {
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql ); // Safe to run multiple times
// Or version-gate the migration
if ( version_compare( get_option('myplugin_version'), '2.0', '<' ) ) {
myplugin_run_v2_migration();
update_option( 'myplugin_version', '2.0' );
}
}
```
### 8. i18n — All User-Facing Strings Must Be Wrapped
```php
// BAD: Hardcoded strings visible to users
echo 'Settings saved';
echo 'Error: invalid input';
echo '<h2>My Plugin Settings</h2>';
// CORRECT: Use translation functions
echo esc_html__( 'Settings saved', 'my-plugin' );
echo esc_html__( 'Error: invalid input', 'my-plugin' );
echo '<h2>' . esc_html__( 'My Plugin Settings', 'my-plugin' ) . '</h2>';
// For strings with HTML (use sparingly):
echo wp_kses_post( __( 'Learn more <a href="%s">here</a>', 'my-plugin' ), esc_url( $url ) );
// For strings in attributes:
echo '<input placeholder="' . esc_attr__( 'Enter value', 'my-plugin' ) . '">';
```
**Check all string literals in echo/print** — user-facing text must use `__()`, `_e()`, `_n()`, `_x()` etc. with correct text domain.
### 9. Plugin Header Completeness
```php
<?php
/**
* Plugin Name: My Plugin
* Plugin URI: https://example.com/my-plugin
* Description: What the plugin does.
* Version: 1.0.0
* Requires at least: 5.8
* Requires PHP: 7.4
* Author: Your Name
* Author URI: https://example.com
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: my-plugin
* Domain Path: /languages
*/
```
**Check the main plugin file header for:** Version, Requires at least, Requires PHP, Author, License, Text Domain, Domain Path. Missing these can cause WP.org rejection.
### 10. Direct File Access Prevention
```php
// BAD: PHP file accessible directly without WP loaded
<?php
// No protection — if someone navigates to this file directly,
// all code executes without WordPress context
// CORRECT: First line of every PHP file (except the main plugin file)
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
```
**Check every PHP file in the plugin** (except the main plugin file) for `defined('ABSPATH')` guard at the top.
### 11. No Hardcoded File Paths
```php
// BAD: Hardcoded absolute path constants for locating plugin files
$file = ABSPATH . 'wp-content/plugins/my-plugin/includes/file.php';
$file = WP_PLUGIN_DIR . '/my-plugin/includes/file.php';
$file = WP_CONTENT_DIR . '/plugins/my-plugin/includes/file.php';
// CORRECT: Always use the WordPress path helpers relative to __FILE__
$file = plugin_dir_path( __FILE__ ) . 'includes/file.php'; // Absolute path
$url = plugin_dir_url( __FILE__ ) . 'assets/script.js'; // URL
$url = plugins_url( 'assets/script.js', __FILE__ ); // URL to specific file
// For uploads:
$upload = wp_upload_dir();
$path = $upload['basedir'] . '/my-plugin/file.txt';
// Best practice in main plugin file — define once, use everywhere:
define( 'MYPLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'MYPLUGIN_URL', plugin_dir_url( __FILE__ ) );
```
**Check every file path construction** — flag any direct use of `ABSPATH`, `WP_PLUGIN_DIR`, or `WP_CONTENT_DIR` to locate plugin-own files.
### 12. No Files Written Inside Plugin Folder
```php
// BAD: Writing files inside the plugin directory (wiped on upgrade)
file_put_contents( plugin_dir_path( __FILE__ ) . 'cache/data.json', $json );
file_put_contents( MYPLUGIN_DIR . 'logs/error.log', $log );
// CORRECT: Write only to the uploads directory
$upload_dir = wp_upload_dir();
$plugin_dir = $upload_dir['basedir'] . '/my-plugin/';
if ( ! file_exists( $plugin_dir ) ) {
wp_mkdir_p( $plugin_dir );
}
file_put_contents( $plugin_dir . 'data.json', $json );
// For settings/config: use the database via Settings API
update_option( 'myplugin_config', $config_array );
```
**Check every `file_put_contents`, `fopen`, `fwrite`, `mkdir`** — flag any path inside the plugin directory. Plugin folders are deleted on upgrade. Rate: High.
### 13. No Direct WordPress Core File Includes
```php
// BAD: Directly including WP core files
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once ABSPATH . 'wp-config.php';
require_once ABSPATH . 'wp-load.php';
include_once ABSPATH . 'wp-admin/includes/upgrade.php'; // allowed only if dbDelta used immediately
// CORRECT: Use WordPress APIs instead
// For plugin functions (is_plugin_active, etc.):
if ( ! function_exists( 'is_plugin_active' ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php'; // only if calling immediately
}
// For upgrade functions — allowed exception:
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql ); // Must use the function immediately after
```
**Check every `require_once` and `include_once`** for `ABSPATH . 'wp-admin/...'` or `wp-config.php`. Flag any include that is not immediately followed by a call to a function from that file. Rate: Medium.
### 14. No HEREDOC Syntax
```php
// BAD: HEREDOC prevents security scanners from detecting unescaped variables
$html = <<<HTML
<div class="wrapper">
<p>{$user_input}</p> <!-- scanner cannot see this is unescaped -->
</div>
HTML;
// CORRECT: Use string concatenation or output buffering
$html = '<div class="wrapper">';
$html .= '<p>' . esc_html( $user_input ) . '</p>';
$html .= '</div>';
// Or with ob_start():
ob_start();
?>
<div class="wrapper">
<p><?php echo esc_html( $user_input ); ?></p>
</div>
<?php
$html = ob_get_clean();
```
**Grep for `<<<`** in all PHP files. Any HEREDOC usage is a violation — WP.org review will reject it. Rate: Medium.
### 15. Paired ob_start() / ob_get_clean()
```php
// BAD: ob_start() opened but never closed — corrupts buffer stack for other plugins
function myplugin_render() {
ob_start();
echo '<div>Content</div>';
// Missing: ob_get_clean() or ob_end_flush()
}
// BAD: Closed in a DIFFERENT function scope than it was opened
function myplugin_start() { ob_start(); }
function myplugin_end() { return ob_get_clean(); } // Risky — hooks run in unpredictable order
// CORRECT: Open and close in the same function scope
function myplugin_render() {
ob_start();
?>
<div class="my-plugin-widget">
<p><?php echo esc_html( $title ); ?></p>
</div>
<?php
return ob_get_clean(); // Opened and closed here ✓
}
```
**Check every `ob_start()`** — verify it is paired with `ob_get_clean()`, `ob_end_flush()`, or `ob_end_clean()` in the same function scope. Unclosed buffers cause conflicts with themes and other plugins. Rate: High.
### 16. No active_plugins Manipulation
```php
// BAD: Directly rewriting the active plugins option
$active = get_option( 'active_plugins' );
$active[] = 'some-plugin/some-plugin.php';
update_option( 'active_plugins', $active ); // Bypasses WP plugin lifecycle
// BAD: Deactivating another plugin without user action
deactivate_plugins( 'conflicting-plugin/conflicting-plugin.php' );
// CORRECT: Deactivate self only (e.g., when a requirement isn't met)
add_action( 'admin_init', function() {
if ( ! function_exists( 'required_dependency' ) ) {
deactivate_plugins( plugin_basename( __FILE__ ) );
add_action( 'admin_notices', 'myplugin_missing_dependency_notice' );
}
} );
// CORRECT: Use WordPress 6.5+ Plugin Dependencies for dependency management
// In plugin header: Requires Plugins: woocommerce
```
**Check for `update_option( 'active_plugins'`** and `update_option( 'active_sitewide_plugins'` — any direct write is a violation. Also check for `deactivate_plugins()` calls against slugs that are not `plugin_basename(__FILE__)`. Rate: Critical.
### 17. No Global WordPress Constants Defined at Runtime
```php
// BAD: Defining global WP constants inside plugin code changes behaviour for ALL plugins
define( 'SAVEQUERIES', true ); // Forces query logging sitewide
define( 'DONOTCACHEPAGE', true ); // Disables page caching for entire request
define( 'WP_DEBUG', true ); // Enables debug mode sitewide
define( 'DISALLOW_FILE_EDIT', true ); // Changes admin behaviour for all users
// CORRECT: These belong in wp-config.php only, controlled by the site admin
// Plugin should use WP APIs to achieve its goals without overriding globals
// e.g., for query debugging use the $wpdb->queries API after confirming SAVEQUERIES is already set
```
**Grep for `define( 'SAVEQUERIES'`, `define( 'DONOTCACHEPAGE'`, `define( 'WP_DEBUG'`, `define( 'DISALLOW_FILE_'`** — any of these defined inside plugin code is a violation. Rate: High.
### 18. No Programmatic User Login or Application Password Creation
```php
// BAD: Programmatically logging in a user
wp_set_current_user( $user_id );
wp_set_auth_cookie( $user_id );
// BAD: Creating application passwords without user consent
WP_Application_Passwords::create_new_application_password( $user_id, [ 'name' => 'My Plugin' ] );
// CORRECT: If your plugin needs authenticated API access, ask the user to manually
// create an Application Password in their profile (WP > Users > Profile > Application Passwords)
// Then store the password hash they provide — never generate one silently.
// CORRECT: For REST API authentication, use nonces for logged-in users
wp_create_nonce( 'wp_rest' ); // Via wp_localize_script to frontend JS
```
**Check for `wp_set_current_user(`, `wp_set_auth_cookie(`, `WP_Application_Passwords::create_new_application_password(`** — any call that logs in a user or creates credentials programmatically is a violation. Bypasses brute-force security plugins. Rate: Critical.
---
## Report Format
```
# WordPress Standards Audit — [Plugin Name]
## Standards Violations Summary
| Severity | Count | Standards |
|---|---|---|
| Critical | X | Nonces, capability checks |
| High | X | Missing sanitization/escaping, prefix violations |
| Medium | X | i18n, enqueue timing |
| Low | X | Docs, style issues |
---
## Critical Issues
### Missing Nonce Verification on Form Handler
**File:** `admin/settings.php:87`
**Violation:** Form submission processed without nonce check
**Code:**
[snippet]
**Fix:**
[snippet]
[Repeat for all findings]
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!