k1LoW/tbls: database documentation as Markdown you commit to git
tbls is a CI-Friendly tool to document a database, written in Go.
At a glance
- What is it?
- tbls generates GitHub Flavored Markdown schema docs from a live database, diffs them in CI, and lints schema conventions. It is a single Go binary, which is the whole reason it fits a pipeline.
- Who is it for?
- Adopt tbls if your schema lives in git review and you want the generated docs to live there too, and if a single Go binary with no runtime fits your CI image. Skip it if you need a browsable web catalog with search, lineage or access control; tbls writes files and stops there.
- 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 2 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 September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem tbls solves: schema knowledge that never reaches the repository
Schema documentation usually lives in one of two bad places. Either it is a wiki page that drifts the moment someone runs an ALTER TABLE, or it is a paid catalog tool that requires a server, an account and a network path from your CI runner. tbls takes a third position. It connects to a database, reads the catalog, and writes the result as GitHub Flavored Markdown files into your repository. The README describes the project as a "CI-Friendly tool to document a database, written in Go," and the CI part is not decoration: the output is plain text, so it diffs, reviews and merges like any other file.
The audience is narrow and easy to identify. You are a backend or platform engineer whose schema changes go through pull requests. You want the ER diagram and column list to appear in the same review as the migration that changed them. You do not want to run a documentation service, and you do not want the docs to be a second source of truth that nobody updates. If your team already treats the repository as the record of what the system is, tbls fits that assumption rather than fighting it.
It is the wrong tool for a data catalog use case. There is no query interface, no lineage graph, no ownership metadata, no access control. tbls produces documents. If your organisation needs to answer "who owns this table and what reads it," tbls will not answer that.
How tbls reads a schema and turns it into files
The mechanism is a connect, introspect, render pipeline. You supply a DSN, either as a command argument or in a .tbls.yml file. tbls opens a connection through the driver for that database, queries the system catalog for tables, columns, types, nullability, defaults, indexes, constraints and comments, and then renders the collected structure through an output layer. The repository layout reflects this split: there are datasource/, drivers/, output/, ddl/, config/ and schema/ directories, and go.mod pulls in per-database drivers such as github.com/lib/pq for PostgreSQL, github.com/go-sql-driver/mysql, github.com/microsoft/go-mssqldb, github.com/snowflakedb/gosnowflake/v2, cloud.google.com/go/bigquery, cloud.google.com/go/spanner, github.com/aws/aws-sdk-go-v2/service/dynamodb, github.com/ClickHouse/clickhouse-go/v2 and go.mongodb.org/mongo-driver. Support breadth is a consequence of compiled-in drivers, not plugins.
The output side is where the design decision sits. The default is GFM, and the sample directory in the repository shows the same schema rendered in several ways: sample/mermaid/, sample/png/, sample/font/, sample/number/, sample/dict/ and the per-database samples like sample/postgres/ and sample/mysql/. The README's configuration table lists an ER diagram setting alongside document format, so the diagram syntax is a configuration choice rather than a fixed template. Because everything is generated from a live connection and written to disk, the document is a build artifact. That is the core trade-off: you get reproducibility and reviewability, and you give up anything interactive.
The README is explicit that diffing has a boundary: "tbls diff shows the difference Markdown documents only." So the comparison is between rendered text, not between two live catalogs in a structured sense. That matters when you expect the diff to catch semantic changes that happen to render identically.
Install and first run
There are several install paths and they are all documented in the README. Homebrew is the shortest on macOS or Linux:
brew install tblsGo users can install from source, which requires Go 1.25.8 according to go.mod:
go install github.com/k1LoW/tbls@latestA Docker image is published as ghcr.io/k1low/tbls:latest, and the README's quick start runs it against a working directory mounted into the container. Debian and RPM packages are attached to each release, and there is a temporary shell snippet served from the repository's use file for one-off runs.
Once installed, the fastest possible result is a single command with the DSN inline. The README's quick start uses this form:
$ tbls doc postgres://dbuser:dbpass@hostname:5432/dbnameFor a repository you intend to keep, the documented approach is a .tbls.yml file at the root. The README gives this example, and the two keys shown are the ones you will set first:
# .tbls.yml
dsn: postgres://dbuser:dbpass@localhost:5432/dbname
docPath: doc/schemaWith that file in place, running tbls doc with no arguments reads the configuration, connects, and writes the document tree under docPath. The README notes that the default docPath is dbdoc when you do not set one. After the run you should see a README.md plus one file per table in the target directory, and the README's sample document is the reference for what that output looks like. Commit both the configuration and the generated directory, which is what the README's getting started section does with git add .tbls.yml doc/schema.
Regeneration, diffing and the linter
The daily workflow has three commands. tbls doc regenerates the documents. tbls diff compares the database against the generated documents, or against another database, and prints a unified diff. tbls lint checks the schema against rules you declare in .tbls.yml.
Regeneration has a documented sharp edge. The README states that --force overwrites existing documents but does not remove files belonging to tables that no longer exist, while --rm-dist removes everything under docPath before generating. If you drop a table and run only --force, the stale file stays in the repository and keeps appearing in reviews. --rm-dist is the correct choice for a clean pipeline, with the obvious consequence that anything you hand-edited inside docPath is destroyed.
The diff command is the part that makes CI enforcement possible. The README shows it printing a diff against doc/schema/README.md and doc/schema/users.md after an ALTER TABLE added a column, and it also supports comparing two DSNs directly, as in tbls diff with a local and a production URL. A pipeline that runs tbls diff and fails on non-empty output turns documentation drift into a build failure. Because the README states the diff works on Markdown documents only, the comparison is textual, and formatting changes in the output layer will show up as differences even when the schema did not change.
The linter is configured under a lint key. The README's example begins with requireColumnComm, a rule about column comments, and the configuration section lists lint alongside comments, relations and viewpoints as separate concerns. This is where tbls stops being a renderer and starts expressing opinions about schema conventions. The rule set is not fully enumerated in the README excerpt, so check the configuration documentation before assuming a rule exists.
Where tbls stops being the right tool
The strongest limitation is the one the README states outright: diff operates on Markdown. If you need a structural comparison of two schemas independent of rendering, you are using the wrong command for that job, and tbls does not offer a separate structural diff mode in the documentation.
Credential handling is the second constraint. The DSN sits in .tbls.yml, which is a file you commit. The README addresses password encoding, warning that symbols such as # and < in a database password must be URL-encoded, and the configuration section includes a page on expanding environment variables. That page exists because embedding a production password in a tracked YAML file is a bad idea. Read it before you put a real credential in the file. The README does not document a secrets backend, so the environment variable expansion is the mechanism you have.
The third constraint is scope. tbls reads catalog metadata. It does not model data, it does not sample rows, and it does not tell you which application code touches which table. If your question is about data rather than structure, tbls is silent.
Finally, the generated document is only as good as the comments in your database. Column and table comments flow through from the catalog, and the linter can require them, but tbls cannot invent descriptions for columns that have none. A schema with no comments produces a document with empty description cells.
Alternatives and how the approach differs
SchemaSpy is the closest comparison in purpose. It is a Java tool that connects to a database and produces an HTML site with a browsable interface, search and per-table pages. The difference is the artifact. SchemaSpy gives you a generated website you host somewhere; tbls gives you Markdown files you commit. If your reviewers live in a browser and you want a link to send people, SchemaSpy's output is more directly useful. If your reviewers live in pull requests and you want the documentation to be part of the repository's history, tbls is the better fit. The two tools also differ in runtime: tbls ships as a single Go binary and the README lists deb, RPM, Homebrew, MacPorts, aqua, Docker and go install as installation routes, while SchemaSpy requires a JVM.
DBML-based tooling, such as dbdocs, takes a different route entirely. You write the schema definition in DBML and the tool renders documentation from that source file. This inverts the direction of truth: the definition is the input, not the database. That is attractive when you want to design before you build, and it is a liability when the database is the thing that actually exists. tbls reads the live catalog, so the document cannot describe a schema that is not there. The cost is that you need a reachable database in whatever environment runs the generation.
For teams already on GitHub Actions, the setup-tbls action is the documented integration path, and the README's workflow example checks out .tbls.yml, installs the tool and runs tbls doc on pushes to main.
Licence, maintenance and upgrade cost
tbls is MIT licensed, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are included. That is a permissive licence with no copyleft obligation on your own code. The practical implication of embedding it in a CI pipeline is minimal, but the licence text in the repository is the authority, not this summary.
Maintenance looks current. The last push to the default branch was on 2026-09-18, and the most recent release listed is v1.96.0 on 2026-09-03, following v1.95.0 on 2026-07-11 and v1.94.5 on 2026-04-27. The repository is not archived. Release cadence is not a promise, but the gaps here are weeks rather than quarters.
Upgrade cost concentrates in two places. The first is output format: because the generated Markdown is committed, a change in how tables or columns are rendered produces a large diff across every document, and that diff has to be reviewed and merged. The README's --rm-dist flag makes the regeneration clean, but it does not make the resulting diff small. The second is configuration: the .tbls.yml schema has grown to cover lint rules, relations, viewpoints, dictionaries, filters and personalized templates, and a key that changes meaning between versions will show up as either a failed run or a changed document. Pinning a version in CI and reading CHANGELOG.md before bumping is the cheap insurance here.
Editorial conclusion
Adopt tbls if your schema lives in git review and you want the generated docs to live there too, and if a single Go binary with no runtime fits your CI image. Skip it if you need a browsable web catalog with search, lineage or access control; tbls writes files and stops there. Before committing, run tbls diff against the same DSN you will use in CI, confirm the output format in .tbls.yml is the one your reviewers read, and check that your database driver is on the datasource list.
Frequently asked questions
What is k1LoW/tbls used for?
It documents a database by connecting to it, reading the schema and writing GitHub Flavored Markdown files into your repository. The README also describes it as working as a linter for a database, and it can diff the live schema against the committed documents.
How do I install tbls?
The README lists deb and RPM packages, Homebrew, MacPorts, aqua, a manual binary download, go install github.com/k1LoW/tbls@latest, and the Docker image ghcr.io/k1low/tbls:latest. Homebrew is the shortest path on macOS or Linux.
Which databases does tbls support?
The repository topics and the driver dependencies in go.mod cover PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, BigQuery, Spanner, DynamoDB, Snowflake, Redshift, ClickHouse, MongoDB and Databricks. The README points to a support datasource section for the authoritative list.
Can tbls run in a CI pipeline?
Yes. The README describes it as CI-Friendly, ships a single binary, and documents a GitHub Actions workflow that checks out .tbls.yml, uses the k1low/setup-tbls action and runs tbls doc. A tbls diff step can fail the build when the committed documents no longer match the database.
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/k1low-tbls)