Skip to content

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/pdf
Step 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