Because of security reasons we are of course required to use esc_html__() for WP development. This is annoying because if you'd want to pass a single quote into your strings, you'd have to use sprintf() to make it work. Otherwise you just get ' printed out, instead of a '.

Without using sprintf():

esc_html__( 'Wasn\'t your favorite color red?', 'domain' );
// Output: Wasn't your favorite color red?
esc_html__( 'Provided reason isn\'t selected', 'domain' );
// Output: Provided reason isn't selected

With the use of sprintf() I can get single quotes to work (of course).

sprintf( esc_html__( 'Wasn%st your favorite color red?', 'domain' ), '\'' );
// Output: Wasn't your favorite color red?
sprintf( esc_html__( 'Provided reason isn%st selected', 'domain' ), '\'' );
// Output: Provided reason isn't selected

I'd like to know if there is a different way to achieve the same output. I am afraid there is none, but I thought why not give it a shot, who knows.

  • 1
    That is exactly how it is supposed to work. The esc_html is going to escape the single quotes to ', and in HTML it will show it correctly. What you are doing is escaping the HTML first, without single quotes, and then the sprintf replaces the $s for the \', so you are not preparing that single quote to be printed in HTML
    – Raba
    Jan 19, 2020 at 20:42
  • Yes I know it is supposed to do that. The problem was that it wasn't displaying correctly in HTML, but this was apparently due to the fact that I parsed it into JS via wp_localize_script(). And then into the DOM, which causes it to not be converted back into a straight quote. A way around it is to use smart/curly quote. As explained in the Accepted Answer. Thanks for your help, much appreciated! Jan 20, 2020 at 21:34

2 Answers 2


Hmmm, a different way to output you say?

<?php if ( 'en_US' === get_locale() ) : ?>
    Wasn't your favorite color red?
<?php endif; ?>


But seriously, I mean anything can be done if you want to do it. If you look at esc_html__ - you will see that it's just calling esc_html, which is filterable after the safe text has been determined by _wp_specialchars. Looking at _wp_specialchars you can see that it's simply doing a string replace on single quotes to the encoded entity. If you want to undo what it has done, just do the inverse logic to replace the entity with a single quote. Something like this would work to achieve what you want:

add_filter( 'esc_html', function( $safe_text ) {
    return str_replace( '&#039;', "'", $safe_text );

The real question is should you do it? Some people often refer to ' and " as dumb quotes. Ideally these should be used in code, whereas and are sometimes referred to as smart quotes, fancy quotes, or typographic quotes. These are commonly thought as being displayable quotes, or quotes that belong to your content. They are meant to look prettier, and more accurately display the opening and closing of quoted text you want to display to a user/visitor. As a general rule of thumb, if you're displaying the text for someone to read - it's usually better to use smart quotes - which in part is why _wp_special_chars does that conversion. This is also done in the wptexturize filter throughout WordPress for displayed content - things like content, comments, nav_menu titles, link_name, term_description, bloginfo values, and widget_titles to name just a few places.

Obviously there isn't a huge amount of risk involved by using dumb quotes for HTML output - but there are some scenarios where it could cause an issue if something was poorly written/overlooked. By filtering that value, you would need to be more responsible than the snippet I provided, and do something to ensure you're only filtering values that you are adding via plugin/theme code to ensure that you don't cause open vectors from someone else's code. Ideally you'd want to check that the text being filtered belongs to your textdomain before tampering. This is another reason why it's not a good idea to change the filtered value here as it's extremely uncommon to use this filter - and VERY common for plugins and themes to have code that is ran through it.

So should you do it? No, but if you have an absolutely necessary need for this behavior - sure there's ways to make it happen, even your example shows a way to annoyingly work around it. I've even seen people convert it back via JS when the page is loaded, which seems a bit drastic. In modern times though, it's not being done so much for the security aspect, but more for the display value it provides to people reading content. Just don't be silly and think that allowing it in user input forms, or doing the same technique esc_attr is a good idea though.

  • Thanks, this is what I was looking for. I just have to use the smart/curly quotes in my situation. I am using wp_localize_script() and then storing the strings that are required to be translated. I noticed that the straight quote actually displays correctly when I call esc_html__() directly in my HTML. So basically the javascript has access to the string, which contains &#039; then I put it into the DOM and it stays like regular text. So it is kind of a combination of using the above two functions I guess. I will stick to the smart/curly quotes. Thanks for the detailed answer! Jan 20, 2020 at 21:31

The source code for esc_html() uses $safe_text = _wp_specialchars($safe_text, ENT_QUOTES); to handle quotes. The description for the second parameter of the _wp_specialchars() function states:

Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Converts single and double quotes, as well as converting HTML named entities (that are not also XML named entities) to their code points if set to ENT_XML1.

With that said, you can hook into the filter to change the quote type like this:

 * Filters a string cleaned and escaped for output in HTML.
 * Text passed to esc_html() is stripped of invalid or special characters
 * before output.
 * @param string $safe_text The text after it has been escaped.
 * @param string $text The text prior to being escaped.
 * @return string The text after it has been escaped.
function custom_esc_html($safe_text, $text) {
    $safe_text = wp_check_invalid_utf8($text);
    return _wp_specialchars($safe_text, ENT_COMPAT); // Only convert double quotes
add_filter('esc_html', 'custom_esc_html', 20, 2);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.