expl3 (“expl-three”) is the programming language that powers modern LaTeX from the inside. It grew out of the effort once called “LaTeX3,” and today it is loaded by default in the LaTeX kernel. It goes well beyond what you can write with \newcommand: it gives you variables, data structures, integer arithmetic, and even floating point — a serious programming layer on which xparse, siunitx, and fontspec are built. This page walks through how to switch into its syntax, its distinctive naming scheme, and its core modules.
What expl3 is
TeX is fundamentally a macro processor: you define commands (macros) with \def or \newcommand. But writing a large package exposed how inconsistent TeX’s raw primitives are — expansion control and variable handling became a craft. expl3 gives the TeX and e-TeX primitives new names, names functions and variables systematically, and makes each function’s argument types explicit. Built up over many years by the LaTeX Project, it is, in effect, both a standard library and a programming language for LaTeX.
One fact matters in practice: expl3 is loaded by default in modern LaTeX kernels. The official interface3 manual states it plainly: “With an up-to-date LaTeX 2ε kernel, this material is loaded as part of the format.” So you no longer need \usepackage{expl3}. Most users never write expl3 directly — they meet it indirectly through xparse (\NewDocumentCommand) and packages such as siunitx, fontspec, and l3keys2e, all of which are built on it.
Switching syntax — \ExplSyntaxOn
You write expl3 code inside a region bounded by \ExplSyntaxOn and \ExplSyntaxOff. Within that region two things about how characters are read (their category codes) change. First, spaces and newlines are ignored, so you can indent your code and put gaps between tokens to keep it readable. Second, **_ (underscore) and : (colon) become “letters,”** so they may appear inside command names — something ordinary LaTeX forbids — letting you build the long descriptive names expl3 favors.
Because spaces are ignored, producing a real space in the output needs a different notation. Inside an expl3 region you write an explicit space as ~ (a tilde). So to typeset “Item: apple” you write something like Item:~#1, since a bare space would simply vanish. Once you internalize that one rule, the rest reads and writes much like an ordinary programming language.
% expl3 はカーネルに含まれるので \usepackage{expl3} は不要
\ExplSyntaxOn
% ここでは空白・改行が無視され、 _ と : が命令名に使える
\tl_new:N \l_greeting_tl
\tl_set:Nn \l_greeting_tl { Hello,~world! }
\tl_use:N \l_greeting_tl
\ExplSyntaxOffThe naming scheme and argument signatures
An expl3 function name has the form \⟨module⟩_⟨description⟩:⟨arg-signature⟩. Everything up to the first _ is the module (the data type or area it belongs to); everything up to the : is the descriptive name; and what follows the : is the argument signature (the “arg-spec”). In \seq_put_right:Nn, for instance, the module is seq (sequences), the description is put_right (append on the right), and the signature is Nn. Each letter of the signature says how that argument is processed before it is passed on.
The main argument specifiers are below (drawn from the official interface3 manual). N and n are the most basic, and they alone get you a long way. The expanding ones — x, e, o, f — and the value-extracting ones — V, v — can wait until you are comfortable.
| Specifier | Meaning |
|---|---|
N | No manipulation; a single token (usually one control sequence). |
n | No manipulation; a braced group of tokens. |
c | Turns the argument into a **control sequence via \csname** before use. |
V / v | Passes the value of a variable (V from a single token, v builds the name first). |
o | Expands the argument once before use. |
x / e | Full expansion (x is like \edef, not expandable; e uses \expanded). |
f | Expands up to the first unexpandable token (left to right). |
p | A TeX parameter text (#1#2…); used when defining a function. |
Variables are named the same way, but begin with a single letter for scope. l_ means local (changed only within the current TeX group), g_ means global, and c_ means constant (its value should not change). A type identifier ends the name: _tl (token list), _int (integer), _seq (sequence), _prop (property list), _clist (comma list), _fp (floating point), and so on. So \l_my_name_tl reads at a glance as “a local token-list variable,” and \g_counter_int as “a global integer.” Each module also provides scratch variables — disposable temporaries — such as \l_tmpa_tl and \l_tmpb_int.
The core modules
expl3 is split into modules by data type, and they all share the same rhythm: create, set, use. Start with defining functions. \cs_new:Npn defines a new function and raises an error if one of that name already exists (cs is “control sequence”). \cs_set:Npn defines one too, but only within the current TeX group, and it does not complain about redefinition. Both use :Npn, where N is the function being defined, p is its parameter text (#1#2…), and n is the body (the replacement text).
Next, **token lists (tl)** — the most basic variable, usable much like a string. Declare one with \tl_new:N, replace its contents with \tl_set:Nn (the old contents are discarded), and typeset it with \tl_use:N. **Integers (int)** follow the same shape: declare with \int_new:N, assign with \int_set:Nn, and use \int_eval:n to evaluate an integer expression and return its value (for example, \int_eval:n { 2 + 3 * 4 } is 14).
**Sequences (seq)** are lists with access at both ends (also usable as stacks). Create one with \seq_new:N, append an element on the right with \seq_put_right:Nn, and walk every element with \seq_map_inline:Nn, which hands each item to your code as #1. **Property lists (prop)** are dictionaries — key-to-value maps. Create one with \prop_new:N and store a key/value pair with \prop_put:Nnn (the Nnn being variable, key, value).
**Comma lists (clist)** handle exactly what the name suggests — a comma-separated list of values — and every command begins \clist_ (\clist_new:N, \clist_set:Nn, and so on). Finally, **floating point (fp)**: \fp_eval:n evaluates an expression with decimals and supports many scientific functions such as sin, sqrt, and pi (for example \fp_eval:n { sqrt(2) } or \fp_eval:n { 2 * pi }). Choosing between it and the integer-only \int_eval:n is the thing to keep straight.
A small example — a function and a sequence
Putting these together: the example below defines a function \example_add:n with \cs_new:Npn that adds one item to a sequence, calls it a few times, then prints every item list-style with \seq_map_inline:Nn. Notice that the _ and : in the command names are no trouble — we are inside an \ExplSyntaxOn region.
\documentclass{article}
\begin{document}
\ExplSyntaxOn
% シーケンスを 1 個用意する
\seq_new:N \l_example_fruits_seq
% 「右端に 1 項目追加する」関数を定義する
\cs_new:Npn \example_add:n #1
{ \seq_put_right:Nn \l_example_fruits_seq {#1} }
% 何度か呼ぶ
\example_add:n { apple }
\example_add:n { banana }
\example_add:n { cherry }
% 全項目を出力する(各項目が #1 に入る。 ~ は空白)
\seq_map_inline:Nn \l_example_fruits_seq
{ Fruit:~#1 \par }
\ExplSyntaxOff
\end{document}Compiling this prints “Fruit: apple,” “Fruit: banana,” and “Fruit: cherry” on three separate lines. The #1 in \cs_new:Npn is the defined function’s argument, while the #1 in \seq_map_inline:Nn is each item being mapped over — both arrive as n-type (the braced contents). Note the ~ in Fruit:~#1: replace it with a bare space and the space is ignored, giving the run-together “Fruit:apple.”
How it is used in practice
For ordinary document writing you rarely need to write expl3 directly. But the moment you set out to write a package or class, expl3 is now effectively the standard. A common pairing is to take a user-facing command with \NewDocumentCommand (xparse) and implement its body in expl3. Keep two things distinct: xparse’s argument specification (document-level arguments such as m, O{...}, s) is not the same as expl3’s argument signature (programming-level processing such as N, n). The former is covered in detail on the xparse page.
Learning expl3 lets you write, clearly, things that are awkward with \newcommand alone. A few points worth remembering:
- Always wrap code in
\ExplSyntaxOn…\ExplSyntaxOff. Inside, spaces are ignored; use~when you need one in the output. - No
\usepackage{expl3}is needed — it is in the kernel; inside a.styyou can write it straight after the package declaration. - Functions are
\⟨module⟩_⟨description⟩:⟨signature⟩; variables are\⟨scope⟩_⟨name⟩_⟨type⟩, with scopel_/g_/c_. - Do not invent command names. The naming is strict — the official interface3 manual (texdoc interface3) is the authoritative source.