CLI tool
mdp/qrterminal avatar
mdp/qrterminal

qrterminal draws a scannable QR code in half block characters

QR Codes in your terminal

559 stars66 forksGoMIT

At a glance

What is it?
qrterminal is a Go library and command line tool that renders QR codes as text, either full ASCII blocks or, since a rewrite, using half blocks so the code fits in a third of the vertical space. It is MIT licensed, depends on rsc.io/qr, and ships a static binary in a scratch Docker image.
Who is it for?
Adopt qrterminal if you need to print a QR code into a terminal, a log line or a CI output, and you want half block rendering so the code is compact enough for a terminal window. Do not adopt it if you need to produce a PNG or SVG file, since this library writes characters to a writer, and remember that the rendered code must be scannable from a screen rather than printed on paper.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 28 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Half blocks, and why the code shrank

qrterminal is a Go library for generating QR codes in the terminal, and it has an interesting history that shows up in the API. It was originally a port of the NodeJS version from the qrcode-terminal project, and it was recently updated to allow for smaller code generation using ASCII half blocks.

That change is the whole point of the library. A QR code rendered with full block characters occupies square character cells but looks twice as tall as it is wide, because a character cell is roughly twice as tall as it is wide. The half block character occupies the upper or lower half of a cell, so a pair of half blocks encodes two rows of modules in one row of text. The result is a code that scans the same but takes roughly a third of the vertical space.

The documentation shows both forms as images, one labelled full size ASCII block QR Code and the other smaller half blocks in the terminal.

For a terminal use case this is the difference between a code that needs a tall window and one that fits in a normal prompt, and it is also why the library takes a config with a half blocks flag rather than always using one form or the other.

Three dependencies, and one that does the real work

The module is github.com/mdp/qrterminal/v4 and it declares go 1.25.0, so it needs a recent Go toolchain. The direct dependencies are three and they divide up cleanly.

rsc.io/qr is the encoder. Everything about the QR format itself, the encoding of the payload, the error correction and the module placement, comes from that library, and qrterminal's job is to take the resulting matrix and paint it into characters.

golang.org/x/term and github.com/mattn/go-colorable are both there for terminal handling. go-colorable covers the case where the output is redirected to a file or piped, and x/term provides the lower level terminal handling. There is one indirect dependency, go-isatty, which does the same job at an even lower level.

The short dependency list is worth noting because of what is absent. The library pulls in no image encoding library and no font library, because it never produces an image. Everything it needs is a QR encoder and a way to know what kind of output stream it is writing to.

The repository is small in the same way. At the top level there is qrterminal.go and qrterminal_test.go, a cmd directory holding the command, a go.mod and go.sum, a Makefile, a Dockerfile, a .goreleaser.yaml and a CHANGELOG.

The library API, from one call to a full config

The simplest use is a single call that writes to a writer:

go
import (
    "github.com/mdp/qrterminal/v4"
    "os"
    )

func main() {
  // Generate a 'dense' qrcode with the 'Low' level error correction and write it to Stdout
  qrterminal.Generate("https://github.com/mdp/qrterminal", qrterminal.L, os.Stdout)
}

The three arguments are the content, the error correction level as a constant, and the writer. The level constants are named after the QR standard levels, so qrterminal.L is Low and qrterminal.M is Medium.

When you need more control there is a config struct and a second function. The documentation example builds a large inverted barcode with medium redundancy and a one pixel border:

go
import (
    "github.com/mdp/qrterminal/v4"
    "os"
    )

func main() {
  config := qrterminal.Config{
      Level: qrterminal.M,
      Writer: os.Stdout,
      BlackChar: qrterminal.WHITE,
      WhiteChar: qrterminal.BLACK,
      QuietZone: 1,
  }
  qrterminal.GenerateWithConfig("https://github.com/mdp/qrterminal", config)
}

The BlackChar and WhiteChar fields are the inversion trick, and they are named constants, qrterminal.WHITE and qrterminal.BLACK, so the field names read oddly at first. Setting BlackChar to the white constant produces a barcode whose dark modules are drawn as light characters, which is what you want on a dark terminal theme where the default foreground is light.

QuietZone sets the border width, and the QR standard requires a quiet zone around the code so scanners can lock on, so this is not a cosmetic setting.

The half block variant, which is a one line change

The half block form uses the same config struct with one flag set:

go
import (
    "github.com/mdp/qrterminal/v4"
    "os"
    )

func main() {
  config := qrterminal.Config{
      HalfBlocks: true,
      Level: qrterminal.M,
      Writer: os.Stdout,
  }
  qrterminal.GenerateWithConfig("https://github.com/mdp/qrterminal", config)
}

HalfBlocks true is the only difference, and the level and writer are unchanged, so switching between a full block code and a half block code is a one line edit in an application that already uses the config form.

That is a good sign about the API design. The compact rendering is the feature that justifies the library over the original port, and it does not require a different function, a different type or a different calling convention. It is a field on the same struct.

The library also has to work when the output is not a terminal, and the two terminal related dependencies are the mechanism for that. A QR code written into a log file or piped into another program should still be readable, so the library detects the stream and adapts.

Four ways to get the binary, and a scratch image

The command line tool is distributed four ways. On macOS there is a Homebrew tap, with the install command `brew install mdp/tap/qrterminal`. On other platforms you download from the releases page. From source you can install it with `go install github.com/mdp/qrterminal/v4/cmd/qrterminal@latest`. And there is a container image, pulled with `docker pull ghcr.io/mdp/qrterminal:latest`.

The Docker image is the minimal kind, three lines of Dockerfile building on scratch:

dockerfile
FROM scratch
COPY qrterminal /
ENTRYPOINT ["/qrterminal"]

Building from scratch means no base distribution, no shell and no package manager in the image, only the static binary. For a tool that prints a QR code, that is the right size of artefact, and it is also why the image is safe to run in a pipeline with no shell to attack.

The Makefile matches that style, with a build target, a release target calling goreleaser, a reltest target that does a snapshot release without publishing, and a test target running go test with coverage.

The release history shows a major version jump recently. The tags are v4.0.0 from 2026-09-25, v3.2.1 from 2025-03-19 and v3.2.0 from 2023-11-06, and the last push was on 2026-09-25, the same day as the v4 tag. The v4 bump is the import path change, since the module is now github.com/mdp/qrterminal/v4, which in Go means the old v3 import path stops resolving.

Piping is the feature that makes it useful in scripts

The command line usage is deliberately short. A basic code is printed with `qrterminal https://github.com/mdp/qrterminal`, medium error correction is selected with `qrterminal https://github.com/mdp/qrterminal -l M`, and the container equivalent is `docker run --rm ghcr.io/mdp/qrterminal:latest 'https://github.com/mdp/qrterminal'`.

The detail that makes this genuinely useful in scripts is that you can pipe text in through standard input, so a file becomes a code with no arguments at all: `cat wireguard_peer.conf | qrterminal`. The same thing in a container is `cat wireguard_peer.conf | docker run --rm -i ghcr.io/mdp/qrterminal:latest`.

That is a real pattern for terminal sessions. A WireGuard peer configuration is a small block of text that has to move from one machine to another, and printing it as a QR code makes the transfer a camera rather than a copy and paste. The container form needs the -i flag to keep standard input open, which is the usual Docker detail.

The error correction level flag matters for that use case. A code scanned from a screen at an angle, or a code on a curved surface, will drop modules, and a higher error correction level trades size for resilience.

Editorial conclusion

Adopt qrterminal if you need to print a QR code into a terminal, a log line or a CI output, and you want half block rendering so the code is compact enough for a terminal window. Do not adopt it if you need to produce a PNG or SVG file, since this library writes characters to a writer, and remember that the rendered code must be scannable from a screen rather than printed on paper. Before you use it, check three things: which error correction level you want, because the default is Low and a higher level produces a denser code, whether the half block output is legible in your terminal font, since that depends on how the font renders block characters, and whether the output is going to a terminal at all, since the dependencies on golang.org/x/term and go-colorable exist to detect and adapt to that.

Frequently asked questions

How do I install qrterminal?

On macOS with brew install mdp/tap/qrterminal, from the releases page on other platforms, from source with go install github.com/mdp/qrterminal/v4/cmd/qrterminal@latest, or as a container with docker pull ghcr.io/mdp/qrterminal:latest. As a library in an application, the import path is github.com/mdp/qrterminal/v4.

What is the difference between full block and half block QR codes in qrterminal?

Full block rendering uses one character per module row, which looks square in character cells but is visually twice as tall as wide. The half block mode was added so the code is smaller, using upper and lower half block characters to encode two module rows per text row.

How do I print a QR code from text on standard input?

Pipe the text to the command, for example cat wireguard_peer.conf | qrterminal. The container form needs the -i flag so standard input stays open: cat wireguard_peer.conf | docker run --rm -i ghcr.io/mdp/qrterminal:latest.

Which error correction levels does qrterminal support?

The level is passed as a constant such as qrterminal.L for Low and qrterminal.M for Medium, and the command line takes it with the -l flag, for example qrterminal https://github.com/mdp/qrterminal -l M. A higher level means a denser code that survives more damage.

Can qrterminal produce an inverted code or set the border width?

Yes, through the Config struct used with GenerateWithConfig. BlackChar and WhiteChar are set to the WHITE and BLACK constants to invert the code, and QuietZone sets the border width, which scanners need in order to lock on to the code.

Official sources

  1. Issues
  2. License: MIT
  3. mdp/qrterminal on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mdp-qrterminal.svg)](https://hysenlabs.com/projects/mdp-qrterminal)

Was this useful?