HTML to PDF in Go
Send HTML (or a URL) to the FrameForge API and get a PDF back. It is rendered in real Chromium, so modern CSS, web fonts and JavaScript all work. No browser to install or babysit.
The code
Go standard library. Set FRAMEFORGE_KEY to your API key.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
body, _ := json.Marshal(map[string]any{
"html": "<h1>Invoice #1001</h1><p>Total: £49.00</p>",
"format": "pdf",
"width": 794, // A4 at 96 DPI
"height": 1123,
})
req, _ := http.NewRequest("POST", "https://api.frameforgeapi.com/render", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", os.Getenv("FRAMEFORGE_KEY"))
client := &http.Client{Timeout: 60 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("render failed: %d %s", res.StatusCode, data))
}
os.WriteFile("invoice.pdf", data, 0o644)
}
How it works
POST /rendertakes JSON with exactly one ofhtml(up to 1 MiB) orurl, plusformat(png,jpegorpdf),widthandheight(1 to 3840 px) andfullPage.- The response body is the file itself (
application/pdf), not JSON, so write the bytes straight to disk or stream them to the user. - Page size: the PDF page is
width×heightpixels.794 × 1123is A4 at 96 DPI and816 × 1056is US Letter. Content longer than one page flows onto more pages of the same size. WithfullPage: trueyou get one tall page instead. - Margins: pages have no printer margins, so add padding in your HTML (for example
body { padding: 48px }). - Caching: identical requests within 24 hours are served from cache and don't count against your quota. Check the
X-FrameForge-Cacheresponse header. - Errors come back as JSON with an
errorcode:401bad key,429rate limited,400 blocked_urlfor private or local addresses.
Rendering a URL instead of HTML
{"url": "https://example.com/report", "format": "pdf", "width": 1280, "height": 800, "fullPage": true}
Doing it yourself instead
chromedp drives Chrome from Go without external dependencies, and Gotenberg is a self-hosted Docker service for HTML-to-PDF.
An API makes sense when you don't want Chromium on your servers, or when you need images (PNG/JPEG) and PDFs from the same call. If you render a few PDFs a month on a server you already run, self-hosting is perfectly reasonable.
Free tool: Website to PDF. Other languages: Node.js · Python · PHP · Ruby · Java · C# / .NET · curl / Bash · Next.js · Laravel · Django · Ruby on Rails