Class options & authoring

\documentclass[unknownoption]{article} compiles just fine. A misspelled class option is not an error: LaTeX buries LaTeX Warning: Unused global option(s): in the log and hands you a PDF, so you rarely notice. Write \usepackage[unknownoption]{color} instead and the run stops dead. That asymmetry is not a quirk — it follows from a deliberate design in which an unrecognised option has a different default destination in a class than in a package. Starting from that mechanism, this page builds up \DeclareOption and \ProcessOptions, the newer \DeclareKeys, and finally \LoadClass, which lets your own class stand on an existing one.

Why a misspelled class option does not stop the build

The answer is spelled out in the official clsguide. If a class file contains no \DeclareOption*, every option it did not declare is silently passed on to all packages. If a package file contains no \DeclareOption*, each option it did not declare produces an error. So a class option is carried around on the assumption that somebody may still want it, and only when nobody has claimed it by \begin{document} does LaTeX report LaTeX Warning: Unused global option(s): followed by the leftover names in square brackets. A package option, by contrast, has nowhere else to go, so an unrecognised name becomes ! LaTeX Error: Unknown option 'unknownoption' for package 'color'. immediately.

The design makes sense: options that the class itself does not know but a later package will claim — global options such as \documentclass[dvipsnames]{article} — are used every day. The price is that typos stay silent. Two habits pay off in practice. First, search the log for Unused global option after every build. Second, put \listfiles at the top of the preamble so the end of the log lists every file that was loaded, with its version. Incidentally, calling \OptionNotUsed inside an option’s code deliberately sends that option to the same unused list.

The difference between a class (.cls) and a package (.sty)

The clsguide states the test in one line: if the commands could be used with any document class, make them a package; if not, make them a class. A class defines the kind of document itself and is loaded exactly once, with \documentclass. A package is loaded with \usepackage, any number of them, and adds features independent of the document kind. The guide’s own example makes it concrete: a company class for printing letters on its own headed paper builds on letter but cannot be used with any other class, so it is ownlet.cls; the graphics package for including images works with every class, so it is graphics.sty.

There are two kinds of class as well: the free-standing ones such as article, report and letter, and those that are extensions or variations of another class — the clsguide names proc, which is built on article, as an example of the second. Any class you write yourself is almost certainly of the second kind, because building a page design from nothing rarely pays. The authoring conventions of a .cls and a .sty are nearly identical; the commands simply come in matched Class and Package pairs (\ProvidesClass\ProvidesPackage, \LoadClass\RequirePackage, \PassOptionsToClass\PassOptionsToPackage).

The standard options your class should accept

Users will pass options to your class exactly as they would to a standard one, so at minimum you want the usual cast: 10pt / 11pt / 12pt for the base body size, a4paper / letterpaper for paper, onecolumn / twocolumn for columns, oneside / twoside for the printing side, and draft, which marks overfull lines with a black bar (its opposite is final). You need not implement any of them yourself: as we will see, the standard move is to forward them to the base class.

OptionMeaningDefault
10pt / 11pt / 12ptbase body font size10pt
a4paper / letterpaperpaper size (also b5paper, legalpaper, …)letterpaper
onecolumn / twocolumnone column / two columnsonecolumn
oneside / twosideone-sided / two-sided layoutoneside (but twoside for book)
draft / finalwhether overfull lines get a black barfinal

If you want the class itself to decide what happens when the user specifies nothing, write \ExecuteOptions{a4paper,11pt} before \ProcessOptions. It declares “run the code for these options up front,” and the clsguide presents exactly this form as the way to give a class its default design. Using these options from the document’s \documentclass[...] side, and book-specific ones such as openright, belong to the class-and-preamble page. From here on we concentrate on writing the class that receives them.

Identify the file up front: \NeedsTeXFormat and \ProvidesClass

The first two lines of a class file (myclass.cls) are almost boilerplate. \NeedsTeXFormat{LaTeX2e} declares that the file is meant for LaTeX2e. Then \ProvidesClass{myclass}[2026/01/01 v1.0 My example class] announces the class name, release date, version and a short description. Where that line earns its keep is the log: compile, and you get Document Class: myclass 2026/01/01 v1.0 My example class. When a co-author says the document will not build, asking for that one line tells you at once whether they are holding an old .cls.

The bracketed part is optional, but supplying it lets users demand a minimum version through the date (in YYYY/MM/DD form), as in \documentclass{myclass}[2026/01/01]. If you are writing a package the counterpart is \ProvidesPackage{mypackage}[2026/01/01 v1.0 ...], and \NeedsTeXFormat is shared by both. As a firm rule, the name in \ProvidesClass must match the actual file name — inside myclass.cls, always write \ProvidesClass{myclass}.

latex
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{myclass}[2026/01/01 v1.0 My example class]

Declaring options: \DeclareOption and \CurrentOption

Each option your class accepts is declared with \DeclareOption{option}{code}. When the user specifies that option, its code runs the moment \ProcessOptions is reached (see below). The code may be any valid LaTeX construct, but in practice it is most often a single line that sets a boolean flag created with \newif; doing the heavy work later, by inspecting the flag, avoids accidents of ordering.

The catch-all for options you did not declare is the starred \DeclareOption*{code}, inside which \CurrentOption expands to the name of the option currently being processed. The single most common line in a custom class uses it to forward any unknown option straight to the base class. Thanks to that line, users can pass 10pt or a4paper as a matter of course even though you never declared them — you have taken the “a class passes it on silently” default from the opening section and pointed it at a destination you chose.

latex
% pass anything we do not handle ourselves on to article
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}

Process the options, then load the base class: \ProcessOptions and \LoadClass

Declaring options does nothing on its own; the code of the selected options runs only when you call \ProcessOptions. In practice you almost always write \ProcessOptions\relax. Because a starred \ProcessOptions* also exists, the trailing \relax reliably selects the unstarred form and prevents unnecessary look-ahead and possibly misleading errors — the clsguide recommends it explicitly. The unstarred form processes options in the order you declared them; the starred form processes them in the order the caller listed them.

Building a page design from nothing rarely pays, so most custom classes stand on an existing one. That is \LoadClass[options]{article}, which pulls in all of article.cls’s commands and styling. The command may be used only inside a class file, and at most once per class file. Order matters: so that the options the user gave on \documentclass[...] reach the base class, put \LoadClass after option processing (\ProcessOptions) — declare the forwarding, let \ProcessOptions dispatch, then load the base. If you simply want to hand over exactly the options your class received, \LoadClassWithOptions{article} is the shortcut; in a package, use \RequirePackage in place of \LoadClass, and \RequirePackageWithOptions for the pass-everything case.

Everything after \LoadClass is where the character of your class finally goes: redefine heading styles with \renewcommand, adjust margins with \setlength, define new commands and environments with \newcommand / \newenvironment. Any extra packages you need are loaded from here with \RequirePackage. Put the other way round: remember that only option declaration and processing belong before \LoadClass, and questions of ordering stop being questions.

A complete minimal class that extends article

Putting it all together gives this minimal .cls. It builds on article, adds its own draft option, forwards unknown options to article, supplies a4paper as a default, and finally sets the margins and the section numbering to its own taste. Save it as myclass.cls next to your manuscript and use it from a document with \documentclass[11pt,a4paper,draft]{myclass}.

latex
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{myclass}[2026/01/01 v1.0 My example class]

% --- declare options ---
\newif\if@my@draft \@my@draftfalse
\DeclareOption{draft}{\@my@drafttrue}
% forward everything else to article
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}

% --- defaults, then execute, then load the base class ---
\ExecuteOptions{a4paper}
\ProcessOptions\relax
\LoadClass{article}

% --- this class's own character ---
\RequirePackage[margin=25mm]{geometry}
\setlength{\parindent}{0pt}
\renewcommand{\thesection}{\Alph{section}}
\if@my@draft
  \AtBeginDocument{\typeout{myclass: DRAFT MODE}}
\fi

\endinput

The closing \endinput tells LaTeX that the file ends here; it is conventional to include it, and notes or samples written after it are never read. To turn this into a package, swap \ProvidesClass for \ProvidesPackage and \LoadClass for \RequirePackage: the same skeleton becomes a .sty.

The modern way: \DeclareKeys and \ProcessKeyOptions

\DeclareOption is still perfectly valid, but it is built around present/absent switches; an option that carries a value, such as logo=acme.pdf, leaves you parsing it yourself. So the LaTeX kernel now provides a key–value interface of its own: declare keys with \DeclareKeys and process them with \ProcessKeyOptions. Each key name carries a “property” that decides its behaviour; the basic ones are .code (run arbitrary code), .if / .ifnot (set a TeX boolean switch), .store (save the value in a macro) and .usage (whether the option may be given only at load time, anywhere in the preamble, or without restriction). Unknown keys go to \DeclareUnknownKeyHandler, and once you call \ProcessKeyOptions there is no need to call \ProcessOptions as well.

latex
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{keyclass}[2026/01/01 v1.0 Key-value demo class]

\DeclareKeys[keyclass]{
  draft.if   = @keyclass@draft,
  logo.store = \@keyclass@logo,
  logo.usage = load
}
% anything that is not one of our keys goes to article
\DeclareUnknownKeyHandler[keyclass]{%
  \PassOptionsToClass{\CurrentOption}{article}}
\ProcessKeyOptions[keyclass]   % no \ProcessOptions needed
\LoadClass{article}

\endinput

This mechanism originally came from the l3keys2e package, whose core has since been folded into the LaTeX2ε kernel (the kernel shipped with TeX Live 2024 is LaTeX2e 2023-11-01 and provides \DeclareKeys, \ProcessKeyOptions and \SetKeys). Existing packages still load l3keys2e in places — jlreq.cls, for instance, runs \RequirePackage{l3keys2e} near its top. The choice is simple: if even one option takes a value, use \DeclareKeys; if they are all on/off switches, \DeclareOption is enough. To change settings after loading, use \SetKeys.

The minimal test to run before sharing

A class affects the whole document the instant it is loaded, so settle its behaviour with a tiny test document before writing real content. There are only two things to check: that standard options such as 11pt or twocolumn are reaching the base class, and that only your own options are being handled by your own code. If it does not behave as expected here, the cause is almost certainly the position of \ProcessOptions, the forwarding in \DeclareOption*, or the order of \LoadClass.

latex
\listfiles                     % log every file and version that is loaded
\documentclass[11pt,a4paper,draft]{myclass}
\begin{document}
\section{Smoke test}
Check the body size, the paper, the draft switch,
the heading style and the margins.
\end{document}
  • Does the log identify the class? Confirm that the Document Class: myclass ... line, with the date and version you wrote in \ProvidesClass, appears in the .log. A mismatch between file name and class name will confuse someone later.
  • Did you preserve the standard options? If 11pt or twocolumn is ignored, revisit the forwarding in \DeclareOption* or the position of \LoadClass.
  • Misspell an option on purpose. Build \documentclass[nosuchoption]{myclass} and check that Unused global option(s) appears in the log. If it does not, some package you forward to is swallowing it silently.
  • Keep everything after \endinput empty. Notes or samples left at the end will be read as input the moment that marker goes missing.

Think about the shape you distribute

A custom class is really tested not when it first works on your machine but when someone else loads it in another environment. At minimum, keep the .cls, a short sample document, a README and a changelog in one directory, and verify that the sample compiles as-is. In the README, separate the options you forward to the base class from the options your class handles itself, so users can trace where 11pt takes effect. Once the thing grows, move to LaTeX’s own doc and docstrip: keep source and commentary together in a .dtx and generate the .cls from an .ins, which puts distribution and documentation on one track.

terminal
myclass/
  myclass.cls
  sample.tex
  README.md
  CHANGELOG.md

Finally, put \listfiles in the sample. The end of the log then lists every file that was loaded together with its version, so you can tell at a glance whether a user is carrying an old local myclass.cls or whether the packages you expect are actually being loaded. A class is the foundation of the whole document: over the long run, careful loading order, option processing and log information pay off far more reliably than one more body-text macro.