Need to capture website screenshots from a Go application? Whether you're building a monitoring tool, a link-preview service, or an archiving pipeline, this tutorial shows you how to go from URL to PNG image with just the Go standard library — no CGo, no browser binaries to install.
We'll use the URLSnap REST API: you send an HTTP request, get back an image. No Chromium process management, no memory leaks to worry about.
No external packages needed — only the Go standard library (net/http, os, io). Go 1.18+ recommended.
curl -X POST https://urlsnap.dev/api/register \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
# {"key":"us_abc123...","message":"Free tier: 20 requests/day"}
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
)
const apiKey = "us_your_key_here" // get free at urlsnap.dev
func screenshotURL(targetURL, outputPath string) error {
params := url.Values{}
params.Set("url", targetURL)
params.Set("format", "png")
params.Set("full_page", "true")
params.Set("width", "1280")
req, err := http.NewRequest("GET",
"https://urlsnap.dev/api/screenshot?"+params.Encode(), nil)
if err != nil {
return err
}
req.Header.Set("x-api-key", apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API error %d: %s", resp.StatusCode, body)
}
f, err := os.Create(outputPath)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func main() {
if err := screenshotURL("https://example.com", "screenshot.png"); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
fmt.Println("Screenshot saved to screenshot.png")
}
| Parameter | Type | Default | Description |
|---|---|---|---|
| url | string | required | Target URL to screenshot |
| width | integer | 1280 | Viewport width in pixels |
| height | integer | 800 | Viewport height in pixels |
| format | string | png | png or jpeg |
| full_page | bool | false | Capture full scrollable page |
| delay | integer | 0 | Wait ms after load, max 5000 |
package urlsnap
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"time"
)
type Client struct {
APIKey string
HTTPClient *http.Client
}
type ScreenshotOptions struct {
Width int
Height int
Format string // "png" or "jpeg"
FullPage bool
DelayMS int
}
func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
HTTPClient: &http.Client{Timeout: 60 * time.Second},
}
}
func (c *Client) Screenshot(targetURL, outputPath string, opts ScreenshotOptions) error {
params := url.Values{}
params.Set("url", targetURL)
if opts.Width > 0 {
params.Set("width", strconv.Itoa(opts.Width))
}
if opts.Height > 0 {
params.Set("height", strconv.Itoa(opts.Height))
}
if opts.Format != "" {
params.Set("format", opts.Format)
}
if opts.FullPage {
params.Set("full_page", "true")
}
if opts.DelayMS > 0 {
params.Set("delay", strconv.Itoa(opts.DelayMS))
}
req, err := http.NewRequest("GET",
"https://urlsnap.dev/api/screenshot?"+params.Encode(), nil)
if err != nil {
return err
}
req.Header.Set("x-api-key", c.APIKey)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
return fmt.Errorf("daily limit reached — upgrade at urlsnap.dev/#pricing")
}
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API error %d: %s", resp.StatusCode, body)
}
f, err := os.Create(outputPath)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
package main
import (
"fmt"
"sync"
)
func batchScreenshot(client *urlsnap.Client, urls []string) {
var wg sync.WaitGroup
sem := make(chan struct{}, 3) // max 3 concurrent requests
for i, u := range urls {
wg.Add(1)
go func(i int, u string) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
outputPath := fmt.Sprintf("screenshot_%d.png", i)
opts := urlsnap.ScreenshotOptions{Width: 1280, FullPage: true}
if err := client.Screenshot(u, outputPath, opts); err != nil {
fmt.Printf("Error for %s: %v\n", u, err)
return
}
fmt.Printf("Saved %s\n", outputPath)
}(i, u)
}
wg.Wait()
}
func main() {
client := urlsnap.NewClient("us_your_key_here")
urls := []string{
"https://example.com",
"https://github.com",
"https://golang.org",
}
batchScreenshot(client, urls)
}
func (c *Client) PDF(targetURL, outputPath, format string) error {
params := url.Values{}
params.Set("url", targetURL)
params.Set("format", format) // "A4", "Letter", "Legal"
req, _ := http.NewRequest("GET",
"https://urlsnap.dev/api/pdf?"+params.Encode(), nil)
req.Header.Set("x-api-key", c.APIKey)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("PDF error %d: %s", resp.StatusCode, body)
}
f, _ := os.Create(outputPath)
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
// Usage:
// client.PDF("https://example.com", "page.pdf", "A4")
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type UsageInfo struct {
Plan string `json:"plan"`
RequestsToday int `json:"requests_today"`
DailyLimit int `json:"daily_limit"`
RequestsTotal int `json:"requests_total"`
}
func checkUsage(apiKey string) (*UsageInfo, error) {
req, _ := http.NewRequest("GET", "https://urlsnap.dev/api/me", nil)
req.Header.Set("x-api-key", apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var info UsageInfo
return &info, json.NewDecoder(resp.Body).Decode(&info)
}
func main() {
info, err := checkUsage("us_your_key_here")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Plan: %s\nUsed today: %d/%d\nAll-time: %d\n",
info.Plan, info.RequestsToday, info.DailyLimit, info.RequestsTotal)
}
Free tier: 20 screenshots/day. No credit card, no CGo, no browser setup.
Get your free API key →