Best Practices

Secure WordPress Plugin Development Best Practices

Learn essential security practices for WordPress plugin development. Build secure plugins that protect users from common vulnerabilities.

S
Sarah Chen
7 min read
1,175 views
WordPress plugin development security best practices guide

Developing secure WordPress plugins protects the millions of sites that may install your code. Understanding and implementing security best practices is essential for responsible plugin development.

Security Fundamentals

Core Principles

  • Never trust user input
  • Escape all output
  • Use WordPress APIs
  • Follow least privilege
  • Fail securely

Input Validation

Sanitization Functions

// Always sanitize input data
$text = sanitize_text_field($_POST['title']);
$email = sanitize_email($_POST['email']);
$url = esc_url_raw($_POST['website']);
$html = wp_kses_post($_POST['content']);
$int = absint($_POST['quantity']);
$key = sanitize_key($_POST['option_name']);
$filename = sanitize_file_name($_POST['file']);

Validation Examples

// Validate before processing
function process_form_data($data) {
    $errors = array();

    // Required field
    if (empty($data['name'])) {
        $errors[] = 'Name is required';
    }

    // Email validation
    if (!is_email($data['email'])) {
        $errors[] = 'Invalid email address';
    }

    // Number range
    $quantity = absint($data['quantity']);
    if ($quantity < 1 || $quantity > 100) {
        $errors[] = 'Quantity must be between 1 and 100';
    }

    return $errors;
}

Output Escaping

Context-Appropriate Escaping

// HTML context
echo '

' . esc_html($user_input) . '

'; // HTML attributes echo ''; // URLs echo 'Link'; // JavaScript echo ''; // SQL (use prepared statements instead) $wpdb->prepare("SELECT * FROM table WHERE id = %d", $id);

Database Security

Prepared Statements

global $wpdb;

// CORRECT: Always use prepare()
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}my_table WHERE user_id = %d AND status = %s",
        $user_id,
        $status
    )
);

// WRONG: Direct variable insertion
$results = $wpdb->get_results(
    "SELECT * FROM {$wpdb->prefix}my_table WHERE user_id = $user_id"
);

Table Creation

// Use dbDelta for safe table creation
function create_plugin_table() {
    global $wpdb;
    $charset_collate = $wpdb->get_charset_collate();
    $table_name = $wpdb->prefix . 'my_plugin_data';

    $sql = "CREATE TABLE $table_name (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        data text NOT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY  (id),
        KEY user_id (user_id)
    ) $charset_collate;";

    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

Nonce Verification

Form Nonces

// In form
wp_nonce_field('my_plugin_action', 'my_plugin_nonce');

// Verification
function handle_form_submission() {
    if (!isset($_POST['my_plugin_nonce']) ||
        !wp_verify_nonce($_POST['my_plugin_nonce'], 'my_plugin_action')) {
        wp_die('Security check failed');
    }

    // Process form...
}

AJAX Nonces

// Enqueue with nonce
wp_localize_script('my-script', 'MyPlugin', array(
    'ajax_url' => admin_url('admin-ajax.php'),
    'nonce' => wp_create_nonce('my_ajax_nonce')
));

// AJAX handler
add_action('wp_ajax_my_action', function() {
    check_ajax_referer('my_ajax_nonce', 'nonce');

    // Process request...
    wp_send_json_success($data);
});

Capability Checks

Permission Verification

// Check capabilities before actions
function admin_page_handler() {
    if (!current_user_can('manage_options')) {
        wp_die('Unauthorized access');
    }

    // Admin functionality...
}

// For post-specific actions
function edit_post_handler($post_id) {
    if (!current_user_can('edit_post', $post_id)) {
        wp_die('You cannot edit this post');
    }

    // Edit functionality...
}

File Security

Safe File Operations

// Validate file uploads
function handle_file_upload($file) {
    $allowed_types = array('image/jpeg', 'image/png', 'image/gif');

    // Check MIME type
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);

    if (!in_array($mime, $allowed_types)) {
        return new WP_Error('invalid_type', 'Invalid file type');
    }

    // Use WordPress upload handling
    $upload = wp_handle_upload($file, array('test_form' => false));

    if (isset($upload['error'])) {
        return new WP_Error('upload_error', $upload['error']);
    }

    return $upload;
}

Options and Settings

Settings API

// Register settings with sanitization
register_setting(
    'my_plugin_options',
    'my_plugin_settings',
    array(
        'type' => 'array',
        'sanitize_callback' => 'sanitize_my_settings'
    )
);

function sanitize_my_settings($input) {
    $sanitized = array();
    $sanitized['api_key'] = sanitize_text_field($input['api_key'] ?? '');
    $sanitized['enabled'] = !empty($input['enabled']);
    $sanitized['limit'] = absint($input['limit'] ?? 10);

    return $sanitized;
}

Security Review Checklist

  • All user input sanitized
  • All output escaped
  • Prepared statements for SQL
  • Nonces for forms and AJAX
  • Capability checks implemented
  • File uploads validated
  • No direct file access
  • Debug code removed

Conclusion

Secure plugin development requires consistent application of WordPress security APIs and best practices. Validate input, escape output, verify permissions, and use WordPress functions for all security-critical operations.

Share:
S
Written by Sarah Chen

WP Folder Shield Team

Related Articles

Automated vs Manual WordPress Malware Scanning: Which is Better?
Automated vs Manual WordPress Malware Scanning: Which is Better?

Compare automated and manual WordPress malware scanning approaches. Learn when to use each method...

January 17, 2026
Preventing WordPress Malware: 10 Essential Security Practices
Preventing WordPress Malware: 10 Essential Security Practices

Learn 10 essential security practices to prevent WordPress malware infections. Protect your site...

January 13, 2026
WordPress Directory Browsing: Why and How to Disable It
WordPress Directory Browsing: Why and How to Disable It

Learn why WordPress directory browsing is a security risk and how to disable it. Prevent attackers...

January 12, 2026

Ready to Secure Your WordPress Site?

Get complete protection with WP Folder Shield.

Get Started