386

I want to display text to HTML by a JavaScript function. How can I escape HTML special characters in JavaScript? Is there an API?

5
  • 22
    This is not a duplicate, since this question does not asks about jQuery. I am interested only in this one, since I do not use jQuery... Commented Aug 7, 2013 at 16:08
  • 7
    possible duplicate of HtmlSpecialChars equivalent in Javascript? Commented Aug 7, 2013 at 16:19
  • 1
    Note that the browsers are working on a new HTML Sanitizer API. Commented Jan 26, 2022 at 20:05
  • @Flimm The link you provided is broken now. Commented Feb 14 at 16:29
  • @AdrianWiik It looks like the HTML Sanitizer API was deprecated: developer.chrome.com/blog/sanitizer-api-deprecation Commented Feb 16 at 14:20

17 Answers 17

557

Here's a solution that will work in practically every web browser:

function escapeHtml(unsafe) {
  return unsafe
    .replace(/&/g, "&")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

If you only support modern web browsers (2020+), then you can use the new replaceAll function:

const escapeHtml = unsafe => {
  return unsafe
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
};
Sign up to request clarification or add additional context in comments.

18 Comments

Why "&#039;" and not "&apos;" ?
I think regular expressions in replace() calls are unnecessary. Plain old single-character strings would do just as well.
is there any standard API or this is the only way?
|
83

function escapeHtml(html){
  var text = document.createTextNode(html);
  var p = document.createElement('p');
  p.appendChild(text);
  return p.innerHTML;
}

// Escape while typing & print result
document.querySelector('input').addEventListener('input', e => {
  console.clear();
  console.log( escapeHtml(e.target.value) );
});
<input style='width:90%; padding:6px;' placeholder='&lt;b&gt;cool&lt;/b&gt;'>

2 Comments

Working Here but Not working for me offline in browser
Note that this doesn't escape quotes (" or ') so strings from this function can still do damage if they are used in HTML tag attributes.
59

You can use jQuery's .text() function.

For example:

http://jsfiddle.net/9H6Ch/

From the jQuery documentation regarding the .text() function:

We need to be aware that this method escapes the string provided as necessary so that it will render correctly in HTML. To do so, it calls the DOM method .createTextNode(), does not interpret the string as HTML.

Previous Versions of the jQuery Documentation worded it this way (emphasis added):

We need to be aware that this method escapes the string provided as necessary so that it will render correctly in HTML. To do so, it calls the DOM method .createTextNode(), which replaces special characters with their HTML entity equivalents (such as &lt; for <).

2 Comments

You can even use it on a fresh element if you just want to convert like this: const str = "foo<>'\"&"; $('<div>').text(str).html() yields foo&lt;&gt;'"&amp;
Note that this leaves quotes ' and " unescaped, which may trip you up
58

Using Lodash:

_.escape('fred, barney, & pebbles');
// => 'fred, barney, &amp; pebbles'

Source code

3 Comments

what is the opposite of this? name of the function that does the opposite of this?
Doesn't seem to work for IP addresses when you try _.escape(192.168.1.1), but if I add quotes, then it works: _.escape('52.60.62.147') even though I'm referencing a variable where the value is not a string. LoDash is so great!
52

This is, by far, the fastest way I have seen it done. Plus, it does it all without adding, removing, or changing elements on the page.

function escapeHTML(unsafeText) {
    let div = document.createElement('div');
    div.innerText = unsafeText;
    return div.innerHTML;

⚠️ Warning: it does not escape quotes so you can't use the output inside attribute values in HTML code. E.g. var divCode = '<div data-title="' + escapeHTML('Jerry "Bull" Winston') + '">Div content</div>' will yield invalid HTML!

5 Comments

Warning: it does not escape quotes so you can't use the output inside attribute values in HTML code. E.g. var divCode = '<div data-title="' + escapeHTML('Jerry "Bull" Winston') + '">Div content</div>' will yield invalid HTML!
Using div.textContent instead of div.innerText would probably be more idiomatic.
Just wondering, would repeatedly calling this eventually leave document full of extra div elements? Or does it get garbage collected?
@Magnus The div isn't attached to the DOM, so it will eventually be garbage collected. So no, this will not fill the document with useless elements.
Using div.innerText = unsafeText causes the function to return all line-breaks (\n) as <br>. This unwanted side-effect can be avoided by using div.textContent = unsafeText instead. Would be great, if the code in the answer could be updated to reflect this.
51

I think I found the proper way to do it...

// Create a DOM Text node:
var text_node = document.createTextNode(unescaped_text);

// Get the HTML element where you want to insert the text into:
var elem = document.getElementById('msg_span');

// Optional: clear its old contents
//elem.innerHTML = '';

// Append the text node into it:
elem.appendChild(text_node);

4 Comments

I learnt something new about HTML today. w3schools.com/jsref/met_document_createtextnode.asp.
Be aware that the content of the text node is not escaped if you try to access it like this: document.createTextNode("<script>alert('Attack!')</script>").textContent
This is the correct way if all you're doing is setting text. That's also textContent but apparently it's not well supported. This won't work however if you're building up a string with some parts text some html, then you need to still escape.
I really like this, because it's using the DOM properly. It feels less "hacky" than most of the other options.
25

By the books

When editing HTML content between <tags>, use "HTML Entity Encoding":

For for editing the HTML content between an opening and closing tag using JavaScript, OWASP recommends you to "look at the .textContent attribute. It is a Safe Sink and will automatically HTML Entity Encode."

When editing HTML attributes use recommended "HTML Attribute Encoding":

Previously I offered the function below to do the encoding yourself, but there are "Safe Sinks" for HTML Attribute Encoding as well. For example, the second argument of the setAttribute function is allowed to be "dangerous" because it will be encoded automatically. Please refer to this page for more safe places to put potentially dangerous content: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#safe-sinks

Manual method for HTML Attributes:

It's better to not do it yourself, but if you so desire, OWASP recommends that "[e]xcept for alphanumeric characters, [you should] escape all characters with ASCII values less than 256 with the &#xHH; format (or a named entity if available) to prevent switching out of [an] attribute."

So here's a function that does that, with a usage example:

function escapeHTML(unsafe) {
  return unsafe.replace(
    /[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u00FF]/g,
    c => '&#' + ('000' + c.charCodeAt(0)).slice(-4) + ';'
  )
}

document.querySelector('div').innerHTML =
  '<span class=' +
  escapeHTML('"fakeclass" onclick="alert("test")') +
  '>' +
  escapeHTML('<script>alert("inspect the attributes")\u003C/script>') +
  '</span>'
<div></div>

You should verify the entity ranges I have provided to validate the safety of the function yourself. You could also use this regular expression which has better readability and should cover the same character codes, but is about 10% less performant in my browser:

/(?![0-9A-Za-z])[\u0000-\u00FF]/g

Comments

22

It was interesting to find a better solution:

var escapeHTML = function(unsafe) {
  return unsafe.replace(/[&<"']/g, function(m) {
    switch (m) {
      case '&':
        return '&amp;';
      case '<':
        return '&lt;';
      case '"':
        return '&quot;';
      default:
        return '&#039;';
    }
  });
};

I do not parse > because it does not break XML/HTML code in the result.

Here are the benchmarks: http://jsperf.com/regexpairs Also, I created a universal escape function: http://jsperf.com/regexpairs2

7 Comments

It's interesting to see that using the switch is significantly faster than the map. I didn't expect this! Thanks for sharing!
There are many many more unicode characters than you could possible code & take into account. I wouldn't recommend this manual method at all.
Why would you escape multi-byte characters at all? Just use UTF-8 everywhere.
Skipping > can potentially break code. You must keep in mind that inside the <> is also html. In that case skipping > will break. If you're only escaping for between tags then you probably only need escape < and &.
It can be simplified to unsafe.replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)}`)
|
18

The most concise and performant way to display unencoded text is to use textContent property.

Faster than using innerHTML. And that's without taking into account escaping overhead.

document.body.textContent = 'a <b> c </b>';

Comments

8

DOM Elements support converting text to HTML by assigning to innerText. innerText is not a function but assigning to it works as if the text were escaped.

document.querySelectorAll('#id')[0].innerText = 'unsafe " String >><>';

2 Comments

At least in Chrome assigning multiline text adds <br> elements in place of newlines, that can break certain elements, like styles or scripts. The createTextNode is not prone to this problem.
innerText has some legacy/spec issues. Better to use textContent.
6

You can encode every character in your string:

function encode(e){return e.replace(/[^]/g,function(e){return"&#"+e.charCodeAt(0)+";"})}

Or just target the main characters to worry about (&, inebreaks, <, >, " and ') like:

function encode(r){
return r.replace(/[\x26\x0A\<>'"]/g,function(r){return"&#"+r.charCodeAt(0)+";"})
}

test.value=encode('How to encode\nonly html tags &<>\'" nice & fast!');

/*************
* \x26 is &ampersand (it has to be first),
* \x0A is newline,
*************/
<textarea id=test rows="9" cols="55">&#119;&#119;&#119;&#46;&#87;&#72;&#65;&#75;&#46;&#99;&#111;&#109;</textarea>

1 Comment

Writing your own escape function is generally a bad idea. Other answers are better in this regard.
4

A universal one-liner, working in browsers and Node.js:

const html = unsafe.replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`)

1 Comment

Small remark to the code above, the html entity should be closed with a semicolon, so it should be &#${c.charCodeAt(0)};
3

If you already use modules in your application, you can use escape-html module.

import escapeHtml from 'escape-html';
const unsafeString = '<script>alert("XSS");</script>';
const safeString = escapeHtml(unsafeString);

Comments

1

For a quick one-liner, the following works:

const escaped = new Option(unescaped).innerHTML;

For example:

const unescaped = "<h1>Header</h1>";
const escaped = new Option(unescaped).innerHTML; // "&lt;h1&gt;Header&lt;/h1&gt;"

2 Comments

This doesn't escape double quotes and single quotes, which makes it unsafe for use within HTML attributes.
@Flimm While good to be aware of, nothing in the way the question is worded makes me think the question asker was intending to use this unescaped HTML within an attribute. If someone does intend to use it in that manner, you're right that further encoding is needed, since attributes have different restrictions.
0

I came across this issue when building a DOM structure. This question helped me solve it. I wanted to use a double chevron as a path separator, but appending a new text node directly resulted in the escaped character code showing, rather than the character itself:

var _div = document.createElement('div');
var _separator = document.createTextNode('&raquo;');
//_div.appendChild(_separator); /* This resulted in '&raquo;' being displayed */
_div.innerHTML = _separator.textContent; /* This was key */

Comments

-1

Just write the code in between <pre><code class="html-escape">....</code></pre>. Make sure you add the class name in the code tag. It will escape all the HTML snippet written in
<pre><code class="html-escape">....</code></pre>.

const escape = {
    '"': '&quot;',
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
}
const codeWrappers = document.querySelectorAll('.html-escape')
if (codeWrappers.length > 0) {
    codeWrappers.forEach(code => {
        const htmlCode = code.innerHTML
        const escapeString = htmlCode.replace(/"|&|<|>/g, function (matched) {
            return escape[matched];
        });
        code.innerHTML = escapeString
    })
}
<pre>
    <code class="language-html html-escape">
        <div class="card">
            <div class="card-header-img" style="background-image: url('/assets/card-sample.png');"></div>
            <div class="card-body">
                <p class="card-title">Card Title</p>
                <p class="card-subtitle">Srcondary text</p>
                <p class="card-text">Greyhound divisively hello coldly wonderfully marginally far upon
                    excluding.</p>
                <button class="btn">Go to </button>
                <button class="btn btn-outline">Go to </button>
            </div>
        </div>
    </code>
</pre>

Comments

-1

I think you should change the way to do it, don't try to escape HTML to use innerHTML after, it is wrong. You should create an element with createElement and use innerText to add an insecure input, and then use appendChild, prependChild, inserAfter or inserBefore.

Solution for Vanilla JavaScript in a DOM environment

Instead of:

// vulnerable
const html = "<b>Hello World!</b>"
const element = `<div>${html}</div>`

document.body.innerHTML = element 

You should do:

// secure
const html = '<b>Hello World!</b>'
const element = document.createElement('div')
element.innerText = html 

document.body.appendChild(element)

⚠️ Warning Never do document.body.innerHTML = element.innerText

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.