HTML encoder

Escape special characters for HTML documents or decode HTML entities back to plain text.

Docify's HTML encoder escapes the five markup-significant characters in your browser: & → &amp;, < → &lt;, > → &gt;, " → &quot;, and apostrophe → &#39;. The decoder reverses these plus numeric character references like &#60; (decimal) or &#x3C; (hex). This is not URL encoding, not a sanitizer, and not the full HTML named-entity table. Nothing is uploaded.

Plain text
Encoded output

How it works

  1. Encoding replaces markup characters with their entity equivalents. Ampersand is replaced first to prevent double-encoding (e.g., < would become &lt; if ampersand came second).
  2. Decoding matches the five named entities plus numeric character references using a regular expression. The hex prefix must be lowercase x (not X), and all forms require a trailing semicolon.
  3. Numeric references use String.fromCodePoint() and accept Unicode scalars from 1 to 0x10FFFF. Surrogate code points (0xD800–0xDFFF) and out-of-range values stay as written.
  4. This is not a sanitizer. It does not prevent XSS or validate HTML structure. Use proper sanitization libraries for user-generated content.

Character reference table

CharacterNamed entityDecimalHex
&&amp;&#38;&#x26;
<&lt;&#60;&#x3C;
>&gt;&#62;&#x3E;
"&quot;&#34;&#x22;
'&apos; or &#39;&#39;&#x27;

Named vs Numeric HTML Entities

This page encodes the five markup-significant characters with a mix of named and numeric character references, then decodes those five lowercase names plus decimal and hex numeric refs. A named reference is a mnemonic (&lt;). A numeric one is the Unicode code point: decimal &#60; or hex &#x3C;. Ampersand is replaced first so a later pass cannot double-escape an entity. This is not URL percent-encoding and not a sanitizer. For the longer comparison, see Named vs Numeric HTML Entities: Names vs Code Points.

AspectNamedNumeric
SpecHTML named character references; this page recognizes only amp, lt, gt, quot, and aposDecimal &#N; or hex &#xN; (WHATWG / HTML numeric character references)
EncodeWrites &amp;, &lt;, &gt;, and &quot;Apostrophe is &#39; (HTML 4.01 never defined &apos;)
Sample sentencePrice: 5 < 10 & 10 > 5, "sale" isn't on. encodes as Price: 5 &lt; 10 &amp; 10 &gt; 5, &quot;sale&quot; isn&#39;t on.
DecodeExpands those five lowercase names; &nbsp;, &copy;, &eacute;, &AMP;, and &Lt; stay written&#60; and &#x3C; become <; hex digits may be mixed case, but the x prefix must be lowercase — &#X3C; stays written
Semicolon&lt without a trailing semicolon stays &lt&#60 without a semicolon stays &#60
Unicode / rangeEncode leaves non-ASCII such as é (U+00E9) unchanged; there is no full HTML5 named-entity tableScalars 1–0x10FFFF except surrogates; &#xE9; is NFC é; e&#x301; is NFD e + U+0301; &#0; and &#xD800; stay written

Decode of &#x3c; (lowercase hex digits) is the same < as &#x3C;. This page is not encodeURIComponent and not a WHATWG HTML parser — browsers may expand more names and accept a missing semicolon. Percent-encoding belongs on URL encoder; composing NFD e + U+0301 to NFC é belongs on Unicode normalizer.

HTML entities in Python and Node

The snippets below escape the five markup-significant characters for HTML text — the same encode path this page uses. They are not URL percent-encoding, not a sanitizer, and not the full HTML named-entity table. For names versus code points, see Named vs Numeric HTML Entities: Names vs Code Points.

Node.js with the same replacements

Node has no built-in HTML encoder. The replacements below match this page: ampersand first, then angle brackets, then quotes, with apostrophe as &#39; (not &apos;). Decode named entities before &amp; so a once-encoded &amp;lt; stays &lt;:

function encodeHtml(text) {
  return text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

const encoded = encodeHtml('Price: 5 < 10 & 10 > 5, "sale" isn\'t on.');
// Price: 5 &lt; 10 &amp; 10 &gt; 5, &quot;sale&quot; isn&#39;t on.

const decoded = encoded
  .replace(/&lt;/g, '<')
  .replace(/&gt;/g, '>')
  .replace(/&quot;/g, '"')
  .replace(/&#39;|&apos;/g, "'")
  .replace(/&amp;/g, '&');
// Price: 5 < 10 & 10 > 5, "sale" isn't on.

Python with html.escape

CPython html.escape with quote=True writes the same four named entities as this page, but apostrophe becomes &#x27; instead of &#39;. This page decodes both numeric forms. quote=False leaves quotes and apostrophes unchanged. html.unescape expands the full HTML5 named-entity table (&nbsp;, &copy;); this page only expands amp, lt, gt, quot, apos, plus decimal and hex numeric references:

import html

encoded = html.escape('Price: 5 < 10 & 10 > 5, "sale" isn\'t on.', quote=True)
# Price: 5 &lt; 10 &amp; 10 &gt; 5, &quot;sale&quot; isn&#x27;t on.

decoded = html.unescape(encoded)
# Price: 5 < 10 & 10 > 5, "sale" isn't on.

Non-ASCII letters such as é stay unchanged in Node, CPython escape, and this page. Numeric references like &#60; and &#x3C; still need this page's decimal/hex path, or Python unescape — the five-name replace above does not expand them. Use a sanitizer, not these snippets, for untrusted HTML.

HTML entities in the shell

There is no POSIX HTML encoder. jq 1.7 @html writes the same four named entities this page uses for ampersand, angle brackets, and quotes (&amp;, &lt;, &gt;, &quot;). Apostrophe becomes &apos; in jq; this page writes &#39;; CPython html.escape writes &#x27;. Decode on this page accepts all three. jq is not POSIX.

jq @html

Pass -R so the input is a string — without it, jq 1.7 errors jq: parse error: Invalid numeric literal at line 1, column 6 on this sentence. -r writes the encoded string without extra quotes (without -r the same command writes a JSON string). -s slurps a trailing newline as a literal newline — @html does not turn that newline into an entity. Use printf '%s' or jq -n --arg, not echo with -s:

jq -nr --arg s "Price: 5 < 10 & 10 > 5, \"sale\" isn't on." '$s|@html'
# Price: 5 &lt; 10 &amp; 10 &gt; 5, &quot;sale&quot; isn&apos;t on.

printf '%s' 'hello < world' | jq -Rr @html
# hello &lt; world

printf '%s' 'hello < world' | jq @html
# jq: parse error: Invalid numeric literal at line 1, column 6

printf '%s' 'hello < world' | jq -R @html
# "hello &lt; world"

printf '%s' 'café' | jq -Rr @html
# café

printf '%s' "'" | jq -Rr @html
# &apos;

Already-encoded text

Ampersand is replaced first, so already-encoded &lt; becomes &amp;lt; — the same double-encoding this page's Encode writes. jq 1.7 has no @htmld. Decode with this page's Decode mode, the Node replacements above, or Python html.unescape (that last one also expands &nbsp; and the rest of the HTML5 named table; this page does not):

printf '%s' '&lt; &amp; &gt;' | jq -Rr @html
# &amp;lt; &amp;amp; &amp;gt;

printf '%s' '&lt;' | jq -Rr @htmld
# jq: error (at <stdin>:0): htmld is not a valid format

jq -nr '{"a":1} | @html'
# {&quot;a&quot;:1}

@uri percent-encodes UTF-8 bytes; that belongs on the URL encoder, not here. @text leaves < and & unchanged. Tab and newline stay as control characters in jq and on this page — they are not numeric references. Use a sanitizer, not these snippets, for untrusted HTML.

FAQ

Does this tool upload my text?
No. Encode, decode, and copy run entirely in your browser using JavaScript string replacement. The page never sends your text to a server.
Which characters does Encode escape?
Only the five markup-significant characters: & becomes &amp;, < becomes &lt;, > becomes &gt;, " becomes &quot;, and the apostrophe becomes &#39;. Ampersand is replaced first to prevent double-encoding. Non-ASCII characters like é stay unchanged.
Why is the apostrophe &#39; instead of &apos;?
&apos; was not defined in HTML 4.01 (only in XML and HTML5). The numeric reference &#39; is universally supported across all HTML and XML parsers. Decode accepts both forms.
What named entities does Decode support?
Decode expands only the five core entities: &amp;, &lt;, &gt;, &quot;, and &apos;. It also decodes decimal (&#60;) and hex (&#x3C;) numeric character references. Other named entities like &nbsp; or &copy; stay as written.
Does the trailing semicolon matter?
Yes. This tool requires a trailing semicolon for all entities. &lt without a semicolon stays as &lt, and &#60 without a semicolon stays as &#60. A WHATWG HTML parser is more lenient, but this tool follows strict parsing rules.
Is this the same as URL encoding?
No. HTML encoding escapes markup characters for safe display in HTML documents. URL encoding (percent-encoding) escapes characters for URLs using %XX notation. Use our URL Encoder tool for URL encoding.

Related