Take a LaTeX document that loads hyperref, wipe the auxiliary files, and compile it exactly once. The resulting PDF contains no bookmarks at all — the outline appears only on the second pass. The reason lies in how bookmarks are built: hyperref writes the headings out to a side file called jobname.out and reads it back at the start of the next run before putting anything in the PDF. This page follows that mechanism, then the bookmark package that throws the .out file away and gets it right in one pass, the PDF metadata you set with \hypersetup, the newer \DocumentMetadata entry point, and the mojibake that ruins Japanese bookmarks — each claim checked against real pdfinfo output.
hyperref builds the bookmarks from your headings
Writing \usepackage{hyperref} is enough to turn \chapter, \section and \subsection into a PDF outline. You configure it with package options or \hypersetup{}, and four keys cover most needs: bookmarks (on by default), bookmarksnumbered (put the section numbers in the bookmarks too), bookmarksopen (start expanded), and bookmarksopenlevel=N (how deep to expand). Open the intermediate jobname.out and you find a list of LaTeX macro calls — with the strings not in plain text but in UTF-16BE, so even an English heading appears as \376\377\000C\000o\000v\000e\000r, a \000 before every character. The leading \376\377 is the UTF-16 byte-order mark, because that is how PDF defines its text strings.
\usepackage[bookmarksnumbered,bookmarksopen,bookmarksopenlevel=1]{hyperref}
% or set the same keys later
\hypersetup{bookmarksopenlevel=1}% report.out after three passes — hyperref stores the outline here
\BOOKMARK [0][]{cover.0}{\376\377\000C\000o\000v\000e\000r}{}% 1
\BOOKMARK [0][]{chapter.1}{...1 Foundations...}{}% 2
\BOOKMARK [1][-]{section.1.1}{...1.1 First section...}{chapter.1}% 3
\BOOKMARK [2][-]{subsection.1.1.1}{...1.1.1 A subsection...}{section.1.1}% 4The default depth comes not from the bookmark machinery but from the table of contents. The report class sets tocdepth to 2 (down to subsection), so a \subsubsection never reaches the outline. That is deliberate: the two lists are meant to have the same granularity. When you want the bookmarks deeper than the contents, use bookmarksdepth — measured, adding bookmarksdepth=4 made a \subsubsection appear in the outline while it stayed out of the table of contents. Conversely bookmarksdepth=1 folds the outline back to sections.
| Option | Effect | Default |
|---|---|---|
bookmarks | whether to build the outline at all | true |
bookmarksnumbered | include the section numbers in the labels | false |
bookmarksopen | show the tree expanded on opening | false |
bookmarksopenlevel | how many levels start expanded | all of them |
bookmarksdepth | the deepest level that reaches the outline | follows tocdepth |
Adding a bookmark where there is no heading: \pdfbookmark
For places that never pass through a sectioning command — a cover page, the table of contents, an unnumbered preface — write \pdfbookmark[level]{visible text}{anchor} directly. The level in the first argument is a number (\chapter is 0, \section is 1), and the anchor in the third must be unique within the document, or the destinations collide. To add an entry at the current level there is \currentpdfbookmark{text}{anchor}, and one level deeper \belowpdfbookmark{text}{anchor}. The commonest real use is bookmarking the table of contents itself: one line before \tableofcontents. Without it you ship the peculiar PDF in which the reader can jump anywhere except back to the contents.
\begin{document}
\pdfbookmark[0]{Cover}{cover} % level 0, same rank as \chapter
\maketitle
\clearpage
\pdfbookmark[1]{Contents}{toc} % the classic missing bookmark
\tableofcontents
\chapter{Foundations}The bookmark package: throw the .out file away and get it right in one pass
Load Heiko Oberdiek’s bookmark package after hyperref (TeX Live 2024 ships v1.31, dated 2023-12-10) and the whole bookmark mechanism is replaced. Measure it and the effect is immediate: with plain hyperref, the PDF produced by the first pass from a clean directory has no /Outlines object at all, and only the second pass carries the seven entries. Add bookmark and all seven are there on the first pass. The trick is simple — bookmark writes no .out file (you can confirm it: none appears in the directory). It routes the outline through the .aux file instead, so the stale-side-file step disappears. hyperref’s own bookmarks switch themselves off, so nothing clashes.
The second gain is styling. \bookmarksetup{} accepts numbered (include the section numbers), open and openlevel, and per-entry appearance — color=blue, bold, italic. Look inside the generated PDF and each outline item really does carry a /C [ … ] colour entry. To change just one entry, put \bookmarksetupnext{color=red} immediately before it. Colouring only the appendices and the index of a long report makes the sidebar far easier to read at a glance.
\usepackage{hyperref}
\usepackage{bookmark} % must come after hyperref
\bookmarksetup{numbered, open, openlevel=1, color=blue}
% one entry only
\bookmarksetupnext{color=red, bold}
\chapter{Appendix}PDF metadata: naming the title and author with \hypersetup
What the viewer shows under “Document Properties” is decided by four keys in \hypersetup{}: pdftitle, pdfauthor, pdfsubject and pdfkeywords. They are not copied from \title and \author, so you have to write both — hyperref needs the values before \maketitle runs. Whether it worked is one pdfinfo call away. The pdfcreator and pdfproducer fields identify the producing software and are normally filled in for you: pdfLaTeX with hyperref reports Creator: LaTeX with hyperref and Producer: pdfTeX-1.40.26. You can overwrite those two, but doing so destroys the only trace of how the file was made, so it is safer to leave them alone.
\usepackage{hyperref}
\hypersetup{
pdftitle={Measured Bookmarks},
pdfauthor={Ada Lovelace},
pdfsubject={PDF navigation},
pdfkeywords={LaTeX, hyperref, bookmarks}
}$ pdfinfo report.pdf
Title: Measured Bookmarks
Subject: PDF navigation
Keywords: LaTeX, hyperref, bookmarks
Author: Ada Lovelace
Creator: LaTeX with hyperref
Producer: pdfTeX-1.40.26
Pages: 5
Page size: 595.276 x 841.89 pts (A4)
PDF version: 1.5Accented characters now go in as they are. hyperref 7.01h, shipped with TeX Live 2024, defaults internally to \Hy@unicodetrue, so pdftitle={Théorie des catégories — Übersicht} comes back out of pdfinfo intact even under pdfLaTeX; the unicode option that used to be required is no longer needed. What does still bite is that \hypersetup values are written straight into the PDF as strings, so the practical rule is keep macros out of them: pdftitle={How to use \LaTeX{}} is asking for an expansion failure, while a plain pdftitle={How to use LaTeX} always works.
\DocumentMetadata: the new entry point for metadata and tagging
\DocumentMetadata{…} is a newer LaTeX kernel declaration that goes before \documentclass. It genuinely works in TeX Live 2024 and accepts keys such as lang=en-GB (the document language), pdfversion=2.0, pdfstandard=A-2B (the PDF/A level, from A-1B through A-4) and uncompress (turn all compression off). Even a single line has a visible effect: pdfinfo flips Metadata Stream from no to yes, because the PDF now carries an XMP metadata stream. Your existing \hypersetup keys keep working alongside it, and both sets of values land in the PDF as measured.
Beyond that lies the tagged PDF. Add testphase={phase-III}, run pdflatex twice, and pdfinfo reports Tagged: yes — LaTeX has begun writing the structure of your paragraphs and headings into the PDF’s structure tree. As the key name says, this is still a test phase, so it is not something to switch on unconditionally in a final submission; but it is worth knowing that a working version of it ships in stock TeX Live. Be aware too that \DocumentMetadata has a side effect on paper size, so check the dimensions of the PDF when adding it to an existing document — the details are on “Producing and controlling the PDF”.
\DocumentMetadata{pdfversion=2.0, lang=en-GB, testphase={phase-III}}
\documentclass{article}
\usepackage{hyperref}
\hypersetup{pdftitle={Tagged Test}, pdfauthor={Ada Lovelace}}
% pdfinfo then reports: Tagged: yes / Metadata Stream: yes / PDF version: 2.0When Japanese bookmarks turn to mojibake: pxjahyper and the dvipdfmx option
Getting Japanese bookmarks right with upLaTeX and dvipdfmx takes two separate fixes. The first is telling hyperref which driver it is writing for. Write plain \usepackage{hyperref} and the log says Package hyperref Info: Driver (default): hdvips. — you are producing DVI, but hyperref emits \specials aimed at dvips. Feed that DVI to dvipdfmx and you get a run of dvipdfmx:warning: Unknown token "SDict" and Interpreting special command ps: (ps:) failed., and a PDF with no bookmarks and no links at all. Write \usepackage[dvipdfmx]{hyperref} instead and the log reads Driver: hdvipdfm. with zero warnings.
The second fix is the character encoding. Correcting the driver brings the outline back, but a Japanese heading arrives as something like æ鞥æ鲬èꪞã膮èꚋå螺ã膗. The .out file shows why: 日 should become the two bytes \145\345 in UTF-16BE, but instead its three UTF-8 bytes are each treated as a separate character and padded out to \000\346\000\227\000\245. Add \usepackage{pxjahyper} (by Takayuki Yato; TeX Live 2024 ships v1.3) and the .out becomes proper UTF-16BE — \376\377\145\345\147\054\212\236… — while pdfinfo starts reporting a readable Title: 日本語のタイトル. Crucially, this repairs pdftitle and pdfauthor at the same time as the bookmarks.
% upLaTeX -> dvipdfmx: both lines are needed
\documentclass{ujarticle}
\usepackage[dvipdfmx]{hyperref} % without this: dvipdfmx warning, no outline
\usepackage{pxjahyper} % without this: mojibake in the outline
\hypersetup{pdftitle={...}, pdfauthor={...}}This two-part dance is needed only on the (u)pLaTeX DVI route. LuaLaTeX with LuaTeX-ja gets there with a plain \usepackage{hyperref} — the log says Driver (autodetected): hluatex. and the .out file is correct UTF-16BE from the start. XeLaTeX with xeCJK likewise produces readable bookmarks with no extra package. If mojibake in Japanese bookmarks keeps biting you, switching engines can be the shortest route out. One ordering note: hyperref should be loaded late, but cleveref must come after hyperref, and getting it wrong stops the run with ! Package cleveref Error: cleveref must be loaded after hyperref!. If varioref is in play too, the order is hyperref, then varioref, then cleveref.