Back to Blog 4 min read

PDF Generation and Download in Laravel-Vue By Snappy

In this guide, we'll explore how to implement a robust PDF generation and download system using Laravel for the backend and Vue.js for the frontend....


In this guide, we'll explore how to implement a robust PDF generation and download system using Laravel for the backend and Vue.js for the frontend. We'll specifically focus on generating purchase order PDFs using the wkhtmltopdf library through Laravel Snappy.






Prerequisites






Setting Up Laravel Snappy



First, install the Laravel Snappy package



composer require barryvdh/laravel-snappy



I'm assuming that you are using a Linux OS, In ubuntu I used which wkhtmltopdf to see the binary path.






Configure your config/snappy.php



return [
    'pdf' => [
        'enabled' => true,
        'binary'  => '/usr/bin/wkhtmltopdf',
        'timeout' => 120,
        'options' => [
            'enable-local-file-access' => true,
            'encoding' => 'UTF-8',
            'page-size' => 'A4'
        ],
    ]
];



Creating the PDF Template



Create a blade template for your PDF (resources/views/pdf_templates/purchase_order.blade.php)



<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Purchase Order</title>
    <style>
        /* Inline styles for better PDF rendering */
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
        }
        /* ... more styles ... */
    </style>
</head>
<body>
    <!-- Your PDF content structure -->
    <div class="header">
        <h1>Purchase Order #{{ $purchaseOrder->po_number }}</h1>
    </div>
    <!-- ... more content ... -->
</body>
</html>



Backend Implementation



Create your controller methods:



class PurchaseOrderController extends Controller
{
    public function downloadInvoiceSnappyPdf(PurchaseOrder $purchaseOrder)
    {
        try {
            $pdf = SnappyPdf::loadView('pdf_templates.purchase_order', [
                'purchaseOrder' => $purchaseOrder
            ])
            ->setOption('encoding', 'UTF-8')
            ->setOption('margin-top', 10);
            
            return $pdf->download($purchaseOrder->po_number . '.pdf');
        } catch (\Exception $e) {
            return response()->json(['error' => 'Failed to generate PDF'], 500);
        }
    }
}



Frontend Implementation



Vue Service ( In short you need to make an Api Call, In my case I'm using service files for example: PurchaseOrderService.js, InvoiceService.js)



Create a service to handle API calls:



// services/PurchaseOrderService.js
export const exportPurchaseOrderInvoice = async (id) => {
    const response = await axios.get(`/api/purchase-orders/${id}/invoice/pdf`, {
        responseType: 'blob'
    });
    return response.data;
};



Vue Component



Implement the download functionality in your component: (You can point this to click event)



// components/PurchaseOrder.vue
export default {
    methods: {
        async downloadPdf() {
            try {
                const response = await exportPurchaseOrderInvoice(this.getSelectedItem.id);
                const blob = new Blob([response], { type: 'application/pdf' });
                const url = URL.createObjectURL(blob);
                const link = document.createElement('a');
                link.href = url;
                link.download = `${this.getSelectedItem.po_number}.pdf`;
                link.click();
                URL.revokeObjectURL(url);
            } catch {
                this.$notification.$emit('error', 'Failed to download PDF');
            }
        }
    }
}



Optimizing the Implementation



For better code organization and reusability, we can create a composable:



Introduction To Composition Api For Newbie



// composables/useDownloader.js
import { ref } from 'vue';

export function useDownloader() {
    const isDownloading = ref(false);

    const downloadFile = async (getData, filename) => {
        isDownloading.value = true;
        try {
            const response = await getData();
            const url = URL.createObjectURL(new Blob([response]));
            const link = document.createElement('a');
            link.href = url;
            link.download = filename;
            link.click();
            URL.revokeObjectURL(url);
        } finally {
            isDownloading.value = false;
        }
    };

    return { downloadFile, isDownloading };
}



It's more likely creating a hook or a helper function in Laravel.



// TopButtons.vue
import { useDownloader } from '@/composables/useDownloader';
import { exportPurchaseOrderInvoice } from '@/services/PurchaseOrderService';

export default {
  name: 'TopButtons',
  props: {
    showBuilder: Boolean,
    loading: Boolean,
    instance: Object
  },
  
  setup(props) {
    const { downloadFile, isDownloading } = useDownloader();

    // Method to handle PDF download
    const downloadPdf = async () => {
      try {
        await downloadFile(
          // Pass the function that gets the data
          () => exportPurchaseOrderInvoice(props.instance.getSelectedItem.id),
          // Set the filename using PO number
          `${props.instance.getSelectedItem.po_number}.pdf`
        );
      } catch (error) {
        // Handle any errors
        console.error('PDF download failed:', error);
      }
    };

    return {
      downloadPdf,
      isDownloading
    };
  },

  // In template
  template: `
    <div>
      <v-btn
        color="#F3F4FA"
        :loading="isDownloading"
        @click="downloadPdf"
      >
        <v-icon size="22">
          {{ mdiPdfBox }}
        </v-icon>
      </v-btn>
    </div>
  `
});



Common Issues and Solutions



1. wkhtmltopdf Installation Issues



If you encounter issues with wkhtmltopdf, ensure proper installation:








2. PDF Rendering Issues






3. File Download Issues






Best Practices






Additional Resources






Happy coding! 🚀