By Ivan Rocha Cardoso - Full-Stack Developer at SiGlobal
Have you ever needed to generate a PDF report or Excel spreadsheet directly from your app without depending on a backend? Today I'll show you how to do this using only Vue.js + Quasar.
Works 100% in the browser, exports tables, reports, and dashboards in a simple and professional way.
Introduction: Why generate PDF/Excel on the frontend?
Common use cases:
- Sales reports that need to be printed or emailed
- Dashboards with data that users want to save locally
- Data tables that need to be exported for analysis
- Offline applications that can't depend on server to generate files
- Cost reduction of infrastructure (no need for server-side libraries)
Frontend approach advantages:
✅ Performance: Distributed processing on users' devices
✅ Simplicity: No additional APIs to maintain
✅ Offline: Works even without internet connection
✅ Cost: Reduces server load
⚙️ Setup: Starting the project
1. Creating the Quasar project
# Install Quasar CLI (if you don't have it yet) npm install -g @quasar/cli # Create new project quasar create pdf-excel-project # or npm init quasar@latest # Navigate to directory cd pdf-excel-project
2. Installing required dependencies
# For PDF generation npm install pdfmake # For Excel generation npm install xlsx # Auxiliary dependencies for types (optional) npm install --save-dev @types/pdfmake
3. Project structure
src/ ├── components/ │ ├── ExportPDF.vue │ └── ExportExcel.vue ├── pages/ │ └── ExportDemo.vue └── utils/ ├── pdfGenerator.js └── excelGenerator.js
Practical Example 1: Export QTable to Excel
ExportExcel.vue Component
<template> <div class="q-pa-md"> <q-table title="Monthly Sales" :rows="salesData" :columns="columns" row-key="id" class="q-mb-md" /> <q-btn color="green" icon="download" label="Export Excel" @click="exportToExcel" /> </div> </template> <script> import * as XLSX from 'xlsx' export default { name: 'ExportExcel', data() { return { columns: [ { name: 'id', label: 'ID', field: 'id', align: 'left' }, { name: 'product', label: 'Product', field: 'product', align: 'left' }, { name: 'salesperson', label: 'Salesperson', field: 'salesperson', align: 'left' }, { name: 'amount', label: 'Amount', field: 'amount', format: val => `$${val}` }, { name: 'date', label: 'Date', field: 'date' } ], salesData: [ { id: 1, product: 'Dell Laptop', salesperson: 'John Smith', amount: 2500.00, date: '2024-01-15' }, { id: 2, product: 'Logitech Mouse', salesperson: 'Mary Johnson', amount: 85.50, date: '2024-01-16' }, { id: 3, product: 'Mechanical Keyboard', salesperson: 'Peter Wilson', amount: 350.00, date: '2024-01-17' }, { id: 4, product: '24" Monitor', salesperson: 'Anna Davis', amount: 800.00, date: '2024-01-18' } ] } }, methods: { exportToExcel() { // Prepare data for Excel const worksheetData = [ // Header ['ID', 'Product', 'Salesperson', 'Amount', 'Date'], // Data ...this.salesData.map(row => [ row.id, row.product, row.salesperson, row.amount, row.date ]) ] // Create worksheet const worksheet = XLSX.utils.aoa_to_sheet(worksheetData) // Create workbook const workbook = XLSX.utils.book_new() XLSX.utils.book_append_sheet(workbook, worksheet, 'Sales') // Export file XLSX.writeFile(workbook, `sales_${new Date().toISOString().split('T')[0]}.xlsx`) this.$q.notify({ color: 'green-4', textColor: 'white', icon: 'cloud_download', message: 'Excel exported successfully!' }) } } } </script>
📄 Practical Example 2: Generate PDF with Logo and Formatting
ExportPDF.vue Component
<template> <div class="q-pa-md"> <q-card class="q-mb-md"> <q-card-section> <div class="text-h6">Sales Report</div> <div class="text-subtitle2">Data visualization</div> </q-card-section> <q-card-section> <q-table :rows="salesData" :columns="columns" row-key="id" hide-pagination :rows-per-page-options="[0]" /> </q-card-section> </q-card> <q-btn color="red" icon="picture_as_pdf" label="Generate PDF" @click="generatePDF" /> </div> </template> <script> import pdfMake from 'pdfmake/build/pdfmake' import pdfFonts from 'pdfmake/build/vfs_fonts' // Configure fonts pdfMake.vfs = pdfFonts export default { name: 'ExportPDF', data() { return { columns: [ { name: 'id', label: 'ID', field: 'id', align: 'left' }, { name: 'product', label: 'Product', field: 'product', align: 'left' }, { name: 'salesperson', label: 'Salesperson', field: 'salesperson', align: 'left' }, { name: 'amount', label: 'Amount', field: 'amount', format: val => `$${val}` }, { name: 'date', label: 'Date', field: 'date' } ], salesData: [ { id: 1, product: 'Dell Laptop', salesperson: 'John Smith', amount: 2500.00, date: '2025-01-15' }, { id: 2, product: 'Logitech Mouse', salesperson: 'Mary Johnson', amount: 85.50, date: '2025-01-16' }, { id: 3, product: 'Mechanical Keyboard', salesperson: 'Peter Wilson', amount: 350.00, date: '2025-01-17' }, { id: 4, product: '24" Monitor', salesperson: 'Anna Davis', amount: 800.00, date: '2025-01-18' } ] } }, methods: { generatePDF() { // Prepare table data for PDF const tableBody = [ // Table header [ { text: 'ID', style: 'tableHeader' }, { text: 'Product', style: 'tableHeader' }, { text: 'Salesperson', style: 'tableHeader' }, { text: 'Amount', style: 'tableHeader' }, { text: 'Date', style: 'tableHeader' } ], // Table data ...this.salesData.map(row => [ row.id.toString(), row.product, row.salesperson, `$${row.amount.toFixed(2)}`, row.date ]) ] // Calculate total const total = this.salesData.reduce((sum, item) => sum + item.amount, 0) // Define PDF structure const docDefinition = { content: [ // Header { columns: [ { text: 'SALES REPORT', style: 'header' }, { text: `Date: ${new Date().toLocaleDateString('en-US')}`, style: 'subheader', alignment: 'right' } ] }, // Divider line { text: '', margin: [0, 10, 0, 10] }, // Table { table: { headerRows: 1, widths: ['auto', '*', '*', 'auto', 'auto'], body: tableBody }, layout: { fillColor: function (rowIndex) { return (rowIndex % 2 === 0) ? '#f3f3f3' : null } } }, // Total { text: `Grand Total: $${total.toFixed(2)}`, style: 'total', margin: [0, 20, 0, 0] }, // Footer { text: 'Report automatically generated by the system', style: 'footer', margin: [0, 30, 0, 0] } ], // Styles styles: { header: { fontSize: 18, bold: true, color: '#2c3e50' }, subheader: { fontSize: 12, color: '#7f8c8d' }, tableHeader: { bold: true, fontSize: 12, color: 'white', fillColor: '#3498db' }, total: { fontSize: 14, bold: true, alignment: 'right', color: '#27ae60' }, footer: { fontSize: 10, italics: true, alignment: 'center', color: '#95a5a6' } } } // Generate and download PDF pdfMake.createPdf(docDefinition).download(`sales_report_${new Date().toISOString().split('T')[0]}.pdf`) this.$q.notify({ color: 'red-4', textColor: 'white', icon: 'picture_as_pdf', message: 'PDF generated successfully!' }) } } } </script>
Advanced Improvements
1. Excel with custom formatting
// utils/excelGenerator.js import * as XLSX from 'xlsx' export function exportAdvancedExcel(data, filename) { const worksheet = XLSX.utils.aoa_to_sheet(data) // Apply styles (limited in XLSX) const range = XLSX.utils.decode_range(worksheet['!ref']) // Cell formatting for (let row = range.s.r; row <= range.e.r; row++) { for (let col = range.s.c; col <= range.e.c; col++) { const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }) if (row === 0) { // Bold header if (!worksheet[cellAddress]) continue worksheet[cellAddress].s = { font: { bold: true }, fill: { fgColor: { rgb: "3498db" } } } } } } // Adjust column widths worksheet['!cols'] = [ { width: 10 }, // ID { width: 30 }, // Product { width: 20 }, // Salesperson { width: 15 }, // Amount { width: 12 } // Date ] const workbook = XLSX.utils.book_new() XLSX.utils.book_append_sheet(workbook, worksheet, 'Data') XLSX.writeFile(workbook, filename) }
2. PDF with custom logo
// utils/pdfGenerator.js import pdfMake from 'pdfmake/build/pdfmake' export function generatePDFWithLogo(data, logoBase64) { const docDefinition = { content: [ // Logo and header { columns: [ { image: logoBase64, // Base64 image width: 100, height: 50 }, { text: 'COMPANY XYZ\nManagement Report', style: 'headerWithLogo', alignment: 'right' } ] }, // Rest of content... ], styles: { headerWithLogo: { fontSize: 14, bold: true, color: '#2c3e50' } } } return pdfMake.createPdf(docDefinition) }
Main page integrating everything
indexPage.vue
Replace the contents of the src/pages/indexPage.vue
file, use the contents;
<template> <q-page class="q-pa-md"> <div class="text-h4 q-mb-lg text-center"> 📊 Demo: PDF & Excel on Frontend </div> <div class="row q-gutter-md"> <div class="col-12 col-md-5"> <ExportExcel /> </div> <div class="col-12 col-md-5"> <ExportPDF /> </div> </div> <q-separator class="q-my-lg" /> <div class="text-center"> <p class="text-body1"> ✨ No backend, no complications! ✨ </p> <p class="text-caption text-grey-7"> 100% browser file generation using Vue.js + Quasar </p> </div> </q-page> </template> <script> import ExportExcel from 'components/ExportExcel.vue' import ExportPDF from 'components/ExportPDF.vue' export default { name: 'ExportDemo', components: { ExportExcel, ExportPDF } } </script>
Conclusion
With these implementations, you have a complete export system that:
- ✅ Works offline - Doesn't depend on server
- ✅ High performance - Client-side processing
- ✅ Flexible - Easy to customize styles and layouts
- ✅ Professional - Good-looking PDFs and Excel files
- ✅ Scalable - Can be integrated into any Quasar project
Next steps:
- Implement charts in PDF using Chart.js
- Add multiple sheets in Excel
- Create reusable templates
- Integrate with upload components for logos
🔗 Useful links
- GitHub project files
💡 Tip: Save this tutorial and try out the code! The best way to learn is by practicing.
🚀 Enjoyed the content? Share with other developers and leave your feedback in the comments!
Top comments (0)