This document describes how WPForms coding standards differ from standard WordPress PHPCS rules. Use this as a reference when writing code for WPForms projects.
- Disabled WordPress PHPCS Rules
- Modified WordPress PHPCS Rules
- Formatting Rules
- PHP Rules
- Comment & Documentation Rules
These WordPress PHPCS rules are disabled in WPForms projects:
Generic.WhiteSpace.ScopeIndent- Less strict indenting allowedGeneric.Functions.FunctionCallArgumentSpacing- Less strict argument spacingWordPress.WhiteSpace.PrecisionAlignment- Precision alignment isn't enforced
WordPress.Files.FileName.InvalidClassFileName- Allows PSR-4 class file namingWordPress.Files.FileName.NotHyphenatedLowercase- Allows PSR-4 naming conventions
Squiz.Commenting.InlineComment.SpacingAfter- Less strict inline comment spacingSquiz.Commenting.FileComment.*- File-level PHPDoc isn't required
WordPress.PHP.YodaConditions.NotYoda- Yoda conditions are NOT required
What this means: Write conditions naturally ($var === 'value'), not in Yoda style ('value' === $var).
// ✅ CORRECT - Natural style
if ( $status === 'active' ) {
// ...
}
// ❌ WRONG - Yoda style not required
if ( 'active' === $status ) {
// ...
}Universal.Arrays.DisallowShortArraySyntax- Short array syntax[]is allowed and preferred
// ✅ CORRECT - Use short array syntax
$items = [ 'foo', 'bar', 'baz' ];
// ❌ WRONG - Long array syntax discouraged
$items = array( 'foo', 'bar', 'baz' );WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents- Directfile_get_contents()allowed
These rules have different thresholds in WPForms:
- Complexity limit: 6 (warning)
- Absolute complexity limit: 10 (error)
What this means: Keep functions simple with fewer conditional branches.
- Absolute nesting level: 3
What this means: Avoid deeply nested code. Maximum 3 levels of nesting.
// ✅ CORRECT - 2 levels of nesting
if ( $condition ) {
foreach ( $items as $item ) {
process( $item );
}
}
// ❌ WRONG - Too many nesting levels
if ( $condition ) {
if ( $other ) {
foreach ( $items as $item ) {
if ( $item->valid ) { // 4 levels!
process( $item );
}
}
}
}Rule: Add an empty line before return statements (except when return is the only statement in function body).
// ✅ CORRECT
public function get_name() {
$name = $this->process_name();
return $name;
}
// ✅ CORRECT - Single return statement, no empty line needed
public function get_id() {
return $this->id;
}
// ❌ WRONG - Missing empty line before return
public function get_name() {
$name = $this->process_name();
return $name;
}Rule: Add an empty line after the opening brace of a function (except for empty functions).
// ✅ CORRECT
public function process() {
$data = $this->get_data();
return $data;
}
// ✅ CORRECT - Empty function, no empty line needed
public function __construct() {
}
// ❌ WRONG - Missing empty line after opening brace
public function process() {
$data = $this->get_data();
return $data;
}Rule: Add an empty line after assignment statements (with exceptions).
Exceptions: No empty line needed when followed by:
- Closing brace
} - Another assignment statement
- Comments
breakin switch statement
// ✅ CORRECT
$name = 'John';
$this->process( $name );
// ✅ CORRECT - Multiple assignments together
$first = 'John';
$last = 'Doe';
echo $first . ' ' . $last;
// ❌ WRONG - Missing empty line after assignment
$name = 'John';
$this->process( $name );Rules:
- Add empty line before
switchstatement - Add empty line after
switchstatement closing brace - No empty line between
casestatements - Add empty line before
caseafterbreak - No empty line before
break - No empty line before closing brace of
switch
// ✅ CORRECT
$result = some_function();
switch ( $status ) {
case 'pending':
do_pending();
break;
case 'active':
do_active();
break;
default:
do_default();
break;
}
$next_step = process();
// ❌ WRONG - Missing empty lines
$result = some_function();
switch ( $status ) {
case 'pending':
do_pending();
break;
case 'active':
do_active();
break;
}
$next_step = process();Rule 1: Prefer use statements over fully qualified names.
// ✅ CORRECT
use WPForms\Admin\Settings;
$settings = new Settings();
// ❌ WRONG - Don't use fully qualified names
$settings = new \WPForms\Admin\Settings();Rule 2: Remove leading backslash when only one level deep.
// ✅ CORRECT
use DateTime;
// ❌ WRONG - Unnecessary leading backslash
use \DateTime;Rule 3: Remove unused use statements.
Rule: Write conditions in natural order (variable on left, value on right).
// ✅ CORRECT
if ( $count === 0 ) { }
if ( $item !== null ) { }
if ( $user->is_active() === true ) { }
// ❌ WRONG - Don't use Yoda conditions
if ( 0 === $count ) { }
if ( null !== $item ) { }Note: The custom sniff actively enforces natural conditions.
Rule 1: All add_action() and add_filter() calls must be inside a hooks() method within classes.
// ✅ CORRECT
class MyClass {
public function hooks() {
add_action( 'init', [ $this, 'init' ] );
add_filter( 'the_content', [ $this, 'filter_content' ] );
}
}
// ❌ WRONG - Hooks outside of hooks() method
class MyClass {
public function __construct() {
add_action( 'init', [ $this, 'init' ] );
}
}Rule: Hook names should start with the fully qualified class name converted to snake_case.
// For class: WPForms\Admin\Settings
// ✅ CORRECT
do_action( 'wpforms_admin_settings_init' );
apply_filters( 'wpforms_admin_settings_data', $data );
// ❌ WRONG - Doesn't match class name pattern
do_action( 'my_custom_init' );Rule: Use the correct text domain for i18n functions based on file location.
// In wpforms-lite files
// ✅ CORRECT
__( 'Some text', 'wpforms-lite' );
// In wpforms pro files (src/Pro/)
// ✅ CORRECT
__( 'Some text', 'wpforms' );
// ❌ WRONG - Incorrect domain
__( 'Some text', 'wrong-domain' );
// ❌ WRONG - Missing domain
__( 'Some text' );Rule 1: PHPDoc descriptions must end with punctuation (., !, ?).
// ✅ CORRECT
/**
* Process user data.
*
* @since 1.5.0
*/
// ❌ WRONG - Missing punctuation
/**
* Process user data
*
* @since 1.5.0
*/Rule 2: PHPDoc description must start on the line after /**, not on the same line.
// ✅ CORRECT
/**
* Process user data.
*/
// ❌ WRONG - Description on same line as opening
/** Process user data.
*/Rule 1: All functions, classes, interfaces, traits, constants, class properties, and define() calls must have @since tag.
// ✅ CORRECT
/**
* Process user data.
*
* @since 1.5.0
*
* @param array $data User data.
*
* @return bool
*/
public function process( $data ) { }
// ❌ WRONG - Missing @since tag
/**
* Process user data.
*
* @param array $data User data.
*
* @return bool
*/
public function process( $data ) { }Rule 2: @since tag must have a valid version number (X.Y.Z format).
// ✅ CORRECT
@since 1.5.0
@since 2.0.0
// ❌ WRONG
@since 1.5
@since v1.5.0
@since TBDRule 3: Add empty line after @since tag (except when followed by @deprecated or it's the last tag).
// ✅ CORRECT
/**
* Description.
*
* @since 1.5.0
*
* @param string $name Name.
*
* @return void
*/
// ✅ CORRECT - No empty line before @deprecated
/**
* Description.
*
* @since 1.5.0
* @deprecated 2.0.0
*
* @param string $name Name.
*/Rule 1: @deprecated tag must have a valid version number.
Rule 2: Add empty line after @deprecated tag (unless it's the last tag).
Rule: Only one space after @param and @return tags.
// ✅ CORRECT
@param string $name The name.
@return bool Success status.
// ❌ WRONG - Multiple spaces
@param string $name The name.
@return bool Success status.Rules for translators: comments:
- Use colon after "translators"
- Must have a description
- Description must end with punctuation (
.,!,?) - Use
/* */style comments
// ✅ CORRECT
/* translators: %s is the user's name. */
printf( __( 'Hello, %s!', 'wpforms' ), $name );
// ❌ WRONG - Missing colon
/* translators %s is the user's name. */
printf( __( 'Hello, %s!', 'wpforms' ), $name );
// ❌ WRONG - Missing description
/* translators: */
printf( __( 'Hello, %s!', 'wpforms' ), $name );
// ❌ WRONG - Missing punctuation
/* translators: %s is the user's name */
printf( __( 'Hello, %s!', 'wpforms' ), $name );
// ❌ WRONG - Wrong comment style
// translators: %s is the user's name.
printf( __( 'Hello, %s!', 'wpforms' ), $name );Rule 1: All do_action() and apply_filters() calls must have PHPDoc comment directly above them (on the line immediately before).
// ✅ CORRECT
/**
* Fires after settings are saved.
*
* @since 1.5.0
*
* @param array $settings The saved settings.
*/
do_action( 'wpforms_settings_saved', $settings );
// ❌ WRONG - Missing PHPDoc
do_action( 'wpforms_settings_saved', $settings );
// ❌ WRONG - PHPDoc not directly above
/**
* Fires after settings are saved.
*/
do_action( 'wpforms_settings_saved', $settings );Rule 2: Hook PHPDoc must have a short description ending with punctuation.
// ✅ CORRECT
/**
* Fires after settings are saved.
*
* @since 1.5.0
*/
do_action( 'wpforms_settings_saved' );
// ❌ WRONG - Missing description
/**
* @since 1.5.0
*/
do_action( 'wpforms_settings_saved' );
// ❌ WRONG - Missing punctuation
/**
* Fires after settings are saved
*
* @since 1.5.0
*/
do_action( 'wpforms_settings_saved' );Rule 3: Hook PHPDoc must have @since tag with a valid version.
Rule 4: Hook PHPDoc must have @param tags matching the number of arguments passed to the hook (excluding the hook name itself).
// ✅ CORRECT - 2 arguments, 2 @param tags
/**
* Filters the settings data.
*
* @since 1.5.0
*
* @param array $settings The settings data.
* @param int $user_id The user ID.
*/
apply_filters( 'wpforms_settings', $settings, $user_id );
// ❌ WRONG - 2 arguments but only 1 @param tag
/**
* Filters the settings data.
*
* @since 1.5.0
*
* @param array $settings The settings data.
*/
apply_filters( 'wpforms_settings', $settings, $user_id );Rule 5: Hook @param tags must be aligned (types, variable names, and descriptions should line up).
// ✅ CORRECT - Properly aligned
/**
* @param array $settings The settings data.
* @param int $user_id The user ID.
* @param string $context The context string.
*/
// ❌ WRONG - Not aligned
/**
* @param array $settings The settings data.
* @param int $user_id The user ID.
* @param string $context The context string.
*/Rule 6: Hook @param descriptions must end with punctuation.
Rule 7: For deprecated hooks, use do_action_deprecated() or apply_filters_deprecated() and include @deprecated tag.
// ✅ CORRECT
/**
* Fires when user is processed.
*
* @since 1.0.0
* @deprecated 2.0.0 Use wpforms_user_process_v2 instead.
*
* @param int $user_id User ID.
*/
do_action_deprecated( 'wpforms_user_process', [ $user_id ], '2.0.0', 'wpforms_user_process_v2' );
// ❌ WRONG - Using regular do_action for deprecated hook
/**
* @since 1.0.0
* @deprecated 2.0.0
*/
do_action( 'wpforms_user_process', $user_id );
// ❌ WRONG - Using do_action_deprecated without @deprecated tag
/**
* @since 1.0.0
*/
do_action_deprecated( 'wpforms_user_process', [ $user_id ], '2.0.0' );Rule 1: All define() calls must have PHPDoc comment directly above them.
Rule 2: define() PHPDoc must have a short description ending with punctuation.
Rule 3: define() PHPDoc must have @since tag with a valid version.
Rule 4: No empty line should appear after @since tag in define() PHPDoc (different from functions/classes).
// ✅ CORRECT
/**
* The plugin version number.
*
* @since 1.0.0
*/
define( 'WPFORMS_VERSION', '1.8.0' );
// ❌ WRONG - Missing PHPDoc
define( 'WPFORMS_VERSION', '1.8.0' );
// ❌ WRONG - Missing punctuation
/**
* The plugin version number
*
* @since 1.0.0
*/
define( 'WPFORMS_VERSION', '1.8.0' );
// ❌ WRONG - Empty line after @since
/**
* The plugin version number.
*
* @since 1.0.0
*
*/
define( 'WPFORMS_VERSION', '1.8.0' );Special comments allowed (won't trigger PHPCS errors):
// ✅ CORRECT - PhpStorm/IntelliJ language injection
// language=JSON
$json = '{"foo": "bar"}';
// ✅ CORRECT - PHPDoc language injection
/**
* @lang SQL
*/
$sql = "SELECT * FROM users";-
Empty lines:
- After function opening brace
- Before return statement (unless it's the only statement)
- After assignments (with exceptions)
- Before/after switch statements
- Before case statements after break
- No empty line before break in switch
-
No Yoda conditions — Write naturally:
$var === 'value' -
Short array syntax — Use
[]notarray()
-
@since tag required on:
- All functions, classes, interfaces, traits
- Class properties
- Constants and
define()calls - All hooks (do_action/apply_filters)
-
PHPDoc descriptions must:
- End with punctuation (
.,!,?) - Start on the next line after
/**opening - Not be on the same line as
/**
- End with punctuation (
-
Hook documentation:
- PHPDoc required directly above all hooks
- Must have description with punctuation
- Must have
@sincetag - Must have
@paramtags matching argument count @paramtags must be aligned- Use
*_deprecated()functions with@deprecatedtag for old hooks
-
Define constants:
- Must have PHPDoc with description
- Must have
@sincetag - No empty line after
@since
-
Hooks in hooks() method - All
add_action()/add_filter()calls must be insidehooks()method in classes -
Text domain required - Always specify domain in i18n functions
-
Use statements over fully qualified names — Import classes at top
-
Hook names - Should start with the class name in snake_case
-
Translator comments - Must have colon, description, and punctuation
-
Complexity limits — Keep functions under 6 complexity, 3 nesting levels
-
Tag spacing — Only one space after
@paramand@return