How to escape outputs with esc_html, esc_attr and esc_url in WordPress

Contents

Why escaping matters in WordPress

Escaping output is the last and critical line of defense against cross-site scripting (XSS) and other injection attacks. Any time you print user-supplied data, data from the database, or values that may contain untrusted content into HTML, attributes or URLs, you must escape them for the exact context in which they will appear.

Sanitization vs Escaping — the difference

Sanitization is cleaning data on input or before storing it (for example with sanitize_text_field() or esc_url_raw() before saving URLs). Escaping is encoding the data on output for the specific context (for example with esc_html(), esc_attr(), esc_url()). Both are necessary: sanitize when receiving/storing, escape when printing.

Context-specific escaping

Never use a generic escape for all contexts. Use the function that matches the output context:

  • Between HTML elements (text/node content): use esc_html().
  • Inside HTML attribute values (quoted): use esc_attr().
  • For URLs (href, src): use esc_url() (and esc_url_raw() for saving).

esc_html()

Use esc_html() to escape data when you output it between tags — for example inside an elements body. esc_html() converts HTML special characters to HTML entities so any tags or script wont be executed by the browser.

When to use

  • Post titles and any plain text shown as content (not allowing HTML).
  • Widget or option values printed in template content.
  • Any dynamic text node between tags.

Examples (correct usage)

lt?php
// Echo a post title safely inside H2
echo lth2gt . esc_html( get_the_title() ) . lt/h2gt

// Output saved option
intro = get_option( site_intro,  )
echo ltpgt . esc_html( intro ) . lt/pgt 
?gt

Common mistakes

  • Using esc_attr() instead of esc_html() for content between tags may not escape everything needed for textual context (escape attributes with esc_attr()).
  • Escaping too early (escaping before filtering or translation) can be problematic escape right before output.

esc_attr()

Use esc_attr() for values that will be placed inside HTML attributes (for example title, alt, value, class, data-, id, style attributes). esc_attr() escapes quotes and other characters that could break an attribute value and lead to injection.

When to use

  • Any attribute value inside quotes: title=quot…quot, class=quot…quot, data-=quot…quot.
  • Value attributes in form inputs, alt attributes for images, IDs, CSS classes.

Examples

lt?php
// Safe attribute use in a link
title = get_post_meta( post->ID, link_title, true )
url   = get_post_meta( post->ID, link_url, true )

echo lta href= . esc_url( url ) .  title= . esc_attr( title ) . gt
     . esc_html( title )  // body text separately escaped
     . lt/agt

// Data attribute with JSON-encoded structured data
data = array( id =gt 123, slug =gt my-post )
echo ltdiv data-info= . esc_attr( wp_json_encode( data ) ) . gtInfolt/divgt
?gt

Notes

  • Always escape class names or IDs with esc_attr() when they are dynamic.
  • If a value is a list of multiple URLs (e.g. srcset), use an appropriate sanitization and then esc_attr() for the entire attribute value because it isnt a single URL.

esc_url()

Use esc_url() for URLs you output into href, src, action, or similar attributes. It removes potentially dangerous protocols and encodes characters so the URL is safe for HTML output. For storing a URL in the database, use esc_url_raw() instead.

Behavior and caveats

  • esc_url() strips or neutralizes disallowed protocols (javascript:, vbscript:, data: — though data: may be allowed with caution).
  • If the URL is invalid or unsafe, esc_url() often returns an empty string. Always handle the possibility of an empty return before printing into an href or src, or provide a safe fallback.
  • esc_url() is intended for display output esc_url_raw() is intended for saving input.

Examples

lt?php
// Safe link output
url = get_post_meta( post->ID, external_link, true )
if ( url ) {
    echo lta href= . esc_url( url ) .  target=_blank rel=noopenergtVisitlt/agt
}

// Using esc_url() with printf and placeholders
label = Read more
printf(
    lta href=%s title=%sgt%slt/agt,
    esc_url( url ),        // URL escaped for href
    esc_attr( label ),     // title attribute
    esc_html( label )      // anchor text
)
?gt

Putting the three together — practical templates

When building markup with multiple dynamic parts, escape each part with the function appropriate for its context. Never assemble a single escaped string and reuse it in different contexts.

Correct example: link with title and body text

lt?php
title = get_post_meta( post->ID, link_title, true )
url   = get_post_meta( post->ID, link_url, true )
label = get_post_meta( post->ID, link_label, true )

echo lta href= . esc_url( url ) .  title= . esc_attr( title ) . gt
   . esc_html( label )
   . lt/agt
?gt

Using printf safely

lt?php
// Use placeholders and escape each placeholder value appropriately
printf(
    lta href=%s title=%sgt%slt/agt,
    esc_url( url ),
    esc_attr( title ),
    esc_html( label )
)
?gt

Escaping with Translations (i18n)

When strings are translatable, escape after translation. WordPress provides helper functions that combine translation and escaping like esc_html__(), esc_html_e(), esc_attr__(), esc_attr_e(). For strings with placeholders, translate first, then escape dynamic values and/or use translation wrappers correctly.

Examples

lt?php
// Simple translated and escaped echo
esc_html_e( Hello world, my-text-domain )

// Translated string with placeholder — escape dynamic part separately
name = get_user_meta( user_id, display_name, true )
printf(
    esc_html__( Welcome, %s, my-text-domain ),
    esc_html( name )
)

// If translation includes HTML or you need to allow a limited set of tags, use wp_kses_post() or wp_kses() on the translated string or placeholders.
?gt

Common pitfalls and best practices

  1. Do not escape twice. Escaping twice can lead to broken output (for example, ampamp instead of amp). Escape once at the point of output.
  2. Escape late. Store raw (or properly sanitized) values and escape right before rendering to HTML. This preserves flexibility and prevents double-escaping.
  3. Match the escape to the context. Use esc_html() for element content, esc_attr() for quoted attributes, esc_url() for URLs.
  4. Provide fallbacks. esc_url() can return an empty string. Check and provide a safe fallback if needed.
  5. Sanitize on save, escape on output. For example: sanitize_text_field() when saving form data esc_html() when printing it back in the template.
  6. When allowing limited HTML use wp_kses() or wp_kses_post() to whitelist allowed tags rather than using esc_html() which strips all tags.
  7. Be careful with attr vs HTML contexts. Quotes and certain characters are treated differently — always use the correct function for that spot.

Quick checklist for template authors

  • Text between tags: esc_html()
  • Attribute values in quotes: esc_attr()
  • URLs in href/src: esc_url()
  • Save URLs: esc_url_raw()
  • Allowing limited HTML: wp_kses() / wp_kses_post()
  • Translatable strings: escape after translation (esc_html__ / esc_attr__ etc.)

Advanced notes and edge cases

  • For JavaScript contexts, use wp_json_encode() and esc_js() where appropriate for localized script data prefer wp_localize_script() or wp_add_inline_script() with safe encoding.
  • For CSS values inserted into a style attribute, escape with esc_attr() and validate values (e.g. whitelist allowed CSS fragments) because CSS injection is possible.
  • For srcset attributes (comma-separated URLs), sanitize each URL if possible, then use esc_attr() on the final srcset string.
  • When outputting HTML produced by built-in WordPress functions that already escape their output, review documentation to avoid double-escaping. If unsure, inspect the function source.

Examples: wrong vs right

Wrong:

lt?php
// Unsafe: prints raw user input directly into attribute and HTML
echo lta href= . user_url .  title= . user_title . gt . user_title . lt/agt
?gt

Right:

lt?php
// Safe: escape each value for its context
echo lta href= . esc_url( user_url ) .  title= . esc_attr( user_title ) . gt
   . esc_html( user_title )
   . lt/agt
?gt

Testing and verification

  • Use automated tests and code reviews to ensure escaping occurs at output points.
  • Use browser developer tools to inspect rendered HTML to confirm characters are entity-encoded where expected.
  • Fuzz or try example malicious strings (e.g. ltscriptgtalert(xss)lt/scriptgt or javascript:alert(1)) to verify they are neutralized.

Summary

Follow the core rule: escape for the exact context in which the value will be used. Use esc_html() for visible text, esc_attr() for attributes, and esc_url() for URLs. Sanitize input on save, escape at output, and when allowing HTML use whitelisting (wp_kses). Doing so consistently prevents XSS and keeps your themes and plugins secure.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

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