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

  1. Repair the PDF: 'mutool clean in.pdf out.pdf' or 'qpdf --linearize in.pdf out.pdf' rebuilds the page tree references.
  2. Confirm the file is complete (not truncated mid-download) by checking its size and re-fetching.
  3. Open in a strict reader to confirm it is actually broken (not a PDF.js-specific parsing gap), then report/file the producer bug.
  4. 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

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


AI-assisted analysis of mozilla/pdf.js@5903d58d58 (2026-08-13). Data as JSON: /api/errors/3b6ff54e95facd14. Report an issue: GitHub.