DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to take screenshots and generate PDFs in Go

How to Take Screenshots and Generate PDFs in Go

Go has no headless browser library. When Go services need to capture screenshots or generate PDFs, the usual approaches are: shell out to Chrome, run a sidecar Python process, or reach for a cgo-wrapped browser binding. All of these add operational complexity to what should be a simple output.

Here's the clean approach: one HTTP POST, binary response, standard library.

Screenshot from a URL

package main

import (
    "bytes"
    "encoding/json"
    "io"
    "net/http"
    "os"
)

func Screenshot(url string) ([]byte, error) {
    payload, _ := json.Marshal(map[string]interface{}{
        "url":          url,
        "fullPage":     true,
        "blockBanners": true,
    })

    req, _ := http.NewRequest("POST", "https://pagebolt.dev/api/v1/screenshot", bytes.NewBuffer(payload))
    req.Header.Set("x-api-key", os.Getenv("PAGEBOLT_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

func main() {
    img, err := Screenshot("https://example.com")
    if err != nil {
        panic(err)
    }
    os.WriteFile("screenshot.png", img, 0644)
}
Enter fullscreen mode Exit fullscreen mode

PDF from a URL

func PDF(url string) ([]byte, error) {
    payload, _ := json.Marshal(map[string]interface{}{
        "url":          url,
        "blockBanners": true,
    })

    req, _ := http.NewRequest("POST", "https://pagebolt.dev/api/v1/pdf", bytes.NewBuffer(payload))
    req.Header.Set("x-api-key", os.Getenv("PAGEBOLT_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}
Enter fullscreen mode Exit fullscreen mode

PDF from HTML

If you're generating documents from templates (invoices, reports), pass html instead of url:

import "html/template"

type Invoice struct {
    ID     string
    Amount string
    Due    string
}

const invoiceTmpl = `<!DOCTYPE html>
<html><head><style>
  body { font-family: system-ui; padding: 40px; }
  .amount { font-size: 32px; font-weight: bold; }
</style></head>
<body>
  <h1>Invoice #{{.ID}}</h1>
  <p>Due: {{.Due}}</p>
  <div class="amount">{{.Amount}}</div>
</body></html>`

func InvoicePDF(inv Invoice) ([]byte, error) {
    var buf bytes.Buffer
    t := template.Must(template.New("invoice").Parse(invoiceTmpl))
    t.Execute(&buf, inv)

    payload, _ := json.Marshal(map[string]interface{}{
        "html": buf.String(),
    })

    req, _ := http.NewRequest("POST", "https://pagebolt.dev/api/v1/pdf", bytes.NewBuffer(payload))
    req.Header.Set("x-api-key", os.Getenv("PAGEBOLT_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}
Enter fullscreen mode Exit fullscreen mode

Use in an HTTP handler

func screenshotHandler(w http.ResponseWriter, r *http.Request) {
    targetURL := r.URL.Query().Get("url")
    if targetURL == "" {
        http.Error(w, "url required", http.StatusBadRequest)
        return
    }

    img, err := Screenshot(targetURL)
    if err != nil {
        http.Error(w, "capture failed", http.StatusBadGateway)
        return
    }

    w.Header().Set("Content-Type", "image/png")
    w.Write(img)
}

func main() {
    http.HandleFunc("/screenshot", screenshotHandler)
    http.ListenAndServe(":8080", nil)
}
Enter fullscreen mode Exit fullscreen mode

Record a narrated video

The same pattern extends to video recording — useful for Go services that generate docs, demos, or changelogs:

type Step struct {
    Action   string `json:"action"`
    URL      string `json:"url,omitempty"`
    Selector string `json:"selector,omitempty"`
    Note     string `json:"note,omitempty"`
}

type AudioGuide struct {
    Enabled bool   `json:"enabled"`
    Voice   string `json:"voice"`
    Script  string `json:"script"`
}

type VideoRequest struct {
    Steps      []Step     `json:"steps"`
    AudioGuide AudioGuide `json:"audioGuide"`
    Pace       string     `json:"pace"`
}

func RecordVideo() ([]byte, error) {
    body := VideoRequest{
        Steps: []Step{
            {Action: "navigate", URL: "https://yourapp.com", Note: "Open the app"},
            {Action: "click", Selector: "#get-started", Note: "Get started"},
        },
        AudioGuide: AudioGuide{
            Enabled: true,
            Voice:   "nova",
            Script:  "Here's the app. {{1}} {{2}} One click to begin.",
        },
        Pace: "slow",
    }

    payload, _ := json.Marshal(body)
    req, _ := http.NewRequest("POST", "https://pagebolt.dev/api/v1/video", bytes.NewBuffer(payload))
    req.Header.Set("x-api-key", os.Getenv("PAGEBOLT_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}
Enter fullscreen mode Exit fullscreen mode

No CGo, no subprocess, no browser binary. Pure Go, standard library HTTP client.


Try it free — 100 requests/month, no credit card. → pagebolt.dev

Top comments (0)