# Generate a QR code in Go

> With skip2/go-qrcode, one line writes a file: qrcode.WriteFile("https://example.com", qrcode.Medium, 512, "qr.png"). Encode returns PNG bytes for HTTP handlers. Alternatively fetch the keyless API with net/http and url.QueryEscape the payload. Verify styled codes with GET /api/v1/verify before shipping.

Source: https://useqr.app/docs/developers/generate-a-qr-code-in-go · Last reviewed 2026-08-21 · UseQR is free forever, no signup.

---

## The one-liner: skip2/go-qrcode

`github.com/skip2/go-qrcode` is the established Go library: pure Go, no cgo, no imaging
dependency:

```go
package main

import qrcode "github.com/skip2/go-qrcode"

func main() {
	err := qrcode.WriteFile("https://example.com", qrcode.Medium, 512, "qr.png")
	if err != nil {
		panic(err)
	}
}
```

`qrcode.Low`, `Medium`, `High` and `Highest` map to the four
[error-correction levels](/docs/spec/error-correction-levels-explained) L, M, Q and H. The
third argument is the image size in pixels; the library includes the 4-module
[quiet zone](/glossary/quiet-zone) by default (there is a `DisableBorder` flag: leave it
alone).

## PNG bytes for an HTTP handler

`Encode` returns the bytes directly, which slots into `net/http` without touching disk:

```go
func qrHandler(w http.ResponseWriter, r *http.Request) {
	data := r.URL.Query().Get("data")
	png, err := qrcode.Encode(data, qrcode.Medium, 512)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	w.Header().Set("Content-Type", "image/png")
	w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
	w.Write(png)
}
```

The `immutable` header is safe because the same input always produces identical output.
For high-volume runs (one code per order or asset) a goroutine pool over `WriteFile` is
plenty; patterns in [bulk generation at scale](/docs/developers/bulk-qr-generation-at-scale).

## The zero-dependency route: net/http

If you want styling (colours, module shapes) or typed payloads (WiFi, UPI, vCard) without
writing renderer code, fetch the [keyless API](/docs/developers/free-qr-code-api-no-key):

```go
target := "https://example.com/sale?src=poster"
resp, err := http.Get(
	"https://useqr.app/api/v1/qr?data=" + url.QueryEscape(target) + "&size=1024",
)
if err != nil { /* handle */ }
defer resp.Body.Close()

f, _ := os.Create("qr.png")
defer f.Close()
io.Copy(f, resp.Body)
```

`url.QueryEscape` is the step people skip: a raw `&` in the payload becomes a second query
parameter and truncates the encoded data without any error. No key, no auth: the same
endpoint works [from curl](/docs/developers/generate-a-qr-code-with-curl) for quick
comparison while debugging.

Note that go-qrcode outputs PNG only. For [print work](/docs/print/qr-code-size-for-print)
you want SVG, which the API returns with `format=svg`.

## Verify before shipping

Decode-what-you-encode is one struct and one GET:

```go
var report struct {
	Scannable bool     `json:"scannable"`
	Issues    []string `json:"issues"`
}
resp, _ := http.Get("https://useqr.app/api/v1/verify?data=" + url.QueryEscape(target))
json.NewDecoder(resp.Body).Decode(&report)
if !report.Scannable {
	log.Fatalf("qr failed verification: %v", report.Issues)
}
```

The endpoint renders the code, rasterises it and reads it back with a real decoder, the
[decode-verify loop](/docs/developers/why-verify-that-your-qr-code-decodes) without owning
a decoder dependency.

## FAQ

### What is the standard Go library for QR codes?
github.com/skip2/go-qrcode: pure Go, no cgo. WriteFile writes a PNG in one call and Encode returns the bytes for HTTP handlers.

### How do I set error correction in go-qrcode?
Pass qrcode.Low, Medium, High or Highest as the second argument. They correspond to the standard L, M, Q and H levels.

### Can go-qrcode output SVG?
No, it produces PNG (and terminal strings). For SVG output, call the keyless API with format=svg or post-process the matrix yourself.

### How do I encode a URL that contains query parameters?
Run it through url.QueryEscape before appending it to the API call. An unescaped ampersand silently truncates the payload at the server.

## Try it

- https://useqr.app/url
- https://useqr.app/json
- https://useqr.app/validate
