| 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