@types/pg review
You can give a PostgreSQL query the wrong row type and still satisfy these declarations. @types/pg 8.23.1 supplies TypeScript declarations for pg. Its query declarations accept SQL as a string and let the caller choose the result-row generic. That gives your editor a contract for Pool, Client, query configuration and returned rows. It leaves a consequential gap: a row type you write yourself does not prove that the SQL selects those columns or returns those values. Use the package to describe your database boundary, then decide where that boundary needs runtime checks.
Add @types/pg when your TypeScript application uses pg and needs its contracts. Keep row types beside the SQL and validate uncertain output before trusting it; annotating the expected result cannot establish what the database returned.
Answers from our run
@types/pg or postgres: which should you use?
postgres: Consider postgres 4.0.0 when you are choosing a PostgreSQL driver and want one that declares its own types entry. Changing drivers needs a separate API and deployment evaluation. Add @types/pg when your TypeScript application uses pg and needs its contracts.
When should you not use @types/pg?
You expect the compiler to infer selected columns by reading SQL strings. These declarations accept strings and trust your chosen row generic.
Use it if
- Your TypeScript service already uses pg and needs declarations for its Pool, Client and query results.
- A shared database helper should state its input tuple and returned row shape instead of passing untyped results to every caller.
- You want the compiler to check configuration objects and distinguish object rows from rowMode: array results.
- You will review row annotations alongside SQL and keep runtime checks where database output crosses a trust boundary.
- You expect the compiler to infer selected columns by reading SQL strings. These declarations accept strings and trust your chosen row generic.
- You need runtime validation of database output. A declaration file cannot check a value returned by a database.
- You want to connect to PostgreSQL after installing only this package. Install the pg runtime separately.
- Your chosen driver already supplies the declarations you use. Adding @types/pg does not automatically describe another driver.
- You cannot review dependency updates: 8.23.1 declares wildcard dependencies on @types/node and pg-protocol, so keep a lockfile and check resolution changes.
Setup reality
Install pg as the runtime dependency and @types/pg as a development dependency in an application that compiles TypeScript before deployment. For example, npm install pg followed by npm install -D @types/pg@8.23.1. Import application values and types from 'pg', not from '@types/pg'. The inspected pg 8.23.0 metadata declares JavaScript entry points without a types entry; @types/pg 8.23.1 separately declares index.d.ts and import/require declaration exports. That is the package split, not a tested compatibility guarantee for every driver release.
The examples below were read against the published 8.23.1 declarations. We did not install the package, run a compiler, connect to PostgreSQL or execute these queries for this guide. Supply your application's pool where a function accepts one. Query literals use PostgreSQL casts so the expected string or boolean is visible in the SQL; changing a row generic still cannot change the returned value. The declaration metadata lists TypeScript 5.6. Check the compiler and module-resolution settings used by your project rather than treating that metadata as a test of your build.
The annoying part is maintaining two descriptions of one query: SQL and the row type you attach to it. Aliases, nullable columns, parser choices and query edits can make the descriptions disagree. An omitted row generic defaults to any in the inspected query overloads. An explicit tuple can constrain the values passed to a helper, but it cannot count placeholders inside arbitrary SQL strings. QueryResultBase declares rowCount as number | null. Handle that union instead of asserting that every command reports a count.
Pin the declaration package in your lockfile and review its dependency tree. The release depends on pg-types ^2.2.0, @types/node * and pg-protocol *. A pg major number and an @types/pg major number are not a complete compatibility policy. Compile your own query helpers during upgrades. For connection lifecycle and operational choices, read the existing pg driver guide.
npm recorded 92,361,055 downloads for @types/pg over 2026-10-22 through 2026-10-28. Those are package downloads, not unique developers or evidence that a particular query is safe. Scores below use an editorial 1-5 rubric: 1 means substantial obstacles, 3 means useful with material caveats, and 5 means strong inspected support with few caveats. Intermediate scores sit between those anchors. API stability here means clarity of the inspected contract, not a measured history of breaking changes; maintenance means dependency and ownership clarity, not a response-time audit.
Patterns
Check a pool configuration object type-pool-config
import type { PoolConfig } from 'pg';
export const config: PoolConfig = {
application_name: 'billing-worker',
max: 4,
connectionTimeoutMillis: 3000,
};Declaration-based illustration, unexecuted. PoolConfig extends ClientConfig. These values are examples, not sizing advice, and this object does not create a connection.
Describe the object row you expect describe-object-row
import type { Pool } from 'pg';
type LabelRow = { label: string };
export async function readLabel(pool: Pool) {
const result = await pool.query<LabelRow>(
"SELECT 'ready'::text AS label",
);
return result.rows[0]?.label;
}Unexecuted declaration-based illustration. The row generic supplies label to TypeScript. The optional access also allows for a missing row; the generic itself does not establish row presence.
Recognize a believable but wrong annotation expose-generic-limit
import type { Pool } from 'pg';
export async function wrongContract(pool: Pool) {
const result = await pool.query<{ label: number }>(
"SELECT 'ready'::text AS label",
);
return result.rows[0]?.label;
}Deliberately wrong row contract, not recommended application code. The declaration accepts a caller-selected numeric label while the SQL asks for text. This is a reading of the overload, not a recorded compiler or database result.
Check a value before returning it check-unknown-row
import type { Pool } from 'pg';
export async function checkedLabel(pool: Pool): Promise<string> {
const result = await pool.query<{ label: unknown }>(
"SELECT 'ready'::text AS label",
);
const row = result.rows[0];
if (!row || typeof row.label !== 'string') {
throw new Error('Expected one text label');
}
return row.label;
}Unexecuted illustration. The explicit unknown forces a check before string use in this function. This guard checks one property and row presence, not an entire database schema.
State the parameter tuple separately type-input-tuple
import type { Pool } from 'pg';
type EchoRow = { label: string };
export async function echo(pool: Pool, label: string) {
return pool.query<EchoRow, [string]>(
'SELECT $1::text AS label',
[label],
);
}Unexecuted illustration of both generics. QueryConfigValues accepts an array-shaped type, so [string] describes one string input. It does not relate $1 in the SQL text to that tuple.
Keep SQL and values in a typed config type-query-config
import type { QueryConfig } from 'pg';
export function labelQuery(label: string): QueryConfig<[string]> {
return {
text: 'SELECT $1::text AS label',
values: [label],
};
}Unexecuted illustration. QueryConfig requires text and permits values with the supplied tuple type. It describes query input, not the output row shape.
Use the array-result overload deliberately describe-array-row
import type { Pool } from 'pg';
export async function readPair(pool: Pool) {
const result = await pool.query<[string, boolean]>({
text: "SELECT 'ready'::text, true::boolean",
rowMode: 'array',
});
const row = result.rows[0];
return row ? { label: row[0], active: row[1] } : undefined;
}Unexecuted declaration-based illustration. QueryArrayConfig requires the literal array rowMode and selects QueryArrayResult. Column order is your responsibility; a tuple annotation does not inspect SQL positions.
Preserve the nullable command count handle-null-count
import type { QueryResultBase } from 'pg';
export function describeCount(result: QueryResultBase): string {
return result.rowCount === null
? 'No row count reported'
: `${result.rowCount} rows reported`;
}Unexecuted type illustration. The declaration allows a null rowCount. Do not turn it into zero unless your application deliberately treats an absent count and a zero count as equivalent.
Accept a checked-out client explicitly preserve-client-boundary
import type { PoolClient, QueryResult } from 'pg';
type StateRow = { ready: boolean };
export async function readState(
client: PoolClient,
): Promise<QueryResult<StateRow>> {
return client.query<StateRow>('SELECT true::boolean AS ready');
}Unexecuted illustration. PoolClient extends Client and adds release(err?: Error | boolean). This helper accepts the client without claiming ownership of its release or demonstrating transaction or concurrency behavior.
Keep a notification payload optional type-notification-payload
import type { Notification } from 'pg';
export function readNotice(message: Notification): string {
return message.payload ?? '';
}Unexecuted type illustration. Notification declares channel and processId, but payload is optional. Choosing an empty string here is an application policy, not evidence that every notification carries text.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| postgres | npm | Consider postgres 4.0.0 when you are choosing a PostgreSQL driver and want one that declares its own types entry. Changing drivers needs a separate API and deployment evaluation. |
| @neondatabase/serverless | npm | Consider @neondatabase/serverless 1.2.0 when evaluating a driver aimed at serverless environments; its package declares an index.d.ts entry. It is a driver choice, not a declarations-only replacement for an existing pg application. |
More web backend guides
urllib3 · requests · ws · undici · anyio · httpx · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.