Empty PDF files getting created when uploading to Google Drive using API

I’m having trouble with my PDF upload function. When I generate a shipping receipt using a PDF library and try to upload it to Google Drive through their API, the file gets uploaded but it shows up completely empty. The weird thing is that the file size looks correct, just like the original receipt I generated.

I have another function that works fine - when I call generateReceipt and use document.pipe(res), the PDF displays perfectly. But when I pass the same data to my Google Drive upload script, it creates a blank PDF file. The upload itself works without errors.

Has anyone run into this before? I’m not sure what’s causing the content to disappear during the upload process.

generate_receipt.js

module.exports.generateReceipt = async (
    senderInfo,
    receiverInfo,
    customerName,
    packageWeight,
    barcodeImage,
    trackingNumber
) => {
    const document = new PDFDocument();

    // Main border
    document.roundedRect(15, 15, 580, 710, 8).stroke();
    // Company logo
    document.image("images/logo.png", 50, 40, { fit: [90, 90] }).stroke();

    document.rect(25, 25, 190, 140).stroke();
    document.fontSize(18).font("Helvetica-Bold").text("Sender:", 240, 40).moveDown(0.4);
    document.fontSize(16).font("Helvetica").text(senderInfo);

    document.rect(230, 25, 360, 140).stroke();
    document.fontSize(18).font("Helvetica-Bold").text("Recipient:", 35, 190).moveDown(0.4);
    document.fontSize(16).font("Helvetica").text(receiverInfo).moveDown(0.8);
    document.fontSize(16).font("Helvetica-Bold").text(customerName).moveDown(0.8);

    document.rect(25, 180, 560, 290).stroke();

    document.rect(25, 480, 260, 240).stroke();
    document
        .fontSize(18)
        .font("Helvetica")
        .text(`Tracking: ${trackingNumber}`, 35, 490)
        .moveDown(0.4);
    document.fontSize(18).font("Helvetica").text(`Weight: ${packageWeight}`).moveDown(0.4);

    document.image(barcodeImage, 310, 450, { width: 280 });

    document.rect(300, 480, 290, 240).stroke();

    document.end();

    return await getStream(document);
};

main.js

// setup code here
try {
    const {
        senderInfo,
        customerName,
        packageId,
        trackingNumber,
        receiverInfo,
        packageWeight,
    } = req.body;

    console.log("Creating barcode image");
    const barcodeImage = await generateBarcode(packageId.toString());
    // generate receipt
    console.log("Creating shipping receipt");
    const dataStream = await generateReceipt(
        senderInfo,
        receiverInfo,
        customerName,
        packageWeight,
        barcodeImage,
        trackingNumber
    );

    console.log("Starting Google Drive upload");
    uploadToCloud(trackingNumber, dataStream);

    return res.status(200).send("Receipt uploaded successfully");
} catch (error) {
    throw res.status(500).send(error);
}

upload-to-cloud.js

module.exports.uploadToCloud = (trackingNumber, dataStream) => {
// ... authentication setup

    function createFile(auth) {
        const targetFolder = `1mNxP34qrWvXC8Hy2fko6lZGx9QRsVbT`;

        const metadata = {
            name: `receipt_${trackingNumber}.pdf`,
            parents: [targetFolder],
        };

        const uploadData = {
            mimeType: "application/pdf",
            body: dataStream,
        };

        const driveAPI = google.drive({ version: "v3", auth });

        driveAPI.files.create(
            {
                resource: metadata,
                media: uploadData,
                fields: "id",
            },
            function (error, result) {
                if (error) {
                    throw error;
                } else {
                    console.log("upload completed");
                }
            }
        );
    }

I hit the same issue with PDF streams and file uploads. Your stream pointer’s sitting at the end after PDF generation finishes. When you pass that exhausted stream to Google Drive API, there’s nothing left to read. Call dataStream.seek(0) before uploading, or just store the PDF as a temp buffer first. You could tweak your generateReceipt function to return both - a stream for direct response and a buffer for uploads. I also had luck using PassThrough streams to create multiple readable instances from the same PDF data. Check if your getStream utility can return data in different formats - might save you from restructuring everything.

Classic stream issue. Your PDF stream gets consumed during generation, so when the Drive API tries to read it, there’s nothing left. Skip streams entirely - write the PDF to a temp file first, then upload that. Use fs.writeFileSync('./temp.pdf', pdfData) and read it back for upload. It’s messy but works every time without dealing with stream resets or buffers.

Had the same issue last year - it’s a stream consumption problem. When you call getStream(document) to convert your PDF, that stream gets consumed once and becomes useless for anything else. The Google Drive API eats up the stream during upload, so there’s nothing left for the actual file content. Fix is simple: convert your stream to a buffer first, then create a fresh readable stream from that buffer for the upload. In your generateReceipt function, don’t return the stream directly - use getStream.buffer(document) instead. Then in your upload function, create a new stream from the buffer with Readable.from(buffer) before sending it to the Drive API. This keeps your PDF content intact while letting the API consume the stream like it wants to.