HTML Beautifier
The HTML beautifier turns compressed, minified, or single-line markup into a readable document whose indentation follows the element hierarchy.
Run — free
Paste a complete page, a template fragment, or markup copied from a generated response and receive stable two-space indentation without network access or nondeterministic processing. The formatter recognizes comments, declarations, quoted attributes, raw script and style contents, self-closing syntax, and standard HTML void elements. It also rejects unexpected, mismatched, or unclosed ordinary tags so incomplete markup is reported instead of being presented as a successful formatted document.
Reveal the hierarchy hidden inside minified HTML
Minification is useful when delivering a page to a browser, but it removes the visual structure that helps people understand markup. A navigation bar, nested list, card grid, or form can become one continuous line where opening and closing tags are hard to pair. This formatter restores that structure by scanning the document in source order and assigning indentation from the active tag hierarchy. Every ordinary opening element increases the indentation level, and its matching closing element returns the output to the preceding level. Text is placed at the depth of its containing element, while comments and declarations receive the indentation appropriate to their position in the document. The result uses a stable two-space style that works well for inspection, documentation, code review, and comparison. It does not invent elements, reorder attributes, or redesign the markup. Its purpose is narrower and safer: make the hierarchy already present in the submitted HTML visible, predictable, and easier to follow without relying on a browser to repair the source behind the scenes.
Handle the HTML constructs that do not need closing tags
Not every angle-bracket token should change the nesting depth. Standard void elements such as img, input, meta, link, br, and hr are complete without an explicit closing tag, so the formatter emits them without adding them to the open-element stack. Explicit XML-style self-closing syntax receives the same treatment. Comments, document type declarations, CDATA sections, and processing instructions are kept as complete tokens rather than interpreted as ordinary elements. Attribute values are scanned with awareness of single and double quotes, allowing a greater-than character inside a quoted value without ending the tag early. Script, style, and textarea elements also need special handling because their contents can include symbols that resemble markup. Their contents are preserved until the matching closing tag instead of being split into false child elements. These rules let the formatter represent common pages and reusable fragments accurately while remaining a deterministic, dependency-free parser. Identical input produces identical output, and processing never depends on a network request, current time, random value, browser DOM repair, or external formatting service.
Catch incomplete tag structures before reuse
Formatting often begins with markup copied from a log, an email template, a server response, a content management field, or a generated build artifact. Those sources can be truncated, and a visually plausible fragment may still be missing a closing tag. The formatter keeps a stack of ordinary open elements and requires closing tags to match in last-opened, first-closed order. An unexpected closing tag fails immediately. A closing tag that conflicts with the active element reports which closing tag was expected first. If the end of the input arrives while an ordinary element remains open, the request returns an invalid-input error naming that element. Standard void elements and explicit self-closing tags are the intentional exceptions because HTML does not require a separate closing token for them. This check is structural rather than a complete standards-conformance validator: it does not judge every permitted parent-child relationship, attribute name, custom-element convention, or document-level rule. Use it to expose hierarchy and catch incomplete tag pairing before review, storage, documentation, or another processing step. Browser formatting is available locally, and each automated API request uses the published base price of $0.002.
What you can do with it
Inspect a minified production page
Expand one-line markup into a readable hierarchy before tracing a component, link, form control, or embedded section.
Prepare template markup for review
Apply stable indentation so reviewers can compare structure without spending time manually pairing nested opening and closing tags.
Check a copied HTML fragment
Detect a mismatched or missing closing tag after copying markup from a response, editor field, message, log, or build output.
FAQ
What does the HTML beautifier change?
It adds line breaks and two-space indentation based on tag nesting, and it normalizes whitespace between ordinary pieces of text.
Which tags can remain unclosed?
Standard HTML void elements, including img, input, meta, link, br, and hr, do not need closing tags. Tags written with explicit self-closing syntax are also accepted.
Does it preserve attributes and embedded code?
Yes. It keeps tag attributes, respects quoted attribute values, and treats script, style, and textarea contents as raw text until their matching closing tags.
Is this a complete HTML standards validator?
No. It validates tag pairing and several token boundaries, but it does not enforce every document, content-model, accessibility, or attribute rule in the HTML standard.
How much does an API request cost?
Each API request uses the published base price of $0.002. The browser version can run locally without an API request.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/dev2/beautify-html \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<!doctype html><html><body><main><h1>Hello</h1><p>Readable HTML.</p></main></body></html>"}'const res = await fetch("https://api.kit.forhosting.com/dev2/beautify-html", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"html": "<!doctype html><html><body><main><h1>Hello</h1><p>Readable HTML.</p></main></body></html>"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev2/beautify-html",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"html": "<!doctype html><html><body><main><h1>Hello</h1><p>Readable HTML.</p></main></body></html>"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev2/beautify-html", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"html":"<!doctype html><html><body><main><h1>Hello</h1><p>Readable HTML.</p></main></body></html>"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"html":"<!doctype html><html><body><main><h1>Hello</h1><p>Readable HTML.</p></main></body></html>"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev2/beautify-html", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"html": "<!doctype html><html><body><main><h1>Hello</h1><p>Readable HTML.</p></main></body></html>"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev2.beautify_html",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |