Documentation
Everything you need to lint .sql files with ESLint —
install, configure, write rules.
Requirements
- Node.js 22 or later
- ESM-only package (
"type": "module") - Runs on macOS, Linux and Windows. The WebAssembly binary ships inside the package.
Installation
npm install --save-dev postgresql-eslint-parser
# or
pnpm add -D postgresql-eslint-parser
# or
yarn add --dev postgresql-eslint-parserESLint configuration
Use ESLint's flat config and point the parser at .sql files.
// eslint.config.js
import postgresqlParser from "postgresql-eslint-parser";
export default [
{
files: ["**/*.sql"],
languageOptions: {
parser: postgresqlParser,
},
rules: {
// your SQL-specific rules
},
},
];ESLint directives in SQL comments
ESLint's directive scanner reads eslint-disable-style
comments. The parser surfaces SQL comments as ESLint comment nodes, so
the usual directives work in .sql files:
-- eslint-disable-next-line no-select-star
SELECT * FROM users;
/* eslint-disable some-rule */
SELECT id FROM users;
/* eslint-enable some-rule */| SQL comment | ESLint comment node |
|---|---|
-- ... | { type: "Line", value: "..." } |
/* ... */ | { type: "Block", value: "..." } |
Linting PL function bodies
CREATE FUNCTION and CREATE PROCEDURE bodies
written in another language are exposed as EmbeddedCode nodes on the parent statement. The parser stays language-agnostic —
it captures the source text, the absolute range/loc inside the SQL,
the lower-cased LANGUAGE clause, and the quote style
("dollar" or "single"), and stops there. C
functions written as the two-argument AS 'lib', 'sym' form are skipped because they are not source code.
To lint these bodies, plug the postgresql-eslint-parser/processor subpath into your
flat config. It emits one virtual file per body (0.js, 1.py, …) and translates lint messages back to the SQL
coordinate system. Any PL is supported as long as you give it an
extension and configure an ESLint parser for that extension:
// eslint.config.js
import postgresqlParser from "postgresql-eslint-parser";
import { createPlProcessor } from "postgresql-eslint-parser/processor";
import tsParser from "@typescript-eslint/parser";
const plProcessor = createPlProcessor({
languages: {
plv8: ".js",
plv8u: ".js",
plpgsql: ".plpgsql",
plpython3u: ".py",
},
// "skip" (default) silently drops bodies whose language is not mapped;
// "error" throws so unhandled PLs do not pass silently.
unknown: "skip",
});
export default [
{
files: ["**/*.sql"],
languageOptions: { parser: postgresqlParser },
processor: plProcessor,
},
{
files: ["**/*.sql/*.js"],
languageOptions: { parser: tsParser },
rules: {
// your JS rules for plv8 bodies
},
},
];Need to enumerate bodies for tooling that isn't ESLint? Use the
low-level extractEmbeddedCode helper exported from the
main entry point — it returns every EmbeddedCode node in
source order:
import { parseForESLint, extractEmbeddedCode } from "postgresql-eslint-parser";
const { ast } = parseForESLint(sql);
for (const body of extractEmbeddedCode(ast)) {
console.log(`${body.language}: ${body.source.length} chars at ${body.range[0]}`);
}'' escapes, the
processor drops fixes (line/column positions are still reported).Programmatic API
parse(code: string)
Parses SQL and returns a Program AST node.
parseForESLint(code: string)
Returns { ast, visitorKeys, scopeManager } — the shape
ESLint expects from a custom parser. scopeManager is
currently always null.
Ast namespace
All node types (Program, SelectStmt, SQLStatementNode, SQLParseError, …) are
re-exported under Ast for use in TypeScript rule authoring.
import parser, { parse, parseForESLint, type Ast } from "postgresql-eslint-parser";
const program = parse("SELECT 1");
const { ast, visitorKeys } = parseForESLint("SELECT 1");
function isSelect(node: Ast.SQLStatementNode): node is Ast.SelectStmt {
return node.type === "SelectStmt";
}extractEmbeddedCode(program: Program): EmbeddedCode[]
Returns every EmbeddedCode node (PL function body) in
source order. Each entry carries language, source, absolute range / loc in
the SQL file, and quoteStyle. See Linting PL function bodies for the full flow.
createPlProcessor(options): Processor
Exported from postgresql-eslint-parser/processor. Takes
a { languages, unknown? } options object and returns
a generic ESLint processor that emits one virtual file per PL body
with the configured extension.
Syntax errors
parseForESLint does not throw on broken
SQL. The lint run keeps going, and the program body holds a single SQLParseError node so other tooling can still introspect:
interface SQLParseError {
type: "SQLParseError";
range: [number, number];
loc: Ast.SourceLocation;
error: string; // human-readable message from libpg-query
raw: string; // the original source
}Writing custom rules
Visitor keys are exported, so the regular ESLint rule shape works:
// no-select-star.ts
import type { Rule } from "eslint";
import type { Ast } from "postgresql-eslint-parser";
export const noSelectStar: Rule.RuleModule = {
meta: {
type: "suggestion",
messages: { star: "SELECT * is disallowed — list columns explicitly." },
},
create(context) {
return {
SelectStmt(node: Ast.SelectStmt) {
const target = node.targetList ?? [];
const hasStar = target.some((t) => t?.ResTarget?.val?.A_Star);
if (hasStar) context.report({ node, messageId: "star" });
},
};
},
};