| name | wordpress-core |
| description | Core WordPress plugin development fundamentals including file structure, security patterns (sanitize, escape, nonces, capabilities), hooks system (actions/filters), database operations with wpdb, and coding standards. Use when creating plugins, implementing security, or working with core WordPress APIs. |
WordPress Core Development Skill
Core WordPress plugin development knowledge. Focus on fundamentals, security, and standard patterns.
Target: WordPress 6.8+
WordPress Plugin File Structure
Standard organization for WordPress plugins:
plugin-name/
├── plugin-name.php # Main plugin file (header, activation)
├── uninstall.php # Uninstall cleanup (optional)
├── includes/
│ ├── class-plugin-name.php # Main plugin class
│ ├── class-activator.php # Activation logic
│ └── class-deactivator.php # Deactivation logic
├── admin/
│ ├── class-admin.php # Admin-specific functionality
│ ├── css/
│ ├── js/
│ └── partials/ # Admin view templates
├── public/
│ ├── class-public.php # Public-facing functionality
│ ├── css/
│ ├── js/
│ └── partials/ # Public view templates
└── languages/ # Translation files
Key Principles:
- Main plugin file contains header and initialization only
- Separate admin and public functionality
- Use classes for organization (recommended)
- Follow WordPress naming conventions (lowercase, hyphens)
Plugin Header Format
Every plugin must have a header in the main file:
<?php
if ( ! defined( 'WPINC' ) ) {
die;
}
Required Fields: Plugin Name
Recommended: Description, Version, Author, License, Text Domain
Security Principles (CRITICAL)
WordPress security follows three golden rules:
1. Sanitize Input (Trust Nothing)
Always sanitize data coming from users:
$clean_text = sanitize_text_field( $_POST['user_input'] );
$clean_email = sanitize_email( $_POST['email'] );
$clean_url = esc_url_raw( $_POST['url'] );
$clean_id = absint( $_POST['post_id'] );
$clean_array = array_map( 'sanitize_text_field', $_POST['items'] );
Common Functions:
sanitize_text_field() - Strips tags, scripts
sanitize_email() - Validates email format
sanitize_key() - Lowercase alphanumeric, dashes, underscores
sanitize_title() - Creates slug-safe string
esc_url_raw() - Database-safe URL
absint() - Absolute integer (positive only)
intval() - Integer conversion
2. Escape Output (Never Trust Data)
Always escape data when outputting to HTML:
echo esc_html( $user_data );
echo '<input value="' . esc_attr( $value ) . '">';
echo '<a href="' . esc_url( $link ) . '">Link</a>';
echo '<script>var data = ' . wp_json_encode( $data ) . ';</script>';
echo '<textarea>' . esc_textarea( $content ) . '</textarea>';
Common Functions:
esc_html() - Escapes HTML (converts < > & " ')
esc_attr() - Escapes HTML attributes
esc_url() - Escapes URLs for output
esc_js() - Escapes JavaScript strings
esc_textarea() - Escapes textarea content
wp_kses_post() - Allows safe HTML (like posts)
3. Nonces (Verify Intent)
Use nonces to prevent CSRF attacks:
wp_nonce_field( 'my_action_name', 'my_nonce_field' );
if ( ! isset( $_POST['my_nonce_field'] ) ||
! wp_verify_nonce( $_POST['my_nonce_field'], 'my_action_name' ) ) {
die( 'Security check failed' );
}
$url = wp_nonce_url( 'admin.php?action=delete&id=123', 'delete_item_123' );
if ( ! isset( $_GET['_wpnonce'] ) ||
! wp_verify_nonce( $_GET['_wpnonce'], 'delete_item_123' ) ) {
die( 'Security check failed' );
}
4. Capability Checks (Authorization)
Verify user has permission:
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'You do not have sufficient permissions' );
}
current_user_can( 'edit_posts' );
current_user_can( 'publish_pages' );
current_user_can( 'edit_others_posts' );
Security Checklist:
Hooks System (Actions and Filters)
WordPress uses hooks to allow plugins to modify behavior.
Actions (Do Something)
Execute code at specific points:
add_action( 'init', 'my_function' );
add_action( 'wp_enqueue_scripts', 'my_enqueue_function' );
add_action( 'admin_menu', 'my_menu_function' );
do_action( 'my_plugin_custom_action', $param1, $param2 );
Common Action Hooks:
init - WordPress initialized, user authenticated
admin_init - Admin area initialized
admin_menu - Time to add admin menu items
wp_enqueue_scripts - Enqueue public scripts/styles
admin_enqueue_scripts - Enqueue admin scripts/styles
wp_head - Output to <head> section
wp_footer - Output before </body>
save_post - Post is being saved
admin_notices - Display admin notices
Filters (Modify Data)
Modify values before they're used:
add_filter( 'the_content', 'my_content_filter' );
add_filter( 'the_title', 'my_title_filter', 10, 2 );
function my_content_filter( $content ) {
return $content . '<p>Added text</p>';
}
$value = apply_filters( 'my_plugin_custom_filter', $value, $arg1, $arg2 );
Common Filter Hooks:
the_content - Post content
the_title - Post title
the_excerpt - Post excerpt
wp_mail - Email parameters
upload_mimes - Allowed upload types
login_redirect - Where to redirect after login
Priority and Arguments:
add_filter( 'the_title', 'my_filter', 5 );
add_filter( 'the_title', 'my_filter', 10, 2 );
function my_filter( $title, $post_id ) {
return $title;
}
Database Interactions (wpdb)
Use $wpdb global for database queries.
Basic Queries
global $wpdb;
$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts}" );
$post = $wpdb->get_row( "SELECT * FROM {$wpdb->posts} WHERE ID = 1" );
$posts = $wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE post_status = 'publish'" );
$titles = $wpdb->get_col( "SELECT post_title FROM {$wpdb->posts}" );
Prepared Statements (ALWAYS)
Never use string concatenation with user input:
$wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE ID = {$_GET['id']}" );
$id = absint( $_GET['id'] );
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d",
$id
) );
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s",
$post_type,
$status
) );
Placeholders:
%s - String
%d - Integer
%f - Float
Insert/Update/Delete
$wpdb->insert(
$wpdb->prefix . 'my_table',
array(
'column1' => 'value1',
'column2' => 123
),
array( '%s', '%d' )
);
$wpdb->update(
$wpdb->prefix . 'my_table',
array( 'column1' => 'new_value' ),
array( 'ID' => 123 ),
array( '%s' ),
array( '%d' )
);
$wpdb->delete(
$wpdb->prefix . 'my_table',
array( 'ID' => 123 ),
array( '%d' )
);
Table Prefix: Always use $wpdb->prefix for custom tables
WordPress Coding Standards
Follow WordPress PHP coding standards:
Naming Conventions:
- Functions:
lowercase_with_underscores()
- Classes:
Class_Name_With_Underscores
- Constants:
UPPERCASE_WITH_UNDERSCORES
- Files:
lowercase-with-hyphens.php
Indentation: Tabs (not spaces)
Bracing:
if ( condition ) {
}
Spacing:
if ( condition ) {
}
$x = $y + $z;
my_function( $arg1, $arg2 );
Yoda Conditions (constants on left):
if ( 'yes' === $value ) {
}
Helper Functions
Plugin paths:
plugin_dir_path( __FILE__ );
plugin_dir_url( __FILE__ );
plugin_basename( __FILE__ );
Get options:
get_option( 'option_name', 'default_value' );
update_option( 'option_name', $new_value );
delete_option( 'option_name' );
Post meta:
get_post_meta( $post_id, 'meta_key', true );
update_post_meta( $post_id, 'meta_key', $value );
delete_post_meta( $post_id, 'meta_key' );
Transients (temporary cached data):
set_transient( 'my_transient', $value, 3600 );
$value = get_transient( 'my_transient' );
delete_transient( 'my_transient' );
Progressive Disclosure
For advanced topics, see:
Related Skills
- wordpress-blocks - Block development, Block Hooks API, Interactivity API
- wordpress-modern - Performance optimization, modern WP 6.8 features
Best Practices Summary
- Security First: Sanitize input, escape output, verify nonces, check capabilities
- Use WordPress APIs: Don't reinvent the wheel, use built-in functions
- Follow Standards: WordPress coding standards for consistency
- Prepare Statements: Always use
$wpdb->prepare() for queries
- Prefix Everything: Avoid naming conflicts with unique prefixes
- Test: Test activation, deactivation, different user roles
- Document: Comment complex logic, use PHPDoc blocks