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.
- 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 or Kerberos without the separate auth/kerberos module.
- 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 86 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 November 14, 2026, and from our analysis. They are not legal advice.
Star history
- lib/pq9,974 ★ · +15
| Repository | 2026-09-22 | 2026-11-14 |
|---|---|---|
| lib/pq | 10k | 10k |
15 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
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.
go get github.com/lib/pqThe following excerpt combines import declarations with a function-body fragment from the README; add a package and function to form a complete program. The blank import registers the driver, and sql.Open creates a pool without necessarily establishing a connection; call Ping to check connectivity.
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: it is what flushes the buffered data.
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.
lib/pq versus pgx
The main alternative is pgx, and the 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. With pgx you can use its native interface for PostgreSQL-specific features or keep database/sql through its compatibility layer. lib/pq also uses database/sql, including limitations such as the absence of LastInsertId and the connection-timeout behavior described above.
The README establishes 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 your code already uses database/sql, switching to pgx can preserve that interface, but driver-specific behavior, connection settings and extensions still need review.
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. If your organization requires an approved licence identifier in dependency tooling, resolve that before adoption rather than after.
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 services for pgbouncer, pgpool, cockroach, pg18 and pg19.
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 or Kerberos without the separate auth/kerberos module. 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 Go’s database/sql. pgx offers both a native PostgreSQL API and a database/sql compatibility layer, so you can choose pgx without leaving database/sql. Review driver-specific code and behavior when switching.
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)Star history chart for your README
[](https://hysenlabs.com/projects/lib-pq)Was this useful?