How to Generate PDFs in Go with LightningPDF - Complete Tutorial
Learn how to generate PDFs in Go using LightningPDF API. Covers HTML to PDF, templates, batch generation, error handling, and best practices.
How to Generate PDFs in Go with LightningPDF
Generating PDFs in Go is a common requirement for invoices, reports, receipts, and other documents. While there are several Go PDF libraries available, using an API like LightningPDF offers better performance, easier maintenance, and more features than building your own solution.
PDF generation in Go is the process of programmatically creating PDF documents from HTML, templates, or structured data using Go's standard library or a cloud API, enabling automated document workflows in Go applications.
This tutorial covers everything you need to generate production-quality PDFs in Go using LightningPDF.
Why Use an API Instead of a Library?
Before we dive in, let's address the elephant in the room: why use an API instead of a Go library like gofpdf or gopdf?
Go PDF Libraries:
- ❌ Limited HTML/CSS support
- ❌ Manual layout calculations
- ❌ No built-in templates
- ❌ Memory-intensive for complex documents
- ❌ Difficult to maintain (page breaks, fonts, etc.)
LightningPDF API:
- ✅ Full HTML/CSS support (like Chrome)
- ✅ Automatic layout calculations
- ✅ 15+ starter templates
- ✅ Sub-100ms generation for invoices
- ✅ Zero maintenance (we handle infrastructure)
Let's get started.
Prerequisites
# Install Go (1.20+)
go version
# Create new project
mkdir pdf-demo && cd pdf-demo
go mod init pdf-demo
Quick Start: Your First PDF
Step 1: Get API Key
- Sign up at lightningpdf.dev/signup (free, 50 PDFs/month)
- Copy your API key from the dashboard
Step 2: Basic HTML to PDF
// main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
const (
apiURL = "https://lightningpdf.dev/api/v1/pdf/generate"
apiKey = "your_api_key_here" // Replace with your actual key
)
type PDFRequest struct {
HTML string `json:"html"`
Format string `json:"format,omitempty"`
}
func generatePDF(html string) ([]byte, error) {
// Prepare request
reqBody := PDFRequest{
HTML: html,
Format: "A4",
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("marshal error: %w", err)
}
// Make request
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("request error: %w", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("http error: %w", err)
}
defer resp.Body.Close()
// Check response
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, string(body))
}
// Read PDF bytes
pdfData, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read error: %w", err)
}
return pdfData, nil
}
func main() {
html := `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>Hello from Go!</h1>
<p>This PDF was generated using LightningPDF API.</p>
</body>
</html>
`
pdf, err := generatePDF(html)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
// Save to file
err = os.WriteFile("output.pdf", pdf, 0644)
if err != nil {
fmt.Printf("Save error: %v\n", err)
os.Exit(1)
}
fmt.Println("PDF generated successfully: output.pdf")
}
Run it:
go run main.go
# PDF generated successfully: output.pdf
Congratulations! You just generated your first PDF with Go.
Real-World Example: Invoice Generation
Let's build a proper invoice generator:
// invoice.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"net/http"
"os"
"time"
)
const apiKey = "your_api_key_here"
type Invoice struct {
Number string
Date time.Time
DueDate time.Time
CustomerName string
CustomerEmail string
Items []LineItem
Subtotal float64
Tax float64
Total float64
}
type LineItem struct {
Description string
Quantity int
Price float64
Total float64
}
func (i *Invoice) Calculate() {
i.Subtotal = 0
for _, item := range i.Items {
item.Total = float64(item.Quantity) * item.Price
i.Subtotal += item.Total
}
i.Tax = i.Subtotal * 0.10 // 10% tax
i.Total = i.Subtotal + i.Tax
}
const invoiceTemplate = `
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Helvetica', 'Arial', sans-serif;
padding: 40px;
color: #333;
}
.header {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 2px solid #4F46E5;
}
.company { font-size: 24px; font-weight: bold; color: #4F46E5; }
.invoice-info { text-align: right; }
.invoice-number { font-size: 18px; font-weight: bold; }
.customer {
margin-bottom: 30px;
padding: 20px;
background: #F3F4F6;
border-radius: 8px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 30px 0;
}
th {
background: #4F46E5;
color: white;
padding: 12px;
text-align: left;
}
td {
padding: 12px;
border-bottom: 1px solid #E5E7EB;
}
.text-right { text-align: right; }
.totals {
margin-left: auto;
width: 300px;
margin-top: 20px;
}
.totals tr td { border: none; padding: 8px; }
.totals .total {
font-size: 18px;
font-weight: bold;
padding-top: 12px;
border-top: 2px solid #4F46E5;
}
.footer {
margin-top: 60px;
text-align: center;
color: #6B7280;
font-size: 12px;
}
</style>
</head>
<body>
<div class="header">
<div class="company">ACME Corp</div>
<div class="invoice-info">
<div class="invoice-number">Invoice #{{.Number}}</div>
<div>Date: {{.Date.Format "Jan 2, 2006"}}</div>
<div>Due: {{.DueDate.Format "Jan 2, 2006"}}</div>
</div>
</div>
<div class="customer">
<strong>Bill To:</strong><br>
{{.CustomerName}}<br>
{{.CustomerEmail}}
</div>
<table>
<thead>
<tr>
<th>Description</th>
<th class="text-right">Qty</th>
<th class="text-right">Price</th>
<th class="text-right">Total</th>
</tr>
</thead>
<tbody>
{{range .Items}}
<tr>
<td>{{.Description}}</td>
<td class="text-right">{{.Quantity}}</td>
<td class="text-right">${{printf "%.2f" .Price}}</td>
<td class="text-right">${{printf "%.2f" .Total}}</td>
</tr>
{{end}}
</tbody>
</table>
<table class="totals">
<tr>
<td>Subtotal:</td>
<td class="text-right">${{printf "%.2f" .Subtotal}}</td>
</tr>
<tr>
<td>Tax (10%):</td>
<td class="text-right">${{printf "%.2f" .Tax}}</td>
</tr>
<tr class="total">
<td>Total:</td>
<td class="text-right">${{printf "%.2f" .Total}}</td>
</tr>
</table>
<div class="footer">
Thank you for your business!<br>
Questions? Contact us at billing@acmecorp.com
</div>
</body>
</html>
`
func generateInvoicePDF(invoice *Invoice) ([]byte, error) {
// Calculate totals
invoice.Calculate()
// Render HTML template
tmpl, err := template.New("invoice").Parse(invoiceTemplate)
if err != nil {
return nil, fmt.Errorf("template error: %w", err)
}
var htmlBuf bytes.Buffer
if err := tmpl.Execute(&htmlBuf, invoice); err != nil {
return nil, fmt.Errorf("template execute error: %w", err)
}
// Call LightningPDF API
reqBody := map[string]interface{}{
"html": htmlBuf.String(),
"format": "A4",
}
jsonData, _ := json.Marshal(reqBody)
req, err := http.NewRequest(
"POST",
"https://lightningpdf.dev/api/v1/pdf/generate",
bytes.NewBuffer(jsonData),
)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %d", resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
func main() {
invoice := &Invoice{
Number: "INV-2026-001",
Date: time.Now(),
DueDate: time.Now().AddDate(0, 0, 30),
CustomerName: "Jane Smith",
CustomerEmail: "jane@example.com",
Items: []LineItem{
{Description: "Website Development", Quantity: 1, Price: 5000.00},
{Description: "Logo Design", Quantity: 2, Price: 500.00},
{Description: "Hosting (Annual)", Quantity: 1, Price: 299.00},
},
}
pdf, err := generateInvoicePDF(invoice)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
filename := fmt.Sprintf("invoice-%s.pdf", invoice.Number)
os.WriteFile(filename, pdf, 0644)
fmt.Printf("Invoice generated: %s\n", filename)
}
This generates a professional invoice in <100ms using LightningPDF's native engine.
Using Templates
Instead of HTML strings, use LightningPDF templates:
type TemplateRequest struct {
TemplateID string `json:"template_id"`
Data map[string]interface{} `json:"data"`
}
func generateFromTemplate(templateID string, data map[string]interface{}) ([]byte, error) {
reqBody := TemplateRequest{
TemplateID: templateID,
Data: data,
}
jsonData, _ := json.Marshal(reqBody)
req, _ := http.NewRequest(
"POST",
"https://lightningpdf.dev/api/v1/pdf/generate",
bytes.NewBuffer(jsonData),
)
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// Usage
pdf, err := generateFromTemplate("invoice-v2", map[string]interface{}{
"invoice_number": "INV-001",
"customer_name": "John Doe",
"amount": 1000.00,
})
Batch Generation
Generate multiple PDFs in one API call:
type BatchRequest struct {
TemplateID string `json:"template_id"`
Data []map[string]interface{} `json:"data"`
}
func generateBatch(templateID string, invoices []Invoice) ([]byte, error) {
// Convert invoices to data array
data := make([]map[string]interface{}, len(invoices))
for i, inv := range invoices {
data[i] = map[string]interface{}{
"invoice_number": inv.Number,
"customer_name": inv.CustomerName,
"total": inv.Total,
// ... more fields
}
}
reqBody := BatchRequest{
TemplateID: templateID,
Data: data,
}
jsonData, _ := json.Marshal(reqBody)
req, _ := http.NewRequest(
"POST",
"https://lightningpdf.dev/api/v1/pdf/batch",
bytes.NewBuffer(jsonData),
)
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
// Returns ZIP file with all PDFs
return io.ReadAll(resp.Body)
}
Error Handling
Production-ready error handling:
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func generatePDFWithRetry(html string, maxRetries int) ([]byte, error) {
var lastErr error
for attempt := 0; attempt < maxRetries; attempt++ {
pdf, err := generatePDF(html)
if err == nil {
return pdf, nil
}
lastErr = err
// Exponential backoff
backoff := time.Duration(attempt+1) * time.Second
time.Sleep(backoff)
}
return nil, fmt.Errorf("failed after %d attempts: %w", maxRetries, lastErr)
}
Best Practices
1. Reuse HTTP Client
var httpClient = &http.Client{
Timeout: 30 * time.Second,
}
// Use httpClient instead of http.DefaultClient
2. Use Context for Timeouts
func generatePDFWithContext(ctx context.Context, html string) ([]byte, error) {
req, _ := http.NewRequestWithContext(
ctx,
"POST",
apiURL,
bytes.NewBuffer(jsonData),
)
// ... rest of request
}
3. Pool Goroutines for Batch Jobs
func generateManyPDFs(invoices []Invoice) error {
sem := make(chan struct{}, 10) // Max 10 concurrent
errCh := make(chan error, len(invoices))
for _, inv := range invoices {
go func(invoice Invoice) {
sem <- struct{}{} // Acquire
defer func() { <-sem }() // Release
pdf, err := generateInvoicePDF(&invoice)
if err != nil {
errCh <- err
return
}
// Save PDF...
}(inv)
}
// Wait and check errors...
return nil
}
Conclusion
LightningPDF makes PDF generation in Go simple and fast:
- Sub-100ms for invoices (native engine)
- No dependencies (just
net/http) - Production-ready templates
- Batch API for high volumes
- Zero maintenance (we handle infrastructure)
Ready to start? Get your free API key — 50 PDFs/month, no credit card required.
Next steps:
- Browse the template marketplace
- Read the API documentation
- View pricing plans
Frequently Asked Questions
How do I generate a PDF in Go?
The simplest way to generate PDFs in Go is using the LightningPDF API with Go's standard net/http package. Send a POST request with your HTML content to the API endpoint, and receive PDF bytes in the response. No external dependencies or browser installations are required.
Is LightningPDF faster than gofpdf for PDF generation?
LightningPDF's native engine generates PDFs in under 100 milliseconds, comparable to or faster than local Go libraries like gofpdf. The key advantage is full HTML and CSS support without manual layout calculations, plus template management and batch processing that local libraries cannot provide.
Can I generate PDFs in Go without a browser?
Yes, LightningPDF's native Go engine generates PDFs without launching a browser, achieving sub-100ms performance for invoices, receipts, and reports. For complex layouts requiring full CSS support, the API automatically routes to a Chromium engine, but your Go code stays the same.
How do I batch generate PDFs in Go?
LightningPDF's batch API lets you generate hundreds of PDFs in a single HTTP request from Go. Send an array of data objects with a template ID, and receive a ZIP file containing all generated PDFs. You can also use Go goroutines with a semaphore pattern for concurrent individual requests.
Related Reading
- Generate PDFs in Node.js — Node.js integration guide
- Generate PDFs in Python — Python integration guide
- HTML to PDF: The Complete Guide — All approaches compared
- How to Fix PDF Page Breaks — Solve page break issues
- Best PDF APIs in 2026 — Full API comparison
- LightningPDF vs Puppeteer — Why use an API instead of DIY
LightningPDF Team
Building fast, reliable PDF generation tools for developers.