AsciiQR
Render text as a scannable QR code in your terminal.
┌─────────────────────────────────────┐
│ │
│ │
│ █▀▀▀▀▀█ ██▀▀▄ ▄█▀▄▀▀█ █▀▀▀▀▀█ │
│ █ ███ █ ▄▄█ ▄▀▀▀▄▄▀ ▄ █ ███ █ │
│ █ ▀▀▀ █ ▀█ ▀▀▄ █ █ ▀▀▀ █ │
│ ▀▀▀▀▀▀▀ █▄█ ▀▄█ ▀▄▀▄█ ▀▀▀▀▀▀▀ │
│ ▀ ▀█ ▀▀▀▄▄▀ ▄▀▀▄██▀█▄ █▄▄▀ ▀█ │
│ ▀▀▀ ▄▀▀█ █ ▀█▄██ ▀▄ ▀█▄▀▄ ▀▀▄ │
│ ▀▄ ▀▀▀▀ ▀ ▀▀▄ ▀▀█ ▄ ▀█▄▄ │
│ █▀▄█▀█▀▀▄▀▀▄▄▄▀ ▀▀▀██▄█▄▄ ▀█▀ │
│ ▀██▀▀▄▀▄▀▄█▄█▄ ▄▀ ▀▀▀ ▄▀█▄█ │
│ ▀ ▄▀█▀▄▀▄█▄▀ ▀▀▄█ ▀ ███ █ │
│ ▀▀ ▀▀ ▄▄▄ ▀█▄ ▄▀▀ █▀▀▀███▄▄ │
│ █▀▀▀▀▀█ █▄█▀▀▀ █ ▄▀█ ▀ ██ █▄ │
│ █ ███ █ ▄▀▄██▄█▄▄█ ▄▀█▀██▄▀▀▄ │
│ █ ▀▀▀ █ ▀▀▀ ▄▄▀▄ ▀ █▄ █ ▄▀▄▀ │
│ ▀▀▀▀▀▀▀ ▀▀ ▀▀▀ ▀▀▀▀▀ ▀ │
│ │
│ │
└─────────────────────────────────────┘
That code is real. Point your phone at it.
Install
go install github.com/jbrahy/asciiqr@latest
Usage
asciiqr "https://example.com"
echo "https://example.com" | asciiqr
Flags
| Flag |
Default |
Description |
-ec |
M |
Error-correction level: L (7%), M (15%), Q (25%), H (30%) |
-ascii |
off |
Double-width ASCII (##) instead of half-blocks; no ANSI |
-invert |
off |
Flip polarity |
-no-color |
off |
Suppress ANSI colors, keep half-block glyphs |
-no-frame |
off |
Omit the border drawn around the quiet zone |
-version |
|
Print version and exit |
Use it as a library
The packages are importable, so you can generate QR codes inside your own program
without shelling out to the binary.
go get github.com/jbrahy/asciiqr
package main
import (
"fmt"
"github.com/jbrahy/asciiqr/qr"
"github.com/jbrahy/asciiqr/render"
)
func main() {
matrix, err := qr.Encode("https://example.com", qr.LevelMedium)
if err != nil {
panic(err)
}
fmt.Print(render.Render(matrix, render.Options{
Mode: render.HalfBlock, // or render.ASCII
Frame: true,
Color: true, // only when writing to a terminal
}))
}
The API is deliberately small:
| Symbol |
Purpose |
qr.Encode(text, level) ([][]bool, error) |
Encode to a module matrix, indexed [row][col], true = dark, quiet zone included |
qr.ParseLevel(string) (Level, error) |
Parse "L", "M", "Q", "H" (case-insensitive) |
qr.QuietZone |
The 4-module margin constant |
render.Render(matrix, Options) string |
Matrix to newline-terminated text |
render.Options{Mode, Invert, Color, Frame} |
Rendering controls; zero value is half-block, no frame, no ANSI |
render is pure — matrix in, string out, no I/O and no environment inspection — so it
is safe to call from anywhere, including concurrently. Deciding whether the
destination is a terminal is the caller's job.
Because qr.Encode returns a plain [][]bool, you can also skip the renderer entirely
and draw the matrix yourself however you like.
Why it scans
Most terminal QR generators produce something that looks like a QR code. Four
details decide whether a phone can actually read it, and each one fails silently:
Aspect ratio. Terminal cells are about twice as tall as they are wide, so one
character per module yields a vertically stretched code that many scanners reject.
AsciiQR packs two module rows into each line using half-block glyphs (▀ ▄ █), which
restores square modules and halves the height.
Polarity. QR readers need dark modules on a light background. Print block
characters in a dark-themed terminal and you get light-on-dark — inverted — which many
phone scanners refuse. On a terminal, AsciiQR forces a white background with black
text so the code reads correctly under any theme. When piped or redirected, ANSI is
dropped and the output assumes a light background.
Quiet zone. The spec requires 4 light modules on every side; without them scanners
cannot locate the symbol. The underlying encoder does not add one, so AsciiQR does.
The border. That quiet zone is whitespace, and whitespace is fragile — paste it
anywhere that trims trailing spaces and the margin silently vanishes, taking
scannability with it. The drawn frame sits entirely outside the quiet zone, making
the margin visible and paste-safe without touching it.
Notes
Use -ascii for log files, plain-text documents, or terminals without UTF-8.
If the QR is wider than your terminal it will wrap and become unscannable. AsciiQR
warns on stderr when that happens — shrink your terminal font, or use a lower -ec
level to reduce the code's size.
Development
go test ./...
The suite decodes its own rendered output back to the original string across all four
error-correction levels, both render modes, inverted polarity, framed output, and
600-byte content. A passing run is evidence the codes scan, not merely that the
renderer agrees with itself.
Two limits are documented rather than glossed over, because the decoder is more
forgiving than a phone is:
- Round-trip tests cannot catch a missing quiet zone — the decoder reads a bare
matrix with no border at all. Hardcoded assertions in
qr cover it instead.
- Round-trip tests cannot catch on-screen aspect ratio, since they rebuild a
square-pixel image from the parsed grid.
Design notes and the implementation plan live in docs/superpowers/.
Credits
Created by John Brahy and Tre Brahy.
Built on yeqown/go-qrcode for encoding and
makiuchi-d/gozxing for decode verification
in tests.
License
MIT — see LICENSE.