Philipp Lehman is best known for biblatex and csquotes, but the piece of his that runs quietly in the most preambles is probably the third one, etoolbox. The reason comes down to almost a single command: \patchcmd, which replaces just part of somebody else’s macro instead of redefining the whole thing. There is a catch, though. If the search text is not found, \patchcmd does nothing at all — no error, no warning. That is usually what is behind a preamble tweak that mysteriously “stops working” the day after a package update. This page covers etoolbox’s tests, flags, hooks and patches, then pgfkeys, the engine LaTeX packages use to build key=value interfaces, and finally \fpeval for real-number arithmetic.
What etoolbox is: an e-TeX toolbox with a LaTeX face
etoolbox is a programming toolbox for people writing classes and packages. It rewraps the low-level primitives that e-TeX added into something that feels like LaTeX2e, and adds a large helping of general-purpose conveniences on top. The version in TeX Live 2024 is 2.5k, dated 5 October 2020, and the copyright notice carries two names: Philipp Lehman (2007–2011) and Joseph Wright (2015–2020). Every modern TeX engine includes e-TeX, so \usepackage{etoolbox} is all it takes. Even now that expl3, the LaTeX3 programming layer, is widespread, etoolbox survives because it fits straight into the LaTeX2e world: arguments are plain #1, branching is the familiar {true}{false} pair, and above all there is \patchcmd for retro-fixing somebody else’s package. For real preamble work, that combination is hard to beat.
Writing tests: \ifdef, \ifdefempty, \ifstrequal
Every test in etoolbox has the same shape: a final {⟨code if true⟩}{⟨code if false⟩} pair. No \fi to remember, no agonising over where \else goes. “Is this command already defined?” is \ifdef{\cmd}{true}{false}, or \ifcsdef{name}{true}{false} if you have the name as a string (with \ifundef and \ifcsundef as the negatives). For strings there is \ifblank for “is it nothing but spaces?”, its negation \notblank, \ifstrequal{string}{string}{true}{false} for equality of two strings, \ifdefempty{\cmd}{true}{false} for “is this macro’s body empty?”, and \ifstrempty{string}{true}{false} for “is the string itself empty?”. Do not confuse these with the similarly named \ifdefined, which is an e-TeX primitive, not an etoolbox two-way branch.
\usepackage{etoolbox}
% provide a command only if nobody defined it yet
\ifdef{\highlight}
{} % already there: leave it alone
{\newcommand{\highlight}[1]{\textbf{#1}}}
% behave differently on an empty argument
\newcommand{\field}[1]{\ifblank{#1}{(none)}{#1}}
% numeric tests, same two-way shape
\ifnumcomp{\value{page}}{>}{10}{late}{early}
\ifnumodd{\value{page}}{recto}{verso}There is one distinction here that even the documentation makes easy to miss: \ifstrequal and \ifdefstring are not expandable. Look at the etoolbox source and you find both defined with \newrobustcmd — that is, carrying e-TeX’s \protected prefix — so they do not behave as expected inside \edef, \typeout or \csname. Write \typeout{\ifstrequal{abc}{abc}{SAME}{DIFF}} and the log shows not SAME but the literal \ifstrequal {abc}{abc}{SAME}{DIFF}. \ifdefempty, by contrast, is expandable and leaves only the result behind inside an \edef. All of them branch correctly in the document body; the difference appears only inside \edef — knowing where that line falls saves a day of chasing a test that “does not work.”
Boolean flags: \newtoggle or \newbool?
The default choice should be \newtoggle, and the reason is namespacing: a toggle lives in its own namespace, so it can never collide with an existing command. Declare it with \newtoggle{draft}, switch it with \toggletrue{draft} / \togglefalse{draft} (or \settoggle{draft}{true}), branch with \iftoggle{draft}{⟨true⟩}{⟨false⟩}, and invert with \nottoggle. The other family, the bool, offers the same shape — \newbool{draft}, \setbool{draft}{true}, \booltrue, \boolfalse, \ifbool{draft}{⟨true⟩}{⟨false⟩} — but internally uses the same machinery as LaTeX’s \newif, so it consumes one command name, \ifdraft. That is the deciding point: choose a bool when you need to interoperate with existing \newif-based code, and a toggle for everything else.
| Command | Meaning | Note |
|---|---|---|
\newtoggle{f} | Declares flag f, initially false | Own namespace; consumes no command name |
\settoggle{f}{v} | Sets f to v (true / false) | Same as \toggletrue / \togglefalse |
\iftoggle{f}{T}{F} | T when true, F when false | Three arguments; no \fi needed |
\newbool{f} | The bool version of a flag | Same machinery as \newif; claims one command name |
\ifbool{f}{T}{F} | The bool version of the branch | Interoperates with existing \newif-based code |
\newrobustcmd and \robustify: making a macro that does not break
\newrobustcmd is written exactly like \newcommand but produces a robust command. The difference shows instantly under \meaning: a command made with \newcommand reports \long macro:->…, whereas one made with \newrobustcmd reports \protected\long macro:->…. In other words, it skips LaTeX’s traditional two-step \protect dance and uses e-TeX’s \protected prefix directly. That is why it can sit inside a moving argument — a heading or a caption — without being expanded and broken on its way into the table-of-contents file. For a fragile command somebody else already defined, \robustify{\cmd} hardens an existing definition in place.
\patchcmd: replacing just part of somebody else’s macro
\patchcmd finds a search string inside the body of an already-defined macro and replaces just that. It takes five arguments: \patchcmd{\cmd}{⟨search⟩}{⟨replace⟩}{⟨on success⟩}{⟨on failure⟩}. If the search text is found, it substitutes and runs the fourth argument; if not, it leaves the macro untouched and runs the fifth. Only the first occurrence is replaced — with two \small in the body, only the earlier one changes. Here is a genuinely useful case. The thebibliography environment of the article class opens with \section*{\refname}, so replacing that \section* with \section turns the bibliography into a numbered section that also appears in the table of contents. Measured, the .toc file duly received \contentsline {section}{\numberline {2}References} and the patch did exactly what it promised.
\usepackage{etoolbox}
\makeatletter % the target usually contains @
\patchcmd{\thebibliography}
{\section*} % search
{\section} % replace
{\typeout{bibliography patch applied}} % on success
{\PackageWarning{mypkg}{bibliography patch failed}} % on failure
\makeatother
% result: "References" becomes a numbered section and enters the ToC
% .toc -> \contentsline {section}{\numberline {2}References}{1}{}When a patch silently does nothing: \tracingpatches and xpatch
A failed \patchcmd is completely silent. Measured: hand it a pattern that does not match and leave both branches empty, and the run finishes with zero errors and zero warnings, leaving no trace in the log. Hence the iron rule — never leave the failure branch empty; put a \PackageWarning in it. Then you get Package mypkg Warning: bibliography patch failed on input line 5. and you find out the day after the update rather than months later. To work out why, put \tracingpatches in the preamble: etoolbox.def is loaded and a diagnosis for every patch is written to the log.
[debug] tracing \patchcmd on input line 5
[debug] analyzing '\thebibliography'
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] ++ macro can be retokenized cleanly
[debug] -- search pattern not found in replacement text
[debug] analyzing '\nosuchcommand'
[debug] -- control sequence is undefined or \relax
[debug] analyzing '\LaTeX'
[debug] -- macro cannot be retokenized cleanly
[debug] -> the macro may have been defined under a category
[debug] code regime different from the current oneThe diagnoses come in three kinds. “The search pattern is not in the body” (-- search pattern not found in replacement text) is the classic sign that a package update changed the definition; inspect the new one with \show and rewrite your search text. “The command is undefined” (-- control sequence is undefined or \relax) means you are patching too early — move the patch later, for instance into \AtBeginDocument. The third, “cannot be retokenized cleanly” (-- macro cannot be retokenized cleanly), is a category-code problem: the macro was defined under a different catcode regime from the current one, so check that you are patching inside \makeatletter.
And there is one failure that does not even show up in the diagnosis: \patchcmd does not work on a command that takes an optional argument. Ask \meaning about an \opt defined as \newcommand{\opt}[2][X]{...} and you get \@protected@testopt \opt \\opt {X} — \opt is only a front door that dispatches, and the real body lives in a separate command called \\opt. So \patchcmd{\opt}{small}{LARGE} searches the front door and fails. For that case use \xpatchcmd from the xpatch package, which extends etoolbox: measured, the identical arguments succeeded and the inner macro came out as \long macro:[#1]#2-><#1|#2|LARGE>. xpatch also supplies the matching commands for environments.
Hooks, appending and lists: getting your code into somebody else’s
If you can avoid rewriting a macro body, do. etoolbox supplies a generous set of hooks for “run this code at that moment.” Beginning and end of document are the LaTeX kernel’s \AtBeginDocument and \AtEndDocument, but etoolbox adds \AtEndPreamble (the very end of the preamble), \AfterEndDocument (truly last), and hooks around a specific environment: \AtBeginEnvironment{⟨env⟩}{⟨code⟩}, \AtEndEnvironment, \BeforeBeginEnvironment and \AfterEndEnvironment. To add to an existing macro or hook afterwards, use \appto{\cmd}{⟨code⟩} (at the end) and \preto{\cmd}{⟨code⟩} (at the start); \gappto is the global variant and \eappto expands the added code first. For a macro that takes arguments, use \apptocmd / \pretocmd, which carry success and failure branches — and these too merely run the failure branch on an undefined command without raising an error, so the same caution as \patchcmd applies.
\usepackage{etoolbox}
% run code every time an environment starts -- no patching required
\AtBeginEnvironment{quote}{\itshape}
\AtBeginEnvironment{itemize}{\setlength{\itemsep}{2pt}}
% append to a macro that takes an argument (note the two branches)
\newcommand{\greet}[1]{Hello #1}
\apptocmd{\greet}{!}{}{\PackageWarning{mypkg}{could not extend \string\greet}}
% \greet is now \long macro:#1->Hello #1!
% lightweight lists and loops
\listadd{\mylist}{alpha}\listadd{\mylist}{beta}
\newcommand{\asitem}[1]{\item #1}
\begin{itemize}\forlistloop{\asitem}{\mylist}\end{itemize}
\begin{itemize}\forcsvlist{\asitem}{apples, pears, plums}\end{itemize}The list side is covered too. \listadd{\mylist}{⟨item⟩} appends to an internal list, and \forlistloop{⟨handler⟩}{\mylist} applies a one-argument handler to each element. When you already have a comma-separated string in hand, \docsvlist{a,b,c} and \forcsvlist{⟨handler⟩}{a,b,c} are the quick options, and \DeclareListParser builds a parser for a separator of your own choosing. In practice the commonest use is taking a package option and iterating over it as a list.
pgfkeys: giving your own tool a key=value interface
pgfkeys is the key=value engine that ships inside PGF/TikZ. TikZ’s familiar [draw, thick, fill=blue] syntax, and the \…setup{...}-style interfaces of many packages, are largely built on it (in TeX Live 2024, PGF is version 3.1.10, copyright Till Tantau). At its centre sits one command, \pgfkeys{/my/key=value}. Keys are separated into namespaces by /-delimited paths (families), and each key is given a handler that says what to do when the key is called. Defining a key, in short, is choosing a handler.
.store in, .code, .is choice: choosing the right handler
Three handlers cover most real work: .store in=\macro to keep the value verbatim, .code={... #1 ...} to run code with the value (which arrives as #1), and .is choice to enumerate a fixed set of options. On top of that, .default=value supplies the value used when the key is called without =value, and .initial=value gives the key a starting value (readable with \pgfkeysvalueof{/path/key}). If your package exposes an entry point such as \mypkgsetup{...}, the idiom is \pgfqkeys{/mypkg}{⟨key list⟩} — the “q” is for quick, and it is shorthand for \pgfkeys{/mypkg/.cd, ⟨key list⟩}. Wrap that in a one-line macro and your users configure everything with short key names alone.
\usepackage{pgfkeys}
\pgfkeys{
/book/title/.store in = \bookTitle,
/book/edition/.store in = \bookEd,
/book/edition/.default = 1, % value used when called bare
/book/pages/.initial = 100, % starting value
/book/layout/.is choice, % a fixed set of options
/book/layout/wide/.code = {\def\bookLayout{WIDE}},
/book/layout/narrow/.code = {\def\bookLayout{NARROW}},
/book/note/.code = {\def\bookNote{<<#1>>}}, % #1 is the value passed in
}
\pgfkeys{/book/title=TeX by Topic, /book/edition, /book/layout=wide}
\pgfkeysvalueof{/book/pages} % -> 100
% a one-line entry point for your users
\newcommand{\mypkgsetup}[1]{\pgfqkeys{/book}{#1}}
\mypkgsetup{title = My Report, edition = 2}The pgfkeys error messages are helpfully specific, and good search terms besides. Pass a key that was never defined and you get ! Package pgfkeys Error: I do not know the key '/book/nosuchkey', to which you passed '1', and I am going to ignore it. Perhaps you misspelled it. Pass a choice that is not in an .is choice list and you get ! Package pgfkeys Error: Choice 'sideways' unknown in choice key '/book/layout'. I am going to ignore this key. Both ignore the problem and carry on — typesetting does not stop, so a misspelled key goes unnoticed unless you read the log. On the LaTeX3 side there is an equivalent, l3keys (\keys_define:nn and friends). A reasonable split: l3keys when writing a new package in expl3, pgfkeys when matching TikZ-derived code or an existing codebase.
Real-number arithmetic: \fpeval no longer needs xfp
TeX’s integer arithmetic strains as soon as decimals are involved — division in \numexpr rounds, for one. That is what \fpeval is for: \fpeval{1/3} gives 0.3333333333333333, \fpeval{sqrt(2)} gives 1.414213562373095, \fpeval{sind(30)} gives 0.5, and \fpeval{round(2/3, 4)} gives 0.6667. To combine it with a length, just append the unit: \setlength{\x}{\fpeval{345/7}pt}. One claim worth dating explicitly: in the LaTeX2e shipped with TeX Live 2024 (the 2023-11-01 release), \fpeval, \inteval and \dimeval live in the kernel, and \usepackage{xfp} is not required. xfp itself now defines them with \ProvideExpandableDocumentCommand — “supply them if they are missing” — so loading it does no harm, and it remains the safe choice if you also have to support older installations.
Finally, a rough guide to combining the three. To change somebody else’s behaviour slightly from your preamble, reach for etoolbox — and always put a warning in the failure branch. To give your own package a configuration interface, reach for pgfkeys or l3keys. To compute a dimension or a ratio, reach for \fpeval. And the first question is always whether you can avoid patching at all: try \renewcommand on a public command, then a hook such as \AtBeginEnvironment, then a proper package option — and only when none of those work should you draw \patchcmd. A patch may work today, but it is guaranteed only until tomorrow’s package update.