Follow these guidelines to create effective, performant, and user-friendly validation checks.
Keep Validation Simple
Validation runs frequently as users edit, so keep logic simple and fast. Avoid complex calculations or heavy processing.
Do This
// Simple, fast validation
if ( checkName === 'title_required' ) {
return !!( attributes.title && attributes.title.trim() );
}
Avoid This
// Slow, complex validation
if ( checkName === 'title_required' ) {
const words = attributes.title.split(' ');
const uniqueWords = new Set(words);
const wordFrequency = {};
words.forEach(word => {
wordFrequency[word] = (wordFrequency[word] || 0) + 1;
});
// ... more complex processing
return someComplexCondition;
}
Provide Clear Messages
Write error and warning messages that clearly explain the problem and how to fix it. Avoid technical jargon when possible.
Do This
'error_msg' => __( 'Image must include alt text for screen reader users.', 'my-plugin' )
Avoid This
'error_msg' => __( 'Alt attribute validation failed.', 'my-plugin' )
Message Guidelines
- Be specific – Tell users exactly what’s wrong
- Be actionable – Explain how to fix the issue
- Be concise – Keep messages short and scannable
- Be helpful – Focus on the solution, not the problem
- Avoid jargon – Use plain language that content creators understand
Use Appropriate Severity
Choose the right severity level for each check based on its importance.
Use ‘error’ for:
- Critical accessibility issues (WCAG violations)
- Required fields that prevent content from working
- Security or data integrity issues
Use ‘warning’ for:
- Recommendations that improve quality
- Best practices that aren’t strictly required
- Style guide suggestions
Use ‘settings’ for:
- Checks where site administrators should decide severity
- Checks that may vary by site or use case
- Most checks (this is the recommended default)
Test Thoroughly
Test your validation with various content scenarios to ensure it works correctly.
Test Cases to Consider
- Empty values – What happens with no content?
- Edge cases – Test boundary conditions (max length, min length, etc.)
- Special characters – Test with unicode, HTML entities, etc.
- Different user workflows – Test copy/paste, block patterns, etc.
- Multiple blocks – Test with many blocks in the editor
- Nested blocks – Test with complex block hierarchies
Support Multiple Languages
Always wrap messages in translation functions to support internationalized sites.
Do This
array(
'error_msg' => __( 'Title is required.', 'my-plugin' ),
'warning_msg' => __( 'Title is recommended.', 'my-plugin' ),
'description' => __( 'Helps screen readers understand content.', 'my-plugin' ),
)
Avoid This
array(
'error_msg' => 'Title is required.',
'warning_msg' => 'Title is recommended.',
'description' => 'Helps screen readers understand content.',
)
Common Patterns
Single Validation for Multiple Blocks
Register the same check for different block types:
$config = array(
'error_msg' => __( 'Alt text is required.', 'my-plugin' ),
'warning_msg' => __( 'Alt text is recommended.', 'my-plugin' ),
'description' => __( 'Ensures images are accessible.', 'my-plugin' ),
'type' => 'settings',
'category' => 'accessibility',
);
$registry->register_block_check( 'core/image', 'alt_text_required', $config );
$registry->register_block_check( 'my-plugin/gallery', 'alt_text_required', $config );
Then handle both in JavaScript with a single switch case:
switch ( checkName ) {
case 'alt_text_required':
// Works for both core/image and my-plugin/gallery
return !!( attributes.alt && attributes.alt.trim() );
}
Multiple Validations for Single Block
Register multiple checks for one block:
$registry->register_block_check(
'my-plugin/card',
'title_required',
array(
'error_msg' => __( 'Card title is required.', 'my-plugin' ),
'type' => 'error',
)
);
$registry->register_block_check(
'my-plugin/card',
'image_required',
array(
'error_msg' => __( 'Card image is required.', 'my-plugin' ),
'type' => 'settings',
)
);
Use switch statements in JavaScript to handle each check efficiently:
addFilter(
'ba11yc_validate_block',
'my-plugin/validation',
( isValid, blockType, attributes, checkName, block ) => {
if ( blockType !== 'my-plugin/card' ) {
return isValid;
}
switch ( checkName ) {
case 'title_required':
return !!( attributes.title && attributes.title.trim() );
case 'image_required':
return !!( attributes.imageUrl && attributes.imageUrl.trim() );
default:
return isValid;
}
}
);
Conditional Validation
Perform validation only when certain conditions are met:
case 'description_required':
// Only require description if featured is enabled
if ( attributes.isFeatured ) {
return !!( attributes.description && attributes.description.trim() );
}
// If not featured, description is optional
return true;
Post Type Specific Checks
For editor and meta validation, register checks per post type:
// Only validate 'band' post type
$registry->register_editor_check( 'band', 'first_block_heading', $config );
// Validate multiple post types
$registry->register_editor_check( 'post', 'first_block_heading', $config );
$registry->register_editor_check( 'page', 'first_block_heading', $config );
Performance Optimization
Use Early Returns
Return as soon as you know validation doesn’t apply:
// Good - returns immediately if not our block
if ( blockType !== 'my-plugin/custom-block' ) {
return isValid;
}
// Good - returns immediately if not our check
if ( checkName !== 'title_required' ) {
return isValid;
}
Use Switch Statements
Switch statements are faster than multiple if/else chains:
// Good - fast switch statement
switch ( checkName ) {
case 'check_one':
return validateCheckOne();
case 'check_two':
return validateCheckTwo();
default:
return isValid;
}
// Avoid - slower if/else chain
if ( checkName === 'check_one' ) {
return validateCheckOne();
} else if ( checkName === 'check_two' ) {
return validateCheckTwo();
} else {
return isValid;
}
Cache Complex Calculations
If you need to perform complex operations, cache results outside the filter:
// Cache regex patterns
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
addFilter(
'ba11yc_validate_block',
'my-plugin/validation',
( isValid, blockType, attributes, checkName, block ) => {
if ( checkName === 'email_valid' ) {
// Reuse cached pattern instead of creating new one
return emailPattern.test( attributes.email );
}
return isValid;
}
);
Avoid Heavy Operations
Keep validation lightweight:
// Good - simple check
return !!( attributes.title && attributes.title.trim() );
// Avoid - expensive operations
return fetch('/api/validate-title', {
method: 'POST',
body: JSON.stringify({ title: attributes.title })
});