Skip to content

Type Checking

The Postgres Language Server checks your SQL against your database schema without running it. As you type, it reports what Postgres would reject: tables and columns that don't exist, ambiguous column references, operators and functions that don't match their argument types, invalid casts, and values that can't be assigned to a column.

How it Works

When the language server connects to your database, it loads a snapshot of its schema: tables, columns, types, functions, operators, and casts. It then applies the statements of the current file on top of that snapshot, so a table created earlier in a migration is known to the statements after it. Nothing is executed against the database.

Each statement is checked against this catalog by the rules of the typecheck group. The type checks follow Postgres' own algorithms for coercion, overload resolution, and common types, so they agree with what Postgres does at runtime.

The rules only report what Postgres would certainly reject. Whenever the language server can't be sure, for example after a DO block or a CREATE EXTENSION whose effects it can't see, it stays silent. They are tested against Postgres' own regression suite for versions 15 to 18.

What Gets Checked

Rule Reports
unknownRelation SELECT * FROM user when the table is named users
unknownColumn SELECT user_naem FROM users
unknownSchema, unknownFunction, unknownType references to schemas, functions, and types that don't exist
ambiguousColumn a column that exists in several joined tables
missingFromClauseEntry t.col without t in the FROM clause
insertColumnMismatch more values than target columns, or the other way round
operatorTypeMismatch 1 + now()
functionArgumentMismatch length(1), or a call that matches several overloads
invalidCast now()::int
assignmentTypeMismatch INSERT INTO t (qty) VALUES (now())
functionReturnTypeMismatch a LANGUAGE sql function whose final statement doesn't return what the function declares

Configuration

The typecheck rules are configured like any other linter rule or group:

{
  "linter": {
    "rules": { "invalidCast": "warn" },
    "groups": { "typecheck": "error" }
  }
}

typecheck.enabled: false turns type checking off entirely.

You can configure the schemas included in the search path for type checking:

{
  "typecheck": {
    "searchPath": ["public", "app_*", "auth"]
  }
}

The searchPath supports: - Exact schema names (e.g., "public") - Glob patterns (e.g., "app_*" to match app_users, app_products, etc.) - The order matters - schemas are searched in the order specified

Even if not specified, the LSP will always search"public" in last position. A SET search_path in the file takes effect for the statements after it.

Requirements

Type checking needs a database connection to load the schema. Without one, the typecheck rules are skipped. The rules never send your statements to the database.

Legacy: EXPLAIN-based checking

Before the typecheck rules, the language server checked statements by running EXPLAIN on them. This check still runs for SELECT, INSERT, UPDATE, and DELETE statements that only reference objects that exist unchanged in the database, and reports the errors Postgres returns. It needs permission to prepare statements in your database.

Warning

The EXPLAIN-based check is deprecated and will be removed in the next major release. The typecheck rules replace it.