Go HTML to PDF API
Convert any URL into a pixel-perfect PDF with one HTTP request in Go — no chromedp, no Chrome binary in your image, no servers to maintain.
Convert a URL to PDF in Go
No SDK and no browser binaries — the Microlink REST API turns any URL into a hosted PDF with a single HTTP GET. Everything below is standard library, built on net/http, net/url and encoding/json.
Step 01 · Start a module
Nothing to go get. The API is plain HTTP, so the standard library is the only dependency.
go mod init example.com/pdfStep 02 · Convert any URL
Point it at a page, ask for a PDF, and decode the hosted document URL from the JSON response. A shared http.Client carries the timeout and the context cancels the wait with the caller. This helper is reused everywhere below.
main.go
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"time"
)
var client = &http.Client{Timeout: 60 * time.Second}
func pdfURL(ctx context.Context, target string) (string, error) {
endpoint, err := url.Parse("https://api.microlink.io")
if err != nil {
return "", err
}
query := endpoint.Query()
query.Set("url", target)
query.Set("pdf", "true")
query.Set("meta", "false")
endpoint.RawQuery = query.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return "", err
}
res, err := client.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
var payload struct {
Message string `json:"message"`
Data struct {
PDF struct {
URL string `json:"url"`
} `json:"pdf"`
} `json:"data"`
}
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
return "", fmt.Errorf("microlink: %s: %w", res.Status, err)
}
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("microlink: %s: %s", res.Status, payload.Message)
}
if payload.Data.PDF.URL == "" {
return "", errors.New("microlink: no pdf url in response")
}
return payload.Data.PDF.URL, nil
}
func main() {
link, err := pdfURL(context.Background(), "https://example.com")
if err != nil {
panic(err)
}
fmt.Println(link)
}Step 03 · Customize the document
Paper format, margins, orientation, and print CSS are all query params — swap them in for the query block inside pdfURL. Nested options use dot notation, so pdf.format maps to the format field.
options.go
func setOptions(query url.Values, target string) {
query.Set("url", target)
query.Set("pdf.format", "A4") // A0-A6 | Letter | Legal | Tabloid
query.Set("pdf.margin", "0.35cm") // cm, mm, in or px
query.Set("pdf.landscape", "false") // portrait (default) | landscape
query.Set("pdf.scale", "1") // zoom the rendering, 0.1 to 2
query.Set("mediaType", "print") // print CSS stylesheets | screen (default)
query.Set("meta", "false")
}Step 04 · Stream it to disk
The response is a hosted PDF URL on a global CDN. Copy it into a file with a second request that reuses the same client and context, or hand the URL straight to your template.
save.go
func savePDF(ctx context.Context, target string) error {
link, err := pdfURL(ctx, target)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, link, nil)
if err != nil {
return err
}
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("download %s: %s", link, res.Status)
}
file, err := os.Create("document.pdf")
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, res.Body)
return err
}Drop it into your router
A handler, a route, or a standalone binary — the same request becomes your own PDF endpoint, perfect for invoice downloads, nightly reports, and export views in net/http, Gin, Echo or Chi.
- net/http
- Gin
- Echo
- Chi
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// GET /pdf?url=https://example.com
func main() {
router := gin.Default()
router.GET("/pdf", func(c *gin.Context) {
link, err := pdfURL(c.Request.Context(), c.Query("url"))
if err != nil {
c.String(http.StatusBadGateway, err.Error())
return
}
c.Redirect(http.StatusFound, link)
})
router.Run(":3000")
}Every tab reuses the quickstart helper:
func pdfURL(ctx context.Context, target string) (string, error)Ship a binary, not a browser
Rendering a web page to PDF from Go means driving headless Chrome yourself, shelling out to a binary, or drawing the document by hand. The API gives you a real browser rendering engine without any of the infrastructure.
Self-hosted PDF tooling
- chromedp and rod need a Chrome install on every host that runs them
- wkhtmltopdf wrappers shell out to a binary archived upstream
- gofpdf and maroto draw documents from primitives, never from a URL
- Each browser eats hundreds of MB of RAM; workers crash under load
- You build the pooling, queueing, retries and autoscaling
- Fonts, emoji, and modern CSS break differently on every host
Microlink for Go
- One HTTP request — net/http and encoding/json, nothing to go get
- Runs anywhere: serverless, containers, a cron job, your laptop
- Autoscaled managed browser fleet with a 99.9% uptime SLA
- Sub-second cached responses from 340+ edge locations
- A0-A6, Letter, Legal & Tabloid — set as plain query params
- Print stylesheets, custom CSS & DOM interaction, no extra deps
Built for the way you write Go.
A REST API that feels native in Go — one HTTP call, JSON back, and at home in any runtime from a Chi service to an AWS Lambda. Read the PDF guide to go deeper.
No Binaries to Install
Skip chromedp and the Chrome install it expects. There is no rendering engine to download, patch, or keep in sync across hosts.
Standard Library Only
net/http builds the request, net/url builds the query, encoding/json reads the response. No module to add to go.mod.
Router Friendly
Drop it into net/http, Gin, Echo, or Chi as a handler in a few lines. The same request works in every router.
Small Container Images
No browser layer to bundle. A scratch or distroless image stays a static binary, which keeps cold starts on Lambda and Cloud Run short.
Real Browser Rendering
Pages render in Headless Chrome, so JavaScript-driven dashboards and charts come out right — the blind spot of PDF builders that never run scripts.
Zero Infrastructure
No Chrome to pin to a driver version and no browser pool inside your service. Your binary stays a plain HTTP client.
Custom Paper & Layout
Every layout option is a query param: pdf.format, pdf.margin, pdf.landscape, pdf.scale, and pdf.pageRanges.
Screen & Print Media
Set mediaType to print in the same query to apply print stylesheets, or keep the default screen layout.
Generous Free Tier
Start with 25 requests per day — no account, no credit card. Point at pro.microlink.io with an x-api-key header when you scale.
No Binaries to Install
Skip chromedp and the Chrome install it expects. There is no rendering engine to download, patch, or keep in sync across hosts.Standard Library Only
net/http builds the request, net/url builds the query, encoding/json reads the response. No module to add to go.mod.Router Friendly
Drop it into net/http, Gin, Echo, or Chi as a handler in a few lines. The same request works in every router.
Small Container Images
No browser layer to bundle. A scratch or distroless image stays a static binary, which keeps cold starts on Lambda and Cloud Run short.Real Browser Rendering
Pages render in Headless Chrome, so JavaScript-driven dashboards and charts come out right — the blind spot of PDF builders that never run scripts.Zero Infrastructure
No Chrome to pin to a driver version and no browser pool inside your service. Your binary stays a plain HTTP client.
Custom Paper & Layout
Every layout option is a query param: pdf.format, pdf.margin, pdf.landscape, pdf.scale, and pdf.pageRanges.Screen & Print Media
Set mediaType to print in the same query to apply print stylesheets, or keep the default screen layout.Generous Free Tier
Start with 25 requests per day — no account, no credit card. Point at pro.microlink.io with an x-api-key header when you scale.
Try it live in the playground
Paste a URL and see the exact API request before you write a line of Go.
Go PDF FAQ
What Go developers ask before integrating. For formats, limits, and SLA, see the PDF API overview.
Do I need chromedp or a Chrome binary?
No. It is a plain HTTP request to the Microlink API — there is no Chrome to install next to your binary and no driver to pin. The Headless Chrome fleet runs on Microlink's side.
That keeps your container image a static binary, so a scratch or distroless base still works.
Do I need a third-party HTTP client?
No.
net/http sends the request, net/url builds the query string, and encoding/json decodes the response — all standard library.Clients like
resty work the same way if you already use one; the request is an ordinary GET either way.Is it safe to call from many goroutines?
Yes. Each conversion is an independent stateless request, and an
http.Client is safe for concurrent use by multiple goroutines, so one shared client serves your whole worker pool.Concurrency is bounded by your plan rather than your hardware — check the rate limit docs before fanning out widely.
Does it work with Gin, Echo, and Chi?
Yes. Because it is just an HTTP call, it drops into any handler in a few lines — see the tabs above for net/http, Gin, Echo, and Chi, or the PDF guide.
How do I authenticate from Go?
Two things change together: send your key as the
x-api-key header, and point the request at pro.microlink.io instead of api.microlink.io. Sending the header to the free endpoint returns an EPRO error.The helper already builds a request, so add one line before
client.Do(req): req.Header.Set("x-api-key", key). See the authentication docs and pricing.How do I set a timeout?
Both at once, the way the helper above does it: the shared
http.Client carries a Timeout that caps every call, and http.NewRequestWithContext lets a cancelled inbound request abort the one it started.Rendering happens on Microlink's side, so your service only ever waits on the network.
Start converting in Go
Get 25 requests/day with zero commitment — no account, no credit card. Paste the helper into a handler and ship a PDF today.
No login needed
25 reqs/day free
No credit card