lib/pq: the Go PostgreSQL driver for database/sql, and when to pick it over pgx
Go PostgreSQL driver for database/sql
At a glance
- What is it?
- lib/pq registers the "postgres" driver name for Go's database/sql and implements the libpq connection-string format, including runtime parameters. The last push to the repository was on 2026-08-20, and the README documents both its limits and the cases where it is the wrong tool.
- Who is it for?
- Adopt lib/pq if your code is already written against database/sql and you want a driver that accepts libpq-style DSNs, runtime parameters such as search_path or work_mem, COPY FROM STDIN, LISTEN/NOTIFY and pgpass without extra dependencies. Do not adopt it if you need LastInsertId, Kerberos without the separate auth/kerberos module, or a driver whose connection handling is documented beyond the note about connect_timeout.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 40 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 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What lib/pq solves, and who it is for
lib/pq is a PostgreSQL driver for Go's database/sql package. It registers itself under the driver name "postgres", so a program that already speaks the standard library's interfaces can talk to PostgreSQL by importing the package for its side effect and passing a connection string to sql.Open. The README describes it as a Go PostgreSQL driver for database/sql and states that all maintained versions of PostgreSQL are supported, with older versions possibly working but untested.
The audience is Go engineers who want the database/sql abstraction rather than a driver-specific API. The README's own example uses the blank import with the comment "To register the driver", which is the entire integration surface for a typical application. The DSN format is deliberately close to PostgreSQL's libpq, so a connection string copied from a psql environment or a .pgpass workflow usually needs no rewriting. The README says most parameters are supported and should behave identically.
How the driver connects: DSNs, pq.Config and the connector path
There are two documented ways in. The first is the classic string DSN in sql.Open, accepting both key=value and postgres:// URL forms. The second is the pq.Config struct, which can be built by hand or derived from the defaults and environment with pq.NewConfig, then turned into a connector with pq.NewConnectorConfig and handed to sql.OpenDB. The README shows both, and the Config struct's doc comments are named as the full reference for the parameter list.
One design decision stands out. The README says the most notable difference from libpq is that any runtime parameter, such as search_path or work_mem, can appear directly in the connection string, whereas libpq routes those through the options parameter. Both forms work in pq. That makes DSNs like the following valid, and it also means a DSN is doing two jobs at once: locating the server and configuring the session.
sql.Open("postgres", "dbname=pqgo work_mem=100kB search_path=xyz")The README recommends adding connect_timeout to the DSN because database/sql may open new connections asynchronously and therefore does not use the context from QueryContext, PingContext and similar calls. The default is to wait indefinitely. That is a real operational detail, not a footnote: a pool that grows in the background can block on a TCP handshake that no context will cancel.
Installing lib/pq and running a first query
The repository is a Go module, github.com/lib/pq, and go.mod declares go 1.23. Installation is the standard module fetch. The README does not print a go get line, so the import path is the source of truth.
go get github.com/lib/pqThe README's connecting example is the shortest working program. It imports the driver for registration, opens a pool, and then calls Ping because, as the README puts it, sql.Open only creates a connection pool and does not actually establish a connection.
import (
"database/sql"
"log"
_ "github.com/lib/pq" // To register the driver.
)
db, err := sql.Open("postgres", "host=localhost dbname=pqgo connect_timeout=5")
if err != nil {
log.Fatal(err)
}
defer db.Close()After that, Ping is the check that the connection actually works. If you prefer the struct route, pq.Config takes Host, Port, User and ConnectTimeout, and pq.NewConnectorConfig converts it into something sql.OpenDB accepts. A first real query is ordinary database/sql code: the driver does not change QueryRow, Query or Exec. The first thing to notice in practice is error shape. Failures come back as pq.Error, and pq.As converts an error to that type so you can match on a SQLSTATE such as pqerror.UniqueViolation instead of parsing strings.
Error detail and the pq.Error type
Error handling is where lib/pq does more than the minimum. A pq.Error's Error() string contains the message and the SQLSTATE code, for example a duplicate key violation rendered as pq: duplicate key value violates unique constraint "users_lower_idx" (23505). ErrorWithDetail() adds the DETAIL and CONTEXT fields when PostgreSQL sends them, which for the same violation includes the offending value, and for a JSON syntax error includes the line, column and a caret pointing at the bad token.
That matters for logging and for tests. The README's example uses pq.As with pqerror.UniqueViolation to detect a duplicate email and wrap it in an application error. Because the code is a SQLSTATE constant rather than a message substring, the check survives localization and message changes. The trade-off is that ErrorWithDetail is a separate method, so code that logs err.Error() silently discards the DETAIL and CONTEXT text that usually explains what went wrong.
COPY, LISTEN/NOTIFY, notices and authentication
Bulk loading goes through the protocol's COPY FROM STDIN. The README's instruction is to prepare a COPY ... FROM STDIN statement inside a transaction, execute the returned sql.Stmt repeatedly to copy data, and then call Exec once with no arguments to flush buffered data. The package documentation and an example are linked from the README, but the flush step is the part worth remembering: omit it and the transaction does not complete cleanly.
LISTEN/NOTIFY is exposed through pq.Listener. The README's example constructs one with pq.NewListener, calls Listen on a channel, and then receives from the listener's Notify channel; a nil notification signals that the listener is closing. Notifications arrive for every NOTIFY on that channel, with the channel name and payload available on the notification value.
NOTICE messages, such as the one PostgreSQL emits for DROP TABLE IF EXISTS on a missing table, are not returned as errors. The README is explicit that they are notices rather than errors, and points at ConnectorWithNoticeHandler for a callback. For authentication, PASSWORD, MD5 and SCRAM-SHA256 work out of the box, and pgpass files are read. GSS/Kerberos lives in a separate module, github.com/lib/pq/auth/kerberos, registered through pq.RegisterGSSProvider in an init function. The README gives the reason: most users do not need Kerberos and should not carry the dependency.
Where lib/pq is the wrong tool
LastInsertId is not supported, and the README gives the reason: the PostgreSQL protocol has no facility for it. Code ported from MySQL or SQLite that relies on sql.Result.LastInsertId will fail here. The documented replacement is INSERT ... RETURNING with QueryRow or Query, which the README notes also works in SQLite and MariaDB with the same syntax.
Timestamps are the second sharp edge. For timestamptz, pq uses the timezone configured on the server, as libpq does, and the connection string's timestamp parameter can change it; the README recommends UTC generally. For timestamp without time zone, pq always uses time.FixedZone("", 0), so the value you get back is not in the server's zone. An application that mixes the two column types and assumes one timezone policy will be wrong about one of them.
The third is scope. lib/pq is a database/sql driver, so it inherits database/sql's pooling and gives you no driver-level control beyond what the DSN and connector offer. The README's own warning about connect_timeout, and the fact that asynchronous connection opens ignore the context passed to QueryContext, are consequences of that boundary rather than bugs. If you need per-connection instrumentation or a driver-native API, this is not the layer for it. The README also does not document rollback behaviour or pool tuning, so treat those as database/sql concerns.
lib/pq versus pgx
The comparison people search for is lib/pq against pgx, and the honest difference is architectural. lib/pq is a driver for database/sql: you import it, you call sql.Open("postgres", ...), and your code is written against the standard interfaces. pgx is a PostgreSQL driver with its own API, and it also offers a database/sql compatibility layer. Choosing pgx means writing against its native interface to get PostgreSQL-specific features directly; choosing lib/pq means staying inside database/sql and accepting its abstraction limits, such as the absence of LastInsertId and the context behaviour described above.
Nothing in the README positions lib/pq against pgx, and this article does not benchmark them. What the README does establish is the shape of the trade: libpq-compatible DSNs, runtime parameters in the connection string, COPY FROM STDIN, LISTEN/NOTIFY, pq.Error with DETAIL and CONTEXT, and Kerberos kept in a separate module. If those are what you need and your code is already database/sql, the migration cost to anything else is the cost of leaving that interface, not the cost of the driver.
Maintenance, licence and upgrade cost
The repository is not archived, and the last push was on 2026-08-20. Recent releases are v1.12.1 on 2026-03-30, v1.12.2 on 2026-04-02 and v1.12.3 on 2026-04-03. The go.mod declares go 1.23, which sets the minimum toolchain for consumers of the module.
The licence field for this repository is NOASSERTION, meaning the metadata does not carry a recognized SPDX identifier. The repository does contain a LICENSE file at the top level, and that file is the thing to read; this article makes no claim about its contents and gives no legal advice. If your organization requires an approved licence identifier in dependency tooling, resolve that before adoption rather than after.
Upgrade cost inside v1 is small by design: the public surface is the driver name, the DSN and Config, pq.Error, pq.As, pq.Listener and the COPY path. The moving parts are the Go version floor in go.mod and the PostgreSQL versions covered by the support policy the README links to. The repository's compose.yaml defines service profiles for testing against pgbouncer, pgpool, cockroach, pg18 and pg19, which is the shape of the compatibility surface the project maintains.
Editorial conclusion
Adopt lib/pq if your code is already written against database/sql and you want a driver that accepts libpq-style DSNs, runtime parameters such as search_path or work_mem, COPY FROM STDIN, LISTEN/NOTIFY and pgpass without extra dependencies. Do not adopt it if you need LastInsertId, Kerberos without the separate auth/kerberos module, or a driver whose connection handling is documented beyond the note about connect_timeout. Before committing, verify your PostgreSQL version against the support policy the README links to, and decide whether the timestamp= connection parameter or the server timezone is the right default for your timestamptz columns.
Frequently asked questions
What is lib/pq in Go?
It is a PostgreSQL driver for Go's database/sql package. It registers the driver name "postgres", so you import it for its side effect and pass a libpq-style DSN to sql.Open.
What is github.com/lib/pq?
It is the Go module path for the package, declared as module github.com/lib/pq in go.mod. The README points at pkg.go.dev/github.com/lib/pq for the API docs, including the Config struct's parameter list.
How does lib/pq compare with pgx?
lib/pq is a driver for database/sql and your code is written against the standard interfaces. pgx has its own driver API and also offers a database/sql compatibility layer, so choosing it means writing against its native interface instead.
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/lib-pq)