Stirling-Tools/Stirling-PDF · critical · Error

PDFium: failed to create destination document

Error message

PDFium: failed to create destination document

What it means

`createMultiSourceDocument` calls `FPDF_CreateNewDocument()` (PDFium's allocator for a fresh empty PDF) and checks for a null pointer. PDFium returns 0 only when it cannot allocate the document object — overwhelmingly a sign of WASM heap exhaustion or the module being in a broken state, since an empty document is otherwise always creatable.

Source

Thrown at frontend/editor/src/core/services/pdfExportService.ts:118

        `Failed to export PDF: ${error instanceof Error ? error.message : "Unknown error"}`,
        { cause: error },
      );
    }
  }

  /**
   * Create a PDF document from multiple source files using PDFium WASM.
   */
  private async createMultiSourceDocument(
    sourceFiles: Map<string, File>,
    pages: PDFPage[],
  ): Promise<Blob> {
    const m = await getPdfiumModule();

    // Create destination document
    const destDocPtr = m.FPDF_CreateNewDocument();
    if (!destDocPtr)
      throw new Error("PDFium: failed to create destination document");

    // Load all source documents once and cache them
    const loadedDocs = new Map<string, number>();

    try {
      for (const [fileId, file] of sourceFiles) {
        try {
          const arrayBuffer = await file.arrayBuffer();
          const docPtr = await openRawDocumentSafe(arrayBuffer);
          loadedDocs.set(fileId, docPtr);
        } catch (error) {
          console.warn(`Failed to load source file ${fileId}:`, error);
        }
      }

      let insertIdx = 0;
      for (const page of pages) {
        if (page.isBlankPage || page.originalPageNumber === -1) {

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Call `resetPdfiumModule()` once and retry — this clears `_docDataPtrs` and rebuilds a fresh WASM instance.
  2. Ensure every opened source doc is closed via `closeRawDocument`/`closeDocAndFreeBuffer` so the heap doesn't accumulate; audit for missing close paths.
  3. Limit concurrent open PDFium documents; process sequentially and close before opening the next.
  4. If reproducible, reduce the working set (export fewer files at once) or move heavy merging server-side.

Example fix

// before
const destDocPtr = m.FPDF_CreateNewDocument();
if (!destDocPtr) throw new Error("PDFium: failed to create destination document");

// after (retry once after resetting the module)
let destDocPtr = m.FPDF_CreateNewDocument();
if (!destDocPtr) {
  resetPdfiumModule();
  const m2 = await getPdfiumModule();
  destDocPtr = m2.FPDF_CreateNewDocument();
  if (!destDocPtr) throw new Error("PDFium: failed to create destination document");
}
Defensive patterns

Strategy: retry

Validate before calling

// Heuristic: bound simultaneous open PDFium documents before merging
const MAX_OPEN_DOCS = 8;
if (sourceFiles.size > MAX_OPEN_DOCS) {
  console.warn(`Merging ${sourceFiles.size} files; consider sequential close to avoid WASM heap exhaustion.`);
}

Try / catch

async function withModuleRetry<T>(fn: () => Promise<T>): Promise<T> {
  try { return await fn(); }
  catch (e) {
    if (e instanceof Error && e.message.includes("failed to create destination document")) {
      resetPdfiumModule();
      return await fn();
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: Exporting after loading many large source files into PDFium (the WASM heap fills up); the PDFium module was partially corrupted by a prior bad pointer operation; an earlier `resetPdfiumModule()` left state inconsistent. Note: unlike `createSingleDocument`, the source doc pointer is not yet opened here, so nothing is leaked on this throw.

Common situations: Merging several large PDFs that already consumed the WASM 32-bit address space (max ~2-4GB); long-lived sessions where PDFium leaked document handles; Safari's tighter WASM memory limits.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/f2970c1d1974779e. Report an issue: GitHub.