HTML to PDF in Go

Go's local options are chromedp (you now operate a browser fleet) or PDF-drawing libraries like gofpdf (you now position every element by hand). The API route keeps your binary static and your Dockerfile boring — standard library only:

1. Convert HTML to PDF

package main

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

func main() {
	body, _ := json.Marshal(map[string]any{
		"html":    "<h1>Hello from Go</h1><p>Rendered by real Chromium.</p>",
		"options": map[string]any{"format": "A4"},
	})

	req, _ := http.NewRequest("POST", "https://inkpdf.dev/v1/pdf", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("INKPDF_KEY"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 60 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		msg, _ := io.ReadAll(res.Body)
		panic(fmt.Sprintf("render failed: %s", msg))
	}

	pdf, _ := io.ReadAll(res.Body)
	os.WriteFile("hello.pdf", pdf, 0o644)
}

2. Invoice from a struct (no HTML)

type LineItem struct {
	Description string  `json:"description"`
	Quantity    int     `json:"quantity"`
	UnitPrice   float64 `json:"unitPrice"`
}

payload := map[string]any{
	"template": "invoice",
	"data": map[string]any{
		"brand":         map[string]any{"name": "Acme Studio", "color": "#0f766e"},
		"invoiceNumber": "INV-2043",
		"issueDate":     "2026-07-01",
		"dueDate":       "2026-07-31",
		"currency":      "USD",
		"to":            map[string]any{"name": "Northwind Traders"},
		"items": []LineItem{
			{Description: "Design work", Quantity: 10, UnitPrice: 95},
		},
		"taxRate": 8.5,
	},
}

Subtotal, tax, and grand total are computed server-side from quantity × unitPrice.

3. Serve the PDF from an http.Handler

func invoiceHandler(w http.ResponseWriter, r *http.Request) {
	pdf, err := renderInvoice(r.Context(), r.PathValue("id")) // wraps the call above
	if err != nil {
		http.Error(w, "could not generate PDF", http.StatusBadGateway)
		return
	}
	w.Header().Set("Content-Type", "application/pdf")
	w.Header().Set("Content-Disposition", `attachment; filename="invoice.pdf"`)
	w.Write(pdf)
}

Also speaks Markdown and URLs

Swap the html field for markdown (with an optional theme) or url to render any public page. One endpoint, four input modes — details in the API docs.

Try it without writing code

The playground renders live against the real pipeline — 20 free renders a day, no signup.