Edit database tables like Vim buffers. Staged mutations + live SQL preview, transaction undo, schema browser + ER diagrams + DDL, FK navigation, cross-database federation, data profiling, SQL notebooks, AI SQL via Anthropic/OpenAI/Gemini/Ollama, Parquet/CSV/remote files. PostgreSQL · SQLite · MySQL · DuckDB · MotherDuck
dadbod-grip.nvim
D ███████╗███████╗██╗███████╗ A ██╔═════╝██╔══██║██║██╔══██║ D ██║ ███╗██████╔╝██║███████║ b ██║ ██║██╔══██╗██║██╔════╝ o ╚██████╔╝██║ ██║██║██║ d ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝Editable database grids for Neovim. Connect to PostgreSQL, MySQL, SQLite, DuckDB, or MotherDuck and edit tables like Vim buffers. |
![]() Chonk |
Connect to PostgreSQL, MySQL, SQLite, or DuckDB and edit tables like Vim buffers. Rows stage with color coding, preview as SQL, and commit in a single transaction. Undo committed changes. Follow foreign keys through a breadcrumb trail. Open any Markdown file as a runnable SQL notebook and execute individual blocks with <C-CR>. Generate SQL from natural language.
A command palette (<C-p>) surfaces every action without memorizing keymaps. The query pad has SQL syntax highlighting, a formatter, and built-in completion. Every Vim motion works. Nothing installs outside Neovim.
| Editing | Analysis | Schema & AI | |---|---|---| | Command palette <C-p> searchable action list | Data profiling sparkline distributions | FK navigation breadcrumb trail | | SQL Notebooks gn pick .md and .sql files from project | Block execution <C-CR> runs fence under cursor · narrative untouched | Demo notebook :GripStart · seventeen tables · one investigation | | SQL formatter gF sql-formatter · pg_format · Lua fallback | Query Doctor plain-English EXPLAIN | DDL create · rename · drop via UI | | SQL syntax highlighting query pad with treesitter | Visual staging violet · green · red rows | File as table Parquet · CSV · JSON · remote URLs | | Local Files picker open CSV/JSON/Parquet from cwd without typing a path | Live SQL preview float updates as you stage | AI SQL Anthropic · OpenAI · Gemini · Ollama | | Inline cell editing popup with Vim normal mode (<Esc>) | Data diff gD compare tables by primary key | Multi-DB PostgreSQL · SQLite · MySQL · DuckDB · MotherDuck | | Mutation preview full SQL before apply | Column filter builder gF with operators and wildcards | Schema grouping sidebar sections per attached database | | Cross-DB federation :GripAttach Postgres · MySQL · SQLite · MotherDuck | Export CSV · TSV · JSON · SQL · Markdown · Table | Connection health T tests all connection types | | Surface nav 1-3 sidebar · query pad · grid | ER diagram 4 tree-spine layout with FK follow | Remappable keymaps override or disable any key via setup() | | Write mode --write · edit files and write back to disk | Watch mode --watch · auto-refresh grid on a timer | Depth views 5-9 Stats · Columns · FK · Indexes · Constraints |
An example database is included. :GripStart opens it with seventeen tables and something in the consumer incidents that does not add up. See the walkthrough for the full investigation.
Contents
- Quickstart
- Features
- Keybindings
- Commands
- Requirements
- Install
- Configuration
- Usage
- Architecture
- Testing
- Ecosystem
Quickstart
-- lazy.nvim (always latest stable release)
{ "joryeugene/dadbod-grip.nvim", version = "*" }
Then :checkhealth dadbod-grip to verify your setup, :GripStart to explore the demo database, or :GripConnect to pick your own. Schema sidebar + query pad open automatically.
Auto-discovery of local Docker stacks
The picker discovers running postgres containers on every open by reading Docker labels (the same convention DataGrip and Beekeeper Studio use). Add this to any docker-compose.yml and the stack appears in :GripPick the moment it starts, disappears when it stops, no editing of ~/.grip/connections.json required:
services:
postgres:
image: postgres:17
ports:
- "${HOST_PORT:-5432}:5432"
labels:
dev.localdb.kind: postgres
dev.localdb.name: "my project ${BRANCH_SLUG}"
dev.localdb.user: postgres
dev.localdb.database: postgres
dev.localdb.password: postgres
Multiple worktrees on different ports just work: each stack shows up under its own name. Containers without these labels are ignored.
To opt out (no shell-out to docker ps on picker open):
require("dadbod-grip").setup({ discovery = false })
Connection strings
postgresql://user:pass@host:5432/dbname
mysql://user:pass@host:3306/dbname
sqlite:path/to/file.db
duckdb:path/to/file.duckdb
/path/to/file.csv ← direct file (also .parquet .json .xlsx) https://host/data.parquet ← remote file via httpfs
duckdb::memory: ← single-query scratch (tables don't persist between queries)
Cross-database federation (DuckDB as hub)
:GripAttach postgres:dbname=sales host=localhost user=me pg
:GripAttach sqlite:legacy.db legacy
:GripAttach md:cloud_analytics cloud
Then query across all of them:
SELECT pg.customers.name, legacy.orders.total
FROM pg.customers JOIN legacy.orders ON pg.customers.id = legacy.orders.customer_id
Extensions install automatically. Attachments persist and restore on reconnect.
DuckDB: Files, HTTPS, and S3
When your active connection is DuckDB, any file DuckDB can read becomes a live queryable table.
One-shot access (not saved to connections):
:GripOpen ~/data/report.parquet
:GripOpen https://example.com/dataset.parquet
:GripOpen s3://my-bucket/data.parquet
Save as a named connection (appears in gc every time):
gc → + New connection → paste file path or URL → give it a name
Cross-federation: local DuckDB + remote parquet + attached Postgres:
SELECT l.userid, r.eventdate, p.email
FROM local_events l
JOIN read_parquet('s3://my-bucket/events.parquet') r ON l.id = r.id
JOIN pg.users p ON l.userid = p.userid
DuckDB's httpfs extension installs automatically on first use. For S3 access, set AWSACCESSKEYID and AWSSECRETACCESSKEY in your environment. Public buckets work without credentials.
Features
Multi-Engine and Federation
- PostgreSQL, MySQL, SQLite, DuckDB, MotherDuck. Schema browsing, inline editing, and metadata inspection for each engine.
- Cross-database federation via DuckDB. Attach a PostgreSQL production database and a local SQLite alongside a DuckDB analytics file with
:GripAttach, then JOIN across all three with standard SQL. - MotherDuck cloud works as a primary connection or as an attachment to a local DuckDB session.
- Parquet, CSV, JSON, and remote URLs open as live editable tables via DuckDB. No database connection needed for file queries.
- Extensions auto-install. Attaching
postgres:loadspostgresscanner. Attachingsqlite:loadssqlitescanner. No manual INSTALL/LOAD. - Attachments persist in
.grip/connections.jsonand restore automatically when you reconnect.
Data Editing
- Inline cell editing with a popup editor, NULL handling, and type-aware display.
- Visual change staging with color-coded rows (violet=modified, red=deleted, green=inserted).
- Pure SQL generation with live preview before applying changes.
- Transaction safety wraps all DML in BEGIN/COMMIT with ROLLBACK on error.
- Batch editing in visual mode to set, delete, or NULL multiple rows at once.
- Row cloning via
cduplicates the current row as a staged INSERT with primary keys cleared. Edit the PK fields, then apply. - Two-tier undo + redo: local staging undo (50-deep) with
<C-r>redo, plus transaction undo that reverses committed changes (10-deep, with confirmation). NULL values in typed columns (boolean, integer, geometry) are correctly restored as SQL NULL, not empty strings. - Mutation preview:
UPDATE,DELETE, andINSERTfrom the query pad show affected rows before executing. SET values appear teal (modified), DELETE rows appear red, INSERT rows appear green. Pressato execute,uto cancel.
Query and Navigation
- Sort, filter, and pagination using
s/Sto sort,f/<C-f>/Fto filter,gp/gPfor saved filter presets, andH/Lto page (or]p/[p). - Foreign key navigation via
gfto follow a FK to its referenced row, and<C-o>to go back. - Query history via
ghor:GripHistorybrowsing all executed queries with timestamp and SQL preview, stored in.grip/history.jsonl. - Data profiling via
gRor:GripProfileshowing sparkline distributions, completeness, cardinality, and top values per column. - Column statistics via
gSshowing count, distinct, nulls, min/max, and top values. - Aggregate on selection via
gain visual mode showing count/sum/avg/min/max. - Query Doctor via
:GripExplaintranslating EXPLAIN plans into plain-English health checks with cost bars and index suggestions. - AI SQL generation via
Aor:GripAskturning natural language into SQL queries using Anthropic, OpenAI, Gemini, or local Ollama. AI reads existing query pad SQL to modify it rather than generating from scratch. Schema context cached per connection.
SQL Notebooks
- Notebook picker via
gnfrom the grid, query pad, or schema sidebar. Scans.mdand.sqlfiles in your project and shows a preview of each. - Block execution via
<C-CR>with cursor inside any `<pre><code class="lang-sql"></code></pre>fence: that block's SQL executes, surrounding Markdown prose is untouched. - Smart fallback: cursor outside any fence runs the full buffer; visual selection always runs the selected text. Same key, context-aware behavior.
- No special format: any Markdown file with SQL fences is a notebook. Write the question, then a SQL block, then what to look for in the result. Each block runs independently against the current connection.
- Demo notebook: :GripStart
loadsdemo/softrear-internal.mdautomatically — sixteen sections of a data quality investigation, runnable block by block.
Schema and Workflow
- ER diagram via gG
or4: a tree-spine float showing every table with PK/FK/column summary, arranged by FK depth with box-drawing connectors. Presson any table to open its grid. Pressfto follow a foreign key andHto go back (breadcrumb trail updates).Tab/S-Tabcycle between tables. PressgGorqto close. Column names truncate gracefully; overflow columns show a right-aligned+Ncount. Works from the grid, the query pad, and the schema sidebar. - Schema browser via :GripSchema
orgbshowing a sidebar tree with columns, types, and PK/FK markers.gbopens/focuses the browser from any buffer; pressinggbfrom inside closes it. - Table picker via :GripTables
orgT/gtproviding a fuzzy finder with column preview. Available from all three buffers: grid, query pad, and sidebar. In the sidebar,goopens the table under cursor withORDER BY created_at / PK DESCso the latest rows appear first. - SQL query pad via :GripQuery
orq. A persistent scratch buffer that pipes results into editable grids. Clicking a table in the sidebar or picker never replaces pad content: new queries append below existing SQL with a blank separator so all your work stays intact.runs the visual selection or the full buffer; when cursor is inside a<pre><code class="lang-sql"></code></pre>fence, only that block runs.gnopens the notebook picker to load any.mdor.sqlfile.gAreads existing pad content and modifies it rather than generating from scratch. Pressingqor2focuses the pad without overwriting anything. - Built-in SQL completion with table names, column names, SQL keywords, and alias tracking. No extra plugins required. In DuckDB federated sessions, columns from all attached databases appear with schema-qualified names (e.g. pg.users.email
). Works with nvim-cmp (sourcedadbod_grip), blink.cmp, or standalone viaand auto-trigger. - Saved queries via :GripSave
and:GripLoadpersisting to project-local.grip/queries/files. - Connection profiles via :GripConnect
orgCstoring connections in.grip/connections.jsonwithg:dbsbackward compatibility. Connections auto-persist globally (~/.grip/connections.json) so they're available from any project. Connecting opens the full workspace (schema sidebar + query pad) automatically. The picker shows a Local Files (cwd) section listing.csv,.parquet,.json,.xlsx, and other supported files in your working directory so you can open them without typing a path. Presssto save a local file as a named connection. Each connection displays a session-scoped health indicator (*ok,ounknown,xfailed); pressTon any file-based connection to retest it instantly. - Data diff via :GripDiff
orgDcomparing two tables by primary key with color-coded change highlighting. Auto-switches to compact layout on narrow terminals (<120 cols), toggle withgv.
Schema Operations (DDL)
- Table properties via gI
or:GripPropertiesshowing columns, indexes, row count, and table size. - Column rename via R
in properties view or:GripRenamewith DDL preview and confirmation. - Column add/drop via +
and-in properties view with type prompts and destructive confirmation. - Create table via :GripCreate
or+in schema browser with an interactive column designer. - Drop table via :GripDrop
orDin schema browser with typed confirmation and CASCADE awareness.
Display
- Conditional formatting that colors negatives red, booleans green/red, past dates dim, and URLs underlined.
- Column hide/show using -
to hide,g-to restore all, andgHfor a visibility picker. - Smart column auto-fit that distributes extra terminal width to truncated columns.
- Export to clipboard in 6 formats via gE
: CSV, TSV, JSON, SQL INSERT, Markdown, and Grip Table (box-drawing). - Export to file via gX
or:GripExport: saves the current result set as CSV, JSON, or SQL INSERT statements.
Multi-Database
- PostgreSQL, SQLite, MySQL/MariaDB, and DuckDB adapters with adapter-specific metadata queries.
- Multi-schema PostgreSQL: all schemas visible in sidebar (not just public
). Tables from other schemas appear asschema.table. - File-as-table support where :Grip /path/to/data.parquet
opens Parquet/CSV/JSON/XLSX files via DuckDB. - Remote file querying where :Grip https://example.com/data.csv
opens remote files via DuckDB httpfs. - MySQL backslash safety: MySQL sessions use NOBACKSLASHESCAPES
so backslashes in cell values are treated as literals, not escape characters. Values likeC:\path\to\fileround-trip correctly.
File Modes: Watch and Write
Files opened via :Grip support two modes that turn static files into live, editable datasets.
Write mode: :Grip /path/to/data.parquet --write
Stage inline cell edits as normal, then press a to apply. Instead of running DML against a database, grip uses DuckDB's COPY TO to write the modified data back to disk in the original format. Parquet, CSV, TSV, JSON, NDJSON, and Arrow are all supported. A destructive-action confirmation fires before the file is overwritten. Remote https:// URLs are always read-only regardless of the flag.
Watch mode: :Grip /path/to/data.csv --watch or :Grip file.csv --watch=10s
The grid re-runs the query on a timer and updates rows automatically. Default interval is 5 seconds; use --watch=Ns to set a custom one. Watch pauses while you have staged changes so you never lose in-progress edits to a background refresh.
Both modes are available from the connection picker and live on any open grid:
| | Connection picker | Open grid | |---|---|---| | Write mode | ! on a [file] connection | g! to toggle | | Watch mode | W on any connection | gW to toggle |
Active modes show as a colored badge in the grid's winbar: red ✎ WRITE and blue ↺ 5s. Modes are never persisted; always opt-in per session.
Additional
- Composite primary key support for multi-column WHERE clauses.
- Read-only mode is auto-detected when no primary key exists.
- DBUI integration via open_smart()
is optional since grip works standalone. - Live SQL floating preview via gl
shows real-time SQL as you stage changes. - Column type annotations via T
overlays type info on headers. - Row view transpose via K
shows a vertical column-by-column view of the current row. JSON cells are automatically pretty-printed inline. - JSON-aware editing: pressing i
/on a JSON cell pre-fills the editor with formatted, indented JSON for easy inspection and editing. The editor opens wider and taller with JSON syntax highlighting. - Full Vim motions in the cell editor: the editor starts in INSERT mode for quick changes. Press
to drop into NORMAL mode and use any Vim motion (ciw,dw,s,cW, etc.). Pressorto save from either mode; pressqorfrom NORMAL to cancel. A live footer shows INSERT vs NORMAL hints. - Word wrap: long cell values wrap at word boundaries inside the editor float instead of scrolling horizontally.
Keybindings
All keybindings are buffer-local to the grip grid. Press ? for in-buffer help.
Navigation
| Key | Action | |-----|--------| | j/k | Move between rows | | h/l | Move cursor within row | | w/b | Next / previous column | | Tab/S-Tab | Next / previous column | | gg | First data row | | G | Last data row | | 0/^ | First column | | $ | Last column | | - | Hide column under cursor | | g- | Restore all hidden columns | | gH | Column visibility picker | | = | Cycle column width: compact → expanded (full, uncapped) → reset | | {/} | Previous / next modified row | | | Expand cell value in popup | | K | Row view (vertical transpose) | | y | Yank cell value to clipboard | | Y | Yank row as CSV | | gY | Yank entire table as CSV |
Editing
| Key | Action | |-----|--------| | i / | Edit cell under cursor | | n | Set cell to NULL | | p | Paste clipboard into cell | | P | Paste multi-line clipboard into consecutive rows | | o | Insert new row after cursor | | c | Clone row (copy values, clear PKs) | | d | Toggle delete on current row | | u | Undo last edit (multi-level) | | | Redo | | U | Undo all (reset to original) | | a | Apply all staged changes to DB |
Batch Editing (visual mode)
| Key | Action | |-----|--------| | e | Set all selected cells in column to same value | | d | Toggle delete on all selected rows | | n | Set all selected cells in column to NULL | | y | Yank selected cells in column (newline-separated) |
Sort / Filter / Pagination
| Key | Action | |-----|--------| | s | Toggle sort on column (ASC → DESC → off) | | S | Stack secondary sort on column | | f | Quick filter by cell value | | | Freeform WHERE clause filter | | F | Clear all filters | | gp | Load saved filter preset | | gP | Save current filter as preset | | gn | Filter: column IS NULL | | gF | Filter builder (=, !=, >, <, LIKE, IN, IS NULL/NOT NULL) | | X | Reset view (clear sort/filter/page) | | H / L | Previous / next page | | ]p / [p | Previous / next page (alternate) | | ]P / [P | Last / first page |
FK Navigation
| Key | Action | |-----|--------| | gf | Follow foreign key under cursor | | | Go back in FK navigation stack |
Surface Navigation and Depth Views (1-9)
Keys 1–3 navigate between the three primary surfaces. Each key has a primary action (go to that surface) and a secondary action (press again when already there):
| Key | Primary | Secondary (already on that surface) | |-----|---------|-------------------------------------| | 1 | Schema sidebar | Connections picker | | 2 | Query pad | Query history | | 3 | Grid / records | Table picker |
Keys 4–9 are depth views: lenses applied to the current table, available from grid, sidebar, and query pad:
| Key | View | Description | |-----|------|-------------| | 4 | ER diagram | Tree-spine FK map (all tables, box-drawing connectors) | | 5 | Column Stats | Count, null%, distinct count, min, max per column | | 6 | Columns | Name, type, nullable, default, PK/FK markers | | 7 | Foreign Keys | Outbound (this table →) and inbound (→ this table) | | 8 | Indexes | Name, type, unique flag, columns covered | | 9 | Constraints | CHECK, UNIQUE, NOT NULL constraints |
Note: explain query plan is at gQ (Query Doctor).
Analysis & Export
| Key | Action | |-----|--------| | ga | Aggregate selected cells (visual mode) | | gS | Column statistics popup | | gR | Table profile (sparkline distributions) | | gQ | Query Doctor (plain-English EXPLAIN) | | gx | Open URL in current cell (http/https/ftp) | | gD | Diff against another table | | gv | Toggle compact/wide diff layout | | gE | Export to clipboard (CSV, TSV, JSON, SQL INSERT, Markdown, Grip Table) | | gX | Export to file (csv/json/sql). Also :GripExport |
Inspection
| Key | Action | |-----|--------| | gs | Preview staged SQL in float | | gc | Copy staged SQL to clipboard | | gi | Table info (columns, types, PKs) | | gI | Table properties (columns, indexes, stats) | | ge | Explain cell under cursor | | gV | DDL float (CREATE TABLE with columns, PKs, FKs, indexes) |
Schema & Workflow
| Key | Action | |-----|--------| | go / gT / gt | Pick table (fuzzy finder) | | gb | Schema browser (focus if open; close from inside) | | gC / | Switch database connection | | gO | Open read-only query result as editable table | | gW | Toggle watch mode (auto-refresh on timer, default 5s) | | gL | Pin / unpin result. Pinned results survive subsequent query executions. | | gJ | Result switcher: pick from all open grip result buffers (pinned listed first). | | g! | Toggle write mode (apply edits overwrites local file) | | gN | Rename column under cursor | | q | Focus query pad (pre-fills if empty; appends if pad has content) | | gw | Jump to grid (from query pad or sidebar) | | gh | Query history browser | | A | AI SQL generation (natural language) |
Advanced
| Key | Action | |-----|--------| | gl | Toggle live SQL floating preview | | T | Toggle column type annotations | | r | Refresh (re-run query) | | :q | Close grip buffer | | ? | Show help |
Query Pad
| Key | Action | |-----|--------| | | Execute buffer (or SQL fence under cursor in notebooks) or selection (visual) into grip grid | | | Execute and always open result in a new split (never reuses an existing grid) | | | Save query with :GripSave | | gn | Notebook picker (load .md or .sql file from project) | | gq | Load saved query (picker with SQL preview) | | gA | AI SQL generation (natural language) | | gF | Format SQL (external tool cascade: sql-formatter, pg_format, sqlfluff, or Lua) | | go / gT / gt | Table picker | | gh | Query history (with SQL preview) | | gw | Jump to grid window | | gb | Schema browser (focus if open; close from inside) | | gC / | Switch database connection | | gG / 4 | ER diagram float | | 1 | Schema sidebar | | 2 | Query history (secondary; already in query pad) | | 3 | Jump to grid (table picker if no grid is open) | | 5–9 | Jump to grid in depth view (5=Stats, 6=Columns, 7=FK, 8=Indexes, 9=Constraints) |
Schema Sidebar
| Key | Action | |-----|--------| | | Open table in grid | | | Open table in new split | | l / zo | Expand columns | | h / zc | Collapse | | L | Expand all | | H | Collapse all | | / | Filter by name | | F | Clear filter | | n / N | Next / previous table match | | y | Yank table or column name | | r | Refresh schema | | go | Open table under cursor, ORDER BY latest (created_at / PK DESC) | | 1 | Connections picker (secondary; already in sidebar) | | 2 | Open query pad | | 3 | Jump to grid / open table under cursor (table picker if no node) | | 4 | ER diagram float | | 5–9 | Open table under cursor in depth view (5=Stats, 6=Columns, 7=FK, 8=Indexes, 9=Constraints) | | gT / gt | Table picker (fuzzy finder) | | gb / | Close sidebar | | gw | Jump to grid | | gC / gc / | Switch connection | | gh | Query history | | gq | Saved queries | | q | Open query pad | | D | Drop table (with confirmation) | | + | Create table | | ? | Show help |
Commands
| Command | Description | |---------|-------------| | :Grip [table\|SQL\|file\|url] | Open table, run query, or open file as table. Flags: --write (edit file in-place, writes back on apply), --watch (auto-refresh every 5s), --watch=Ns (custom interval in seconds) | | :GripSchema | Toggle schema browser sidebar | | :GripTables | Open table picker with column preview | | :GripQuery [sql] | Open SQL query pad | | :GripSave [name] | Save query pad content to .grip/queries/ | | :GripLoad [name] | Load a saved query (picker if no name) | | :GripHistory | Browse query history (timestamp + SQL preview) | | :GripConnect [url] | Connect and open workspace (schema + query pad) | | :GripExplain [sql] | Query Doctor: plain-English EXPLAIN with tips | | :GripProfile [table] | Profile columns with sparkline distributions | | :GripAsk [question] | AI SQL generation from natural language | | :GripProperties [table] | Show table properties (columns, indexes, stats) | | :GripRename old new | Rename a column in the current table | | :GripCreate | Create a new table interactively | | :GripDiff {table1} {table2} | Compare two tables by PK (compact/wide, toggle gv) | | :GripDrop [table] | Drop a table with typed confirmation | | :GripToggle | Close all grip windows, or reopen if closed |
Requirements
- Neovim 0.10+
- One or more database CLI tools in PATH:
- SQLite: sqlite3
- MySQL/MariaDB: mysql (auto-detects MariaDB and uses --batch output)
- DuckDB: duckdb
Install
lazy.nvim (recommended)
The plugin ships a
lazy.lua spec so all commands work as lazy-load triggers automatically.
version = "*" tracks the latest stable release tag. Omit it to track HEAD (rolling).
<pre><code class="lang-lua">{ "joryeugene/dadbod-grip.nvim", version = "*", -- always latest stable; remove to track HEAD }</code></pre>
With keymaps (recommended):
<pre><code class="lang-lua">{ "joryeugene/dadbod-grip.nvim", version = "*", keys = { { "<leader>db", "<cmd>GripConnect<cr>", desc = "DB connect" }, { "<leader>dg", "<cmd>Grip<cr>", desc = "DB grid" }, { "<leader>dt", "<cmd>GripTables<cr>", desc = "DB tables" }, { "<leader>dq", "<cmd>GripQuery<cr>", desc = "DB query pad" }, { "<leader>ds", "<cmd>GripSchema<cr>", desc = "DB schema" }, { "<leader>dh", "<cmd>GripHistory<cr>", desc = "DB history" }, }, opts = {}, }</code></pre>
Demo (Softrear Analyst Portal, no database needed):
<pre><code class="lang-lua">{ "<leader>dd", "<cmd>GripStart<cr>", desc = "DB demo" },</code></pre>
Completion engines:
dadbod-grip ships built-in SQL completion (tables, columns, aliases, DuckDB federation) with no extra plugins. Completions fire as you type and
opens the menu manually.
To use blink.cmp or nvim-cmp instead, disable the built-in popup and register the source:
<pre><code class="lang-lua">-- blink.cmp require("dadbod-grip").setup({ completion = false })
require("blink.cmp").setup({ sources = { providers = { dadbod_grip = { name = "Grip SQL", module = "dadbod-grip.completion.blink" }, }, }, })</code></pre>
<pre><code class="lang-lua">-- nvim-cmp require("dadbod-grip").setup({ completion = false })
require("cmp").setup({ sources = { { name = "dadbod_grip" }, { name = "nvim_lsp" }, { name = "buffer" }, }, })</code></pre>
Copilot ghost text works alongside either engine (filetype is
sql).
packer.nvim
<pre><code class="lang-lua">use { "joryeugene/dadbod-grip.nvim", tag = "v*", -- latest stable release }</code></pre>
vim-plug
<pre><code class="lang-vim">Plug 'joryeugene/dadbod-grip.nvim', { 'tag': 'v*' }</code></pre>
Configuration
setup() is called automatically by the plugin loader with sensible defaults. Override if needed:
<pre><code class="lang-lua">require("dadbod-grip").setup({ limit = 100, -- default row limit for SELECT queries maxcolwidth = 40, -- max display width per column timeout = 30000, -- query timeout in ms (default: 10000; raise for slow tunnels) completion = true, -- set false to use blink.cmp/nvim-cmp instead connections_path = nil, -- absolute path to a shared connections.json file border = "rounded", picker = "builtin",-- "builtin", "telescope", or "snacks" })</code></pre>
Connections added via the picker save to
.grip/connections.json in the project root. A second file, ~/.grip/connections.json, holds global connections shared across all projects. Both are merged in the picker. When at least one global connection exists, the picker groups connections under "global" and "project" section headers. Press G on any project connection to promote it to global.
Setting
connections_path overrides this two-tier behavior: grip reads and writes connections to that single file only.
Setting
picker to "telescope" or "snacks" delegates simple pickers (table picker, command palette, history) to that backend. Complex pickers (connections, saved queries) always use the built-in picker. Falls back to built-in gracefully when the configured backend is not installed.
AI SQL generation (optional):
<pre><code class="lang-lua">require("dadbod-grip").setup({ ai = { provider = nil, -- nil = auto-detect, or "anthropic"/"openai"/"gemini"/"ollama" model = nil, -- nil = provider default api_key = nil, -- nil = env var, "env:VAR", "cmd:op read ...", or direct string base_url = nil, -- override for ollama or proxy } })</code></pre>
Provider auto-detection priority:
ANTHROPICAPIKEY > OPENAIAPIKEY > GEMINIAPIKEY > ollama (local). Explicit provider setting always wins.
To disable AI entirely (skips schema pre-warm on connection open, shows an info message on
A/gA):
<pre><code class="lang-lua">require("dadbod-grip").setup({ ai = false })</code></pre>
Remapping keymaps
All keymaps are remappable via
setup(). Pass action names as keys. Set a key to false to disable it entirely.
<pre><code class="lang-lua">require("dadbod-grip").setup({ keymaps = { -- remap the command palette off C-p (e.g. if you use C-p for telescope) palette = "<F1>",
-- remap AI to a leader sequence instead of bare A ai = "<leader>da",
-- change apply to <Space> instead of a grid_apply = "<Space>",
-- remap pagination to ][ instead of H/L gridnextpage = "]", gridprevpage = "",
-- disable the live SQL preview toggle if you never use it gridlivesql = false,
-- use <leader>n for notebooks instead of gn open_notebook = "<leader>n", } })</code></pre>
Action names are stable API. The full list is in
lua/dadbod-grip/keymaps.lua.
Common actions worth knowing:
| Action name | Default | Surface | |---|---|---| |
palette | | all | | ai | A | grid + sidebar | | qpad_ai | gA | query pad | | qpad_execute | | query pad | | open_notebook | gn | query pad | | grid_apply | a | grid | | gridfkfollow | gf | grid | | grid_profile | gR | grid | | gridcolstats | gS | grid | | connections | gC | all | | tab1 / tab2 / tab_3 | 1 / 2 / 3 | all |
Usage
Standalone Workflow (no DBUI needed)
<pre><code class="lang-">:GripConnect → pick a database → schema sidebar + query pad open automatically</code></pre>
That's the whole setup. One command. From there:
on a table in the schema sidebar opens the grid in the query pad runs SQL into a grid A in the query pad generates SQL from natural language Everything else (:GripSchema, :GripQuery, :GripTables) still works individually if you prefer.
Quick Examples
<pre><code class="lang-">:Grip users → open table in editable grid :Grip SELECT * FROM orders LIMIT 50 → run arbitrary SQL :Grip /path/to/data.parquet → open Parquet file via DuckDB :Grip /path/to/data.csv --write → edit file in-place (writes back on apply) :Grip /path/to/data.csv --watch → auto-refresh grid every 5s :Grip /path/to/data.csv --watch=10s → auto-refresh with custom interval :Grip https://example.com/data.csv → open remote file via httpfs :GripConnect → pick a connection, open full workspace :GripExplain → EXPLAIN current query in plain English</code></pre>
DBUI Integration (optional)
If you also use [vim-dadbod-ui,
open_smart() detects DBUI context:
- DBUI SQL buffer: opens that table, reuses the dbout window
- dbout result buffer: traces back to the source table name
- Normal buffer: uses the word under cursor as a table name
Public API
<pre><code class="lang-lua">local grip = require("dadbod-grip")
-- Optional config override (auto-called with defaults by plugin loader) grip.setup(opts)
-- Direct open: table name or SQL, connection URL, view options grip.open("users", "postgresql://localhost/mydb", { reuse_win = winid })
-- Smart open: auto-detects DBUI context grip.open_smart()</code></pre>
Architecture
<pre><code class="lang-">:Grip :GripNew :GripQuery :GripAttach :GripStart ... │ ╔═══════════════════════════▼══════════════════════════╗ ║ INIT.LUA ║ ║ parse commands · manage sessions · orchestrate ║ ╚══════╦════════════════════╦══════════════════╦═══════╝ ║ ║ ║ ┌──────▼──────┐ ┌──────────▼──────────┐ ┌───▼──────────┐ │ VIEW.LUA │ │ SCHEMA.LUA │ │ QUERY_PAD │ │ grid · UI │ │ sidebar tree │ │ SQL·notebooks│ │ keymaps │ │ metadata · DDL │ │ gn · C-CR │ └──────┬──────┘ └──────────┬──────────┘ └───┬──────────┘ └────────────────────┼─────────────────┘ │ ┌──────────────── FEATURES ────────────────────────────┐ │ AI.LUA SQL gen · schema context assembly │ │ Anthropic · OpenAI · Gemini · Ollama │ │ DDL.LUA alter · add/drop column · create/drop │ │ DIFF.LUA PK-matched row comparison · colorized │ │ PROFILE.LUA sparkline distributions · col stats │ └──────────────────────────────┬───────────────────────┘ │ ┌──────────────── PURE CORE ───────────────────────────┐ │ no mutations · no I/O · values in, values out │ │ DATA.LUA immutable state transforms │ │ QUERY.LUA query specs as plain values │ │ SQL.LUA pure SQL string generation │ └──────────────────────────────┬───────────────────────┘ │ ╔══════════════════════════════▼═══════════════════════╗ ║ DB.LUA ─ I/O BOUNDARY ║ ║ CSV parse · adapter dispatch · transaction safety ║ ╚═════╦════════════╦════════════╦════════════╦═════════╝ ║ ║ ║ ║ ┌───▼──┐ ┌───▼───┐ ┌───▼───┐ ┌────▼────────┐ │ psql │ │sqlite3│ │ mysql │ │ duckdb │ └──────┘ └───────┘ └───────┘ │ :GripAttach │ │ cross-DB │ │ CSV·parquet │ └─────────────┘</code></pre>
Design principles:
- Immutable state:
data.lua never mutates. Every operation returns a new state table. Query as value: query.lua treats query specs as plain Lua tables composed by pure functions. I/O at the boundary: Only db.lua and adapters run shell commands. Everything else is pure. Adapter pattern: URL scheme → adapter module. Each adapter implements query, execute, getprimarykeys, getcolumninfo, getforeignkeys, getindexes, gettablestats, listtables, and explain. Transaction safety: Apply wraps all DML in BEGIN/COMMIT with ROLLBACK on error. Testing
PostgreSQL
<pre><code class="lang-bash">createdb grip_test psql griptest < tests/seedpg.sql</code></pre>
SQLite
<pre><code class="lang-bash">sqlite3 tests/seedsqlite.db < tests/seedsqlite.sql</code></pre>
MySQL
<pre><code class="lang-bash">mysql -u root -e "CREATE DATABASE IF NOT EXISTS grip_test" mysql -u root griptest < tests/seedmysql.sql</code></pre>
DuckDB
<pre><code class="lang-bash">duckdb tests/seedduckdb.duckdb < tests/seedduckdb.sql</code></pre>
The SQLite DB (
tests/seedsqlite.db) is committed to the repo for zero-setup testing. Seed files share the same 13 tables + 1 view but each has adapter-specific types in typezoo (e.g. PostgreSQL TSVECTOR/RANGE/MACADDR, MySQL SET/YEAR/GEOMETRY, DuckDB HUGEINT/STRUCT/MAP/UNION, SQLite type affinity coercion).
Open each table with
:Grip and verify rendering, editing, sort/filter/pagination, and FK navigation.
Ecosystem
- vim-dadbod started Vim database tooling. Optional. If installed, grip reads its
g:db/g:dbs` variables as connection sources for smooth migration from existing dadbod or DBUI setups.
dadbod-grip.nvim · edit data like a vim buffer · github
