TypeScript and TSX Formatter

Paste TypeScript or a React TSX component and get Prettier-formatted code, with type errors in the syntax flagged at the exact position.

Input

Settings

History

Load from URL

What it supports

TypeScript adds a lot of syntax that a JavaScript-only beautifier chokes on: generics with constraints, satisfies, type predicates (value is Order), mapped and conditional types, declare module blocks, enums, namespaces, abstract classes, parameter properties (constructor(private readonly http: HttpClient)), definite assignment (name!: string), import type / export type, using declarations, and decorators as used by Angular and NestJS. This formatter uses Prettier with its TypeScript parser, so all of it is understood, and the output is what prettier --write src/**/*.ts would give you.

It is aimed at situations where your editor is not set up or not at hand: reviewing a snippet in a ticket, cleaning up a type definition copied from documentation, tidying the code in a blog post, or checking what Prettier will do to a file before you add it to a repository’s format check.

Steps

  1. Paste the code or drop a .ts, .tsx, .mts or .cts file. Type annotations in the text are what auto-detection uses to tell TypeScript from JavaScript.
  2. Format with Ctrl/Cmd+Enter and copy with Ctrl/Cmd+Shift+C. Ctrl/Cmd+K opens the command palette.
  3. If there is a syntax error, formatting stops and the message from the TypeScript parser is shown at its line and column.

Proprietary source code is a reasonable thing to be careful with. Everything here, from parsing to printing, runs in your browser.

There is no minify step for TypeScript. Minifiers work on JavaScript, so compile with tsc, esbuild or your bundler first, then use the JavaScript minifier if you need it.

Options

The five style options are Prettier’s own, with the same defaults Prettier 3 ships:

  • Semicolons: on by default. Turning them off also removes semicolons after interface and type members, which Prettier normalises consistently.
  • Single quotes: use ' for strings, including in import paths. JSX attribute values in .tsx keep double quotes.
  • Trailing commas: All (the Prettier 3 default) adds them to multi-line parameter lists, type parameter lists, enums and tuples as well as objects and arrays. ES5 limits them to places ES5 allowed (objects, arrays) plus type parameter lists, and None removes them.
  • Spaces inside { }: affects object literals, object types, destructuring and named imports alike, so { id: string } becomes {id: string} when switched off.
  • Arrow function parentheses: Avoid drops the parentheses around a single parameter, but only when it has no type annotation; (order: Order) => order.id always keeps them.

Indent and line width apply as for every language.

TS versus TSX

In a .ts file, <string>value is a type assertion. In a .tsx file the same characters start a JSX element, which is why TSX code must use value as string instead. Prettier guesses from the content whether JSX is present and retries in the other mode if the first parse fails, so both kinds of file work. Code that mixes JSX with old-style angle-bracket assertions cannot be valid in either mode; the error “JSX element ‘string’ has no corresponding closing tag” is the giveaway.

A related trap is a generic arrow function in TSX: <T>(value: T) => value reads as a JSX tag. Write <T,>(value: T) => value; Prettier keeps the comma for exactly this reason.

Formatting does not type-check. Code with a type error that is still syntactically valid (a wrong property name, an incompatible assignment) formats normally; run tsc --noEmit for type errors.

Examples

Typed API client with a discriminated union

The union and interface types are broken over lines, and the function body is laid out statement by statement.

Input
type Result<T,E=Error>={ok:true;value:T}|{ok:false;error:E}
export interface Order{id:string;status:'pending'|'paid'|'shipped'|'refunded';items:ReadonlyArray<{sku:string;qty:number;unitPrice:number}>;placedAt:Date}
export async function fetchOrder(id:string,init?:RequestInit):Promise<Result<Order>>{const res=await fetch(`/api/orders/${encodeURIComponent(id)}`,init);if(!res.ok)return{ok:false,error:new Error(`HTTP ${res.status}`)};const body=await res.json() as Omit<Order,'placedAt'>&{placedAt:string};return{ok:true,value:{...body,placedAt:new Date(body.placedAt)}}}
Output
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
export interface Order {
  id: string;
  status: "pending" | "paid" | "shipped" | "refunded";
  items: ReadonlyArray<{ sku: string; qty: number; unitPrice: number }>;
  placedAt: Date;
}
export async function fetchOrder(
  id: string,
  init?: RequestInit,
): Promise<Result<Order>> {
  const res = await fetch(`/api/orders/${encodeURIComponent(id)}`, init);
  if (!res.ok) return { ok: false, error: new Error(`HTTP ${res.status}`) };
  const body = (await res.json()) as Omit<Order, "placedAt"> & {
    placedAt: string;
  };
  return { ok: true, value: { ...body, placedAt: new Date(body.placedAt) } };
}
Open this example in the tool

Angular service with decorators

Imports are split onto separate lines, the decorator sits above its class, and all strings use single quotes.

Input
import {Injectable,inject} from '@angular/core';import {HttpClient,HttpParams} from '@angular/common/http';import type {Observable} from 'rxjs';
@Injectable({providedIn:'root'})export class OrderService{private readonly http=inject(HttpClient);private readonly baseUrl='/api/orders';
list(status?:string,page=1):Observable<Order[]>{const params=new HttpParams({fromObject:{page:String(page),...(status?{status}:{})}});return this.http.get<Order[]>(this.baseUrl,{params})}
cancel(id:string){return this.http.post<void>(`${this.baseUrl}/${id}/cancel`,{})}}
Output
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import type { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class OrderService {
  private readonly http = inject(HttpClient);
  private readonly baseUrl = '/api/orders';
  list(status?: string, page = 1): Observable<Order[]> {
    const params = new HttpParams({
      fromObject: { page: String(page), ...(status ? { status } : {}) },
    });
    return this.http.get<Order[]>(this.baseUrl, { params });
  }
  cancel(id: string) {
    return this.http.post<void>(`${this.baseUrl}/${id}/cancel`, {});
  }
}
Open this example in the tool

React TSX component with generic props

Generic props and nested JSX maps are indented, and untyped single-parameter arrows lose their parentheses.

Input
type Column<Row>={key:keyof Row&string;header:string;render?:(row:Row)=>React.ReactNode}
export function DataTable<Row extends {id:string}>({rows,columns,onSelect}:{rows:Row[];columns:Column<Row>[];onSelect?:(row:Row)=>void}){return <table className="data-table"><thead><tr>{columns.map(c=><th key={c.key}>{c.header}</th>)}</tr></thead><tbody>{rows.map(row=><tr key={row.id} onClick={()=>onSelect?.(row)}>{columns.map(c=><td key={c.key}>{c.render?c.render(row):String(row[c.key])}</td>)}</tr>)}</tbody></table>}
Output
type Column<Row> = {
  key: keyof Row & string;
  header: string;
  render?: (row: Row) => React.ReactNode;
};
export function DataTable<Row extends { id: string }>({
  rows,
  columns,
  onSelect,
}: {
  rows: Row[];
  columns: Column<Row>[];
  onSelect?: (row: Row) => void;
}) {
  return (
    <table className="data-table">
      <thead>
        <tr>
          {columns.map(c => (
            <th key={c.key}>{c.header}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {rows.map(row => (
          <tr key={row.id} onClick={() => onSelect?.(row)}>
            {columns.map(c => (
              <td key={c.key}>
                {c.render ? c.render(row) : String(row[c.key])}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}
Open this example in the tool

Common errors and how to fix them

ErrorCauseFix
'}' expected.An interface, type literal, enum or block was not closed, often because a closing brace was deleted along with the last member.Add the missing } where the declaration ends; the reported position is where the parser ran out of input.
',' expected.Two members of an object, parameter list or type are not separated, or a parenthesis is missing, as in function total(items: Item[] { … }.Insert the missing comma or closing parenthesis just before the reported column.
Type expected.A colon introduces a type annotation but no type follows, for example let total: = 0.Write the type after the colon, or remove the colon and let TypeScript infer it.
JSX element 'string' has no corresponding closing tag.The file contains JSX, so it is parsed as TSX, where <string>value is read as an opening JSX tag instead of a type assertion.Replace angle-bracket assertions with value as string.
Unterminated string literal.A string is missing its closing quote before the end of the line.Close the string, or use a template literal with backticks if it needs to span lines.

Frequently asked questions

Does this check my types?

No. Prettier only parses syntax, so a type mismatch or a misspelt property formats without complaint. Use tsc --noEmit or your editor’s TypeScript server for type checking.

Is the output the same as Prettier in VS Code?

Yes, provided the options match your project’s .prettierrc. This page uses Prettier 3 and its TypeScript parser, the same combination the Prettier extension uses.

Can it format .d.ts declaration files?

Yes. Declaration files are ordinary TypeScript syntax, including declare module, declare global and overloaded function signatures.

How do I format TypeScript on save?

Install Prettier in the project, add the Prettier extension to your editor, and enable format on save (“editor.formatOnSave”: true in VS Code). In CI, run prettier --check to enforce it.

Is my source code uploaded?

No. Parsing and printing happen in your browser, so nothing in the file reaches a server.

Related tools