Tips・The foundations of WordPress customization

Choosing the right escaping function (esc_html, esc_attr, esc_url)

How to choose between the escaping functions you can't do without when writing themes and plugins. Covers the right answer for each output location, the sanitize family for incoming data, and nonces and capability checks, with code.

A rule of thumb when writing themes and plugins—always escape the values you output. The reason is to stop a <script> that snuck into a post or a form field from being executed as-is.

There’s only one rule to learn: choose the function based on where you’re printing.

The right answer for each output location

Where you’re printingFunction to useExample
Text on the pageesc_html()<p><?php echo esc_html($name); ?></p>
A tag’s attribute valueesc_attr()<img alt="<?php echo esc_attr($alt); ?>">
URLs for href, src and the likeesc_url()<a href="<?php echo esc_url($url); ?>">
The contents of a textareaesc_textarea()<textarea><?php echo esc_textarea($v); ?></textarea>
A JavaScript stringesc_js()For inline use (prefer wp_localize_script)
A value where you want to keep the HTMLwp_kses_post()Rich text, post-content equivalents
<?php $img = get_field('photo'); ?>
<a href="<?php echo esc_url(get_permalink()); ?>" class="card">
  <img src="<?php echo esc_url($img['url']); ?>" alt="<?php echo esc_attr($img['alt']); ?>">
  <h3><?php echo esc_html(get_the_title()); ?></h3>
</a>

How WordPress functions handle escaping

“It goes through a function, so it’s safe” isn’t a given. Whether escaping happens differs from function to function.

FunctionBehavior
the_title()Generally output safely
the_permalink()Output safely as a URL
the_content()Passes through post-content filters (HTML allowed)
the_field() (ACF/SCF)Does not escape—prints the value as-is
The get_…() familyOnly returns a value—you escape it yourself

Functions starting with get_ “return a value,” so you have to escape them yourself when you echo. Just internalizing that one point makes your code consistent.

TipsACF field types: a cheat sheet for displaying each oneHow to output ACF values safely, in this cheat sheet

Clean incoming data before you save it

The counterpart to escaping on output is sanitizing before you save.

PurposeFunction
Single-line textsanitize_text_field()
Multi-line textsanitize_textarea_field()
Email addresssanitize_email()
URLesc_url_raw() (for saving)
Integerabsint() / (int)
Slugsanitize_title()
Key namesanitize_key()
Body text where HTML is allowedwp_kses_post()
$name  = sanitize_text_field($_POST['name'] ?? '');
$email = sanitize_email($_POST['email'] ?? '');
$count = absint($_POST['count'] ?? 0);

Values that came from outside ($_POST, $_GET, $_COOKIE) must have their type and shape pinned down before you use them—that’s the baseline stance.

Form saving takes a set of three

Any routine that saves data, in the admin or on the front end, needs all three: nonce, capability and sanitization.

// The form side
function my_form() {
  ?>
  <form method="post">
    <?php wp_nonce_field('my_save_action', 'my_nonce'); ?>
    <input type="text" name="nickname">
    <button type="submit">Save</button>
  </form>
  <?php
}

// The receiving side
add_action('admin_post_my_save', function () {
  // 1. Verify the nonce (did this come from a screen on this site?)
  if (!isset($_POST['my_nonce']) || !wp_verify_nonce($_POST['my_nonce'], 'my_save_action')) {
    wp_die('Invalid request');
  }
  // 2. Check the capability (is this person allowed to do it?)
  if (!current_user_can('edit_posts')) {
    wp_die('You do not have permission');
  }
  // 3. Clean the value, then save
  update_option('my_nickname', sanitize_text_field($_POST['nickname'] ?? ''));

  wp_safe_redirect(admin_url('options-general.php'));
  exit;
});
  • Nonce—a single-use value confirming the submission came from a screen on your site
  • Capabilitycurrent_user_can() confirms “is this person allowed to do this?”
  • Sanitization—pins down the shape of the value

Miss any one of the three and you’ve left a hole. Checking the nonce but not the capability is especially common, so get in the habit of writing them as a set.

TipsWorking with custom fields using get_post_metaAn example of updating meta when a post is saved

Use prepare when you touch the database directly

When you write your own SQL with $wpdb, always pass values through prepare().

global $wpdb;

// ❌ dangerous (this is how SQL injection happens)
$wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_title = '$title'");

// ✅ hand it to a placeholder with prepare
$wpdb->get_results(
  $wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE post_title = %s", $title)
);

Choose between %s (string), %d (integer) and %f (float). And if WP_Query or get_posts() can do the job, using those is safest of all.

TipsHow to fetch posts outside the loop (WP_Query)How to fetch posts with WP_Query

Redirect with wp_safe_redirect

wp_safe_redirect(home_url('/thanks/'));
exit;

wp_safe_redirect() only sends people to URLs on your own site, which prevents “open redirects” that push visitors to an external site. Forget exit and processing continues, so always write them as a pair.

Combining with translation functions

Themes built for multiple languages have functions that combine translation with escaping.

<?php esc_html_e('Contact', 'my-theme'); ?>       <!-- prints -->
<?php echo esc_attr__('Search', 'my-theme'); ?>   <!-- for attributes -->

They’re __() (returns a value) and _e() (prints) with esc_html and esc_attr attached. Once the naming pattern clicks, you can guess the one you need.

Summary

  1. Choose the function by where you’re printing—body esc_html(), attributes esc_attr(), URLs esc_url()
  2. Functions starting with get_ only return a value. Escape them yourself when you echo (the_field() too)
  3. Saving routines need the set of three: nonce, capability (current_user_can) and sanitization
  4. If you write SQL, use $wpdb->prepare(). Redirect with wp_safe_redirect() plus exit

Escaping is a habit with very little to memorize and a lot of payoff. Once your hands do it automatically, your safety level goes up a notch on its own.

FAQ

When do I use esc_html versus esc_attr?
Use esc_html() for body text shown on the page, and esc_attr() for HTML attribute values (alt, class, value and so on). Attribute values involve quote handling, so they get their own function.
Do the_field() and the_title() escape their output?
the_title() is generally output safely, but ACF's the_field() does not escape. When you echo values yourself, run them through esc_html() or esc_url().
What if I want to display a value that contains HTML?
For values that intentionally contain HTML, like rich text, use wp_kses_post(). It keeps only the allowed tags and strips dangerous scripts.
What is a nonce for?
It's a single-use token that confirms a form submission really came from a screen on your own site. In admin save routines, always combine nonce verification with a capability check (current_user_can).