Courses/Grit Desktop/PDF & Excel Export
Course 4 of 5~30 min10 challenges

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
Export: Converting application data into a downloadable file format. The three most common formats are PDF (for human-readable documents), Excel (for structured data that can be edited), and CSV (for universal data interchange).

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:

internal/services/export_service.go (overview)
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.

1

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:

PDF generation (simplified)
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.

2

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:

Excel generation (simplified)
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).

3

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

CSV (Comma-Separated Values): The simplest data format — plain text with values separated by commas. Each line is a row, the first line is headers. CSV files can be opened by any spreadsheet app, text editor, or programming language. They are the universal format for data exchange.
CSV generation (simplified)
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:

blog-export.csv
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-17
4

Challenge: 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:

app.go (export methods)
// 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:

React export button
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)
}
5

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.

6

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
Custom PDF header example
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)
}
For complex PDF layouts (invoices, receipts, certificates), consider using HTML-to-PDF conversion instead of building the layout with Go code. You can write the template in HTML/CSS and convert it to PDF, giving you much more control over the design.
7

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
8

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.

9

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.

10

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?