terminal-to-html: turns ANSI shell output into a browser page
Converts arbitrary shell output (with ANSI) into beautifully rendered HTML
At a glance
- What is it?
- terminal-to-html is a Go library and command that converts arbitrary shell output with ANSI escape codes into rendered HTML, either by piping through a binary or by posting to a small HTTP server. It warns explicitly that it does not sanitise the output against XSS.
- Who is it for?
- terminal-to-html suits a CI system or a developer tool that needs to show raw command output in a browser with its colours intact, and a Go developer who wants the converter as a library rather than a subprocess. It does not suit anything that displays output from untrusted users, because the tool states it is not hardened against cross-site scripting and tells you to run the output through a sanitiser first.
- 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 received new commits within the last day.
- 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 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One binary, two ways in: stdin or a small HTTP server
terminal-to-html is a Go library for converting arbitrary shell output, including ANSI terminal control escape sequences, into rendered HTML. The reference to the ANSI escape code documentation sits in the opening paragraph, which is the right pointer: the whole job is interpreting the colour and control codes a terminal would have interpreted.
The distribution is a single command, and it accepts input two ways.
Piping through the command line:
cat fixtures/pikachu.sh.raw | terminal-to-html -preview > out.htmlOr running it as a web server and posting the content over HTTP:
terminal-to-html -http=:6060 &
curl --data-binary "@fixtures/pikachu.sh.raw" http://localhost:6060/terminal > out.htmlThe second form is the interesting one for infrastructure. A build system that collects logs from many jobs can POST each one to a long-running converter rather than spawning a process per log, and the fixture file used in the example is a raw ANSI file from the repository's fixtures directory.
The library form is the third option, since the module path is github.com/buildkite/terminal-to-html/v3 and the top-level Go files, parser.go, screen.go, element.go, style.go, node.go, output.go and url.go, sit at the root of the module rather than behind an internal package. A Go program can therefore call the converter directly instead of shelling out.
Colouring in the output page is not automatic. The readme points to a sample terminal.css stylesheet and says to wrap the output in an element with the class term-container, for example a div with that class around the terminal output.
The XSS warning is the first thing in the usage section
The readme opens the usage section with a warning, and it is the most important sentence in the documentation for anyone deploying this.
The tool states that terminal-to-html is not hardened against cross-site scripting or other attacks, and that when it is run on user-generated content the output should be passed through an HTML sanitiser prior to displaying it in a browser.
That warning exists because the input is a terminal stream, and terminal streams can contain arbitrary bytes. Escape sequences are how terminals draw colour, and the same mechanism can be used to move the cursor, clear regions and rewrite what is on screen. A converter that reproduces those faithfully in a page inherits the assumption that the content is from a trusted process.
The distinction that matters in practice is between your own build output and someone else's. A CI job running your own test suite produces output you control, and the risk is minimal. A page that shows a log posted by an anonymous user is a different matter, and the sanitiser step is not optional there.
The sane architecture is to keep the conversion and the sanitising as separate stages, with the sanitiser between the converter and the browser, rather than trying to make the converter safe by escaping a few characters. Terminal output has too many ways to carry markup for a blacklist to be reliable.
The tool is MIT licensed and is a rendering component, so the security boundary belongs to whoever embeds it.
Inline images and hyperlinks go beyond basic ANSI
Beyond the standard colour codes, the converter handles two extension mechanisms, and one of them goes beyond what a terminal would do.
The first is iTerm2 inline image support, which is described as basic. Only control sequences with inline=1 are rendered, and preserveAspectRatio is not supported. That is a deliberate narrowing: the iTerm2 format has options the converter ignores, so an image may not appear exactly as it would in a modern terminal.
The second is URL-based images, which is not something ANSI does at all. The readme calls it a way to refer to images from the internet rather than having them transmitted via ANSI, and gives the format as an escape code sequence using 1338:
`1338;url=http://imgur.com/foo.gif;width=100%;height=50px;alt=My Image`
The repository ships an image.sh script to produce that sequence. Note the consequence of the mechanism: the image is fetched by the browser from a URL supplied in the log, which means a log can cause the browser to make a request to an arbitrary host. That is a tracking and information leak vector in exactly the same family as the XSS warning, and it is another reason the sanitiser step is not optional for untrusted content.
Hyperlinks work in the same family with the escape code 1339, and the format is a URL plus a content label, for example 1339;url=https://google.com;content=Google Search, with a link.sh script to produce it. The readme also handles a real parsing edge case: links containing semicolons can be surrounded by either single or double quotation marks, and the example given is a URL with a semicolon inside it.
These two features are why the tool is more than a colour converter. A CI log that prints a clickable link is genuinely more usable in a browser than the same log in a scrollback buffer, and the image support makes log screenshots work.
Installation is a go install or a downloaded binary
If you have Go installed, the readme gives a single command to put terminal-to-html into your GOPATH bin directory:
$ go install github.com/buildkite/terminal-to-html/v3/cmd/terminal-to-htmlThe v3 in the path matters, since the module declares its path as github.com/buildkite/terminal-to-html/v3 with go 1.25.0. The alternative is to download the standalone binary from the repository's releases page, which is the sensible choice for a build agent that should not carry a Go toolchain.
For development, the readme suggests a container that already has the Go cross-compilation tools set up:
$ docker build -t terminal . && docker run -it --rm -v $(pwd):/go/src/github.com/buildkite/terminal-to-html terminal bashThe volume mount at the old GOPATH style path of /go/src/github.com/buildkite/terminal-to-html is not incidental, since it matches the WORKDIR in the Dockerfile and the volume mapping in the compose file, which also mounts the source the same way. The container's CMD is make dist, so a plain docker run on that image builds the distribution rather than starting a shell, and the compose service exists for the release pipeline rather than for local use, passing a GITHUB_RELEASE_ACCESS_TOKEN and a PACKAGECLOUD_TOKEN.
The Dockerfile is pinned by digest to a specific golang image, 1.26.5, and installs zip, ruby, ruby-dev, rpm and locales, then the github-release tool, then the rake, fpm and package_cloud gems. The gems and the release token point at a packaging pipeline that publishes to a hosted package service, and the Makefile dist target builds gzipped archives for Linux, Darwin and the 32-bit armel and i386 targets, plus zips for Windows.
Server side conversion versus a browser side terminal
There is a design fork here, and it is worth naming because it explains who this library is for.
One approach is to convert on the server, as this tool does, and serve finished HTML. The output is a static file that any browser can display with no JavaScript of your own, which is why the sample terminal.css stylesheet approach works and why the result can be cached, archived or attached to a build artifact. The cost is that the conversion has to happen somewhere, either as a subprocess per log or as a service that logs are posted to.
The other approach is to keep the raw ANSI text and let the browser interpret it, with a client side terminal emulator rendering the output live. That avoids a server round trip and keeps the log in its original form, which is attractive when the log is the source of truth and you want to search the raw bytes. The cost is that the rendering depends on a script running in the reader's browser, which is awkward in environments that block scripts, and it is a different rendering from a real terminal.
This library sits firmly on the server side, and the HTTP mode with its port 6060 and its /terminal path is built for exactly the case where many logs are pushed to one converter. The library form of the module is the other server side option for a Go program that would rather not shell out to the binary.
Benchmarks run in Node, not Go
The benchmarking section contains an unusual instruction, and it is the clearest signal about where this project came from.
You are told to run go test -bench . to see raw Go performance, but then that the npm test is the focus, because it best represents the kind of use cases the original code was developed against.
The distinction is between the implementation language and the original implementation. The performance that matters here is the JavaScript path, since the project began as a Node library and the Go version is the port. A Go microbenchmark shows how fast the Go code is, which is a different question from how fast the conversion is for the workload it was written for.
The Makefile shows the Go-side targets: test runs go test, bench runs go test -bench . -benchmem, and clean removes the binary and the dist and bin directories. The default target is all, which depends on test and the binary itself, so the build always runs the test suite first. The binary is built with CGO_ENABLED=0, which produces a static binary with no libc dependency, and with -trimpath and linker flags to strip debug info, which is what makes the cross-compiled archives small.
The version string is derived from source rather than passed in. The Makefile reads version.go, greps for baseVersion and cuts the quoted value, and the version target prints it. The repository has a version.go at the root for exactly that, along with a bk.go that is specific to the Buildkite integration, plus its own test file.
Editorial conclusion
terminal-to-html suits a CI system or a developer tool that needs to show raw command output in a browser with its colours intact, and a Go developer who wants the converter as a library rather than a subprocess. It does not suit anything that displays output from untrusted users, because the tool states it is not hardened against cross-site scripting and tells you to run the output through a sanitiser first. Before using it, decide whether your content is trusted, and if it is not, add sanitising between the converter and the browser rather than relying on the output being escaped.
Frequently asked questions
What is terminal-to-html and what does it do?
It is a Go library and a single command, terminal-to-html, that converts arbitrary shell output including ANSI escape sequences into rendered HTML. Input can arrive on standard input or be posted to a small HTTP server the command starts.
Is terminal-to-html safe for untrusted output?
No. The documentation states that the tool is not hardened against cross-site scripting or other attacks, and that output from user-generated content should be passed through an HTML sanitiser before being displayed in a browser.
How do I install terminal-to-html?
With Go installed, run go install github.com/buildkite/terminal-to-html/v3/cmd/terminal-to-html, which puts the command into your GOPATH bin directory. You can also download a standalone binary from the repository's releases page.
Does terminal-to-html support images and hyperlinks?
It has basic support for iTerm2 inline images, rendering only control sequences with inline=1 and not supporting preserveAspectRatio. It also supports URL-based images through escape code 1338 and hyperlinks through 1339, with image.sh and link.sh provided to produce those sequences.
How do I colour the generated HTML?
Use the sample terminal.css stylesheet from the repository and wrap the output in an element with the class term-container, for example a div with that class around the terminal output.
Official sources
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.
[](https://hysenlabs.com/projects/buildkite-terminal-to-html)Was this useful?