Index

The index at the back of a book — the list of terms with the pages they appear on — is not something LaTeX builds itself. LaTeX's job stops at collecting the \index{…} marks you place in the text and writing them out as a raw list called .idx. Sorting that list into an index is done by a separate program, makeindex. The division of labour has a pedigree: makeindex's own documentation credits Leslie Lamport, the author of LaTeX, with contributing significantly to its design. This page runs from the makeidx package and the \makeindex declaration through entry syntax (! subentries, @ sort keys), the build passes, and the answer to why “Ångström” files after “Zulu”.

The four pieces of an index — and why makeidx is only eight lines

Building an index takes four pieces: \usepackage{makeidx} and \makeindex in the preamble, an \index{term} wherever a term appears, and \printindex where the list should be set. The surprise is that two of those four — \makeindex and \index — are already in the LaTeX kernel (latex.ltx). What the makeidx package adds is \printindex plus \see and \seealso for cross-references, and that really is about eight lines of code. The design tells you something: the heavy work of indexing was meant to live outside LaTeX from the beginning.

  • \usepackage{makeidx} — supplies \printindex and the \see/\seealso commands (preamble).
  • \makeindex — the declaration that opens \jobname.idx and redefines \index into the version that actually writes (preamble only). The terminal reports Writing index file mydoc.idx.
  • \index{term} — the mark you place where a term appears. It prints nothing; only the page number at that spot is recorded.
  • \printindex — the command that typesets the finished index. It amounts to reading the .ind file, and normally sits at the end of the document.

It is worth stressing that \index is an invisible mark. You still write the word in the body yourself and put \index{…} right after it: random numbers\index{random numbers} are used. And there is one pitfall that matters. If you forget \makeindex in the preamble, \index swallows its argument and does nothing — that is literally the kernel's default definition — so no error and no warning appear, and only the index comes out empty. When dozens of \index calls produce nothing at all, suspect that one missing line first.

latex
\documentclass{article}
\usepackage{makeidx}
\makeindex                        % without this line, \index does nothing
\begin{document}

METAFONT\index{METAFONT} draws the shapes,
TeX\index{TeX} sets the type.
We cover random numbers\index{random numbers|textbf} here,
and touch on groups\index{group} and rings\index{ring}.
The treatment of algorithms\index{algorithm|(} starts here ...

% ... several pages later ...
... and the treatment of algorithms\index{algorithm|)} ends here.

\printindex
\end{document}

Writing an entry: the four characters !, @, | and the double quote

The argument of \index has a small syntax of its own, built on four special characters. The point to hold on to is that those four are interpreted by makeindex, not by LaTeX. To LaTeX the argument is just a string, poured into .idx as written. A mistake in the syntax therefore draws no complaint at typesetting time; it surfaces only when makeindex runs, as a warning in the .ilg log.

Subentries use !. The exclamation mark separates levels: \index{animals!cats} puts “cats” under a main entry “animals”. Repeating ! nests further, down to three levels (0, 1 and 2) — that is makeindex's designed limit. Sort keys use @. Written sortkey@display, it separates the string used for ordering from the string actually printed: \index{alpha@$\alpha$} prints α in the index but files it where “alpha” belongs. For symbols and mathematics, whose glyphs sort into nonsense, this is not optional.

Page-number formatting uses |. After the vertical bar, name a command taking one argument (without its leading backslash) and that page number alone is set with it. \index{cat|textbf} is the classic way to bold the page where a term is defined; |textit or a command of your own works just as well. Page ranges use |( and |). When a topic runs over several pages, open with \index{recursion|(} and close with \index{recursion|)} to get 12--15. Note also that makeindex contracts three or more consecutive pages into a range by itself; -r is the option that turns that automatic behaviour off.

Cross-references also go after |. \index{dog|see{pets}} prints “dog, see pets” instead of a page number, and |seealso{…} gives “see also”. Both work by calling the \see and \seealso commands that makeidx defines, so the words themselves change with \seename (“see” by default) and \alsoname (“see also”) if you write in another language. Finally, the double quote escapes: to put !, @, | or the quote itself into an entry as an ordinary character, precede it with a double quote — \index{C"!} yields the entry “C!”. This is where indexes of C and C++ usually come unstuck.

CharacterRoleExample
!Subentry, up to three levels\index{animals!cats}
@Sort key: separates ordering from printing\index{alpha@$\alpha$}
|( |)Open and close a page range\index{recursion|(}\index{recursion|)}
|cmdSet that page number with a command (bold, …)\index{cat|textbf}
|see |seealsoPoint at another entry instead of a page number\index{dog|see{pets}}
"Treat the next special character literally\index{C"!} gives the entry “C!”

Running makeindex: from .idx to .ind, and the No file mydoc.ind. symptom

An index does not finish in one compilation. Like bibtex, it is a three-stage affair with an external program in the middle. First LaTeX collects the \index calls into mydoc.idx — a plain file of \indexentry{term}{page} lines you can open and read. Then makeindex sorts it into a typesettable mydoc.ind. Finally another LaTeX run lets \printindex read mydoc.ind, and the index appears in the document. A record of the sorting is left in mydoc.ilg, which is where you look when an entry's syntax was wrong.

shell
pdflatex mydoc        # writes mydoc.idx  ("Writing index file mydoc.idx")
makeindex mydoc       # mydoc.idx -> mydoc.ind, log in mydoc.ilg
pdflatex mydoc        # \printindex reads mydoc.ind

# -s picks a style file, -o names the output, -t names the log
makeindex -s style.ist -o mydoc.ind -t mydoc.ilg mydoc.idx

Forget the middle step and the symptom is remarkably quiet: no error, no warning, just this one line in the log — No file mydoc.ind.. The reason is the mechanism itself, because \printindex amounts to a call to \@input@, which reads the file if it exists and otherwise prints exactly that line. That silence is why a document can come out looking fine with its entire index missing. In practice, though, latexmk runs the round trip for you: it calls makeindex whenever .idx changes and re-runs LaTeX as many times as needed, so you type the three steps by hand less and less often.

Why “Ångström” sorts after “Zulu”: how makeindex orders entries

What makeindex orders is not the word you see but the sort key — and without an @, the key is simply the entry text. The default order is documented: symbols, then numbers, then letters, with letters compared case-insensitively first, uppercase winning only when the spellings are otherwise identical. As a design for English that is entirely adequate. The trouble is the scope of “letters”, which for makeindex means the English alphabet and digits and nothing else. Hand Ångström and émile to makeindex 2.17 as shipped with TeX Live 2024 and they land not under A and E but at the very end of the index, after Zulu.

shell
# entries written with no sort key at all:
#   +plus   9nine   apple   sea lion   seal   Zulu   Angstrom   emile
# (the last two really spelled Ångström and émile)

makeindex mydoc      # default: word ordering
  +plus / 9nine / apple / sea lion / seal / Zulu / Ångström / émile

makeindex -l mydoc   # letter ordering: blanks do not count
  +plus / 9nine / apple / seal / sea lion / Zulu / Ångström / émile

# the fix is an ASCII sort key, not an accented one:
#   \index{Angstrom@Ångström}   files under A
#   \index{emile@émile}         files under E

Two things follow. First, give accented words an ASCII sort key: \index{Angstrom@Ångström} still prints Ångström but files it under A. A common misreading is that \index{Ångström@Ångström} fixes anything — it does not, because the key side is still non-ASCII. Second, makeindex offers a choice of ordering. The default is word ordering, in which a blank sorts before any letter, so “sea lion” comes before “seal”. Add -l for letter ordering, where blanks do not count at all and “seal” comes first. Dictionary-style listings want -l; telephone-book style wants the default. For German there is also -g, which follows DIN 5007.

In a document with too many accents for hand-written sort keys to be realistic, the quicker route is to change the sorting program itself. xindy — reached from LaTeX through texindy — is built around multilingual collation and ships with TeX Live. Give the same Ångström to texindy -L english -C utf8 and it settles correctly between abacus and zebra, under A, with no sort key at all. The larger the index grows, the cheaper it becomes to swap the collator rather than to type keys.

Japanese indexes: mendex and upmendex

The reasoning of the previous section applies unchanged to Japanese and Chinese, and the symptom is worse. Hand \index{群}, \index{環} and \index{体} to makeindex and they come out in character-code order, without a single warning — an order with no relation whatever to how the words are read. Because nothing errors, an index meant to run in syllabary order can turn out to be in no order at all. The answer here, as with hyperref, is to swap in the purpose-built tool: mendex for pLaTeX, upmendex for upLaTeX and LuaLaTeX. Both are makeindex-compatible, so it is a matter of replacing the word you were already typing.

What you gain is sorting by reading. In the makeindex era each entry needed its reading supplied as reading@display, and voiced marks had to be normalised by hand. upmendex uses ICU (International Components for Unicode) collation to order kana correctly, which removes much of that labour. Beyond that, a dictionary file passed with -d registers readings for words in bulk, so entries can often skip the @ reading entirely. As a rule of thumb: mendex with pLaTeX, upmendex with upLaTeX and LuaLaTeX; supplying readings through @ works in either.

shell
uplatex mydoc                 # writes mydoc.idx
upmendex -s style.ist mydoc   # kana sorted via ICU -> mydoc.ind
uplatex mydoc                 # \printindex reads mydoc.ind

# readings can still be given by hand with @, in either program:
#   \index{さくいん@索引}
#   \index{Knuth@クヌース}

Changing how the index looks: the .ist style file

The look of an index is governed by a style file (.ist), handed over with -s, as in makeindex -s style.ist mydoc. Its format is plain: a list of parameter value pairs, strings in double quotes, % starting a comment to end of line. What you write there instructs makeindex, not LaTeX, and it determines the contents of the .ind file directly. Styles for mendex and upmendex are upward-compatible with makeindex, so an existing .ist carries over unchanged.

  • headings_flag — set it nonzero and a group heading (the letter A, B, … or the symbol group) is inserted whenever the group changes (default 0).
  • heading_prefix / heading_suffix — the strings placed before and after that heading.
  • symhead_positive — the heading given to the symbol group when headings_flag is positive (default "Symbols").
  • delim_0 / delim_1 / delim_2 — the delimiter between an entry at each level and its page numbers (all default to ", "); a dotted leader goes here.
  • item_0 / item_1 / item_x1 — the strings inserted between entries and between levels (line breaks, indentation).
  • preamble / postamble — the code written at the head and foot of .ind (by default \begin{theindex} and \end{theindex}).
  • group_skip — the space inserted at a group boundary (by default \indexspace).
style.ist
% group headings in bold, and a dotted leader before the page number
headings_flag    1
heading_prefix   "{\\bfseries "
heading_suffix   "}\\nopagebreak\n"
delim_0          "\\dotfill "

The modern way: imakeidx and more than one index

imakeidx replaces makeidx and brings two substantial advantages. First, it calls the indexing program automatically during compilation, so an index behaves much like the table of contents. Second, it supports more than one index in a single document — a subject index and a name index, say. You configure it by passing options to \makeindex: name= distinguishes an index, title= sets its heading, intoc lists it in the table of contents, program= chooses the sorting program (makeindex, xindy, texindy, or mendex / upmendex for Japanese) and options= forwards arguments such as -s style.ist. Write one \makeindex per index, route entries with \index[name]{…}, and print with \printindex[name].

Automatic invocation rests on shell escape, and that is the one part that depends on your environment. In the default TeX Live 2024 configuration makeindex sits on the restricted shell-escape allow-list, so imakeidx builds the index even without -shell-escape (run kpsewhich -var-value shell_escape_commands to see your own list). xindy, texindy, mendex and upmendex are not on that list, so those do require -shell-escape. Where shell escape is forbidden outright — some submission systems, strict CI — automatic invocation is unavailable; fall back to calling makeindex yourself in the three-step sequence, or let latexmk handle it.

latex
\documentclass{article}
\usepackage{imakeidx}

% two indexes, built during the compilation
\makeindex[name=subject, title=Subject index, intoc]
\makeindex[name=people,  title=Index of names, intoc,
           options={-s style.ist}]

\begin{document}
Groups\index[subject]{group} matter here.
Knuth\index[people]{Knuth, Donald} wrote TeX.

\printindex[subject]
\printindex[people]
\end{document}

% makeindex runs under restricted shell escape:
%   pdflatex mydoc
% xindy / mendex / upmendex need the full permission:
%   lualatex -shell-escape mydoc