A CodeMirror extension for SQL linting and visual gutter indicators. Built by and used in marimo.
- โก Real-time validation - Per-statement SQL syntax checking as you type, with detailed error messages for every broken statement
- ๐ง Schema-aware linting - Warns about unknown tables, unknown columns, and ambiguous column references based on your schema
- ๐จ Visual gutter - Color-coded statement indicators and error highlighting
- ๐ก Hover tooltips - Schema info, keywords, and column details on hover
- ๐ฎ CTE autocomplete - Statement-scoped completion of CTE names and their output columns
- ๐ท๏ธ Alias resolution - Hover and completion understand table aliases (
SELECT u.name FROM users u) - ๐ FROM-aware column completion - Unqualified prefixes complete the columns of the tables in the statement's FROM clause (
SELECT e->emailwithFROM users), even with many tables in the schema - ๐งญ Navigation - Go-to-definition, reference highlighting, and rename for CTEs and aliases
- ๐ฏ Query-aware resolution - Context-sensitive schema and column suggestions
- ๐ Additional dialects - DuckDB, BigQuery, Dremio
- ๐ ๏ธ Custom renderers - Customizable tooltip rendering for tables, columns, and keywords
npm install @marimo-team/codemirror-sql
# or
pnpm add @marimo-team/codemirror-sqlimport { sql, StandardSQL } from "@codemirror/lang-sql";
import { basicSetup, EditorView } from "codemirror";
import { sqlExtension, cteCompletionSource, aliasColumnCompletionSource, unqualifiedColumnCompletionSource } from "@marimo-team/codemirror-sql";
const schema = {
users: ["id", "name", "email", "active"],
posts: ["id", "title", "content", "user_id"],
};
const editor = new EditorView({
doc: "SELECT * FROM users WHERE active = true",
extensions: [
basicSetup,
sql({
dialect: StandardSQL,
schema: schema,
upperCaseKeywords: true,
}),
StandardSQL.language.data.of({
autocomplete: cteCompletionSource,
}),
StandardSQL.language.data.of({
// Complete `u.` -> columns of `users` in `SELECT ... FROM users u`
autocomplete: aliasColumnCompletionSource({ schema }),
}),
StandardSQL.language.data.of({
// Complete `SELECT e` -> `email` because `FROM users` is in the statement
autocomplete: unqualifiedColumnCompletionSource({ schema }),
}),
sqlExtension({
// Shared by hover tooltips and semantic linting
schema: schema,
linterConfig: {
delay: 250, // Validation delay in ms
},
gutterConfig: {
backgroundColor: "#3b82f6", // Current statement color
errorBackgroundColor: "#ef4444", // Error highlight color
hideWhenNotFocused: true,
},
enableHover: true,
hoverConfig: {
hoverTime: 300,
enableKeywords: true,
enableTables: true,
enableColumns: true,
},
}),
],
parent: document.querySelector("#editor"),
});When a schema is provided (via the top-level schema option, the
sqlSchemaFacet, or semanticLinterConfig.schema), queries are validated
against it: unknown tables, unknown columns, and ambiguous column references
are reported as warnings (configurable per check). Without a schema the
semantic linter is inert.
import { EditorView } from "codemirror";
import { sqlSemanticLinter } from "@marimo-team/codemirror-sql";
const editor = new EditorView({
extensions: [
sqlSemanticLinter({
schema: { users: ["id", "name"], posts: ["id", "user_id"] },
severity: {
unknownTable: "error", // "error" | "warning" | "off" (default: "warning")
unknownColumn: "warning",
ambiguousColumn: "warning",
},
}),
],
parent: document.querySelector("#editor"),
});Checks only run on statements that parse cleanly, and skip anything that
can't be confidently resolved (CTE outputs, subquery results, aliases from
outer scopes), preferring under-reporting over false positives. Semantic
diagnostics carry source: "sql-schema"; syntax diagnostics use
source: "sql-parser". If the schema is provided as a function, it is called
on every lint pass and should be cheap/memoized.
sqlExtension includes navigation for statement-local identifiers (CTE names,
table aliases, select aliases) by default: the references of the identifier
under the cursor are highlighted, and Mod-click (Cmd/Ctrl-click) on a
resolvable identifier jumps to its definition. Keybindings (F12/Mod-b for
go-to-definition, F2 for rename) are opt-in:
import { renameSqlIdentifier, sqlExtension } from "@marimo-team/codemirror-sql";
sqlExtension({
enableNavigation: true, // default
navigationConfig: {
keymap: true, // enable F12 / Mod-b / F2
// Supply your own rename UI (defaults to window.prompt)
prompt: (currentName) => window.prompt(`Rename '${currentName}' to:`, currentName),
},
});
// Rename programmatically: rewrites the definition and all references
// in a single undo step
await renameSqlIdentifier(view, { prompt: () => "new_name" });The pieces are also exported individually: sqlHighlightReferences,
sqlGotoDefinition, sqlNavigationKeymap, gotoSqlDefinition, and
findReferences. Rename returns false when the identifier can't be
confidently resolved โ it never falls back to text search-and-replace.
This extension adds support for additional dialects:
- DuckDB
- BigQuery
- Dremio
The extension includes keyword documentation for common SQL keywords including used in hover and completion,
which can be found in the src/data directory.
See the demo for a full example.
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run demo
pnpm devApache 2.0