{"record":{"id":"0d64e8471707475a","repo":"mozilla/pdf.js","slug":"node-must-be-a-dictionary","errorCode":null,"errorMessage":"Node must be a dictionary.","messagePattern":"Node must be a dictionary\\.","errorType":"exception","errorClass":"FormatError","httpStatus":null,"severity":"error","filePath":"src/core/catalog.js","lineNumber":1612,"sourceCode":"    const visited = new RefSet();\n    visited.put(pageRef);\n\n    while (true) {\n      const node = await xref.fetchAsync(ref);\n      if (\n        isRefsEqual(ref, pageRef) &&\n        !isDict(node, \"Page\") &&\n        !(node instanceof Dict && !node.has(\"Type\") && node.has(\"Contents\"))\n      ) {\n        throw new FormatError(\n          \"The reference does not point to a /Page dictionary.\"\n        );\n      }\n      if (!node) {\n        break;\n      }\n      if (!(node instanceof Dict)) {\n        throw new FormatError(\"Node must be a dictionary.\");\n      }\n      const parentRef = node.getRaw(\"Parent\");\n      if (parentRef instanceof Ref) {\n        if (visited.has(parentRef)) {\n          throw new FormatError(\"Pages tree contains circular reference.\");\n        }\n        visited.put(parentRef);\n      }\n\n      const parent = await node.getAsync(\"Parent\");\n      if (!parent) {\n        break;\n      }\n      if (!(parent instanceof Dict)) {\n        throw new FormatError(\"Parent must be a dictionary.\");\n      }\n\n      const kids = await parent.getAsync(\"Kids\");","sourceCodeStart":1594,"sourceCodeEnd":1630,"githubUrl":"https://github.com/mozilla/pdf.js/blob/5903d58d58e4dd9ce6ffa3834aea8480f06b4ada/src/core/catalog.js#L1594-L1630","documentation":"Thrown in Catalog.getPageIndex() (catalog.js:1612) during the upward Parent-chain walk when a fetched node is not null but is not a Dict. Each node on the chain (Page, then successive Parents) must be a dictionary; a non-dict, non-null node breaks the traversal.","triggerScenarios":"getPageIndex walks ref -> Parent -> Parent...; xref.fetchAsync(ref) returns a truthy non-Dict (stream, array, scalar). Reached after the initial /Page check passed but a parent node resolved to the wrong type, typically due to a corrupt xref or malformed /Parent reference.","commonSituations":"A corrupt /Parent indirect reference resolving to a non-dictionary; a shifted xref pointing /Parent at the wrong object; an edited PDF with a broken parent chain.","solutions":["Repair the PDF with qpdf/mutool to rebuild the /Parent chain.","Run 'qpdf --check' to detect dangling/wrong /Parent references.","Catch the FormatError around getPageIndex and treat the index as unresolvable.","When producing PDFs, ensure every /Parent points to a real /Pages dictionary."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// The parent chain is internal; pre-validation means repairing the PDF.\n// qpdf --check in.pdf   (reports dangling /Parent refs)\n// mutool clean in.pdf out.pdf","typeGuard":null,"tryCatchPattern":"try {\n  const idx = await pdf.getPageIndex(ref);\n} catch (err) {\n  if (/Node must be a dictionary/i.test(err?.message)) {\n    console.warn('Corrupt /Parent chain; repair the PDF', err);\n  } else throw err;\n}","preventionTips":["Repair corrupt /Parent references with qpdf/mutool before loading.","Run 'qpdf --check' to surface dangling refs.","Ensure every /Parent points to a real /Pages dictionary when producing PDFs.","Catch the FormatError and degrade gracefully (index unknown)."],"tags":["pdf-structure","pages-tree","corrupt-pdf","xref","page-retrieval"],"backgroundTag":null,"analyzedSha":"5903d58d58e4dd9ce6ffa3834aea8480f06b4ada","analyzedAt":"2026-08-13T02:28:27.364Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}