PDF & Excel Export
In this course, you will learn how to export data from your desktop app into PDF documents, Excel spreadsheets, and CSV files. You'll understand the export service, the download flow, and how to customize report templates.
Why Export?
Users need to get data out of your application. Common use cases include:
- • Invoices — generate a PDF invoice to email or print for a client
- • Reports — export monthly data summaries for management
- • Data backups — export all records to CSV for archiving or migration
- • Spreadsheets — export data to Excel for further analysis in tools like Google Sheets
The Export Service
Grit desktop apps include a built-in export service in Go with three main methods. The service lives in the internal/services/ directory:
type ExportService struct {
db *gorm.DB
}
func NewExportService(db *gorm.DB) *ExportService {
return &ExportService{db: db}
}
// GeneratePDF creates a formatted PDF document from records
func (s *ExportService) GeneratePDF(resource string) (string, error) {
// Queries data, formats into PDF, saves to temp file
// Returns the file path
}
// GenerateExcel creates an .xlsx spreadsheet from records
func (s *ExportService) GenerateExcel(resource string) (string, error) {
// Queries data, creates Excel workbook with headers and rows
// Returns the file path
}
// GenerateCSV creates a .csv file from records
func (s *ExportService) GenerateCSV(resource string) (string, error) {
// Queries data, writes CSV with headers and rows
// Returns the file path
}Each method queries the database, formats the data, writes it to a temporary file, and returns the file path. The React frontend then uses this path to trigger a download.
Challenge: Find the Export Service
Open the export service file in your project (look in internal/services/). What methods does it have? What Go libraries does it import for PDF and Excel generation?
Generating a PDF
The PDF export creates a formatted document with a title, headers, and a data table. The Go code uses a PDF library to build the document programmatically:
func (s *ExportService) GeneratePDF(resource string) (string, error) {
// 1. Query all records from the database
var blogs []models.Blog
s.db.Find(&blogs)
// 2. Create a new PDF document
pdf := gopdf.New()
pdf.AddPage()
// 3. Add a title
pdf.SetFont("Helvetica", "B", 18)
pdf.Cell(nil, "Blog Export Report")
// 4. Add a table with headers
pdf.SetFont("Helvetica", "B", 10)
headers := []string{"ID", "Title", "Created At", "Status"}
for _, h := range headers {
pdf.Cell(nil, h)
}
// 5. Add data rows
pdf.SetFont("Helvetica", "", 10)
for _, blog := range blogs {
pdf.Cell(nil, blog.Title)
pdf.Cell(nil, blog.CreatedAt.Format("2006-01-02"))
}
// 6. Save to temp file and return path
path := filepath.Join(os.TempDir(), "blog-export.pdf")
pdf.WritePdf(path)
return path, nil
}The generated PDF includes proper formatting: bold headers, alternating row colors, page numbers, and timestamps.
Challenge: Export Blogs to PDF
Make sure you have at least 5 blog posts in your app. Find the export button in the blog list page and click it to generate a PDF. Does the file download? Open it — are all your blog posts listed?
Generating Excel Files
Excel exports create .xlsx files with proper sheets, headers, and data rows. The Go code uses the excelize library:
func (s *ExportService) GenerateExcel(resource string) (string, error) {
// 1. Query all records
var blogs []models.Blog
s.db.Find(&blogs)
// 2. Create a new Excel workbook
f := excelize.NewFile()
sheet := "Blogs"
f.SetSheetName("Sheet1", sheet)
// 3. Write headers (row 1)
headers := []string{"ID", "Title", "Content", "Created At"}
for i, h := range headers {
cell := fmt.Sprintf("%c1", 'A'+i)
f.SetCellValue(sheet, cell, h)
}
// 4. Style headers (bold, background color)
style, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true},
Fill: excelize.Fill{Type: "pattern", Color: []string{"#6c5ce7"}, Pattern: 1},
})
f.SetCellStyle(sheet, "A1", "D1", style)
// 5. Write data rows
for i, blog := range blogs {
row := i + 2
f.SetCellValue(sheet, fmt.Sprintf("A%d", row), blog.ID)
f.SetCellValue(sheet, fmt.Sprintf("B%d", row), blog.Title)
f.SetCellValue(sheet, fmt.Sprintf("C%d", row), blog.Content)
f.SetCellValue(sheet, fmt.Sprintf("D%d", row), blog.CreatedAt.Format("2006-01-02"))
}
// 6. Save to temp file
path := filepath.Join(os.TempDir(), "blog-export.xlsx")
f.SaveAs(path)
return path, nil
}The Excel file includes styled headers, proper column widths, and typed cells (numbers stay as numbers, dates as dates).
Challenge: Export to Excel
Export your blog data to Excel. Open the .xlsx file in Excel, Google Sheets, or LibreOffice. Does it have proper column headers? Are the dates formatted correctly?
CSV Export
func (s *ExportService) GenerateCSV(resource string) (string, error) {
var blogs []models.Blog
s.db.Find(&blogs)
path := filepath.Join(os.TempDir(), "blog-export.csv")
file, _ := os.Create(path)
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// Write headers
writer.Write([]string{"ID", "Title", "Content", "Created At"})
// Write data rows
for _, blog := range blogs {
writer.Write([]string{
fmt.Sprintf("%d", blog.ID),
blog.Title,
blog.Content,
blog.CreatedAt.Format("2006-01-02"),
})
}
return path, nil
}CSV is the simplest format and works everywhere. The output looks like this:
ID,Title,Content,Created At
1,My First Post,Hello world...,2026-03-15
2,Second Post,More content...,2026-03-16
3,Third Post,Even more...,2026-03-17Challenge: Export to CSV
Export your blog data to CSV. Open the file in a text editor (not a spreadsheet app). Can you read the raw data? Each line should be one blog post with comma-separated values.
Download Flow in Desktop
The export flow in a desktop app works differently from a web app. There is no browser download dialog — instead, Go generates the file on disk and the React frontend opens it:
- 1. React calls the Go export method via Wails binding
- 2. Go queries the database and generates the file in a temp directory
- 3. Go returns the file path to React
- 4. React uses the Wails runtime to open the file or show a save dialog
On the Go side, the bound method looks like:
// Export blogs to PDF — returns the file path
func (a *App) ExportBlogsPDF() (string, error) {
return a.exportService.GeneratePDF("blogs")
}
// Export blogs to Excel
func (a *App) ExportBlogsExcel() (string, error) {
return a.exportService.GenerateExcel("blogs")
}
// Export blogs to CSV
func (a *App) ExportBlogsCSV() (string, error) {
return a.exportService.GenerateCSV("blogs")
}On the React side:
import { ExportBlogsPDF } from '../../../wailsjs/go/main/App'
import { BrowserOpenURL } from '../../../wailsjs/runtime'
async function handleExportPDF() {
const filePath = await ExportBlogsPDF()
// Open the file with the system default app
BrowserOpenURL("file://" + filePath)
}Challenge: Trace the Export Flow
Click an export button in the UI. Where does the file save? Check your system's temp directory. Can you find the exported file there? Try opening it directly from that location.
Challenge: Export All Three Formats
Export your blog data to all three formats: PDF, Excel, and CSV. Open each file. Compare them — which format is best for printing? Which is best for editing? Which is best for importing into another app?
Custom Report Templates
You can customize the PDF layout to match your brand. Common customizations include:
- • Header — add your app name, logo, or company information
- • Footer — page numbers, generation date, confidentiality notice
- • Colors — match your brand's accent color for headers and borders
- • Layout — landscape vs portrait, margins, font sizes
func (s *ExportService) addHeader(pdf *gopdf.GoPdf, title string) {
// Company name
pdf.SetFont("Helvetica", "B", 24)
pdf.SetTextColor(108, 92, 231) // Grit purple
pdf.Cell(nil, "My Company")
// Report title
pdf.SetFont("Helvetica", "", 14)
pdf.SetTextColor(144, 144, 168) // Muted text
pdf.Cell(nil, title)
// Date
pdf.SetFont("Helvetica", "", 10)
pdf.Cell(nil, "Generated: " + time.Now().Format("January 2, 2006"))
// Divider line
pdf.Line(20, pdf.GetY()+5, 575, pdf.GetY()+5)
}Challenge: Customize the PDF Header
Open the export service and modify the PDF generation code. Add a custom header with your app's name and today's date. Change the header color to match your theme. Export again — does the new header appear?
What You Learned
- Why export matters — invoices, reports, data backups, spreadsheets
- The export service with GeneratePDF, GenerateExcel, and GenerateCSV methods
- How PDF generation works — headers, tables, formatting with Go libraries
- How Excel generation works — workbooks, sheets, styled headers, typed cells
- How CSV export works — the simplest, most universal data format
- The desktop download flow — Go generates file, returns path, React opens it
- How to customize PDF templates with headers, footers, and brand colors
Challenge: Build an Invoice System
Generate an Invoice resource with fields: customer:string,amount:float,date:date,status:string,paid:bool. Add 10 invoices with different customers and amounts.
Challenge: Export Invoices
Export your invoices to PDF, Excel, and CSV. The PDF should look like a real invoice report. The Excel file should have proper number formatting. The CSV should be importable into Google Sheets.
Challenge: Compare the Three Formats
Open all three exported files side by side. Which format preserves the formatting best (PDF)? Which is easiest to edit (Excel)? Which is the smallest file size (CSV)? When would you use each one?
Enjoying the course?
Help us grow — star us on GitHub, subscribe on YouTube, and follow on LinkedIn.