Web

How to Sanitize, Validate & Store Form Data in WordPress Plugins

Master WordPress security: sanitize form inputs with sanitize_text_field() and sanitize_email(), validate with is_email(), store securely via $wpdb->insert() in custom admin plugins to prevent SQL injection and XSS attacks.

1 answer 1 view

How to properly sanitize, validate, and store form data in a custom WordPress admin plugin?

I’m building a beginner-level custom WordPress plugin that collects student details (name, email, course) from an HTML form on the admin page and stores them in the WordPress database using PHP.

What are the WordPress best practices for:

  • Sanitizing user input before saving to the database
  • Properly validating email fields
  • Preventing security issues like SQL injection and unsafe data storage?

To securely handle form data like student names, emails, and courses in a custom WordPress admin plugin, start by validating inputs with functions like is_email(), then sanitize using sanitize_text_field() for text fields and sanitize_email() for emails, before storing via $wpdb->insert() with format specifiers to block SQL injection. Always add nonce checks with wp_verify_nonce() and capability verification via current_user_can(‘manage_options’) to fend off CSRF attacks and unauthorized access. These WordPress security best practices, straight from the official docs, keep your plugin bulletproof even for beginners.


Contents


Why Security Matters in WordPress Plugins

Ever wonder why a simple admin form can turn into a hacker’s playground? User inputs—names, emails, courses—arrive untrusted from browsers, potentially packed with malicious scripts or junk data. Skip sanitization and validation, and you’re inviting SQL injection, XSS attacks, or just corrupted records.

WordPress pushes a clear mantra: validate first, sanitize second, escape on output. The official sanitization guide spells it out—untrusted data needs checking before database storage or display. For your student details plugin, this means no raw $_POST dumps into $wpdb. Bad idea. It leads to exploits where attackers slip in DROP TABLE commands or JavaScript payloads.

Think lightweight: a name like “” shouldn’t survive to your DB. Same for fake emails. Get this right early, and your plugin scales safely as users pile on.


Nonces and Capability Checks First

Before touching form data, block the wrong people. Who’s submitting? A logged-in admin? Or a sneaky bot?

Hook into admin_post_[action] for AJAX-free handling. First line: capability check.

php
if ( ! current_user_can( 'manage_options' ) ) {
 wp_die( 'No permission to add students.' );
}

This ensures only admins touch your student table. Next, nonces—WordPress’s CSRF shield. Generate one in your form:

php
wp_nonce_field( 'save_student_action', 'student_nonce' );

Verify on submit:

php
if ( ! wp_verify_nonce( $_POST['student_nonce'], 'save_student_action' ) ) {
 wp_die( 'Security check failed.' );
}

The Stack Overflow example nails this sequence. Skip it? Forms become spam magnets. Nonces expire after 24 hours too, adding replay protection. Smart, right?


Sanitizing Form Inputs

Sanitization cleans data post-validation—think stripping tags, fixing encoding, nuking whitespace. Don’t store raw $_POST; WordPress has helpers.

For name and course (plain text):

php
$name = sanitize_text_field( $_POST['student_name'] ?? '' );
$course = sanitize_text_field( $_POST['student_course'] ?? '' );

What does sanitize_text_field() do? Checks UTF-8, converts < to <, strips tags, kills tabs/line breaks. Perfect for short fields, per the WordPress sanitizing docs.

Emails get sanitize_email():

php
$email = sanitize_email( $_POST['student_email'] ?? '' );

It yanks illegal chars while preserving valid ones. But wait—sanitize after validating, as Rudrastyh explains. Why? Sanitization might “fix” bad data you want to reject outright.

Pro tip: Chain them if needed, like wp_kses() for richer inputs (HTML-allowed fields). Keeps things lean without overkill.


Validating User Data

Validation says “nope” to bad data before sanitizing. It’s stricter—reject invalid, don’t fix.

Emails? is_email() is your gatekeeper:

php
if ( ! isset( $_POST['student_email'] ) || ! is_email( $_POST['student_email'] ) ) {
 wp_die( 'Please enter a valid email.' );
}

RFC-compliant checks, no funny business. The validation handbook shows this exact pattern: isset() first, then is_email(), sanitize last.

For names/courses, custom rules shine. Min length? Regex for letters only?

php
if ( strlen( $name ) < 2 || ! preg_match( '/^[a-zA-Z\s]+$/', $name ) ) {
 wp_die( 'Name must be at least 2 letters.' );
}

WP VIP docs hammer this home: validate rigorously. What if someone enters “admin’ OR 1=1”? Validation catches it early. Saves headaches.


Secure Database Storage

DB time. No direct queries with user data—hello, SQL injection.

Create a custom table on activation:

php
register_activation_hook( __FILE__, 'create_student_table' );
function create_student_table() {
 global $wpdb;
 $table = $wpdb->prefix . 'student_details';
 $charset = $wpdb->get_charset_collate();
 $sql = "CREATE TABLE $table (
 id mediumint(9) NOT NULL AUTO_INCREMENT,
 name varchar(100) NOT NULL,
 email varchar(100) NOT NULL,
 course varchar(100) NOT NULL,
 PRIMARY KEY (id)
 ) $charset;";
 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
 dbDelta( $sql );
}

Store safely with $wpdb->insert():

php
global $wpdb;
$table = $wpdb->prefix . 'student_details';
$wpdb->insert(
 $table,
 array(
 'name' => $name,
 'email' => $email,
 'course' => $course
 ),
 array( '%s', '%s', '%s' )
);

Format specifiers (%s) auto-escape. No prepare() needed here—insert handles it. Docs confirm: this prevents injection.

Errors? Check $wpdb->last_error. Boom—data safe.


Full Plugin Example

Pull it together. Here’s a beginner-ready plugin. Drop in /wp-content/plugins/student-manager/ as student-manager.php.

php
<?php
/**
 * Plugin Name: Student Manager
 * Description: Collects student details securely.
 */

// Activation hook for table
register_activation_hook( __FILE__, 'create_student_table' );
function create_student_table() {
 global $wpdb;
 $table = $wpdb->prefix . 'student_details';
 $charset = $wpdb->get_charset_collate();
 $sql = "CREATE TABLE $table (
 id mediumint(9) NOT NULL AUTO_INCREMENT,
 name varchar(100) NOT NULL,
 email varchar(100) NOT NULL,
 course varchar(100) NOT NULL,
 PRIMARY KEY (id)
 ) $charset;";
 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
 dbDelta( $sql );
}

// Admin menu
add_action( 'admin_menu', 'student_admin_menu' );
function student_admin_menu() {
 add_menu_page( 'Students', 'Students', 'manage_options', 'student-manager', 'student_admin_page' );
}

// Admin page
function student_admin_page() {
 if ( isset( $_GET['settings-updated'] ) ) {
 echo '<div class="notice notice-success"><p>Student added!</p></div>';
 }
 ?>
 <div class="wrap">
 <h1>Add Student</h1>
 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
 <?php wp_nonce_field( 'save_student_action', 'student_nonce' ); ?>
 <input type="hidden" name="action" value="save_student">
 <p><label>Name: <input type="text" name="student_name" required></label></p>
 <p><label>Email: <input type="email" name="student_email" required></label></p>
 <p><label>Course: <input type="text" name="student_course" required></label></p>
 <?php submit_button( 'Add Student' ); ?>
 </form>
 </div>
 <?php
}

// Handler
add_action( 'admin_post_save_student', 'save_student_handler' );
function save_student_handler() {
 if ( ! current_user_can( 'manage_options' ) ) {
 wp_die( 'Unauthorized.' );
 }
 if ( ! wp_verify_nonce( $_POST['student_nonce'], 'save_student_action' ) ) {
 wp_die( 'Invalid nonce.' );
 }
 $name = sanitize_text_field( $_POST['student_name'] ?? '' );
 $course = sanitize_text_field( $_POST['student_course'] ?? '' );
 $email = sanitize_email( $_POST['student_email'] ?? '' );
 if ( ! is_email( $_POST['student_email'] ) || strlen( $name ) < 2 ) {
 wp_die( 'Invalid data.' );
 }
 global $wpdb;
 $wpdb->insert(
 $wpdb->prefix . 'student_details',
 array( 'name' => $name, 'email' => $email, 'course' => $course ),
 array( '%s', '%s', '%s' )
 );
 wp_redirect( admin_url( 'admin.php?page=student-manager&settings-updated=true' ) );
 exit;
}

Activate, test. Handles everything: security, UX feedback. Expand with listings or edits later.


Sources

  1. Sanitizing Data – Common APIs Handbook
  2. Validating Data – Common APIs Handbook
  3. How to properly sanitize and store form data in a custom WordPress plugin? – Stack Overflow
  4. Sanitizing, Validating and Escaping Data in WordPress
  5. Validating, sanitizing, and escaping – WordPress VIP Documentation

Conclusion

Nail WordPress sanitize form data with this flow—nonces, capabilities, validate (is_email()), sanitize (sanitize_text_field, sanitize_email()), store ($wpdb->insert())—and your student plugin stays secure. Beginners, copy that example; it’s production-ready. Scale confidently, knowing you’ve dodged the big pitfalls like SQL injection. Questions? Tweak and test on a dev site first.

Authors
Verified by moderation
Moderation
How to Sanitize, Validate & Store Form Data in WordPress Plugins