Open-source project
vdjagilev/nmap-formatter avatar
vdjagilev/nmap-formatter

nmap-formatter and the case for turning scan XML into something readable

A tool that allows you to convert NMAP results to html, csv, json, markdown, graphviz (dot), sqlite, excel and d2-lang. Simply put it's nmap converter.

745 stars61 forksGoMIT

At a glance

What is it?
nmap-formatter is a Go command line tool that reads Nmap XML and writes it out as HTML, CSV, JSON, Markdown, Graphviz dot, SQLite, Excel or d2, and it can also be imported as a Go library. It suits anyone who needs scan results in a report or a database rather than on screen, and it does nothing about the scanning itself.
Who is it for?
nmap-formatter fits a security engineer or a network team that already runs Nmap and needs the result as a deliverable, a spreadsheet row set, a database table or a diagram rather than as terminal output. It is the wrong tool if you have not scanned yet, and it is the wrong tool if you need to change what was scanned, since it only reads the XML the scanner produced.
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 105 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Nmap XML in, one of eight formats out

The tool does one thing: it converts Nmap XML output into a format you can do something with. The description in the repository calls it simply an nmap converter, and the list of outputs is html, csv, json, markdown, graphviz dot, sqlite, excel and d2.

That list is the interesting part, because each output answers a different need. HTML and Markdown are for a report or a ticket. CSV and JSON are for a spreadsheet or a script. Excel is for the person who will not open a CSV. Graphviz dot and d2 are for a topology picture, and the README shows the d2 route ending in a PNG through the `d2` tool. SQLite is for keeping results in a database, which is the one that changes how you work over time, because the output becomes queryable rather than a file you re-read.

The whole interface is a positional format name, an optional path, and flags:

bash
nmap-formatter [html|csv|md|json|dot|sqlite|excel|d2] [path-to-nmap.xml] [flags]

The input can also arrive on standard input, which is the more useful form when you are chaining it with the scanner itself. The README gives the pattern for reading from `stdin` and parsing it, and then repeats it for each output format. For the graphviz case:

bash
cat example.xml | nmap-formatter dot | dot -Tsvg > test.svg
# open test.svg with browser

And for the SQLite case, where the output goes to a file rather than to standard output:

bash
cat example.xml | nmap-formatter sqlite --sqlite-dsn nmap.sqlite
# open nmap.sqlite with any database tool

The d2 route follows the same shape, piping into the external `d2` renderer to produce an image.

Four flags, and one of them changes what your report says

The flag list in the documentation is short, and one entry deserves more attention than the others.

There is `-f, --file [filename]`, which outputs the result to a file, with the default being standard output. There is `--help` for the usage message. There is `--version` for the version, which the documentation notes can also be used in the form `./nmap-formatter version`.

And then there is `--skip-down-hosts`, which skips hosts that are down and, by default, is `true`.

Read that default carefully, because it is a reporting decision rather than a formatting decision, and it is baked into the tool rather than left to the caller. A scan of a network where many hosts are down will produce an output that omits them. For a chart of open ports that is usually what you want. For a report that claims to describe the estate, a set of silently absent hosts is a different proposition, and the difference between the two reports is a single flag.

The documentation also notes that it is possible to change various output options, and points to the usage wiki page for the flags and output options section for the details beyond the four listed here. So the four flags are the visible surface, and the output options are a larger set documented elsewhere.

For anyone scripting this, the practical point is that the tool writes to standard output by default, which means it composes with the rest of a pipeline. Every example that pipes into another tool, dot or d2, relies on that.

The dependency list shows how each output is built

The Go module manifest is short enough to read as a map of the output formats, and the direct dependencies line up with the feature list one for one.

`github.com/xuri/excelize/v2` at v2.11.0 is the Excel writer. `github.com/mattn/go-sqlite3` at v1.14.44 is the SQLite driver, and it is a cgo binding, which the Dockerfile confirms by building with `CGO_ENABLED=1` and installing `gcc` and `musl-dev` in the builder stage. `oss.terrastruct.com/d2` at v0.7.1 is the d2 language support, and its transitive list pulls in a JavaScript engine, `github.com/dop251/goja`, which is what d2 uses to evaluate expressions.

Two dependencies are less obvious and more interesting. `github.com/expr-lang/expr` at v1.17.8 is a Go expression language evaluator, and it is the most likely explanation for the output options the documentation refers to: the tool can evaluate expressions against the scan data to customise what it emits. `github.com/spf13/cobra` at v1.10.2 is the command line framework, which is where the subcommand, flag and help behaviour comes from.

`golang.org/x/net` at v0.56.0 is there for the HTML output path, and the indirect list is long but readable. `github.com/PuerkitoBio/goquery` is an HTML parser, `github.com/alecthomas/chroma/v2` is a syntax highlighter, and `github.com/yuin/goldmark` is a Markdown parser, which together suggest the HTML and Markdown outputs are rendered documents rather than a plain dump. The `github.com/xuri/nfp` and `github.com/xuri/efp` entries are the number and fixed-point format helpers that excelize uses.

The module path carries a `/v3` suffix, `github.com/vdjagilev/nmap-formatter/v3`, and the module requires Go 1.25.0.

Two ways to consume it, and the library route is the quieter one

The repository documents two usage modes, and the second one is easy to overlook.

The obvious one is the binary, installed with the Go toolchain:

bash
go install github.com/vdjagilev/nmap-formatter/v3@latest
cd nmap-formatter
go build

The other is import as a library. The README has a section titled Use as a library and points to a wiki page with examples of how to use the project from Go code.

The layout supports that reading. There is a `formatter/` directory at the top level, separate from `cmd/`, with `main.go` at the root. A `formatter` package that is distinct from the command wiring is the shape of a library that other projects are meant to import, and the module path is versioned with `/v3`, which is what Go requires before you can import a project at a major version above one.

For a team that already runs Nmap across a fleet and wants the results in their own reporting pipeline, the library route is the one that removes the shell step entirely. The formats are then a function call rather than a subprocess, and the output does not have to be parsed back out of standard output.

The repository also carries a `Dockerfile`, which matters for the SQLite format specifically. The image is a two stage build on `golang:1.25-alpine` producing a binary with `CGO_ENABLED=1`, then a runtime stage on `alpine:3.22` that copies the binary in and sets it as the entrypoint. The cgo requirement is the reason the builder stage installs `gcc` and `musl-dev`, and it is the detail to check if you deploy this as a scratch based container.

What it does not do, and where the XML comes from

The scope is narrow and worth stating, because the name invites a wrong assumption.

nmap-formatter does not scan. It reads XML that a scanner produced and converts it. The tool does not run Nmap, does not schedule scans, does not compare scans over time, and does not tell you whether a host is vulnerable. It is a translator between one machine readable format and eight others.

That has a practical consequence about input. The tool consumes Nmap XML, which is one specific output format, and the documentation consistently refers to a path to an nmap.xml file. Anything that produces a different shape of XML, a normalise step, or a different scanner's output, is outside what this tool handles. If your pipeline captures Nmap in its normal line oriented output rather than XML, something has to produce the XML first.

The reverse direction is also out of scope. Nothing here writes scan configurations, schedules a range, or imports results into a vulnerability database.

There is one more constraint that is easy to miss in a Go tool with a cgo dependency. The SQLite output is the feature that makes the tool interesting for ongoing work, and it is also the feature that requires cgo, which means the binary has a C toolchain dependency at build time and a dynamic library at run time. The Dockerfile handles this explicitly with `CGO_ENABLED=1` and the `gcc` and `musl-dev` packages, and the manifest pins `github.com/mattn/go-sqlite3` at v1.14.44. A static binary build will need the same attention.

Against writing your own parser, the difference is the output catalogue

The realistic alternative is to write the conversion yourself, because Nmap XML is a documented format and a parser is not a large job.

The difference in approach comes down to what the alternative has to reproduce. Parsing the XML is the easy half. The hard half is the eight output formats, and each one carries decisions that are not obvious until you have made them. The Excel writer needs a sheet layout and cell types. The SQLite output needs a schema, a table name, and a DSN, and the flag `--sqlite-dsn nmap.sqlite` shows the connection string is a parameter rather than a hard coded path. The HTML output needs styling, and the presence of goquery and chroma in the dependency tree suggests the markup is generated as a real document with highlighted content rather than a table dump. The dot and d2 outputs need a graph model decided in advance, which nodes represent hosts, which represent ports, and how the edges are drawn.

There is also the expression evaluator, and that is the part a hand written converter will not have at all. With `expr-lang/expr` in the dependency list, the output options the documentation refers to are most plausibly expressions evaluated against the scan data, which turns the tool from a fixed set of eight converters into something that can reshape its output per job.

The cost of the dependency is the dependency. You pull in excelize, the d2 toolchain with a JavaScript engine, a cgo SQLite driver, a Markdown parser, a syntax highlighter and a command line framework, and your build needs a C toolchain. For a one-off report on a single scan, a dozen lines of your own parsing is faster. For a pipeline that produces the same eight artefacts for every scan you run, the tool is the cheaper answer.

Editorial conclusion

nmap-formatter fits a security engineer or a network team that already runs Nmap and needs the result as a deliverable, a spreadsheet row set, a database table or a diagram rather than as terminal output. It is the wrong tool if you have not scanned yet, and it is the wrong tool if you need to change what was scanned, since it only reads the XML the scanner produced. Before adopting, check three things: that your Nmap invocation produces XML, that the output format you need is in the list of eight rather than something you have to write yourself, and how the `--skip-down-hosts` default affects your numbers, because hosts that were down are skipped by default and a report that omits them can read very differently from a scan. The code is MIT licensed, the current release is v3.1.4 from 2026-07-10, and the Go module path carries a `/v3` suffix you need for imports.

Frequently asked questions

What is nmap-formatter and what output formats does it support?

It is a Go tool that converts Nmap XML output to html, csv, json, markdown, graphviz dot, sqlite, excel and d2, and it can also be used as a Go library. The format is given as the first positional argument, followed by an optional path to the XML file and flags.

How do I install nmap-formatter?

Run `go install github.com/vdjagilev/nmap-formatter/v3@latest`, then `cd nmap-formatter` and `go build`. The module requires Go 1.25.0, and a Dockerfile is provided for container builds.

Can nmap-formatter read from standard input?

Yes. The documentation shows reading a file from `stdin` and parsing it, for example `cat some.xml | nmap-formatter json`, and the dot, sqlite and d2 examples all use the piped form.

What does the --skip-down-hosts flag do?

It skips hosts that are down, and by default it is `true`. That means hosts which did not respond are left out of the output unless you turn the flag off, which can change how a report reads considerably.

Can I use nmap-formatter as a Go library?

Yes. The repository documents use as a library with examples on its wiki, and the layout separates a `formatter/` package from `cmd/` and `main.go`. The Go module path is `github.com/vdjagilev/nmap-formatter/v3`, so the `/v3` suffix is part of the import path.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. vdjagilev/nmap-formatter on GitHub
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/vdjagilev-nmap-formatter.svg)](https://hysenlabs.com/projects/vdjagilev-nmap-formatter)

Was this useful?