npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@devhub-io/latex-to-unicode

v2.0.1

Published

Fast and lightweight utility to convert LaTeX strings into clean Unicode characters.

Downloads

187

Readme

@devhub-io/latex-to-unicode

Convert LaTeX-style math notation into readable Unicode text. Built for rendering math in places that can't render LaTeX, like Discord messages, Slack, plain-text logs, or terminal output.

Unlike general-purpose LaTeX-to-Unicode converters, this package is explicit about the cases where a faithful conversion is not possible (Unicode does not define subscript/superscript glyphs for every letter) and degrades gracefully instead of silently dropping content or producing inconsistent output.

Why

Most latex-to-unicode style packages are regex-based and break on:

  • Nested braces (\frac{8\pi G}{c^{4}})
  • Escaped underscores (\_{\mu\nu}) — common when the source text was already made "Discord-safe" by escaping underscores to avoid triggering italics
  • Subscripted Greek letters that have no Unicode subscript form at all (there is no subscript μ or ν in Unicode — only β, γ, ρ, φ, χ have true subscript glyphs)

This package handles all three deliberately, rather than as an afterthought.

Install

# npm or any package manager of your choice
npm install @devhub-io/latex-to-unicode

Usage

import { latexToUnicode, isLatex } from "@devhub-io/latex-to-unicode";

const input = String.raw`$$ R_{\mu\nu} - \tfrac{1}{2} R\,g_{\mu\nu} + \Lambda\,g_{\mu\nu} = \frac{8\pi G}{c^{4}}\,T_{\mu\nu} $$`;

console.log(latexToUnicode(input));
// R₍μν₎ - 1/2 R g₍μν₎ + Λ g₍μν₎ = (8πG)/(c⁴) T₍μν₎

API

latexToUnicode(input: string): string

Converts a string containing LaTeX macros, subscripts, superscripts, fractions, and roots into a Unicode-rendered equivalent. Unrecognized commands are left as-is rather than stripped, so partial input degrades predictably instead of losing information silently.

Options

You can customize the execution behavior by passing an optional Options object as the second argument:

| Property | Type | Default | Description | | :------------------ | :-------------------------------------- | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | latexCheck | boolean | true | When true, runs isLatex(input) first and immediately returns the input unmodified if no LaTeX patterns are detected. Set to false to force processing on all strings. | | customMacros | Record<string, string> | {} | A dictionary mapping custom LaTeX macro names (without backslashes) to their desired Unicode equivalents. Overrides default maps. | | fallbackBehaviour | "parentheses" | "raw" | "strip" | "parentheses" | Determines how characters with no native Unicode script counterpart (like Greek letters) are handled. "parentheses" wraps them in script-level parens (e.g., ₍μ₎), "raw" preserves the original LaTeX syntax (e.g., _{\mu}), and "strip" flattens them to plain characters (e.g., μ). |

// Standard usage
const result = latexToUnicode("\\alpha + \\beta"); // α + β

// Force processing even if the string doesn't look like LaTeX
const forcedResult = latexToUnicode("x^2", { latexCheck: false }); // x²

// Custom macros
latexToUnicode("\\R + \\mathbb{Q}", {
  customMacros: { R: "ℝ", mathbb: "" },
}); // ℝ + Q

isLatex(input: string): boolean

Heuristically detects whether a string contains LaTeX/math notation, so you can decide whether it's worth running through latexToUnicode at all. This is not a parser — it scores several independent signals and returns true once enough of them accumulate:

| Signal | Points | | ------------------------------------------------------------------- | ------ | | Math-mode delimiters ($...$, $$...$$, \(...\), \[...\]) | +3 | | Known LaTeX command (Greek letters, \frac, \sqrt, \sum, etc.) | +2 | | Braced subscript/superscript (x_{...}, x^{...}) | +2 | | Any backslash command taking a brace argument | +1 | | Bare superscript (x^2) | +1 |

A string needs a total score of 2 or higher to return true. This keeps ordinary text — snake_case identifiers, filenames, casual x^2-style prose — from being flagged as math, while still catching real LaTeX with a single strong signal like $$...$$ or \frac{}{}.

isLatex("R_{\\mu\\nu} = 8\\pi G T_{\\mu\\nu}"); // true
isLatex("check out my_variable_name in the code"); // false
isLatex("the area is x^2 + y^2"); // false — a single bare superscript isn't enough on its own

extractMacros(input: string): string[]

Parses the input string and extracts all unique LaTeX macro names (commands starting with a backslash). It collects the names, strips the leading backslash, and deduplicates the results.

  • Returns: An array of string macro names (e.g., ["alpha", "beta"]).
  • Behavior: Only captures alphabetical commands; it ignores special single-character escape symbols like \, or \_.
import { extractMacros } from "latex-to-unicode";

extractMacros("\\alpha + \\beta = \\theta + \\alpha");
// Returns: ["alpha", "beta", "theta"]

hasMacro(input: string, macroName: string): boolean

Checks whether a specific LaTeX macro is present in the input string.

  • Smart Matching: You can pass the macro name with or without the leading backslash (e.g., "beta" or "\\beta" both work).
  • Word Boundaries: It uses strict word-boundary matching so that checking for "pi" will not falsely match "varkappa" or "pitchfork".
import { hasMacro } from "latex-to-unicode";

const formula = "\\alpha + \\beta";

hasMacro(formula, "beta"); // Returns: true
hasMacro(formula, "\\beta"); // Returns: true
hasMacro(formula, "pi"); // Returns: false

What it converts

Greek letters

All lowercase and uppercase Greek letters are supported, e.g. \mu → μ, \Lambda → Λ, \varepsilon → ε.

Symbols

Common math symbols, e.g. \infty → ∞, \partial → ∂, \nabla → ∇, \leq → ≤, \to → →, \sum → ∑, \int → ∫. See src/index.ts for the full symbol table.

Fractions and roots

  • \frac{a}{b}, \tfrac{a}{b}, \dfrac{a}{b} → a/b (parenthesized when either side is more than a single token)
  • \sqrt{x} → √(x)

Both are handled with proper brace-depth parsing, so nested expressions like \frac{8\pi G}{c^{4}} resolve correctly instead of breaking on the first }.

Subscripts and superscripts

_{...} and ^{...} (as well as single-character _x / ^x) are converted per-character using real Unicode subscript/superscript glyphs where they exist — digits, most Latin letters, and the five Greek letters with true subscript/superscript forms (β, γ, ρ, φ, χ).

When a character has no Unicode subscript/superscript equivalent (this includes most Greek letters — notably μ and ν, which have no subscript form in the Unicode standard at all), the entire group is wrapped in Unicode subscript/superscript parentheses instead of silently dropping it:

R_{\mu\nu}  →  R₍μν₎
c^{4}       →  c⁴

This is a Unicode limitation, not a bug in this package — there is no subscript μ or ν glyph to fall back to.

Wrapper commands

\text{}, \mathrm{}, \mathbf{}, \boldsymbol{}, and \operatorname{} are unwrapped to their plain content.

Escaped underscores

Input is pre-processed to unescape \_ → _ before parsing, since some sources (e.g. bots generating Discord-safe markdown) escape underscores to prevent italics, which would otherwise break subscript detection entirely.

Cleanup

\left, \right, \big/\Big/\bigg/\Bigg, \,/\;/\!/\: spacing commands, math-mode delimiters ($, $$, \(, \), \[, \]), and any leftover braces are stripped in a final pass, along with whitespace normalization.

Limitations

  • Unicode does not provide subscript or superscript forms for every character. Where no glyph exists, output falls back to subscript/superscript parentheses (₍…₎ / ⁽…⁾) around plain characters rather than a true subscript/superscript rendering. This is a hard limitation of the Unicode standard, not something any converter can work around.
  • This is not a full LaTeX parser — it targets the common subset used in short math snippets (single equations, tensor/index notation, basic symbols), not full documents, matrices, alignment environments, or custom macros.
  • If the source text has already been rendered and re-copied through a client that interprets markdown (e.g. copying displayed Discord text rather than reading raw message content), underscores may already be lost before this package ever sees the string. Always pass the raw source string, not rendered/displayed text.

Contributing

We welcome contributions!

Feel free to open issues or submit pull requests to improve the project.

License

Released under the MIT License. See LICENSE file for more details.