Online HTML Editor

Write or paste HTML, see it rendered, and take it back tidied and sanitised against the allow-list WordPress itself uses.

Enable JavaScript to clean your own HTML; the result below is the example.

Nothing is uploaded. The parsing, the cleaning and the preview all happen in this tab.

Indent with

Dropped 1 <script>. Unwrapped <x-widget>, keeping the text inside. Removed 1 inline event handler. Removed 1 link or source with a scheme this does not allow.

A pasted card

Some bold text, a refused link and a real one.

  • One
  • Two
Words inside an element nothing renders.
  whitespace   here
  is content  
content.html
<div class="card">
	<h2 style="color:#1E56EC">A pasted card</h2>
	<p>
		Some <b>bold</b> text, a <a>refused link</a> and a <a href="/tools/" target="_blank" rel="noopener">real one</a>.
	</p>
	<ul>
		<li>One</li>
		<li>Two</li>
	</ul>
	Words inside an element nothing renders.
	<pre>  whitespace   here
  is content  </pre>
</div>

Write HTML on the left, watch it render on the right, take it back indented and cleaned. The cleaning is the useful part: pasted HTML arrives with inline event handlers, an editor’s leftover attributes, a <script> nobody wanted and a <div> wrapped around nothing.

The allow-list here is close to the one WordPress applies with wp_kses_post, so what comes out is roughly what would survive being saved into a post anyway. The difference is that you see what was removed.

How to use

  1. Type or paste. The preview and the output keep up.
  2. Leave Sanitise on. It holds the output to the allow-list and reports what it took out.
  3. Turn Indent off when the HTML is going into a template and you want one line.
  4. Copy the result.

The preview is always sanitised, whatever the checkbox says. It is rendered in this page, so a <script> in it would be a script in this page.

Example

This goes in:

<div class="card" onclick="track()">
<h2 STYLE="color:#1E56EC">A pasted card</h2>
<p>Some <B>bold</B> text and a <a href="javascript:alert(1)">refused link</a>.</p>
<ul><li>One<li>Two
</ul>
<script>track();</script>
<x-widget>Words inside an element nothing renders.</x-widget>
</div>

and this comes out:

<div class="card">
	<h2 style="color:#1E56EC">A pasted card</h2>
	<p>Some <b>bold</b> text and a <a>refused link</a>.</p>
	<ul>
		<li>One</li>
		<li>Two</li>
	</ul>
	Words inside an element nothing renders.
</div>

with the report:

Dropped 1 <script>. Unwrapped <x-widget>, keeping the text inside.
Removed 1 inline event handler. Removed 1 link or source with a scheme this does not allow.

Four different treatments in one paste, and each one is a decision:

  • <script> goes with its contents. Keeping the contents would put its source code in the page as text.
  • <x-widget> is unwrapped, not dropped. It is not dangerous, it is just not something a browser renders, and the words inside it are the content.
  • onclick goes; class stays. The style stays too, because it only names a colour.
  • The javascript: href goes and the link stays, so you can see where it was.

The unclosed <li> elements are closed and the tag names are lower-cased, because the output is written from a parsed tree rather than patched as text.

Pitfalls

Sanitising off does not turn off the preview’s sanitising. The output panel will give you your <script> back; the preview never renders it. That is not a limitation, it is the only safe way to render a stranger’s HTML in a page.

Whitespace is sometimes content. Inside <pre> and <textarea> it is left exactly as written. Between inline elements a line break is a word gap, so <b>a</b> <i>b</i> is never broken across lines. Everything else is free to be indented.

An <iframe> is dropped, including a video embed. The allow-list does not carry it, because an iframe is a page inside your page. If you need one, paste the output into a context that allows it, or use the WordPress embed block.

A style attribute is kept if it is only styling. One that names expression() or a javascript: URL is dropped whole rather than patched, since a repaired declaration says nothing useful.

This is not a WYSIWYG editor. There are no toolbars: you write the HTML. For writing prose, the markdown editor on this site is the better tool, and it produces HTML too.

Compatibility

The parser handles what pasted markup actually contains: unclosed <li> and <p>, a closing tag nobody opened, uppercase tag names, and a < inside a <script> that is not a tag. It is not a conformant HTML5 parser and does not try to be.

The allow-list covers the elements and attributes wp_kses_post allows, plus aria-* and data-*, minus the ones that only make sense in a form. That is a deliberate subset: it is what content in a WordPress post can contain.

The output is HTML5 with void elements self-closed (<br />), which is valid in both HTML and XHTML. Attribute values are always quoted, including empty ones.

Rendering happens in the browser. The page also renders the example on the server with the same rules, so it reads correctly without JavaScript.

Frequently asked questions

Is this the same as running wp_kses_post?
Close, not identical. The list here is a hand-written subset of the same idea, and it drops a few things kses allows in some contexts. If the goal is “will WordPress keep this”, the answer this gives is conservative: what survives here survives kses.
Why was my <div> kept but my <section> unwrapped?
It should not have been: both are on the list. If an element was unwrapped, it was not one of the ones listed, and the report names it. Custom elements, <font>, <center> and <marquee> are the usual ones.
Can I keep the indentation I already have?
Not exactly. The output is rewritten from the tree, so the indentation is the tool’s. Turn Indent off if you want no indentation at all rather than different indentation.
Does it fix broken HTML?
It closes what was left open and drops closing tags that match nothing, which is most of what “broken” means in practice. It cannot guess where a missing </div> was meant to go: it closes it at the end of its parent.
Why is the preview narrower than my page?
Because it is a box in a page, not a page. Copy the output into a real template to judge the layout; use the preview to check the structure.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.