A LaTeX environment is not a special construct at all. \begin{quote} calls a command named \quote, \end{quote} calls one named \endquote, and that is the entire mechanism; \newenvironment, the tool for defining your own environment, simply writes those two macros in one go. Grasp that pairing and the rest follows in a chain: why an environment name can collide with a command name, why arguments are unavailable in the end code, and why a mismatched \begin and \end produce the error text they do. This page follows that single thread through \newenvironment and \renewenvironment, arguments and optional arguments, the grouping you get for free, and the modern \NewDocumentEnvironment.
How to write \newenvironment: the begin code and the end code
Put \newenvironment{name}{begin code}{end code} in the preamble and \begin{name}…\end{name} becomes available in the body. The first argument is the environment name, written without a backslash; the second is the code run when LaTeX meets \begin{name}, and the third is the code run when it meets \end{name}. The body caught between them is not touched at all — it is typeset in the ordinary way. Designing your own environment therefore comes down to one question: what do you set up on the way in, and what do you clear away on the way out?
% preamble: define a warning environment
\newenvironment{warning}{%
\par\noindent\textbf{Warning:}\itshape
}{%
\par
}
% body: use it
\begin{warning}
This operation cannot be undone.
\end{warning}It is not laziness that leaves the end code in this example as a single \par. Nothing switches off the \itshape (italics) turned on in the begin code, yet the text after the environment comes out upright again. The next two sections explain why, but the short answer is that an environment automatically forms a group. When writing \newenvironment, the knack is to not undo what does not need undoing — an empty {} is a perfectly good end code.
What an environment really is: the macro pair \name and \endname
\begin{name} calls \name, and \end{name} calls \endname. That is not a guess: the definition of \end in latex.ltx literally invokes \csname end#1\endcsname. If you would rather see it than take it on trust, TeX has \show. Run \show\quote and \show\endquote on the standard quote environment and you find one macro that opens a \list and another that is nothing but \endlist. There is no such thing as environment syntax — only two macros paired up by name.
% ask LaTeX what the quote environment is actually made of
\show\quote
% > \quote=\long macro:
% -> \list {}{\rightmargin \leftmargin }\item \relax .
\show\endquote
% > \endquote=\long macro:
% -> \endlist .
% so \begin{quote} ... \end{quote} is, in effect:
% \begingroup \quote ... \endquote \endgroupYou meet the consequence of this almost immediately, in the form of an error. An environment name occupies the command name of the same spelling. Try \newenvironment{alpha}{...}{...} and the run stops with ! LaTeX Error: Command \alpha already defined. — because the Greek letter \alpha is already there. Environment names and command names were never two namespaces; they are one. \newenvironment{quote} fails with Command \quote already defined. for exactly the same reason, and the check is deliberate, to stop you clobbering something by accident. Names such as mywarning or thmbox, unlikely to collide with an existing command, are the safe habit.
The mirror image of the same logic is a well-known restriction on the \newcommand side: \newcommand{\endnotes}{...} is refused even though no such command exists anywhere. If anyone could invent names beginning with end, they would risk colliding with the \endname half that \end{...} dispatches to, so the whole prefix is reserved. The macros page tells that story in full; the point here is that the prohibition over there exists to protect the namespace the environments on this page are built from.
Environments are automatic groups: what reverts and what leaks out
Before it runs the begin code, \begin issues a \begingroup; after it runs the end code, \end issues the matching \endgroup. Begin code, body and end code therefore all sit inside a single group. That is why the warning environment above never had to switch \itshape back off. Doing the same with a macro means wrapping the contents in { … } yourself; in an environment, \begin…\end is that pair of braces. When a formatting change has to stay inside a fixed span, an environment is the more honest tool than a macro — and this is the whole reason.
That does not mean everything reverts at the closing brace. TeX assignments come in local and global flavours, and LaTeX makes counter operations deliberately global: \addtocounter in latex.ltx is written with \global\advance, so a counter you bump with \stepcounter inside an environment is still bumped after \end. That design is why a section or figure number stepped inside an environment does not evaporate. A macro defined with \newcommand inside an environment, by contrast, dies with \end, and using it outside gives ! Undefined control sequence.
| Done inside the environment | After \end | Why |
|---|---|---|
\itshape | reverts | a font switch is a local assignment |
\setlength | reverts | \setlength is a plain, local assignment |
\newcommand | disappears | the definition is local; outside you get ! Undefined control sequence. |
\stepcounter | survives | counter operations are written with \global |
\gdef | survives | an explicitly global definition |
\label | survives | a write to the .aux file is not undone by a group |
Environments that take arguments, and an optional argument with a default
To vary the contents from call to call, write the number of arguments in square brackets after the name and refer to them in the begin code as #1, #2 and so on: \newenvironment{name}[⟨n⟩]{begin code}{end code}, with #1 through #9, nine at most. Add a second pair of brackets — \newenvironment{name}[⟨n⟩][⟨default⟩]{...}{...} — and #1 becomes optional: \begin{name} uses the default, \begin{name}[x] puts x into #1. The syntax is identical to \newcommand, down to the rule that ⟨n⟩ is the total count including the optional argument.
% one mandatory argument
\newenvironment{point}[1]{%
\par\noindent\textbf{#1}\quad
}{%
\par
}
\begin{point}{Conclusion}
Back up early.
\end{point}
% first argument optional, default "Note"
\newenvironment{callout}[1][Note]{%
\par\noindent\textbf{#1:}\itshape
}{%
\par
}
\begin{callout} % label is "Note"
Nothing to configure.
\end{callout}
\begin{callout}[Warning] % #1 becomes "Warning"
This cannot be undone.
\end{callout}Why #1 in the end code is an error, and the standard way around it
The arguments #1, #2, … can be used only in the begin code. Put one in the end code and LaTeX complains not at use time but on the very line that defines it: ! Illegal parameter number in definition of \enddemo. Look at the name the error picks out — \enddemo. It confirms everything above: \newenvironment builds a macro \demo that takes arguments and a macro \enddemo that takes none at all. A #1 in the body of a macro with no parameter text is, to TeX, simply a syntax error. The arguments do not vanish at run time; there was never a slot to receive them.
If you really do need an argument at the close, the standard move is to save the value while still in the begin code. For text, the sturdy choice is a box: reserve one with \newsavebox and fill it with \sbox. Having a macro remember it via \def or \newcommand works too. Because the whole environment is one group, whatever you stored in the begin code survives intact to the end code. The citequote below sets an attribution flush right at the end of a quotation: it takes the source as #1 (default Shakespeare), tucks it into the box \quoteauthor, and pulls it out with \usebox in the end code.
\newsavebox{\quoteauthor}
\newenvironment{citequote}[1][Shakespeare]{%
\sbox\quoteauthor{#1}% save the argument while we still have it
\begin{quotation}%
}{%
\hspace{1em plus 1fill}---\usebox{\quoteauthor}% retrieve it here
\end{quotation}%
}
\begin{citequote}
To be, or not to be.
\end{citequote}
\begin{citequote}[Knuth]
Premature optimization is the root of all evil.
\end{citequote}\renewenvironment, and the starred \newenvironment*
To rework an environment that already exists, use \renewenvironment. Its argument syntax, [⟨n⟩][⟨default⟩] included, is word for word that of \newenvironment; only the precondition differs. Where \newenvironment succeeds only if the name is free, \renewenvironment succeeds only if it is taken, and applying it to a name that does not exist stops with ! LaTeX Error: Environment nosuch undefined. It suits sweeping changes — italicising every quote in a document, say. Remember, though, that redefining an environment supplied by a class or package can drag along any other code that depends on it.
% italicise every quote in the document
\renewenvironment{quote}{%
\list{}{\rightmargin\leftmargin}\item\relax\itshape
}{%
\endlist
}Both \newenvironment and \renewenvironment also have a starred form, written with a * after the name. What the star changes is whether an argument may contain a blank line. Unstarred arguments may span paragraphs (\par); starred ones are short arguments, and a blank line inside one stops the run with ! Paragraph ended before \shortenv was complete. That sounds like a restriction, but it is really a kindness: when a missing closing brace sends an argument running away, the star makes it stop at the next blank line instead of at the end of the document. There is, incidentally, no standard “define only if absent” counterpart to \providecommand for environments — for that, reach for the tool in the next section.
\NewDocumentEnvironment: arguments the end code can actually see
Use \NewDocumentEnvironment{name}{⟨arg-spec⟩}{begin code}{end code} and the saved-box detour becomes unnecessary, because in this interface both halves can refer to the same arguments. Instead of a count, you write an argument specification: letters such as m (mandatory), o (optional), O{default} (optional with a default) and s (an optional star). It began life in the xparse package, but the release of 1 October 2020 folded it into the LaTeX kernel, so it now works without any \usepackage (the xparse page lists the specifiers).
% O{...} is an optional argument with a default; #1 works in both halves
\NewDocumentEnvironment{citequote}{O{Shakespeare}}{%
\begin{quotation}%
}{%
\hspace{1em plus 1fill}---#1%
\end{quotation}%
}
\begin{citequote}[Knuth]
Premature optimization is the root of all evil.
\end{citequote}The same release brought \RenewDocumentEnvironment (rework), \ProvideDocumentEnvironment (define only if absent) and \DeclareDocumentEnvironment (define either way). The environment counterpart of \providecommand, missing from the classic set as noted above, finally arrives here. For new code this family deserves to be the default choice on three counts: it takes several optional arguments, it handles starred variants properly, and its end code can see the arguments. \newenvironment is then something you keep in mind mainly for reading and maintaining existing documents.
Three patterns that cover most custom environments
Almost every environment written in practice falls into one of three patterns. The skeleton is the same in all of them: prepare in the begin code, tidy up in the end code.
- Wrap the body in formatting — set font, size or alignment in the begin code and let the body take that look. Thanks to grouping, the end code can be empty.
- Add space above and below — put vertical space such as
\par\medskipat the head of the begin code and the tail of the end code, framing the body with margins. - Build on an existing environment — open another environment with
\begin{...}in the begin code and close the matching\end{...}in the end code. This is how you seasonquote,centerorlistwith a little extra.
% 1. wrap the body in formatting
\newenvironment{aside}{\par\small\itshape}{\par}
% 2. add vertical space above and below
\newenvironment{spaced}{\par\medskip\noindent}{\par\medskip}
% 3. build on an existing environment
\newenvironment{smallquote}{%
\small\begin{quotation}%
}{%
\end{quotation}%
}The third pattern is the one you reach for most. The underlying environment keeps its own indentation and margins, so the amount you have to add is surprisingly small. Just be sure to close in the end code whatever you opened in the begin code, keeping the pairing intact. And the % characters trailing the lines above are not decoration: without them, the line break at the end of each line enters the body as a single space, and unexplained gaps appear around the environment. Making a habit of the trailing % in multi-line definitions removes a whole class of accidents.
The errors you hit when \begin and \end do not match
\end{name} does more than call \endname: it also checks that name matches the environment currently open. When they disagree you get, for instance, ! LaTeX Error: \begin{sidenote} on input line 5 ended by \end{remarkbox}. — which reports the name of the environment that was opened, and the line where it was opened. That line number is usually more useful than the line the error is reported on. These four are the faces you actually meet.
! LaTeX Error: Environment nosuchenv undefined.— no environment of that name exists. Either a typo, or a forgotten\usepackagefor whatever defines it.! LaTeX Error: \begin{sidenote} on input line 5 ended by \end{remarkbox}.— the name you opened and the name you closed differ.! LaTeX Error: \begin{sidenote} on input line 4 ended by \end{document}.— an\end{sidenote}is missing, so the environment stayed open all the way to\end{document}.! LaTeX Error: \begin{document} ended by \end{nosuchenv}.— you closed an environment that was never opened. This also follows on the heels of an undefined\beginthat has already raised its own error.
Each is a restatement of one symptom: something is not paired. If your custom environment opens another environment in the begin code, suspect the \end{...} in its end code first. And be warned that these errors often drag along unrelated-looking screams such as ! Missing $ inserted. The collateral damage tends to be louder than the cause, so the rule is to read the log from the very first error downwards.