All insights
Uncategorized

WordPress Data Sanitization and Escaping — A Developer’s Guide

Jun 13, 2026 3 min read

Sanitization and escaping are the two habits that separate WordPress code that’s merely working from code that’s actually safe. They get conflated constantly, skipped under deadline pressure, and bolted on after a security audit instead of being baked in from the first line. Here’s how I think about them in practice.

The one rule that prevents most problems

Sanitize on input, escape on output. That’s the whole discipline in a sentence. Data coming into your application gets cleaned before it’s stored; data going out to the browser gets escaped at the moment it’s rendered, every time, regardless of where it came from. The mistake I see most often is trusting data because “we already sanitized it on the way in” — by the time it’s rendered, context has changed, and the only thing that keeps you safe is escaping at output.

Sanitizing input

WordPress gives you purpose-built functions — use the one that matches the data, not a catch-all. A plain text field uses sanitize_text_field(), an email uses sanitize_email(), a URL uses esc_url_raw() before storage, and an integer should simply be cast or run through absint().

$title = sanitize_text_field( $_POST['title'] ?? '' );
$email = sanitize_email( $_POST['email'] ?? '' );
$count = absint( $_POST['count'] ?? 0 );

For richer content where you genuinely want some HTML — a post body, a description — reach for wp_kses_post() or a custom wp_kses() allowlist rather than letting raw markup through. Never trust a field just because it came from an admin screen; capable users get phished too.

Escaping output

Escaping is context-dependent, and that’s the part people miss. The same string needs different treatment depending on where it lands — inside HTML, inside an attribute, inside a URL, or inside JavaScript.

echo esc_html( $title );
printf( '<a href="%s">%s</a>', esc_url( $link ), esc_html( $label ) );
echo '<div class="' . esc_attr( $class ) . '">';
echo wp_kses_post( $rich_content );

Escape as late as possible — ideally right at the point of output — so there’s no window where an “escaped” value gets modified before it’s printed. A value escaped three functions ago is a value you have to trace; a value escaped on the same line you echo it is a value you can verify at a glance.

Database queries

Sanitizing input does not make a query safe. Any query with a variable in it goes through $wpdb->prepare(), full stop. Prepared statements are what actually stop SQL injection; sanitization is a separate concern.

$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->posts} WHERE post_author = %d AND post_status = %s",
        $author_id,
        $status
    )
);

Where this fits with the rest of your security

Sanitization and escaping handle the data. They sit alongside the other half of the picture — verifying intent and permission with nonces and capability checks — which deserves its own treatment. A form handler that escapes perfectly but never checks current_user_can() or a nonce is still wide open. Treat the four together as one habit: check the nonce, check the capability, sanitize the input, escape the output.

None of this is exotic. It’s just applied consistently, on every field, every query, every render — which is exactly why it works.

Conversation

Join the discussion

Thoughts, corrections or war stories from your own builds — all welcome.

0 comments

No comments yet. Be the first.

Leave a Reply

Chat on WhatsApp