Generate PDF Event Tickets with QR Codes in A6 Format using Go
If you are building an event management system, you have probably faced the "ticket generation problem." Most HTML-to-PDF tutorials show you how to create A4 invoices or reports, but what about smaller formats like tickets, badges, or shipping labels?
Event tickets are typically A6 size (105mm × 148mm), and they need QR codes for check-in scanning. Building this from scratch using low-level PDF libraries is painful. Positioning elements, managing fonts, and embedding images requires hundreds of lines of code.
The smarter approach: design your ticket using HTML/CSS, generate a QR code image, and send it to an API. In this tutorial, we will build a Go program that generates professional A6 event tickets with embedded QR codes using the free aPDF.io API.
The Quick Code
package main
import (
"bytes"
"encoding/json"
"fmt"
"image/png"
"io"
"net/http"
"net/url"
"strings"
"github.com/skip2/go-qrcode"
)
const apiToken = "YOUR_API_TOKEN_HERE"
const apiURL = "https://apdf.io/api/pdf/file/create"
type TicketData struct {
EventName string
Attendee string
TicketID string
Date string
Venue string
QRCodeImage string
}
type PDFResponse struct {
File string `json:"file"`
Expiration string `json:"expiration"`
Pages int `json:"pages"`
Size int `json:"size"`
}
func main() {
ticket := TicketData{
EventName: "Tech Summit 2025",
Attendee: "Alex Johnson",
TicketID: "TICKET-2025-789123",
Date: "December 20, 2025, 18:00",
Venue: "Grand Convention Center",
}
// Generate QR code
qrCode, err := qrcode.Encode(ticket.TicketID, qrcode.Medium, 200)
if err != nil {
fmt.Println("Error generating QR code:", err)
return
}
// Convert to base64 data URL
ticket.QRCodeImage = "data:image/png;base64," + base64EncodeBytes(qrCode)
// Generate HTML
html := generateTicketHTML(ticket)
// Create PDF
pdfURL, err := createPDF(html)
if err != nil {
fmt.Println("Error creating PDF:", err)
return
}
fmt.Println("Ticket generated!")
fmt.Println("Download:", pdfURL)
}
func generateTicketHTML(ticket TicketData) string {
return fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<style>
@page {
size: 105mm 148mm;
margin: 0;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Arial', sans-serif;
width: 105mm;
height: 148mm;
background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%);
color: #fff;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 15mm;
}
.header {
text-align: center;
border-bottom: 2px dashed rgba(255,255,255,0.3);
padding-bottom: 10mm;
}
.event-name {
font-size: 20px;
font-weight: bold;
margin-bottom: 3mm;
}
.date {
font-size: 11px;
opacity: 0.9;
}
.attendee-section {
margin-top: 8mm;
}
.label {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 1px;
opacity: 0.8;
margin-bottom: 2mm;
}
.attendee-name {
font-size: 18px;
font-weight: bold;
}
.venue {
font-size: 12px;
margin-top: 3mm;
opacity: 0.9;
}
.qr-section {
text-align: center;
margin-top: auto;
}
.qr-code {
width: 50mm;
height: 50mm;
background: white;
padding: 3mm;
border-radius: 8px;
margin: 0 auto;
}
.ticket-id {
font-size: 9px;
margin-top: 3mm;
font-family: monospace;
opacity: 0.8;
}
</style>
</head>
<body>
<div class="header">
<div class="event-name">%%s</div>
<div class="date">%%s</div>
</div>
<div class="attendee-section">
<div class="label">Attendee</div>
<div class="attendee-name">%%s</div>
<div class="venue">%%s</div>
</div>
<div class="qr-section">
<img src="%%s" class="qr-code" alt="QR Code"/>
<div class="ticket-id">%%s</div>
</div>
</body>
</html>`, ticket.EventName, ticket.Date, ticket.Attendee, ticket.Venue,
ticket.QRCodeImage, ticket.TicketID)
}
func createPDF(html string) (string, error) {
data := url.Values{}
data.Set("html", html)
data.Set("format", "a6")
data.Set("margin_top", "0")
data.Set("margin_bottom", "0")
data.Set("margin_left", "0")
data.Set("margin_right", "0")
req, err := http.NewRequest("POST", apiURL, strings.NewReader(data.Encode()))
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+apiToken)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API error: %%s", string(body))
}
var result PDFResponse
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
return result.File, nil
}
func base64EncodeBytes(data []byte) string {
// Use standard encoding library
return base64.StdEncoding.EncodeToString(data)
}
Step-by-Step Breakdown
1. Set Up Your Project
go mod init ticket-generator
go get github.com/skip2/go-qrcode
Then get your API token from aPDF.io (it's free).
2. The Key Challenge: A6 Format
Most HTML-to-PDF examples show A4 pages. The secret to small formats is the @page
CSS rule combined with the API's format parameter:
@page {
size: 105mm 148mm; // A6 dimensions
margin: 0; // Full bleed design
}
body {
width: 105mm;
height: 148mm;
}
The API call then specifies format: "a6" to match. This ensures your ticket
prints at the correct physical size without scaling issues.
3. Generating the QR Code
The go-qrcode library makes this simple. Generate a QR code and convert
it to a base64 data URL so it can be embedded directly in the HTML:
import "github.com/skip2/go-qrcode"
import "encoding/base64"
// Generate QR code as PNG bytes
qrCode, err := qrcode.Encode("TICKET-2025-789123", qrcode.Medium, 200)
if err != nil {
panic(err)
}
// Convert to data URL for HTML embedding
dataURL := "data:image/png;base64," + base64.StdEncoding.EncodeToString(qrCode)
Now you can use this data URL as an <img src="..."> directly in your HTML template.
No need to upload the image anywhere.
4. The API Request
Send the complete HTML (with embedded QR code) to the API. The key parameters:
data := url.Values{}
data.Set("html", html)
data.Set("format", "a6") // A6 ticket size
data.Set("margin_top", "0") // Full bleed
data.Set("margin_bottom", "0")
data.Set("margin_left", "0")
data.Set("margin_right", "0")
req, _ := http.NewRequest("POST", apiURL, strings.NewReader(data.Encode()))
req.Header.Set("Authorization", "Bearer "+apiToken)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
Run the Program
go run main.go
Output
Ticket generated!
Download: https://apdf-files.s3.eu-central-1.amazonaws.com/abc123def456.pdf
That URL can be emailed directly to your attendee or saved to your database. Note: Download URLs are valid for 1 hour. If you need permanent storage, download the file and save it to your own storage.
Real-World Integration
In a production event system, you would generate tickets when a user completes payment. Here is how you might structure it:
func HandleTicketPurchase(w http.ResponseWriter, r *http.Request) {
// 1. Process payment
payment := processStripePayment(r)
// 2. Create database record
ticket := createTicketRecord(payment.UserID, payment.EventID)
// 3. Generate PDF ticket
pdfURL, err := generateTicket(ticket)
if err != nil {
http.Error(w, "Failed to generate ticket", 500)
return
}
// 4. Email to attendee
sendTicketEmail(ticket.Email, pdfURL)
// 5. Respond to user
json.NewEncoder(w).Encode(map[string]string{
"status": "success",
"ticket_url": pdfURL,
})
}
Other Small Format Use Cases
This technique works for any small-format document:
- Shipping Labels: Use A6 or custom dimensions (e.g., 100mm × 150mm)
- Conference Badges: Set custom width/height for badge printers
- Product Tags: Generate small labels with barcodes for inventory
- Parking Passes: Print compact permits with QR codes
The key is always the same: set the @page size in CSS and match it with the
API's format parameter or custom dimensions.
Next Steps
- Password Protect VIP Tickets: Use the Password Protection endpoint to secure premium tickets
- Merge Multiple Tickets: If someone buys multiple tickets, use the Merge endpoint to combine them into a single PDF