DEV Community

Cover image for The Bird's Way of Printing PDFs under the hood
Daniel Wari
Daniel Wari

Posted on

The Bird's Way of Printing PDFs under the hood

In modern web applications, printing PDFs directly without opening a new browser tab provides a smoother user experience. This technique, sometimes called "silent printing" is particularly useful in business applications where users frequently need to print reports, invoices, or other documents.

Today, I'll share a clean, reusable JavaScript function that handles PDF printing elegantly without disrupting the user's workflow.

The Challenge

Traditionally, PDF printing in web applications follows this flow:

  • Generate the PDF
  • Open it in a new tab
  • Trigger the browser's print dialog
  • Require the use r to close the tab manually

This approach creates unnecessary friction. It interrupts the user's workflow, clutters their browser with tabs, and requires extra clicks to return to the application.

A Better Solution
The function below shows the bird's way of printing a PDF.

/**
 * Prints a PDF silently using a hidden iframe
 * @param {Function} generatePDF - Function that returns a PDF object
 * @param {Object} data - Data required to generate the PDF
 * @param {string} filename - Name for the PDF file (without extension)
 * @param {Function} onSuccess - Optional callback on successful print
 * @param {Function} onError - Optional callback on error
 * @returns {Promise<void>} - Returns a promise that resolves when printing is complete
 */
const printPDFSilently = async (generatePDF, data, filename, onSuccess = () => { }, onError = () => { }) => {
  try {
    const pdf = await generatePDF(data);
    const pdfUrl = URL.createObjectURL(pdf.output("blob"));

    const iframe = document.createElement("iframe");
    iframe.src = pdfUrl;
    iframe.style.display = "none";
    iframe.name = `${filename.replace(/\s+/g, "_")}.pdf`;
    document.body.appendChild(iframe);

    const print = async () => {
      await new Promise((resolve) => {
        iframe.contentWindow?.addEventListener("afterprint", () => {
          URL.revokeObjectURL(pdfUrl);
          document.body.removeChild(iframe);
          resolve();
        });
        iframe.contentWindow?.print();
      });
      onSuccess();
    };

    iframe.contentWindow?.document.readyState === "complete" ? await print() : iframe.onload = print;
  } catch (error) {
    console.error("PDF printing error:", error);
    onError(error);
  }
};
Enter fullscreen mode Exit fullscreen mode

How It Works
Let's break down how this function works:

  1. PDF Generation: The function accepts a generatePDF callback that should return a PDF object (compatible with libraries like jsPDF).
  2. Blob Creation: The PDF is converted to a Blob object, and a temporary URL is created using URL.createObjectURL().
  3. Hidden iframe: The function creates an invisible iframe with the PDF URL as its source.
  4. Print Triggering: When the iframe loads, the function calls iframe.contentWindow.print() to open the system print dialog.
  5. Cleanup: After printing completes, the function revokes the temporary URL and removes the iframe from the DOM
  6. Error Handling: Comprehensive error handling ensures any issues are caught and reported.

Usage Example
Here's how you might use this function in a React component:

import { useState } from 'react';
import { jsPDF } from 'jspdf';
import { toast } from 'sonner';

const ReportPage = ({ userData }) => {
  const [isPrinting, setIsPrinting] = useState(false);

  const generateUserReport = (data) => {
    const doc = new jsPDF();
    doc.text(`User Report: ${data.fullName}`, 20, 20);
    return doc;
  };

  const handlePrintReport = async () => {
    setIsPrinting(true);
    try {
      await printPDFSilently(
        generateUserReport,
        userData,
        `${userData.fullName}_Report`
      );
      toast.success("Printing initiated successfully!");
    } catch (error) {
      toast.error("Failed to prepare for printing. Please try again later.");
    } finally {
      setIsPrinting(false);
    }
  };

  return (
    <div>
      <h1>User Report</h1>
      <button
        onClick={handlePrintReport}
        disabled={isPrinting}
      >
        {isPrinting ? "Preparing…" : "Print Report"}
      </button>
    </div>
  )
};
Enter fullscreen mode Exit fullscreen mode

Benefits
This approach offers several advantages:

  • Improved UX: No tab switching or manual closing required
  • Cleaner Code: Reusable function with clear separation of concerns
  • Memory Efficient: Proper cleanup of resources
  • Flexible: Works with any PDF generation library
  • Customizable: Success and error callbacks for integration with notification systems

Browser Compatibility
This technique works in all modern browsers. However, be aware that:

  • Some browsers may have security settings that block automatic printing
  • Mobile browsers have varying levels of support for the Print API
  • The afterprint event isn't universally supported (though our implementation degrades gracefully)

Conclusion
Silent PDF printing creates a more seamless user experience in web applications. By using the technique outlined above, you can offer your users a smoother workflow without the jarring experience of new tabs opening and closing.

The reusable function we've created handles all the complexity while providing a clean API for your application code. It's a small enhancement that can significantly improve the perceived quality of your web application.

Remember that the generatePDF function is deliberately kept separate, allowing you to use whatever PDF generation library best suits your needs (jsPDF, PDFMake, etc.) while maintaining a consistent printing experience.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

The Most Contextual AI Development Assistant

Pieces.app image

Our centralized storage agent works on-device, unifying various developer tools to proactively capture and enrich useful materials, streamline collaboration, and solve complex problems through a contextual understanding of your unique workflow.

👥 Ideal for solo developers, teams, and cross-company projects

Learn more

👋 Kindness is contagious

If you found this post useful, please drop a ❤️ or a friendly comment!

Okay.