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!