JavaScript Formatter

Use this JavaScript formatter to format JavaScript you received from a minified bundle, an error stack, or a chat message. It splits statements onto their own rows, indents every block to the depth of its braces, and copies strings, comments, template literals, and regular expressions without touching their contents. Everything runs in your browser, so nothing you paste ever leaves the tab.

Preset examples
Renders as you type. Nothing is uploaded.
—

How the Formatter Works

The formatter walks the input once with a small tokenizer and writes each token onto the current row. Beside the walk runs a frame stack that says what kind of text is being read. Ordinary code sits on a code frame, a template literal pushes a template frame, and a substitution inside that literal pushes a second code frame that remembers the brace depth where the substitution started. While the tokenizer is on a template frame, characters are copied exactly as they arrive, so a brace, a slash, or a semicolon in template text never reaches the layout rules. The closing brace of the substitution pops back to the template frame, and the closing backtick pops back to code.

Four kinds of run are copied whole instead of being split into tokens. A quoted string runs to its closing quote with backslash escapes honored, a template literal keeps every character between its backticks, a comment runs from its first slash to its terminator, and a regular expression body runs to the closing slash with character classes handled. That is why a brace inside a string never splits a block, and it is also why the layout never re-indents a comment that lands on a fresh row.

A slash is the one ambiguous character in JavaScript, and the tokenizer reads it from the token in front of it. After a closing parenthesis or bracket, a quote, a number, or an identifier that is not a keyword, the slash is division and it is written as an ordinary operator. After a keyword such as return or typeof, after an operator, or after an opening brace, the slash opens a regular expression, and a scanner then runs to the closing slash with character classes and backslash escapes handled, so a pattern such as /[,;|]/ comes through as a single token. The decision looks at that one token only. When the guess is wrong, as in a division written after a variable named from, the scanner reaches the end of the row and the banner reports an unterminated regular expression rather than laying the text out badly.

The formatter forces a line break after an opening brace, before a closing brace, and after a semicolon or a comma while the parenthesis depth is zero. A closing brace ends the row it sits on, and the token after it starts a fresh row unless it is else, catch, finally, or while, or a symbol that belongs beside it such as a comma, dot, colon, semicolon, or question mark. Every semicolon and every top level comma therefore ends a row, and two rows of input are never joined into one, so a break that relied on automatic semicolon insertion stays where it was.

Parentheses are what keep call arguments together, because the comma break only fires at depth zero, which is why console.log(a, b) stays on one row while a comma in a top level array literal starts the next one. The parenthesis depth is tracked next to the brace depth, so a comma inside a call, an object argument, or a condition list never ends the row it sits on.

Spaces are added where the input left none. A word or a closing parenthesis followed by an opening brace gets one, so if(a){ comes back as if (a) { and render(){ comes back as render() {. The words if, for, while, switch, catch, and function get a space before their opening parenthesis as well, which turns for( into for ( while a plain call such as sum( keeps its tighter form. Whitespace that already exists between two tokens collapses to a single space instead of vanishing, so an expression such as total / count keeps the spacing it arrived with, and a stray space after an opening parenthesis, bracket, brace, or dot is dropped.

Runs of empty lines are capped by the blank line option, which allows zero, one, or two of them in a row and starts at one. Every rule above is decided from the token stream rather than from the current column, so feeding the output back through the formatter returns the same text a second time. That gives a check you can run in the page. Press a preset, copy the output, paste it into the input, and watch the stats line stay where it was.

Indentation is brace depth

Every opening brace raises the level, every closing brace lowers it, and each emitted row carries the indent string repeated once per level. The count comes from braces alone, so a long argument list, a ternary, or an array literal never pushes the next statement sideways. Switch the option to four spaces or a tab and the same depth prints with the new string, because the level and the string are decided separately.

Reading the stats line and the error banner

The stats line under the options reports bytes in, bytes out, and the line count of the output after every render. The byte counts come from a UTF-8 encoder, so a multi byte character inside a string is counted the way a server would count it. The red banner appears only when the formatter refuses to lay the text out, which covers an unbalanced brace, an unbalanced parenthesis, an unbalanced bracket, an unterminated string, an unterminated template literal, an unterminated comment, and a regular expression with no closing slash. The input keeps every character you typed and the output box falls back to your raw text, so the broken input stays on screen until you fix it.

Examples

The four preset buttons load the samples below. Each row pairs the exact input string with the output the formatter returns under the default options, which are a two space indent and one blank line kept.

Preset Before After
Minified loop A one-line function with a for loop and array indexing, all without a single break.
function sum(a,b){let t=0;for(let i=0;i<a.length;i++){t+=a[i]*b[i]}return t}const r=sum([1,2,3],[4,5,6]);console.log(r);
function sum(a,b) {
  let t=0;
  for (let i=0;i<a.length;i++) {
    t+=a[i]*b[i]
  }
  return t
}
const r=sum([1,2,3],[4,5,6]);
console.log(r);
Async template An async function whose template literal contains a nested object inside a substitution.
async function greet(name){const msg=`Hello ${name}, you are ${{ role: "admin" }.role} today`;const reply=await fetch("/api",{method:"POST",body:msg});return reply.status}
async function greet(name) {
  const msg=`Hello ${name}, you are ${{
    role: "admin"
  }.role} today`;
  const reply=await fetch("/api",{
    method:"POST",body:msg
  });
  return reply.status
}
Slash handling A division expression next to two regular expressions, where reading the slash matters.
const ratio = total / count; const cleaned = text.replace(/\s+/g, " "); const rows = data.split(/[,;|]/);
const ratio = total / count;
const cleaned = text.replace(/\s+/g, " ");
const rows = data.split(/[,;|]/);
Config comments An indented configuration block with line and block comments kept where they were.
const config = {
  // where to fetch from
  endpoint: "/v2/items", /* changed in march */
  retries: 3,
  headers: { accept: "application/json" },
  flags: [true, false]
};
const config = {
// where to fetch from
  endpoint: "/v2/items",
/* changed in march */
  retries: 3,
  headers: {
    accept: "application/json"
  },
  flags: [true,
  false]
};

When to Use This Tool

Formatting is a reading task rather than a build step, so this page is worth reaching for when a screenful of code stops making sense. The list below covers the moments where pasting here beats squinting at a long line or retyping a snippet by hand.

  • A stack trace or a vendor file hands you a minified snippet and you need to read one function before patching it.
  • A teammate pasted a one-line function into chat and you want it expanded before it reaches review.
  • A gist or a documentation example is about to land in your file and you want its indentation to match the rest.
  • A diff is full of whitespace noise, so you format both versions first and the remaining changes are the real ones.
  • You are about to commit and want the file laid out the way the rest of the repository is laid out.
  • A review comment needs a short block of code shown with indentation instead of a single long row.
  • You are writing documentation or a lesson and the example has to stay readable in a narrow column.

Best Practices

These habits keep the formatted copy and the file in your repository describing the same content, and they keep the options from producing a diff you did not intend.

  1. Format when the edit is finished. Formatting halfway through and then fixing the output in a second window leaves two copies that drift apart.
  2. Match the indent width to the rest of the project and keep that choice for the whole file, since mixing two-space and four-space blocks makes every later diff harder to read.
  3. Paste the formatted result back into the input once. When the stats line reads the same as before, the output is stable and safe to commit.
  4. Run the formatter before you open the diff rather than during review, so whitespace moves land in one commit and real changes land in another.
  5. Leave the blank line option at one for ordinary files. Zero makes long functions run together, and two only matters when the source already separated sections with double blanks.
  6. Use the Copy button instead of dragging across the output box, so indentation and the last row are taken exactly as rendered.

Common Mistakes

Each of these has happened in a real project. They come from running the page at the wrong point in the workflow or from reading more into the output than it promises.

  1. Formatting a generated file such as a bundle or a framework export and committing it, when the next build overwrites the file anyway and the formatted copy adds nothing but noise to the history.
  2. Treating the formatter as a repair tool. It never inserts a missing semicolon or a missing bracket; it reports what it cannot balance and leaves the input exactly as it was.
  3. Naming a variable with a word such as from, of, get, or set and then dividing on the same row. The tokenizer reads the slash as the opening of a regular expression and the banner answers with an unterminated pattern, so wrap the operand in parentheses or rename the variable.
  4. Pasting output that still holds a secret into a public gist, an issue, or a chat room. Formatting only rearranges text, so a token inside a string is still there afterwards; revoke the value before you share anything.
  5. Editing inside the output box and expecting the page to keep the change. The next keystroke in the input re-renders the box, so copy first and edit in a real file.
  6. Feeding JSX or TSX to the page. The tokenizer has no notion of an element, so a slash inside a tag is read as an operator and the banner reports an unterminated regular expression.

Frequently Asked Questions

Does formatting change what my code does?

No. The formatter only moves whitespace and line breaks. It never reorders statements, renames a variable, or inserts or removes tokens, so the program that runs is the one you pasted. The only additions are indentation, a space before an opening brace, and a space between a keyword and its parenthesis, all of which the language ignores.

What triggers the red banner?

Unbalanced braces, parentheses, or brackets, an unterminated string, template literal, or block comment, and a slash pair that the tokenizer reads as a regular expression with no closing slash. The message appears in the banner under the options, the input keeps every character you typed, and the output box falls back to your raw text instead of a formatted copy. Correct the reported problem and the next keystroke renders again.

Can it tell a regular expression from a division?

Usually. The decision comes from the single token before the slash, so division after a closing parenthesis, a quote, a number, or an ordinary identifier stays division, and a pattern after return, typeof, an operator, or an opening brace is read as a regular expression. Two corners fall outside the rule. A variable named from, of, get, or set and then divided on the same line reads as the start of a pattern, and JSX puts a slash inside a tag, which the tokenizer has no way to know about. Both cases show up as an unterminated regular expression in the banner.

Why is a comment sitting at the far left of the output?

Comments are copied exactly and the layout rules never re-indent one. When a line splits, the comment takes the row it lands on without an indent prefix, so a comment placed after a top level comma starts at column zero. Move the comment onto its own row above the entry it describes and it prints where you expect.

Does the formatter add semicolons?

No. Nothing is inserted and nothing is removed. The newline rules react only to semicolons and commas that are already in the source, so a statement that depended on automatic semicolon insertion keeps the same line boundaries and a statement that was terminated stays terminated.

Will it format TypeScript or JSX?

Plain TypeScript mostly survives, because the punctuation it uses is JavaScript punctuation. Type annotations, interfaces, and generic calls keep their shape, although a long generic list can be split across lines. JSX and TSX are not handled at all, so a slash inside a tag is read as an operator, the banner reports an unterminated regular expression, and the input is left alone.

Is there a size limit, and does my code leave the browser?

There is no limit beyond what the browser can hold in a textarea, and formatting runs entirely in the page. No network request carries your source, so the tool keeps working after the page has loaded even if the connection drops, and closing the tab discards everything.

How to Format JavaScript Online

  1. Paste your JavaScript or load a preset

    Drop any snippet into the input box, or press one of the four preset buttons to load a sample covering a minified loop, a template literal, division beside regular expressions, and a commented configuration block.

  2. Pick indentation and blank line rules

    Choose two spaces, four spaces, or a tab for the indent, then set how many blank lines may survive in a row, which can be zero, one, or two. The output refreshes on every keystroke.

  3. Read the stats and copy the result

    The stats line reports bytes in, bytes out, and line count for the last render, the red banner reports problems such as unbalanced brackets or an unterminated string, and the Copy button takes the output to your clipboard.