How to sanitize and validate inputs with sanitize_text_field and similar in WordPress

Contents

Introduction

This article explains in detail how to properly sanitize and validate inputs in WordPress using sanitize_text_field and related functions. It covers the difference between sanitization and escaping, lists the most important WordPress sanitization and validation helpers, and provides multiple practical examples (form processing, settings API, meta boxes, REST API, AJAX, uploads, and arrays). Every code example is provided in runnable form and placed inside the required preformatted blocks.

Sanitization vs Escaping vs Validation

Understanding three distinct concepts is crucial:

  • Sanitization: Clean input to remove or normalize unwanted characters before storing or further processing (e.g., remove HTML tags, strip control characters).
  • Escaping: Encode data when outputting to a specific context (HTML, attributes, JS, URLs) to prevent XSS (e.g., esc_html, esc_attr, esc_url). Always escape on output.
  • Validation: Check that input meets expectations (format, range, type). Return true/false or normalized values and act accordingly (e.g., is_email, filter_var with FILTER_VALIDATE_INT).

Why use WordPress functions?

WordPress core provides many helpers that are standardized, maintained and account for WP-specific formats. Use them instead of ad-hoc regex where possible. Also combine sanitization and validation with nonce checks and capability checks when handling forms or saving data.

Common WordPress sanitization and validation functions

Function Purpose
sanitize_text_field Strip tags, remove line breaks and invalid UTF-8, remove octets — good for short single-line text.
sanitize_textarea_field Sanitize multi-line textareas (preserves newlines appropriately).
wp_strip_all_tags Remove HTML and PHP tags, keep spaces optionally.
wp_kses, wp_kses_post Allow a set of allowed HTML tags and attributes — safe HTML sanitizer.
sanitize_email Sanitize an email address use is_email to validate.
sanitize_key Sanitize an array key or similar (lowercase alphanumeric and underscores).
sanitize_file_name Remove dangerous characters from filenames.
sanitize_title / sanitize_title_with_dashes Make a string into a slug.
intval, floatval, absint Cast numeric values safely.
esc_url_raw Sanitize URL for storage (use esc_url on output for attribute contexts).
wp_validate_boolean Normalize booleans like 1, 1, true, true, on, off.
is_email Validate email format (returns sanitized email or false).

Principles and best practices

  1. Sanitize on input, escape on output. Do not rely only on escaping at output store clean data when appropriate.
  2. Validate what you expect. If field must be integer, validate and cast if URL, use esc_url_raw and FILTER_VALIDATE_URL.
  3. Never trust client-side checks. Client-side validation is optional UX — always validate on server.
  4. Use nonces and capability checks when processing forms, AJAX requests or saving post meta or options.
  5. Use wpdb->prepare for direct DB queries.
  6. When dealing with HTML from trusted sources, define allowed tags with wp_kses do not blindly allow arbitrary HTML from users.

Example: Processing a simple form (save sanitized input)

This example shows typical steps: check nonce, check capability, sanitize input, validate, store value and escape on output later.

lt?php
// Example: process a settings form submitted via POST.
if ( isset( _POST[my_plugin_settings_submit] ) ) {
    // Check nonce
    if ( ! isset( _POST[my_plugin_nonce] )  ! wp_verify_nonce( _POST[my_plugin_nonce], my_plugin_save ) ) {
        wp_die( Security check failed )
    }

    // Capability check
    if ( ! current_user_can( manage_options ) ) {
        wp_die( Insufficient permissions )
    }

    // Sanitize and validate inputs
    // Short text field
    title = isset( _POST[my_title] ) ? sanitize_text_field( wp_unslash( _POST[my_title] ) ) : 

    // Email
    email_raw = isset( _POST[my_email] ) ? sanitize_email( wp_unslash( _POST[my_email] ) ) : 
    email = is_email( email_raw ) ? email_raw : 

    // Integer
    items_raw = isset( _POST[my_items] ) ? _POST[my_items] : 0
    items = intval( items_raw )

    // Textarea (multi-line)
    description = isset( _POST[my_description] ) ? sanitize_textarea_field( wp_unslash( _POST[my_description] ) ) : 

    // URL (for storage)
    website = isset( _POST[my_website] ) ? esc_url_raw( wp_unslash( _POST[my_website] ) ) : 

    // Save options (sanitize again if desired)
    update_option( my_plugin_title, title )
    update_option( my_plugin_email, email )
    update_option( my_plugin_items, items )
    update_option( my_plugin_description, description )
    update_option( my_plugin_website, website )
}
?gt

Example: Escaping when outputting the saved values

Always escape for the appropriate context when rendering values to the page.

lt?php
title = get_option( my_plugin_title,  )
email = get_option( my_plugin_email,  )
items = intval( get_option( my_plugin_items, 0 ) )
desc = get_option( my_plugin_description,  )
website = get_option( my_plugin_website,  )
?gt

lt!-- In a HTML attribute --gt
ltinput type=text value=lt?php echo esc_attr( title ) ?gt /gt

lt!-- In HTML body --gt
ltpgtlt?php echo esc_html( desc ) ?gtlt/pgt

lt!-- URL in link --gt
lta href=lt?php echo esc_url( website ) ?gtgtVisit sitelt/agt

Example: Settings API with sanitize callback

When using register_setting you can supply a sanitize_callback that runs before the value is saved.

lt?php
function my_plugin_register_settings() {
    register_setting(
        my_plugin_options_group, // option_group
        my_plugin_options,       // option_name (array)
        array(
            type              =gt array,
            sanitize_callback =gt my_plugin_sanitize_options,
        )
    )
}
add_action( admin_init, my_plugin_register_settings )

function my_plugin_sanitize_options( input ) {
    output = array()

    if ( isset( input[title] ) ) {
        output[title] = sanitize_text_field( input[title] )
    }

    if ( isset( input[email] ) ) {
        email = sanitize_email( input[email] )
        output[email] = is_email( email ) ? email : 
    }

    if ( isset( input[enable_feature] ) ) {
        // Normalize booleans
        output[enable_feature] = wp_validate_boolean( input[enable_feature] ) ? 1 : 0
    } else {
        output[enable_feature] = 0
    }

    return output
}
?gt

Example: Saving post meta from metabox (proper sanitization nonce)

lt?php
function myplugin_save_post_meta( post_id ) {
    // Autosave check
    if ( defined( DOING_AUTOSAVE )  DOING_AUTOSAVE ) {
        return
    }

    // Verify nonce
    if ( ! isset( _POST[myplugin_meta_nonce] )  ! wp_verify_nonce( _POST[myplugin_meta_nonce], myplugin_save_meta ) ) {
        return
    }

    // Check permissions
    if ( ! current_user_can( edit_post, post_id ) ) {
        return
    }

    // Example meta: a short text
    if ( isset( _POST[myplugin_short] ) ) {
        short = sanitize_text_field( wp_unslash( _POST[myplugin_short] ) )
        update_post_meta( post_id, _myplugin_short, short )
    }

    // Example meta: allowed HTML content (use wp_kses_post)
    if ( isset( _POST[myplugin_rich] ) ) {
        rich = wp_kses_post( wp_unslash( _POST[myplugin_rich] ) )
        update_post_meta( post_id, _myplugin_rich, rich )
    }

    // Example meta: list of tags (array)
    if ( isset( _POST[myplugin_tags] )  is_array( _POST[myplugin_tags] ) ) {
        tags = array_map( sanitize_text_field, wp_unslash( _POST[myplugin_tags] ) )
        update_post_meta( post_id, _myplugin_tags, tags )
    }
}
add_action( save_post, myplugin_save_post_meta )
?gt

Example: Handling arrays and nested arrays

sanitize_text_field expects a string for arrays use array_map or recursive sanitization. Use wp_slash/wp_unslash appropriately when dealing with magic quotes contexts.

lt?php
function sanitize_recursive_text_fields( data ) {
    if ( is_array( data ) ) {
        return array_map( sanitize_recursive_text_fields, data )
    }
    return sanitize_text_field( data )
}

// Usage:
input = isset( _POST[settings] ) ? wp_unslash( _POST[settings] ) : array()
clean = sanitize_recursive_text_fields( input )
?gt

Example: REST API registration with sanitize_callback and validate_callback

When registering REST fields or endpoints you can define sanitize_callback (for preparing data to store) and validate_callback (to block invalid submissions).

lt?php
register_rest_route( myplugin/v1, /item, array(
    methods  =gt POST,
    callback =gt myplugin_rest_create_item,
    args     =gt array(
        title =gt array(
            required          =gt true,
            sanitize_callback =gt sanitize_text_field,
            validate_callback =gt function( param, request, key ) {
                if ( strlen( param ) gt 100 ) {
                    return new WP_Error( rest_invalid_param, Title is too long, array( status =gt 400 ) )
                }
                return true
            },
        ),
        email =gt array(
            required =gt false,
            sanitize_callback =gt sanitize_email,
            validate_callback =gt function( param ) {
                return empty( param )  is_email( param )
            },
        ),
    ),
) )

function myplugin_rest_create_item( request ) {
    title = request->get_param( title ) // already sanitized
    email = request->get_param( email ) // already sanitized

    // Save or process...
    return rest_ensure_response( array( success =gt true ) )
}
?gt

Example: AJAX request handling

For AJAX handlers use check_ajax_referer and sanitize inputs. Use current_user_can when necessary.

lt?php
add_action( wp_ajax_myplugin_action, myplugin_ajax_handler )
add_action( wp_ajax_nopriv_myplugin_action, myplugin_ajax_handler )

function myplugin_ajax_handler() {
    check_ajax_referer( myplugin_nonce, security )

    name = isset( _POST[name] ) ? sanitize_text_field( wp_unslash( _POST[name] ) ) : 
    email = isset( _POST[email] ) ? sanitize_email( wp_unslash( _POST[email] ) ) : 

    if ( ! is_email( email ) ) {
        wp_send_json_error( array( message =gt Invalid email ), 400 )
    }

    // Continue processing...
    wp_send_json_success( array( message =gt Processed, name =gt name ) )
}
?gt

Sanitizing and validating file uploads

User-uploaded file names and contents need special handling. Use sanitize_file_name for filenames, wp_check_filetype for file types, and proper capability checks. Dont trust MIME types alone — consider scanning with antivirus on high-risk sites.

lt?php
if ( ! empty( _FILES[my_file][name] ) ) {
    file_name = sanitize_file_name( _FILES[my_file][name] )
    file_tmp  = _FILES[my_file][tmp_name]
    file_type = wp_check_filetype( file_name )

    // Allow only certain extensions
    allowed_exts = array( jpg, jpeg, png, pdf )
    if ( ! in_array( file_type[ext], allowed_exts, true ) ) {
        wp_die( Invalid file type )
    }

    // Use wp_handle_sideload or wp_handle_upload (with proper overrides)
    require_once( ABSPATH . wp-admin/includes/file.php )
    overrides = array( test_form =gt false )
    movefile = wp_handle_upload( _FILES[my_file], overrides )

    if ( isset( movefile[error] ) ) {
        wp_die( Upload error:  . esc_html( movefile[error] ) )
    }

    // movefile[url] is safe for output when escaped
}
?gt

Using wp_kses and allowed HTML

If you permit HTML from users (for comments, post content from trusted users, or widget text), dont allow it blindly. Define a whitelist of tags/attributes via wp_kses or use wp_kses_post which uses WordPresss typical allowed set.

lt?php
allowed = array(
    a =gt array(
        href =gt true,
        title =gt true,
        rel =gt true,
    ),
    br =gt array(),
    em =gt array(),
    strong =gt array(),
    p =gt array(),
    ul =gt array(),
    ol =gt array(),
    li =gt array(),
)

content = isset( _POST[user_html] ) ? wp_unslash( _POST[user_html] ) : 
clean_content = wp_kses( content, allowed )

update_option( my_allowed_html, clean_content )
?gt

Using filter_var and PHP native validation

WordPress helpers are great, but you can combine them with PHPs filter_var for e.g. validating integers, URLs etc. For boolean normalization, prefer wp_validate_boolean.

lt?php
age = isset( _POST[age] ) ? _POST[age] : 
if ( filter_var( age, FILTER_VALIDATE_INT, array( options =gt array( min_range =gt 0 ) ) ) === false ) {
    // invalid integer
} else {
    age = intval( age )
}

// Filter validate URL
url = isset( _POST[website] ) ? _POST[website] : 
if ( filter_var( url, FILTER_VALIDATE_URL ) !== false ) {
    url = esc_url_raw( url )
} else {
    url = 
}
?gt

Storing data in the database securely

If you must run custom SQL queries, always use wpdb->prepare and proper placeholders to avoid SQL injection. Even if values are sanitized, parameterized queries are essential.

lt?php
global wpdb
name = sanitize_text_field( _POST[name] )

// Bad: string concatenation (avoid)
// wpdb-gtquery( INSERT INTO {wpdb->prefix}my_table (name) VALUES ( . esc_sql( name ) . ) )

// Good: prepared statement
wpdb->query(
    wpdb->prepare(
        INSERT INTO {wpdb->prefix}my_table (name) VALUES (%s),
        name
    )
)
?gt

Edge cases and pitfalls

  • sanitize_text_field strips more than you might expect — it removes line breaks and tags use sanitize_textarea_field for multi-line content.
  • Double sanitization — repeated sanitization isnt harmful generally, but may alter intended content if you sanitize HTML with strip functions after wp_kses. Plan order of operations.
  • Sanitizing arrays — sanitize_text_field does not accept arrays, so use array_map or recursive functions.
  • Do not use esc_html for storage — esc_ functions are for output. Use esc_url_raw for storing URLs.
  • Beware of encoding — always use functions that handle invalid UTF-8 or call wp_check_invalid_utf8 when needed.

Reference checklist for common field types

  • Single-line text: sanitize_text_field escape with esc_attr on output.
  • Multi-line text: sanitize_textarea_field escape with esc_html on output.
  • HTML with allowed tags: wp_kses or wp_kses_post echo with wp_kses_post or esc_html depending on policy.
  • Email: sanitize_email and is_email escape with antispambot or esc_html.
  • URL: esc_url_raw for storage, esc_url for output.
  • Integer/float: intval/floatval/absint or filter_var with FILTER_VALIDATE_INT store as numeric.
  • Boolean: wp_validate_boolean store consistently as 1/0 or true/false depending on your codebase.
  • Filenames: sanitize_file_name check filetype with wp_check_filetype.

Further reading

See the WordPress developer reference for details on individual functions and for the most current recommendations: https://developer.wordpress.org

Summary

Sanitize input using the appropriate WordPress helpers before storing or processing. Validate inputs according to expectations, always escape on output for the context, and enforce nonces and capability checks when handling submitted data. Use wpdb->prepare when doing database queries and wp_kses when you allow a subset of HTML. The examples above cover the most common contexts: forms, settings, post meta, REST API, AJAX and uploads — adapt them to your plugin or theme and follow the principle: sanitize on input, validate, then escape on output.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *