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

Here is a complete working example. We will break it down step by step afterward.
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

First, initialize your Go module and install the QR code library:
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

Now that you have automated ticket generation, consider these enhancements:
Ready to build?
Get your free API token here
Most APIs charge you per document. aPDF.io is built to be a developer-friendly, free alternative that handles the heavy lifting without the monthly subscription.