mozilla/pdf.js · critical · FormatError
Invalid top-level pages dictionary.
Error message
Invalid top-level pages dictionary.
What it means
Thrown by the toplevelPagesDict getter (catalog.js:374) when the catalog's /Pages entry is not a Dict. /Pages is the root node of the page tree; a non-dictionary value means the page tree is unusable. This getter is invoked eagerly in the Catalog constructor, so it aborts document loading.
Source
Thrown at src/core/catalog.js:374
throw ex;
}
warn("Unable read to structTreeRoot info.");
}
return shadow(this, "structTreeRoot", structTree);
}
#readStructTreeRoot() {
const rawObj = this.#catDict.getRaw("StructTreeRoot"),
obj = this.xref.fetchIfRef(rawObj);
return obj instanceof Dict
? new StructTreeRoot(this.xref, obj, rawObj)
: null;
}
get toplevelPagesDict() {
const pagesObj = this.#catDict.get("Pages");
if (!(pagesObj instanceof Dict)) {
throw new FormatError("Invalid top-level pages dictionary.");
}
return shadow(this, "toplevelPagesDict", pagesObj);
}
get documentOutline() {
let obj = null;
try {
obj = this.#readDocumentOutline();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn("Unable to read document outline.");
}
return shadow(this, "documentOutline", obj);
}
#readDocumentOutline(options = {}) {View on GitHub (pinned to 5903d58d58)
Solutions
- Repair the PDF: 'mutool clean in.pdf out.pdf' or 'qpdf --linearize in.pdf out.pdf' rebuilds the page tree references.
- Confirm the file is complete (not truncated mid-download) by checking its size and re-fetching.
- Open in a strict reader to confirm it is actually broken (not a PDF.js-specific parsing gap), then report/file the producer bug.
- As a last resort, regenerate the PDF from the original source/document.
Defensive patterns
Strategy: try-catch
Validate before calling
// No public API to inspect /Pages before construction; validate input integrity.
function assertCompletePdf(buf) {
const u8 = new Uint8Array(buf);
const tail = new TextDecoder().decode(u8.subarray(Math.max(0, u8.length - 1024)));
if (!/%%EOF/.test(tail)) throw new Error('PDF missing %%EOF — likely truncated');
}
await assertCompletePdf(arrayBuffer); Try / catch
try {
const pdf = await getDocument({ data: arrayBuffer }).promise;
} catch (err) {
if (/top-level pages dictionary/i.test(err?.message)) {
throw new Error('PDF page tree root is invalid; repair with qpdf/mutool.', { cause: err });
}
throw err;
} Prevention
- Ensure the file is complete (ends with %%EOF) before loading.
- Repair the page tree with 'mutool clean' or 'qpdf --linearize'.
- Re-download truncated files from the source.
- Run 'qpdf --check' on user-supplied PDFs before processing.
When it happens
Trigger: During Catalog construction, this.#catDict.get('Pages') returns a non-Dict (null, a wrong object, or a ref that resolves to a non-dictionary). Caused by a malformed /Pages entry or a corrupt xref resolving the ref to the wrong object. Note the comment says XRef.parse is expected to have validated this, so reaching the throw indicates that pre-validation was bypassed or the object changed.
Common situations: Corrupt PDF where /Pages points at a stream or scalar; xref shifted so /Pages resolves to a different object; a hand-crafted/edited PDF with an inline non-dict Pages value; file truncated after the catalog but before a valid page tree.
Related errors
- Catalog object is not a dictionary.
- Page count in top-level pages dictionary is not an integer.
- PageLabel is not a dictionary.
- Invalid type in PageLabel dictionary.
- Invalid style in PageLabel dictionary.
AI-assisted analysis of mozilla/pdf.js@5903d58d58 (2026-08-13).
Data as JSON: /api/errors/3b6ff54e95facd14.
Report an issue: GitHub.