On the TeX Live 2024 install this page was written against, kpsewhich -expand-path='$TEXINPUTS' prints 8,798 directories — about half a million characters of path. And yet \usepackage{amsmath} resolves instantly. The reason is simple: LaTeX hardly ever looks at those directories. This page takes apart the two halves of that trick — the TDS (TeX Directory Structure), which is the map of where every texmf file lives, and kpathsea, the search engine that runs over it — using real command output throughout. Which tree survives an upgrade and which is thrown away? And why is a file dropped into TEXMFHOME the one case that needs no mktexlsr?
The TDS: why one package is scattered over nine directories
The TDS sorts files by kind, not by package, so a single package never sits in a single place. Count amsfonts — the package that provides amssymb — on this TeX Live 2024 install and it occupies nine directories under texmf-dist. Its macros are in tex/latex/amsfonts/, the commented source .dtx files in source/latex/amsfonts/, the manuals in doc/fonts/amsfonts/, and the fonts themselves split further by format across fonts/tfm/, fonts/type1/, fonts/afm/, fonts/map/ and fonts/source/. The plain TeX version gets its own tex/plain/amsfonts/.
$ find /usr/local/texlive/2024/texmf-dist -maxdepth 4 -type d -path '*amsfonts*' | sort
/usr/local/texlive/2024/texmf-dist/doc/fonts/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/afm/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/map/dvips/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/source/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/tfm/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/type1/public/amsfonts
/usr/local/texlive/2024/texmf-dist/source/latex/amsfonts
/usr/local/texlive/2024/texmf-dist/tex/latex/amsfonts
/usr/local/texlive/2024/texmf-dist/tex/plain/amsfontsWhy arrange things this way? The answer is portability. TeX runs on macOS, Unix and Windows, and CTAN (the Comprehensive TeX Archive Network) collects thousands of packages. If every distributor laid files out differently, both the people shipping packages and the tools looking for them would stumble every time. The TDS, drawn up by the TeX Users Group (TUG) in the 1990s, made one set of rules universal: macros go under tex/, fonts under fonts/<type>/<supplier>/<typeface>/. As a result the location of any file can be inferred from the rules alone, on any OS and any distribution. Under tex/ there is one more level, tex/<format>/<package>/, where <format> is latex, plain, generic and so on.
| Directory | What it holds | Measured size (texmf-dist, TeX Live 2024) |
|---|---|---|
doc/ | Package manuals — what texdoc opens | 3.7 GB, 10,099 PDFs alone |
fonts/ | All font files, by format: tfm, vf, type1, opentype, enc, map | 2.9 GB |
tex/ | Macros, classes, styles (.tex .sty .cls), e.g. tex/latex/... | 594 MB |
source/ | Commented sources .dtx and their .ins extraction scripts — the implementation, readable | 426 MB |
scripts/ | OS-independent executable scripts (the bodies of mktexlsr, latexmk, …) | 133 MB |
bibtex/ | Bibliography databases bib/ and bibliography styles bst/ | 26 MB |
web2c/ | Engine configuration; home of texmf.cnf and the format list fmtutil.cnf | 248 KB |
If one line of that table surprises you, it should be this: the documentation weighs more than the software. Of the 7.9 GB in texmf-dist, doc/ accounts for 3.7 GB while the macros in tex/ come to just 594 MB. That is why the TeX Live installer offers to skip documentation at all, and why the Docker images split into -doc and non--doc flavours. Once the layout is in your head you can also use it: when a package behaves in a way you cannot explain, go and read source/latex/<package>/*.dtx directly, and remember that the manual texdoc opens is a real file sitting in doc/.
Which tree survives an upgrade
Only texmf-dist is replaced wholesale. TeX Live creates a directory per year — /usr/local/texlive/2024 — and puts the distribution proper, texmf-dist, inside it. Next year a 2025 appears alongside and texmf-dist is swapped for a brand-new copy. Adding your own files to the distribution is therefore suicide; the flip side is that anything outside the year directory is untouched. It is no accident that TEXMFLOCAL sits at /usr/local/texlive/texmf-local — outside 2024. That is the whole design. TEXMFHOME is further out still, inside your home directory.
# Never guess these paths - ask. Values below: TeX Live 2024 on macOS.
$ kpsewhich -var-value=TEXMFROOT
/usr/local/texlive/2024
$ kpsewhich -var-value=TEXMFLOCAL # note: OUTSIDE the year directory
/usr/local/texlive/texmf-local
$ kpsewhich -var-value=TEXMFHOME # ~/texmf on Linux, ~/Library/texmf on macOS
/Users/you/Library/texmf
$ kpsewhich -var-value=TEXMFVAR
/Users/you/Library/texlive/2024/texmf-var| Variable | Role | What an upgrade does to it |
|---|---|---|
TEXMFDIST | The distribution itself; thousands of packages live here. Never edit by hand | Replaced wholesale. Anything you added is gone |
TEXMFLOCAL | Machine-wide additions, shared by every user | Survives, because it sits outside the year directory |
TEXMFHOME | Your personal tree; your own classes and a journal's style go here | Survives; it is in your home directory and untouched |
TEXMFVAR | Auto-generated cache: formats, font maps, LuaTeX caches | Rebuilt per year; deleting it only forces regeneration |
TEXMFCONFIG | Per-user configuration store, written by updmap and fmtutil | Survives, but lives under a per-year directory |
TEXMFSYSVAR | System-wide counterpart of VAR / CONFIG, written by the -sys commands | TEXMFSYSCONFIG behaves the same; both sit inside the year directory |
TEXMFROOT | Root of the whole installation, /usr/local/texlive/2024 | A new year means a different directory entirely |
When a file of the same name exists in several trees, which one wins? That is settled by a single variable, TEXMF, whose value is nothing but the search priority written out in order. On this TeX Live 2024 it reads as below: leftmost wins, so your own configuration and caches come first, then your personal TEXMFHOME, then the machine-wide TEXMFLOCAL, and the distribution TEXMFDIST last. In other words, dropping mystyle.sty into TEXMFHOME shadows the distribution's copy of the same name — not by overwriting anything, but by the natural ranking personal → site → distribution. The !! markers in front of some entries are explained in the next section.
$ kpsewhich -var-value=TEXMF
{{}/Users/you/Library/texlive/2024/texmf-config,
/Users/you/Library/texlive/2024/texmf-var,
/Users/you/Library/texmf,
!!/usr/local/texlive/texmf-local,
!!/usr/local/texlive/2024/texmf-config,
!!/usr/local/texlive/2024/texmf-var,
!!/usr/local/texlive/2024/texmf-dist}
# Note which entries carry "!!" - and which do not.Is it safe to delete texmf-var?
Everything in it is generated, so in principle nothing is lost by deleting it. Before chanting “delete it and it will fix itself”, though, it is worth looking at what is actually there. On this TeX Live 2024 the system-side texmf-var comes to 259 MB, of which 233 MB is web2c/ holding 53 .fmt files — pdflatex.fmt alone is 7.8 MB. A format file is a canned memory image that saves re-reading latex.ltx and the class files on every run. The user-side texmf-var is larger still at 293 MB, and 257 MB of that is luatex-cache/, the result of LuaTeX parsing fonts. The psfonts.map that updmap writes lives here too.
$ du -sh /usr/local/texlive/2024/texmf-var/*
4.0K ls-R
36K tex
26M fonts
233M web2c # 53 .fmt files; pdflatex.fmt alone is 7.8 MB
$ du -sh "$(kpsewhich -var-value=TEXMFVAR)"/*
32K fonts
2.1M texdoc
12M web2c
22M luatexja
257M luatex-cache # LuaTeX font analysis, rebuilt on demandA practical rule follows. When a stale format file is making things behave oddly, rebuild it with fmtutil-sys --all rather than deleting the directory. Deleting the whole tree is for narrower situations — a corrupted LuaTeX font cache that makes luaotfload emit strange errors, for instance. Wiping it costs only a slow first compile afterwards, but do not confuse texmf-var with texmf-config: take the latter down with it and you lose your updmap settings as well. The regeneration commands themselves are covered on the package and font management page.
How kpathsea actually finds a file
The searching is done by a shared library called kpathsea (kpath search). None of pdftex, xetex, luatex, dvipdfmx or bibtex looks for files itself; they all ask kpathsea “where is amsmath.sty?”. What kpathsea takes in is a single string with rules in it. Three symbols are worth learning: $VAR expands a variable, a trailing // means “everything below this, recursively”, and a leading !! means “do not scan the disk — consult only the filename database described in the next section”. Print TEXINPUTS, the path used to find LaTeX sources, and all three appear at once.
$ kpsewhich -progname=pdflatex -var-value=TEXINPUTS
.:{...the TEXMF list...}/tex/{latex,generic,}//
# The same query, run as a different program:
$ kpsewhich -progname=pdflatex-dev -var-value=TEXINPUTS
.:{...the TEXMF list...}/tex/{latex-dev,latex,generic,}//
# How many real directories does that string stand for?
$ kpsewhich -progname=pdflatex -expand-path='$TEXINPUTS' | tr : '\n' | wc -l
8798Read it like this: . first (the directory holding your manuscript), and failing that, recurse through each texmf tree's tex/ branch in the order latex → generic → everything else. That a file next to the manuscript wins is exactly what you would expect — and it is also where this section's pitfall lives. The other thing to notice is that the first item in {latex,generic,} changes with the name of the running program. Invoked as pdflatex-dev it becomes {latex-dev,latex,generic,}, so the development tree is consulted first. kpathsea gives different answers depending on who is asking. And the 8,798 that -expand-path reported is also a warning: without the ls-R index, that is how many directories would have to be opened on every single lookup.
ls-R and TEXMFDBS: why only TEXMFHOME needs no mktexlsr
The answer fits on one line: TEXMFDBS, the list of trees that carry an index, does not include TEXMFHOME. Opening the 8,798 directories of the previous section every time is out of the question, so kpathsea keeps a filename database called ls-R at the root of each tree and consults that instead. Which trees have an index is spelled out by TEXMFDBS, and on this TeX Live 2024 it lists exactly four — precisely the trees that carried !! in TEXMF. TEXMFHOME is not among them. That is why TEXMFHOME is walked on disk every time, and why a file put there is found the instant it lands.
$ kpsewhich -var-value=TEXMFDBS
{!!/usr/local/texlive/texmf-local,
!!/usr/local/texlive/2024/texmf-config,
!!/usr/local/texlive/2024/texmf-var,
!!/usr/local/texlive/2024/texmf-dist}
# TEXMFHOME is absent from this list.
# The experiment: the SAME file, the SAME TDS layout, two different trees.
$ mkdir -p /tmp/t/tex/latex/demo && touch /tmp/t/tex/latex/demo/demo.sty
$ TEXMFHOME=/tmp/t kpsewhich -progname=pdflatex demo.sty
/tmp/t/tex/latex/demo/demo.sty # found - no ls-R, no mktexlsr
$ TEXMFLOCAL=/tmp/t kpsewhich -progname=pdflatex demo.sty
$ echo $?
1 # NOT found: "!!" means index-onlyThe ls-R file itself is unglamorous plain text. Its first line is always % ls-R -- filename database for kpathsea; do not change this line., after which each directory is listed with its contents. The texmf-dist/ls-R on this machine is 5.2 MB and 276,953 lines long, indexing 228,764 files across 16,063 directories. The command that rebuilds it is mktexlsr, and texhash is a symbolic link to it — the same program under a second name. The practical rule falls out cleanly: place a file by hand in TEXMFLOCAL or a system tree and you need mktexlsr; place it in TEXMFHOME and you do not. The experiment above is the whole reason why. Which command to reach for is covered in detail on the package and font management page.
kpsewhich --all: finding the file an older copy is hiding
kpsewhich --all NAME prints every match, in search order. Plain kpsewhich returns only the first hit — the file that will actually be read — so seeing the second and beyond needs --all. The classic accident, “there are two files with this name and the older one is winning”, becomes visible in a single command. Even on a clean TeX Live 2024, amsmath.sty really does exist twice: the stable copy in tex/latex/amsmath/ and the development copy in tex/latex-dev/amsmath/.
$ kpsewhich --all amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex/amsmath/amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex-dev/amsmath/amsmath.sty
# Same two files, opposite order - because the program name changed the path.
$ kpsewhich --all -progname=pdflatex-dev amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex-dev/amsmath/amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex/amsmath/amsmath.styThose two runs are an experiment you can repeat without touching a thing, and they confirm the rule that the first hit wins. In practice, though, the place this bites is almost always next to the manuscript. Because TEXINPUTS starts with ., an old amsmath.sty or article.cls that drifted into the project folder years ago is read before the distribution's current copy. Worse, it fails in the nastiest possible shape: the document builds on your machine and not on a co-author's. When you meet an error you cannot account for — ! LaTeX Error: Command \... already defined. and its relatives — or a disagreement between two machines, type kpsewhich --all first. It is the shortest path to the answer.
Using kpsewhich: -var-value versus -expand-path
-var-value shows what the configuration says; -expand-path shows what is actually on disk. That gap is what makes them useful for diagnosis. Print TEXMF both ways on this TeX Live 2024 and -var-value lists seven trees complete with their !! markers, while -expand-path returns only five. The two that dropped out — ~/Library/texlive/2024/texmf-config and ~/Library/texmf — simply have not been created yet. So if a tree appears in the configuration but not in the expansion, that directory does not exist. When a file you are sure you put in TEXMFHOME cannot be found, this is the first thing to suspect.
| Command | What it answers | When to reach for it |
|---|---|---|
kpsewhich NAME | The first match — the file that will really be read | Start here: confirm it is the file you think it is |
kpsewhich --all NAME | Every match, in search order | To see whether an older copy is shadowing it |
kpsewhich -var-value=TEXMFHOME | The value the configuration gives a variable, !! markers included | To confirm where a tree is meant to be, without guessing |
kpsewhich -expand-path=$TEXMF | The expansion restricted to directories that actually exist | To catch the gap between configuration and reality |
kpsewhich -show-path=tex | The ordered directory list used for that file type | To trace why things are found in the order they are |
texmf.cnf: where the variables get their values
Every variable so far — TEXMF, TEXINPUTS, the location of each tree — is written down in a configuration file called texmf.cnf. Before doing anything else kpathsea reads it and picks up its operating parameters: the search paths, where each tree sits, memory limits and the rest. The interesting part is that there can be more than one texmf.cnf. kpathsea reads them in order along a dedicated search path, TEXMFCNF, and for any given variable takes the first definition it finds — later files do not override earlier ones. On this machine, two are stacked up.
$ kpsewhich -all texmf.cnf
/usr/local/texlive/2024/texmf.cnf # TeX Live's thin override, read first
/usr/local/texlive/2024/texmf-dist/web2c/texmf.cnf # hundreds of lines of defaultsThe thin texmf.cnf on top — the file of differences TeX Live writes out — is read first, and the fat file of defaults second. So the convention for changing a value permanently is not to edit the distribution's file but to write only the lines you need in a higher-priority location. TEXMFLOCAL/web2c/texmf.cnf is that place. Do it this way and your settings survive a distribution upgrade, and a glance at a few lines tells you what you changed. To sum up: texmf.cnf fixes where the trees are and what the search paths look like, and kpathsea then finds the target in that order, usually via the ls-R index. Those two layers are the whole mechanism behind a single quiet line of \usepackage{...}.
PATH finds the program; kpathsea finds the files
These are two entirely separate mechanisms, and confusing them sends a diagnosis off the rails. kpathsea looks for the files TeX reads — .sty, .cls, fonts — but before any of that the shell has to find the executable itself, pdflatex. That is the operating system's job, and it consists of walking the directories listed in the PATH environment variable in order. TeX Live gathers its executables into a single bin directory per OS and architecture, and on macOS MacTeX provides a year-independent stable link at /Library/TeX/texbin. So pdflatex: command not found is not a kpathsea problem; it is almost certainly a PATH problem. Conversely, ! LaTeX Error: File 'foo.sty' not found. has nothing to do with PATH. The procedure for setting it up belongs to the desktop installation page.
$ which pdflatex
/Library/TeX/texbin/pdflatex
$ readlink /Library/TeX/texbin
Distributions/Programs/texbinWhere to put your own .sty file
Personal files go in TEXMFHOME, lab-wide files in TEXMFLOCAL, and in both cases you keep the TDS layout. That is the whole rule. What you must not do is guess the location — the default TEXMFHOME differs by OS: ~/texmf on Linux but ~/Library/texmf for MacTeX on macOS. So always start from kpsewhich -var-value=TEXMFHOME. Conversely, a file that belongs only to one submission bundle — a conference myconf.cls, a journal journal.sty — may sit next to the manuscript, because TEXINPUTS looks at . first. But putting a generic name such as article.cls beside the manuscript is manufacturing the shadowing accident of the previous section with your own hands.
# Ask for the tree, never hard-code it: this is ~/texmf on Linux,
# ~/Library/texmf on macOS, %USERPROFILE%\texmf on Windows.
HOME_TREE="$(kpsewhich -var-value=TEXMFHOME)"
mkdir -p "$HOME_TREE/tex/latex/thesisstyle"
cp thesisstyle.sty "$HOME_TREE/tex/latex/thesisstyle/"
# Confirm which copy TeX will pick up. No mktexlsr needed for TEXMFHOME.
kpsewhich thesisstyle.sty
kpsewhich --all thesisstyle.sty # and check nothing else shadows itOnce kpsewhich reports the path you expected, the manuscript needs nothing more than \usepackage{thesisstyle}. When it reports nothing, suspect three things in order. (1) Is the file under tex/latex/<package>/? TEXINPUTS only ever looks below tex/. (2) Does the filename match in case? (3) If you put it in a system tree, did you run mktexlsr? Checking in that order turns the symptom from “TeX is broken” into “where on the search map did I put it?” — a question that has an answer.