TeX directory structure & paths

A TeX installation is a pile of tens of thousands of files — macros, classes, fonts, configuration. Why does writing the single line \usepackage{amsmath} reliably find amsmath.sty somewhere on disk? Behind it lie two conventions: a standard for where files live (the TDS) and a mechanism for finding them (kpathsea). This page draws that map.

The TDS: a standard place for everything

Everything starts from a directory tree called a texmf tree — “texmf” being short for *TeX and Metafont*. Beneath it, files branch by kind: macros and classes go under tex/, anything font-related under fonts/, bibliographic data under bibtex/, documentation (manuals) under doc/, scripts under scripts/. Standardising this layout across every system is the job of the TDS (TeX Directory Structure), defined by the TeX Users Group (TUG) in the 1990s.

Why have a standard at all? TeX runs on macOS, Unix, Windows, and more, and CTAN (the Comprehensive TeX Archive Network) collects an enormous pile of packages. If every site arranged files differently, both the people shipping packages and the tools finding them would stumble each time. Once “macros live under tex/” is fixed, the location of any file can be inferred from the rules alone, on any OS and any distribution. Portability is exactly the problem the TDS solves.

The branches have rules of their own. tex/ is split by format, then by package, giving tex/<format>/<package>/ (where <format> is latex, plain, generic, …). The standard article.cls, for instance, sits at the path below — and the kpsewhich tool introduced later will report the very same location on your own machine.

terminal
$ kpsewhich article.cls
/usr/local/texlive/2026/texmf-dist/tex/latex/base/article.cls

Fonts go one level finer: fonts/<type>/<supplier>/<typeface>/. The <type> is the kind of file — tfm for TeX font metrics, vf for virtual fonts, plus type1, opentype, truetype, and so on. The <supplier> is who provided it (public, adobe, ams, …) and <typeface> is the font name (cm = Computer Modern). The table below lists the branches you meet most often and what each holds.

DirectoryWhat it holds
tex/Macros, classes, styles (.tex .sty .cls); e.g. tex/latex/...
fonts/All font files, by type: tfm, vf, type1, opentype, enc, map, …
bibtex/Bibliography databases bib/ and styles bst/
doc/Package manuals and docs (what texdoc opens)
scripts/OS-independent executable scripts (the body of mktexlsr, etc.)
web2c/Engine configuration; home of texmf.cnf and format definitions

Multiple trees, and which one wins

A texmf tree is not one thing. TeX Live bundles several trees with distinct roles, each named by a TEXMF… variable. Why separate them? To keep three things apart: the distribution (read-only, replaced wholesale on every update), what you added (which must not vanish), and auto-generated working data. Mix them, and updating the distribution could wipe out your own styles in an instant.

VariableRole and default location (e.g. TeX Live 2026)
TEXMFDISTThe distribution itself; the bulk of packages. Do not touch. .../texlive/2026/texmf-dist
TEXMFLOCALSite-wide additions, shared by all users. .../texlive/texmf-local
TEXMFHOMEYour personal tree; your own styles and classes go here. ~/texmf (~/Library/texmf on macOS)
TEXMFVARAuto-generated cache (formats, fonts, …); never hand-edit
TEXMFCONFIGPer-user configuration store (written by updmap, etc.)
TEXMFSYSVAR / SYSCONFIGSystem-wide counterparts of VAR / CONFIG above
TEXMFROOTRoot of the whole installation. .../texlive/2026

If a file of the same name exists in several trees, which one wins? That is decided by the TEXMF variable, which simply lists the search priority in order. Querying it returns roughly the following sequence (leftmost wins):

terminal
$ kpsewhich -var-value=TEXMF
{$TEXMFCONFIG,$TEXMFVAR,$TEXMFHOME,!!$TEXMFLOCAL,!!$TEXMFSYSCONFIG,!!$TEXMFSYSVAR,!!$TEXMFDIST}

Read it like this: first your own configuration (TEXMFCONFIG) and working data (TEXMFVAR), then your **personal tree TEXMFHOME**, then the site-wide TEXMFLOCAL, and finally the system config, working data, and the distribution TEXMFDIST. Because of this order, **dropping your own mystyle.sty into TEXMFHOME makes it shadow the distribution’s copy** — no clobbering, just a natural ranking of personal → site → distribution. (The leading !! is explained in the next section.)

kpathsea: how files are found

Even with the layout fixed, who actually finds a file? That is kpathsea (kpse, *kpath search*) — the path-searching library shared by TeX and its companion tools. tex, pdflatex, dvipdfmx, and the rest do not search on their own; they all ask kpathsea, “where is amsmath.sty?”

kpathsea expresses a search path as a string with rules. Three symbols are worth knowing: $VAR expands a variable; a trailing **// means “search everything below this, recursively”; and a leading !! means “do not scan the disk directly — consult only the filename database described below.”** The path for finding LaTeX sources, TEXINPUTS, actually looks like this:

terminal
$ kpsewhich -var-value=TEXINPUTS
.:$TEXMF/tex/{latex,generic,}//

Read it as: look in . (the current directory) first; failing that, recurse through each texmf tree’s tex/ branch in the order latexgeneric → everything else. This gives the intuitive behaviour you expect — a file beside your manuscript wins, otherwise fall back to the distribution.

But truly scanning tens of thousands of directories every time would be far too slow. So kpathsea uses a **filename database called ls-R placed at the root of each tree** — a text listing of which file lives in which directory. Consulting that index instead of walking the disk makes lookups instant. The !! seen earlier means “trust the index only (never touch the real disk)”, and it is attached to trees like the distribution that rarely change.

The flip side: **after adding a file to a tree, TeX may not find it until ls-R is rebuilt** — especially in the !! system trees. The command that regenerates the index is **mktexlsr, also known as texhash**. Personal trees like TEXMFHOME are treated more leniently and often need no rebuild, but after putting something into TEXMFLOCAL the safe move is to run the one line below.

terminal
# Rebuild the ls-R filename databases after adding files to a tree
$ mktexlsr        # texhash is an exact alias

When something goes wrong, the first move is to query files and variables directly with kpsewhich: “which file is actually being read?” and “what does this variable expand to?” are answered instantly, letting you isolate a misconfiguration. The full set of kpsewhich options, and management commands such as tlmgr, are covered in detail on a separate page.

PATH: the road to the commands

kpathsea finds the files TeX reads, but before that the shell must locate the executable itself, pdflatex. That is the OS’s job: it searches the directories listed in the **PATH** environment variable, in order. TeX Live keeps its executables in a single per-OS, per-architecture bin directory, and putting that on PATH is the finishing touch of an install.

The directory name embeds the year and the platform: on Linux /usr/local/texlive/2026/bin/x86_64-linux, on macOS /usr/local/texlive/2026/bin/universal-darwin, on Windows ...\bin\windows. On macOS, MacTeX provides a year-independent, stable symlink at /Library/TeX/texbin, so a yearly upgrade needs no edit to your PATH. You can see what it currently points at like so:

terminal
$ which pdflatex
/Library/TeX/texbin/pdflatex
$ readlink /Library/TeX/texbin
Distributions/Programs/texbin

If you ever see “pdflatex: command not found,” nine times out of ten the bin directory is simply missing from PATH. The OS-by-OS procedure for setting it, and the post-install checks, are left to the installation page.

texmf.cnf: where the settings come from

Where do all the variables so far — TEXMF, TEXINPUTS, the location of each tree — actually come from? The answer is a configuration file called **texmf.cnf. Before doing anything, kpathsea reads it to pick up the operating parameters**: the search paths, where each tree sits, memory limits, and more. The master copy lives under the distribution’s web2c/:

terminal
$ kpsewhich texmf.cnf
/usr/local/texlive/2026/texmf.cnf

The interesting part: there can be more than one texmf.cnf. kpathsea reads texmf.cnf from several places along a dedicated search path (the TEXMFCNF variable), in order, and takes the first definition it finds for any given variable (later files do not override earlier ones). So you leave the distribution’s big default file untouched and drop a small override file in a higher-priority location — a layering scheme that adds only your changes, safely. Adding -all reveals the files actually stacked up:

terminal
$ kpsewhich -all texmf.cnf
/usr/local/texlive/2026/texmf.cnf
/usr/local/texlive/2026/texmf-dist/web2c/texmf.cnf

Here the upper texmf.cnf (TeX Live’s thin override) is read before the lower texmf-dist/web2c/texmf.cnf (hundreds of lines of defaults). When you want to change a value permanently, the convention is not to edit the distribution’s file but to write only the lines you need into a higher-priority location such as TEXMFLOCAL/web2c/texmf.cnf. Do that, and your settings survive a distribution upgrade.

In sum, here is how TeX finds a file: texmf.cnf first fixes where the trees are and what the search paths look like; following that order, kpathsea locates the target (usually via the ls-R index); and PATH provides the entry point to the executable itself. These three layers mesh together so that a single line of \usepackage{...} resolves quietly.