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
/**
* Plugin Name: Your Plugin Name
* Plugin URI: https://example.com/plugin-name
* Description: Brief description of what the plugin does
* Version: 1.0.0
* Author: Your Name
* Author URI: https://example.com
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: plugin-name
* Domain Path: /languages
*/
// If this file is called directly, abort.
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:
// Text input
$clean_text = sanitize_text_field( $_POST['user_input'] );
// Email
$clean_email = sanitize_email( $_POST['email'] );
// URL
$clean_url = esc_url_raw( $_POST['url'] );
// Integer
$clean_id = absint( $_POST['post_id'] );
// Array of text fields
$clean_array = array_map( 'sanitize_text_field', $_POST['items'] );
Common Functions:
sanitize_text_field()- Strips tags, scriptssanitize_email()- Validates email formatsanitize_key()- Lowercase alphanumeric, dashes, underscoressanitize_title()- Creates slug-safe stringesc_url_raw()- Database-safe URLabsint()- Absolute integer (positive only)intval()- Integer conversion
2. Escape Output (Never Trust Data)
Always escape data when outputting to HTML:
// HTML content
echo esc_html( $user_data );
// HTML attributes
echo '<input value="' . esc_attr( $value ) . '">';
// URLs
echo '<a href="' . esc_url( $link ) . '">Link</a>';
// JavaScript
echo '<script>var data = ' . wp_json_encode( $data ) . ';</script>';
// Textarea
echo '<textarea>' . esc_textarea( $content ) . '</textarea>';
Common Functions:
esc_html()- Escapes HTML (converts < > & " ')esc_attr()- Escapes HTML attributesesc_url()- Escapes URLs for outputesc_js()- Escapes JavaScript stringsesc_textarea()- Escapes textarea contentwp_kses_post()- Allows safe HTML (like posts)
3. Nonces (Verify Intent)
Use nonces to prevent CSRF attacks:
// Creating a nonce (in form)
wp_nonce_field( 'my_action_name', 'my_nonce_field' );
// Verifying a nonce (on submission)
if ( ! isset( $_POST['my_nonce_field'] ) ||
! wp_verify_nonce( $_POST['my_nonce_field'], 'my_action_name' ) ) {
die( 'Security check failed' );
}
// URL nonces
$url = wp_nonce_url( 'admin.php?action=delete&id=123', 'delete_item_123' );
// Verify URL nonce
if ( ! isset( $_GET['_wpnonce'] ) ||
! wp_verify_nonce( $_GET['_wpnonce'], 'delete_item_123' ) ) {
die( 'Security check failed' );
}
4. Capability Checks (Authorization)
Verify user has permission:
// Check if user can perform action
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'You do not have sufficient permissions' );
}
// Common capabilities
current_user_can( 'edit_posts' );
current_user_can( 'publish_pages' );
current_user_can( 'edit_others_posts' );
Security Checklist:
- All user input sanitized
- All output escaped
- Nonces verify form submissions
- Capability checks on all admin actions
- Direct file access prevented (
defined( 'WPINC' ))
Hooks System (Actions and Filters)
WordPress uses hooks to allow plugins to modify behavior.
Actions (Do Something)
Execute code at specific points:
// Add an action
add_action( 'init', 'my_function' );
add_action( 'wp_enqueue_scripts', 'my_enqueue_function' );
add_action( 'admin_menu', 'my_menu_function' );
// Custom action (for your plugin)
do_action( 'my_plugin_custom_action', $param1, $param2 );
Common Action Hooks:
init- WordPress initialized, user authenticatedadmin_init- Admin area initializedadmin_menu- Time to add admin menu itemswp_enqueue_scripts- Enqueue public scripts/stylesadmin_enqueue_scripts- Enqueue admin scripts/styleswp_head- Output to<head>sectionwp_footer- Output before</body>save_post- Post is being savedadmin_notices- Display admin notices
Filters (Modify Data)
Modify values before they're used:
// Add a filter
add_filter( 'the_content', 'my_content_filter' );
add_filter( 'the_title', 'my_title_filter', 10, 2 );
function my_content_filter( $content ) {
// Modify and return content
return $content . '<p>Added text</p>';
}
// Custom filter (for your plugin)
$value = apply_filters( 'my_plugin_custom_filter', $value, $arg1, $arg2 );
Common Filter Hooks:
the_content- Post contentthe_title- Post titlethe_excerpt- Post excerptwp_mail- Email parametersupload_mimes- Allowed upload typeslogin_redirect- Where to redirect after login
Priority and Arguments:
// Priority: 10 is default, lower runs earlier
add_filter( 'the_title', 'my_filter', 5 ); // Runs early
// Accept multiple arguments
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;
// Get single value
$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts}" );
// Get single row
$post = $wpdb->get_row( "SELECT * FROM {$wpdb->posts} WHERE ID = 1" );
// Get multiple rows
$posts = $wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE post_status = 'publish'" );
// Get column
$titles = $wpdb->get_col( "SELECT post_title FROM {$wpdb->posts}" );
Prepared Statements (ALWAYS)
Never use string concatenation with user input:
// WRONG - SQL Injection vulnerability
$wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE ID = {$_GET['id']}" );
// CORRECT - Prepared statement
$id = absint( $_GET['id'] );
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d",
$id
) );
// Multiple placeholders
$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
// Insert
$wpdb->insert(
$wpdb->prefix . 'my_table',
array(
'column1' => 'value1',
'column2' => 123
),
array( '%s', '%d' ) // Format
);
// Update
$wpdb->update(
$wpdb->prefix . 'my_table',
array( 'column1' => 'new_value' ), // Data
array( 'ID' => 123 ), // Where
array( '%s' ), // Data format
array( '%d' ) // Where format
);
// Delete
$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 ) {
// Code
}
Spacing:
// Always space after control structures
if ( condition ) {
// Code
}
// Space around operators
$x = $y + $z;
// No space inside parentheses for function calls
my_function( $arg1, $arg2 );
Yoda Conditions (constants on left):
if ( 'yes' === $value ) {
// Prevents accidental assignment
}
Helper Functions
Plugin paths:
plugin_dir_path( __FILE__ ); // /path/to/wp-content/plugins/my-plugin/
plugin_dir_url( __FILE__ ); // https://example.com/wp-content/plugins/my-plugin/
plugin_basename( __FILE__ ); // my-plugin/my-plugin.php
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 ); // Single value
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 ); // 1 hour
$value = get_transient( 'my_transient' );
delete_transient( 'my_transient' );
Progressive Disclosure
For advanced topics, see:
- See {baseDir}/references/security-patterns.md for advanced security patterns
- See {baseDir}/references/hooks-api.md for comprehensive hooks reference
- See {baseDir}/references/database-patterns.md for complex database operations
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