wordpress-core

SKILL.md

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, 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:

// 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 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:

// 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 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 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 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:

// 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:

Related Skills

  • wordpress-blocks - Block development, Block Hooks API, Interactivity API
  • wordpress-modern - Performance optimization, modern WP 6.8 features

Best Practices Summary

  1. Security First: Sanitize input, escape output, verify nonces, check capabilities
  2. Use WordPress APIs: Don't reinvent the wheel, use built-in functions
  3. Follow Standards: WordPress coding standards for consistency
  4. Prepare Statements: Always use $wpdb->prepare() for queries
  5. Prefix Everything: Avoid naming conflicts with unique prefixes
  6. Test: Test activation, deactivation, different user roles
  7. Document: Comment complex logic, use PHPDoc blocks
Weekly Installs
3
GitHub Stars
1
First Seen
Feb 26, 2026
Installed on
cline3
gemini-cli3
github-copilot3
codex3
cursor3
opencode3