Subscribe to Our Mailing List and Stay Up-to-Date! Subscribe

WordPress Plugin Security Best Practices: Prevent Common Vulnerabilities

Plugin security vulnerabilities endanger millions of WordPress sites. As a plugin developer, you’re responsible for protecting user data, preventing attacks, and maintaining WordPress ecosystem trust. This guide covers essential security practices every plugin developer must follow.

Why Plugin Security Matters

WordPress powers 43% of websites globally. Attackers target plugins because:

  • Plugins often handle sensitive data
  • Security vulnerabilities affect thousands of sites using the same plugin
  • Poorly coded plugins provide entry points to otherwise secure sites
  • Plugin vulnerabilities appear in major security databases

A single SQL injection vulnerability in your plugin could compromise thousands of WordPress installations. Security isn’t optional—it’s your primary responsibility as a developer.

Cross-Site Scripting (XSS) Prevention

XSS attacks inject malicious JavaScript into pages, stealing cookies, redirecting users, or modifying content.

Always escape output:

// HTML context
echo '<p>' . esc_html( $user_input ) . '</p>';

// Attribute context
echo '<div class="' . esc_attr( $class_name ) . '">';

// URL context
echo '<a href="' . esc_url( $link ) . '">Link</a>';

// JavaScript context
echo '<script>var data = ' . esc_js( $data ) . ';</script>';

Allow specific HTML with wp_kses():

$allowed_html = array(
    'a' => array(
        'href' => array(),
        'title' => array()
    ),
    'strong' => array(),
    'em' => array()
);
echo wp_kses( $user_content, $allowed_html );

Never output unescaped user data. Ever.

SQL Injection Prevention

SQL injection allows attackers to execute arbitrary database queries.

Always use prepared statements:

global $wpdb;

// WRONG - vulnerable to SQL injection
$user_id = $_GET['user_id'];
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->users} WHERE ID = $user_id" );

// CORRECT - using prepare()
$user_id = intval( $_GET['user_id'] );
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->users} WHERE ID = %d",
        $user_id
    )
);

Placeholders for different data types:

// %d for integers
$wpdb->prepare( "SELECT * FROM table WHERE id = %d", $id );

// %s for strings
$wpdb->prepare( "SELECT * FROM table WHERE name = %s", $name );

// %f for floats
$wpdb->prepare( "SELECT * FROM table WHERE price = %f", $price );

// Multiple values
$wpdb->prepare(
    "SELECT * FROM table WHERE id = %d AND name = %s",
    $id,
    $name
);

Never concatenate variables into SQL queries.

CSRF Protection with Nonces

CSRF attacks trick users into executing unwanted actions.

Create nonces in forms:

<form method="post">
    <?php wp_nonce_field( 'dprt_save_settings', 'dprt_nonce' ); ?>
    <input type="text" name="setting_value">
    <input type="submit" value="Save">
</form>

Verify nonces before processing:

if ( isset( $_POST['submit'] ) ) {
    if ( ! isset( $_POST['dprt_nonce'] ) || ! wp_verify_nonce( $_POST['dprt_nonce'], 'dprt_save_settings' ) ) {
        wp_die( 'Security check failed' );
    }

    // Process form data
}

For admin pages, use check_admin_referer():

check_admin_referer( 'dprt_save_settings', 'dprt_nonce' );

For AJAX:

// JavaScript
$.post( ajaxurl, {
    action: 'dprt_save',
    nonce: dprt_ajax.nonce,
    data: formData
});

// PHP
function dprt_ajax_save() {
    check_ajax_referer( 'dprt_ajax_nonce', 'nonce' );
    // Process request
}

Every form submission, AJAX request, and state-changing action requires nonce verification.

User Capability Checks

Verify users have permission before allowing actions:

// Check specific capability
if ( ! current_user_can( 'manage_options' ) ) {
    wp_die( 'Unauthorized access' );
}

// Check for specific post
if ( ! current_user_can( 'edit_post', $post_id ) ) {
    wp_die( 'You cannot edit this post' );
}

// Check multiple capabilities
if ( ! current_user_can( 'edit_posts' ) && ! current_user_can( 'edit_pages' ) ) {
    return;
}

Common capabilities:

  • manage_options – Administrators only
  • edit_posts – Can edit posts
  • publish_posts – Can publish posts
  • edit_others_posts – Can edit posts by other users

Data Sanitization

Clean all user input before processing:

// Text fields
$text = sanitize_text_field( $_POST['text_field'] );

// Textareas
$content = sanitize_textarea_field( $_POST['content'] );

// Email addresses
$email = sanitize_email( $_POST['email'] );

// URLs
$url = esc_url_raw( $_POST['url'] );

// File names
$filename = sanitize_file_name( $_FILES['file']['name'] );

// HTML class names
$class = sanitize_html_class( $_POST['class'] );

// Keys
$key = sanitize_key( $_POST['key'] );

// Integers
$number = absint( $_POST['number'] );

Sanitize input, escape output. This principle prevents most vulnerabilities.

File Upload Security

File uploads are high-risk operations:

function dprt_handle_file_upload() {
    if ( ! isset( $_FILES['file'] ) ) {
        return;
    }

    // Verify nonce
    check_admin_referer( 'dprt_upload', 'nonce' );

    // Check capability
    if ( ! current_user_can( 'upload_files' ) ) {
        wp_die( 'Insufficient permissions' );
    }

    // Validate file type
    $allowed_types = array( 'image/jpeg', 'image/png', 'image/gif' );
    $file_type = $_FILES['file']['type'];

    if ( ! in_array( $file_type, $allowed_types ) ) {
        wp_die( 'Invalid file type' );
    }

    // Use WordPress upload handler
    $upload = wp_handle_upload(
        $_FILES['file'],
        array( 'test_form' => false )
    );

    if ( isset( $upload['error'] ) ) {
        wp_die( $upload['error'] );
    }

    // File uploaded successfully
    $file_url = $upload['url'];
}

Never trust uploaded files. Validate type, size, and content.

Preventing Direct File Access

Prevent users from accessing plugin files directly:

<?php
// At the top of every PHP file
if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly
}

This prevents attackers from executing PHP files outside WordPress context.

Secure AJAX Implementation

AJAX requests need the same security as form submissions:

// Enqueue script with nonce
function dprt_enqueue_ajax_script() {
    wp_enqueue_script( 'dprt-ajax', plugin_dir_url( __FILE__ ) . 'ajax.js', array( 'jquery' ) );
    wp_localize_script( 'dprt-ajax', 'dprtAjax', array(
        'ajaxurl' => admin_url( 'admin-ajax.php' ),
        'nonce' => wp_create_nonce( 'dprt_ajax' )
    ) );
}

// AJAX handler
function dprt_ajax_handler() {
    // Verify nonce
    check_ajax_referer( 'dprt_ajax', 'nonce' );

    // Check capabilities
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( 'Insufficient permissions' );
    }

    // Sanitize input
    $data = sanitize_text_field( $_POST['data'] );

    // Process and respond
    wp_send_json_success( array( 'result' => $processed_data ) );
}
add_action( 'wp_ajax_dprt_action', 'dprt_ajax_handler' );

Secure Configuration

Never hardcode sensitive information:

// BAD - hardcoded API key
$api_key = 'sk_live_123456789';

// GOOD - use constants or options
define( 'DPRT_API_KEY', 'sk_live_123456789' ); // In wp-config.php
$api_key = defined( 'DPRT_API_KEY' ) ? DPRT_API_KEY : get_option( 'dprt_api_key' );

Store sensitive data in wp-config.php or use environment variables.

Security Checklist

Before releasing your plugin:


  • All user input sanitized

  • All output escaped

  • Nonces on all forms and AJAX requests

  • Capability checks on all admin functions

  • Prepared statements for all database queries

  • File upload validation

  • Direct file access prevention

  • No hardcoded credentials

  • Security review by another developer

  • Testing with security plugins

Conclusion

Security isn’t a feature—it’s a requirement. Sanitize all input, escape all output, verify nonces, check capabilities, and use prepared statements. These practices protect users and maintain WordPress ecosystem trust. Make security your default mindset, not an afterthought.

  • Secure communication
  • Using HTTPS for API calls
  • SSL/TLS certificate validation
  • Third-party library security
  • Keeping dependencies updated
  • Vulnerability scanning tools
  • Security auditing and code review
  • Common security mistakes developers make
  • Security checklist for plugin release
  • Responsible disclosure of vulnerabilities

Includes code examples, security patterns, and testing procedures for building secure, trustworthy WordPress plugins.

  1. WordPress Plugin Security
  2. Data Validation Documentation
  3. OWASP Top 10
  4. WordPress Security White Paper
  5. WPScan Vulnerability Database

Call to Action

Supercharge your development! ACF Copilot Pro generates ACF field groups with AI, exports to PHP, and accelerates custom field workflows—try it free!