Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>SQLite sidekickNew to Visual Studio Code? Get it now.
SQLite sidekick

SQLite sidekick

PrasertKana

|
107 installs
| (0) | Free
Read-only SQLite database viewer with a spreadsheet-like grid, explorer, SQL history, and Jupyter export. Also opens CSV, TSV, PSV, and Excel files as SQLite
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

SQLite sidekick

A SQLite 3 database viewer for Visual Studio Code: read-only unless you turn on Allow Changes for a file.

Open a .sqlite, .sqlite3, .db3, or .db file normally. The viewer checks SQLite's binary header before starting a worker, so an unrelated file using the same .db suffix is refused rather than parsed. For any other extension, or none, run PK SQLite: Open File as SQLite Database.

CSV, TSV, PSV, and Excel files open too, imported into a temporary SQLite copy so SQL, the profile, filters, and exports all work on them — see CSV, TSV, PSV, and Excel files.

No native dependency, and nothing bundled. The engine is node:sqlite, built into the Node that VS Code already ships, so one platform-neutral package of about 110 KB serves every platform.

Remote files work too. Databases reached over Remote - SSH, WSL, Dev Containers, or Codespaces open the same way, as do those in a virtual workspace such as GitHub Remote Repositories — see Remote and virtual workspaces, which covers where the extension is best installed and the one case where a database can be missing its most recent rows.

Requirements

VS Code built on Node 24 or newer, which is where node:sqlite became available without a command-line flag. On an older build the extension reports this and registers nothing, rather than failing later.

Features

  • Every table and view appears as a workbook-style bottom tab. Drag a tab off the strip to open it in a window of its own.
  • Explorer panel listing the database, its tables, views, and their columns, plus its indexes and triggers.
  • Find in database searches every table and column for a value at once, and a result opens that table already filtered to the rows it matched.
  • Foreign-key navigation: jump from a key to the row it references, or to the rows that reference it.
  • SQL suggestions for keywords, types, SQLite's 172 built-in functions, tables, views, and columns, aware of the clause and of FROM aliases.
  • Virtualized rows and columns for large databases.
  • Column names and declared types remain visible while scrolling.
  • Repeating 16-color, color-blind-friendly column palette.
  • A Theme toolbar button that cycles between following VS Code and a colorblind-friendly light or dark theme, built from the Okabe-Ito palette with text held at WCAG AA contrast. The choice is the pkSqlite.theme setting, so every open viewer follows it and it survives a reload.
  • Optional thousands separators for numeric columns, without changing copied or exported values.
  • REAL columns show one consistent number of decimals so the decimal points line up, without the noise digits binary floating point prints.
  • Integers are exact at any size, including beyond 2^53.
  • BLOB columns are shown as a size and never decoded as text.
  • On-demand, wrapping data profiles with key statistics, missing and exact unique counts, distributions, and complete PNG download.
  • Three-state sorting, Excel-style cell/range/row/column selection and keyboard navigation, Tail 10, resizing, auto-fit, and freeze panes.
  • Stacked row filters, run inside SQLite so they narrow the whole table rather than the page on screen, and writable into the SQL editor as the query they stand for.
  • Selection-aware TSV copy and selected-column CSV, TSV, PSV, or JSON export.
  • Focus cell crosshair with matching row and column-header highlights.
  • Session SQL editor with Ctrl/Cmd+Enter execution and cancellation, and syntax highlighting for keywords, types, strings, quoted identifiers, numbers, function calls, and comments.
  • One-click Prettify reformats the query into a consistent clause-per-line layout with indented subqueries.
  • A four-layer read-only guard, described below, and an opt-in Allow Changes mode that lets the SQL editor change the file while ATTACH, extensions, and pragma changes stay blocked.
  • SQL history remains in memory for the current open-document session.
  • Selected history exports to a Python/JupySQL .ipynb notebook that needs no database driver installed, to a plain .sql script, or to Markdown with one sql code block per query.
  • Active tables and the active SQL query result can be exported as CSV. A query result export reruns the query so the file holds every row, not only the rows shown under the display limit.
  • Open databases reload after external changes, including changes that reach only the write-ahead log.
  • Works on databases held remotely — Remote - SSH, WSL, Dev Containers, Codespaces — and in virtual workspaces, which are read through VS Code rather than off the local disk.

Unless you turn on Allow Changes, the database, its -wal, and its -journal are never modified.

Read-only by default, and how

Viewing must never change a database, so the guarantee is enforced four times over rather than once:

  1. The connection is opened read-only. INSERT, CREATE, and DROP fail inside SQLite itself, below any code of ours.
  2. An authorizer allows only reads. This is a security control, not just a correctness one: a read-only SQLite connection can still ATTACH another database and read it, so without this the SQL box would be an arbitrary-file reader. Everything outside a read allowlist is denied, so a future SQLite release cannot introduce a write-capable action that quietly passes.
  3. Defensive mode is on and extension loading is never enabled.
  4. Exactly one statement, and it must return rows. SQLite's prepare() silently ignores anything after the first statement, so a second one is reported rather than dropped.

SELECT, WITH, VALUES, EXPLAIN, and read-only PRAGMA calls are accepted. INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, ATTACH, DETACH, VACUUM, REINDEX, ANALYZE, transaction control, and pragma assignments are all refused.

Allowing changes

The lock button in the toolbar, or PK SQLite: Allow Changes / Make Read-Only, lets the SQL editor change the open file. It is off whenever a file is opened, and turning it on asks you to confirm. Every viewer on the same file follows it, and clicking it again makes the viewer read-only.

With changes allowed, the SQL editor also runs INSERT, UPDATE, DELETE, REPLACE, CREATE and DROP of tables, indexes, views, and triggers, ALTER TABLE, ANALYZE main, and REINDEX main.…. The SQL status and the history show how many rows changed. A RETURNING clause shows its rows as a result tab. After each change the explorer, tabs, and grid reload.

  • Each statement is saved as it runs. There is no Save step and no undo, so make a copy first if you may want the old data back.
  • Still one statement at a time, and BEGIN, COMMIT, and SAVEPOINT are refused, so every statement is its own transaction.
  • Still refused: ATTACH, DETACH, VACUUM, pragma assignments, temporary tables, virtual tables, and load_extension. The SQL box can change the file it shows, and can still neither read nor write any other.
  • Only the SQL editor writes. The grid, the profile, and the exports stay read-only, and exporting or profiling a query never runs it as a change.
  • Tables added to the viewer are not changed. A statement that would write to them is refused. Unqualified ANALYZE and REINDEX are refused while any are added, because they act on every attached database.
  • Not available for a CSV, TSV, PSV, or Excel import, a file in a remote or virtual workspace, or a temporary recovered copy. In each of these, a change would land in a temporary copy rather than your file.
  • Cancel and the timeout cannot interrupt a change that has already started. It either completes or never begins, and the history says a cancelled change may have been saved.

CSV, TSV, PSV, and Excel files

Right-click a .csv, .tsv, .tab, .psv, or .xlsx file in the Explorer and choose Open in SQLite Sidekick, or run PK SQLite: Open CSV, TSV, PSV, or Excel in SQLite Sidekick. These files keep their usual editor otherwise; Reopen Editor With… also offers the viewer.

The file is imported into a temporary SQLite database, which then opens read-only like any other. The source file is only ever read. A CSV, TSV, or PSV becomes one table named after the file; a workbook becomes one table per worksheet, named after the sheet.

  • Delimiters. TSV and TAB are tab-separated and PSV is pipe-separated. A .csv is checked for commas, semicolons, tabs, and pipes, so a semicolon-separated export from a decimal-comma locale reads correctly; the toolbar names any delimiter other than a comma. Quoted fields may contain delimiters, quotes, and line breaks. A UTF-8 or UTF-16 byte-order mark is honored; text without one is read as UTF-8.
  • Column names come from the first row (turn off pkSqlite.tabularHeader when a file has none). Blank names become column1, column2, …, and repeats gain _2. A row longer than the header adds columns rather than losing fields; a shorter one is padded with NULL.
  • Types. A column becomes INTEGER or REAL only when every value in it fits. One stray value keeps it TEXT, and so do leading zeros (007) and integers too large for SQLite (such as 20-digit account numbers), so no value is ever changed by the import. Empty fields are NULL. Set pkSqlite.tabularAllText to import everything as TEXT.
  • Excel. Cells keep their types; formulas contribute their last calculated value; error cells such as #N/A are NULL; TRUE/FALSE are 1 and 0. Dates and times, as Excel formats them, become ISO text such as 2024-03-15 or 2024-03-15 14:30:00. Data may start anywhere on the sheet: leading empty rows and columns are skipped. Reading stops at the first fully empty row after the data unless pkSqlite.excelStopAtEmpty is off. Older .xls and binary .xlsb workbooks are not supported.

Adding files to an open viewer

Any open viewer, a database or an imported file, can take more files as new tabs, so you can join a CSV against a database table without leaving it:

  • Shift+drag CSV, TSV, TAB, PSV, or XLSX files from the VS Code Explorer onto the viewer. VS Code only lets a drop reach a viewer while Shift is held.
  • Or run PK SQLite: Add Data Files to Viewer….

Added tables are imported the same way, into a separate session database the viewer attaches read-only as added; the source database is never written. They carry a file glyph and a dashed left edge — where the Query Result tab's solid edge says "not a relation at all", the dashed one says "not part of this file" — their tooltip names the file they came from, and they appear under added in the Explorer. Each is named clear of the source's tables — a dropped orders.csv next to an orders table becomes orders_2 — so plain names in SQL always mean one table, and added.orders_2 works too. Adding files reloads the viewer, which clears the current query result.

Double-click an added tab (or press F2 on it) to rename its table: a box opens over the label, Enter commits, Escape puts it back. The name is refused if it is empty, begins with sqlite_, is already another added table's, or is one of the source's own relations — a plain name in SQL has to keep meaning one table. Renaming and closing are session-only; the file on disk is untouched.

Each added tab carries an × that closes it: the table is dropped from the session database and the viewer reloads without it. The source file it came from is untouched, so dropping it in again brings it back. Added tables otherwise last until the viewer is closed, and survive the source being reloaded. Files dragged in from the operating system's file manager do not work, because a webview is not given their path; drag them from the VS Code Explorer instead, or use the command. Notebook export reconnects to the source only, so a query that reads an added table will not run there.

Editing the source file re-imports it, just as an externally changed database reloads. PK SQLite: Save as SQLite Database (also in the Explorer menu) writes the same import out as a permanent .sqlite file. Notebook export needs a real database, so save the file as SQLite first to use it.

Row counts

SQLite has no stored row count — count(*) reads every row — so the grid never waits for one. The first page appears immediately, and the toolbar's count carries a leading … — the tab reads … rows — until the count arrives. Above pkSqlite.exactRowCountLimit (five million by default) the count is reported as ≈ N from max(rowid), which is an upper bound that can overcount after deletions; its tooltip says so.

Deep scrolling in a very large table slows down linearly, because SQLite still visits the rows an OFFSET skips.

Grid controls

Click a column header to select and sort it. Repeated clicks cycle through ascending, descending, and the original order; an arrow beside the name shows the active direction. Tables are sorted inside SQLite before pages are loaded, with rowid as a stable tie-breaker where one exists. The Query Result tab sorts the rows currently displayed. Each tab remembers its own sort while the viewer remains open.

Drag the right edge of a column header to resize that column. Double-click the edge to auto-fit just that column to its cached values, or click the Auto-fit all columns button to fit every column to the values currently loaded. Auto-fit is deliberately cache-based, so it does not scan a large table merely to measure text.

Click and drag cells to select a rectangle. Shift+click extends from the active cell. Shift+click a row or column header for a contiguous range, or Ctrl/Cmd+ click to toggle individual headers. The top-left corner or Ctrl/Cmd+A selects the displayed grid; Escape or an empty-grid click clears the selection.

With the grid focused, arrows, Home, End, Page Up, Page Down, Tab, and Enter move the active cell. Shift extends a cell or whole-row/column selection, and Ctrl/Cmd jumps to an edge. Ctrl/Cmd+Space selects the active column, Shift+Space the active row, and both modifiers select all cells. Tail 10 shows the final ten rows in the active sort; Show all restores the grid.

Ctrl/Cmd+C copies the selected rectangle or rows/columns as escaped TSV. Whole-column table copies include headers and every row, not only cached pages; query-result copies use all displayed rows. Ctrl/Cmd+Shift+C adds headers to a row selection. Nulls remain empty and exact raw values are used.

Export in the toolbar opens one menu for both kinds of export: All columns as CSV saves the current table or query result, and the Selected columns entries stream the selected column headers as CSV, TSV, PSV, or a typed JSON array in the active sort order. Right-click a column selection for the same four formats in the context menu. Delimited files include a header and UTF-8 BOM. Query-result exports rerun the successful query so they are not capped by the display limit.

The menu's All tables and views section exports every tab at once. A checklist asks which tables and views to include (all are checked), then Excel workbook writes one .xlsx with a sheet per relation, CSV files writes one .csv per relation into a folder you choose, asking before it replaces files already there, and SQLite database writes one .sqlite holding them all. Both are unfiltered, whatever filter the current tab has. Each sheet has a bold, frozen header row with an autofilter. Integers past 2^53 are written as text so Excel cannot round them, blobs as hex literals, and a sheet stops at Excel's 1,048,575 data rows (the message names any sheet that was cut short). The Query Result tab is not included; export it on its own.

A SQLite export includes tabs added from files, which is the only way to keep those past the session, and offers to open the result when it is done. Every value travels exactly, blobs and large integers included. It is a snapshot of what the tabs hold rather than a copy of the schema: a view becomes a table of its rows, declared types travel as their affinity (VARCHAR(20) becomes TEXT), and primary keys, indexes, constraints, and triggers are not carried.

Focus cell highlights the active cell's row and column, including the matching row and column headers and any frozen copies. Column colors are temporarily turned off and their button disabled while focus cell mode is active; leaving the mode restores the previous color setting.

The comma icon toggles thousands separators for numeric cells. This and the column palette are display controls only: tooltips, copy, SQL, and CSV continue to use exact raw values.

Find in database

Click the magnifier, press Ctrl/Cmd+Shift+H, or run PK SQLite: Find in Database to open the search panel. Type a value and press Enter: every table and view is read, and the columns holding a match are listed with how many values each holds.

Results appear as each table finishes rather than at the end, so a large database shows what it has found while it is still looking, and Stop abandons the rest. Clicking a result opens that table with a contains filter on the matching column already applied, so the grid shows exactly the rows the count stood for.

Some details worth knowing:

  • Matches are values, not rows. A row matching in two columns is counted by both, because each column is counted separately in one pass over the table. The filter a result opens is per column, so its row count is the number that was clicked.
  • BLOB columns are never searched. The viewer does not decode a blob as text anywhere else, and a search would be the one place it changed its mind. A column declared with no type is searched, because it holds whatever was put into it.
  • Stopping is a request, not a promise. As with a cancelled query, the search worker is destroyed rather than asked to stop, and that can only happen between tables — a single very large table finishes first.
  • Searching is case-insensitive for ASCII, and % and _ are ordinary characters, exactly as in the filter bar.

Filter rows

Click the funnel, press Ctrl/Cmd+Shift+F, or run PK SQLite: Filter Rows to open the filter bar. A condition is a column — or Any column, which any field of the row may satisfy — an operator, and a value:

Operator Matches
contains, starts with, ends with text anywhere, at the start, or at the end
equals the whole value, ignoring case
is greater than, is less than numbers when the value is a number, text otherwise
is empty / is not empty NULL or an empty string, and its opposite
is NULL / is not NULL only a true NULL

+ Condition stacks another one; a row must satisfy every condition in the stack. Apply or Enter runs it, Clear empties it, and the funnel carries the number of active conditions. % and _ are matched literally rather than as wildcards. Right-click a cell for Filter by "value", which stacks that value onto whatever the bar is holding.

Filtering from a column header

Point at a column header and click its funnel to filter that column the way a spreadsheet does:

  • Text and other columns list their most frequent values with how many rows hold each; tick the ones to keep. Type in Contains… to narrow the list, which also finds values past the first 200, and tick Every value containing … to keep all of them at once.
  • Number and date columns take a From and To instead, either of which can be left open; the column's own lowest and highest values show as placeholders. Dates compare as text, so write them the way the column holds them (2024-01-31). A range never keeps an empty cell.

Apply replaces whatever filtered that column before; Clear filter removes it. A filtered column's funnel stays lit, and its filter appears in the bar as one sentence — click it to change it, or its ✕ to remove it. Filters on several columns combine with each other and with the conditions typed in the bar. Right-click a cell for Filter Column… and Clear Filter on Column.

The filter becomes a WHERE clause inside SQLite, so it narrows the whole table and not just the rows already loaded — paging, the row count, the data profile, copy, and every export all read through it, and the row count says "filtered" beside the source name. Each tab keeps its own filter while the viewer is open.

A SQL query result is filtered by its own SQL: add a WHERE clause and run it again. Its rows are capped by the display limit, so a filter there would search only the part that fitted rather than the result it appears to.

Turning a filter into SQL

SQL in the filter bar — or PK SQLite: Write Row Filter as SQL — writes the filter into the SQL editor as the query it stands for, and opens the editor on it:

SELECT *
FROM buyers
WHERE name LIKE 'A%' ESCAPE '\'
  AND spend > 100
ORDER BY spend DESC;

It is the same conditions the grid is reading, one per line, plus the active sort, so running it returns the table on screen. From there it is an ordinary query: join to another table, group it, project fewer columns, keep it in the session history, or export it to a notebook. The bar can only express a WHERE clause, so this is the way out of the bar rather than a second way to run it.

While the bar is open it writes what the rows currently say, applied or not; with the bar closed it writes the filter the grid is showing. The editor's previous contents are replaced, as they are when a history entry is clicked.

Freeze panes

Select a cell and click Freeze panes. Everything above and to the left of it stays put while the rest of the grid scrolls, exactly as in Excel — so selecting the cell in row 3, column C pins the first two rows and the first two columns.

The button then reads Unfreeze; clicking it again removes the split. It is also available from the command palette as PK SQLite: Freeze or Unfreeze Panes at Active Cell, which makes it a single keybinding if you want one.

To move a split, unfreeze first and then freeze at the new cell. Selecting the very first cell freezes nothing, since there is nothing above or to its left, and a split always leaves at least one row and one column free, so the grid can never be pinned solid.

Each table remembers its own split, so switching tabs and coming back keeps it.

One difference from Excel: columns you scroll past slide underneath the frozen ones rather than stopping beside them. Freezing the leftmost columns to keep an identifier in view works as expected; freezing many columns and then scrolling far right will hide some behind the frozen block.

Number display

REAL columns are formatted for reading. Every row in a column gets the same number of decimals, so the decimal points line up:

Stored value Shown
1499.95, 20, 3.5 1499.95, 20.00, 3.50
0.30000000000000004 0.3
0.0000012345678 0.000001235

Trailing digits like those in 0.1 + 0.2 are an artifact of binary floating point rather than your data, and are always dropped. Genuine precision beyond that is capped by pkSqlite.maxDecimalDigits, which defaults to 4. A column whose values are too small for the cap widens instead of rounding to zero, and scientific notation is never used.

Integers are never converted to JavaScript numbers or rounded, at any size. A value beyond 2^53 keeps every digit in the grid, tooltip, copy, and CSV. When the comma toggle is on, separators are inserted directly into the display text, so 9007199254740993 is shown as 9,007,199,254,740,993 without losing precision.

Formatting is display only. Ctrl/Cmd+C copies the exact stored value, hovering a cell shows it, and CSV export is unaffected.

Types, and values that disagree with them

SQLite is dynamically typed: a declared column type is an affinity, and any row may store any type. Two consequences are visible in the grid.

A column with no declared type, and every computed expression, reports no type at all. The viewer infers one from the first values it loads and marks it with a trailing ? in a dimmed style — INTEGER? is a guess, INTEGER is a declaration.

A column whose values disagree with its declared affinity is legal and is not flagged. Text in a REAL column is simply shown as text.

BLOB columns

A BLOB cell shows ‹BLOB 1,024 bytes› and is never decoded as text.

Blobs of 1 KB or less travel to the viewer whole, so hovering shows a complete x'…' literal and Ctrl/Cmd+C copies one that pastes straight back into a query. Above that, only the first pkSqlite.blobPreviewBytes (32 by default) are sent, the tooltip says they are leading bytes, and copying yields the size label instead of a partial value that would look complete.

CSV export always writes every byte, whatever the size, because it re-reads the value rather than using the copy the grid holds.

Remote and virtual workspaces

The viewer supports databases opened through VS Code Remote - SSH, WSL, Dev Containers, Codespaces, and desktop virtual workspaces such as GitHub Remote Repositories. Install the extension in the location offered by VS Code, then open the database normally or use Open With.

What decides the behavior is whether the extension is running on the same machine as the database:

  • Installed on the remote — with Install in SSH: …, or in the container or Codespace — the database is an ordinary local file and is opened directly. Nothing is copied.
  • Installed locally against a remote workspace, or in a virtual workspace where no file exists anywhere, the extension reads the resource through VS Code and opens a disposable local snapshot instead. The snapshot is refreshed when the source changes and removed when the viewer closes.

Either way the viewer works. Read-only repositories still support viewing, SQL, profiles, and CSV export to a separate writable location. VS Code offers the install location when you open the database; taking the remote one avoids the copy, and with it the caveat below.

A snapshot copies the database and its sidecars, but a write-ahead log is not always readable that way. So a snapshotted database is the one case that can show stale rows: if it has an un-checkpointed -wal, commits made since the last checkpoint may be missing, and the viewer says so once on opening. Where the file is opened directly there is no such limitation — a database another process is actively writing opens fine and shows that writer's committed rows.

Notebook export is the one feature that needs a directly-opened file rather than a snapshot, because the exported notebook has to reopen the database by path, outside VS Code. It is therefore available on a remote host when the extension is installed there, and unavailable in a virtual workspace.

Opening a file from a link

PK SQLite: Open URL…, or the link button beside the file name in a viewer's toolbar, downloads a data file from a shared link and opens it in the viewer for its kind:

  • a SQLite database
  • an Excel workbook (a Google Sheet opens as a workbook, with every tab)
  • a CSV, TSV, or PSV file

The box offers the link on your clipboard when there is one. It takes links from:

Service Link to paste
Google Sheets A sheet shared with "Anyone with the link", or published to the web
Google Drive A file shared with "Anyone with the link"
OneDrive and SharePoint Share ▸ "Anyone with the link can view" (1drv.ms/…, …sharepoint.com/:x:/…)
Dropbox Share ▸ Copy link
iCloud Drive Share ▸ Share Options ▸ "Anyone with the link"
Box A shared link (app.box.com/s/…) with downloading allowed
GitHub A file in a public repository (github.com/…/blob/… or raw.githubusercontent.com/…)
Amazon S3, Google Cloud Storage, Azure Blob Storage A public object, or a presigned / signed / SAS URL (s3://… and gs://… work too)
Any web address A direct link that downloads the file in a browser

Nothing is downloaded until you ask, and only from the link you gave.

  • Not shared: a link that isn't shared publicly gets a message saying how to share it on that service, followed by every address tried and what each one answered.
  • Encodings: a CSV saved in your computer's legacy code page, such as Windows-874 for Thai, is converted to UTF-8 as it downloads.
  • DuckDB and Parquet: these files are pointed at DuckDB sidekick.

The tab is named after the file, and the file stays read-only: Allow Changes needs a file on disk.

  • Reload from Link: PK SQLite: Reload from Link downloads the file again, and every viewer on it shows the new copy.
  • Restarts: downloads are kept in the extension's storage, so a tab reopened after a restart shows the last copy without going online. A copy nobody has opened for 30 days is removed.

Explorer

Click Explorer in the viewer toolbar to show or hide the tree of the database, its tables, views, and columns. Selecting a table or view opens it in the grid and highlights its bottom tab, so the tree and the tabs stay in step.

The tree is read from SQLite's schema, so expanding a table never scans it, however large it is. Tables and views are grouped separately; relations whose names begin with sqlite_ are internal and hidden.

Indexes and Triggers get groups of their own, shown only when the database has any. Expanding an index lists the columns it covers, in key order; expanding a trigger shows the statement that created it. An index SQLite made for itself — to enforce a UNIQUE constraint or a non-INTEGER PRIMARY KEY — is shown too, marked implicit, because that a column is indexed is worth knowing however the index came about. Turn the group off with pkSqlite.showIndexes.

Right-click a cell for Show schema, which writes that table's CREATE statement into the SQL editor as a comment block.

Right-click the database, a table, or a view in the tree for Generate DDL, which opens its CREATE statements in a new, unsaved SQL editor tab. A table brings its indexes and triggers along with it; the database gives every table, view, index, and trigger, in an order that recreates the schema when run on an empty database. Nothing is written until you save the tab.

Following a foreign key

Right-click a cell in a column that is part of a foreign key and the menu offers to follow it:

  • Go to <table>.<column> opens the referenced table filtered to the row this value points at.
  • Show <table> referencing this goes the other way, listing the rows in another table that point back at this one.

Both are offered only when there is somewhere to go: a key whose target table is missing, a NULL in any part of the key, and a key that references its target's primary key without naming the columns are all described rather than navigated. Composite keys are followed on every column at once.

Click Data profile in the viewer toolbar — or run PK SQLite: Toggle Data Profile — to open the profile pane beside the grid. Drag its left edge to resize it, and close it from the × in its header. Profiles are calculated lazily and cached until the database changes. The Query Result tab is also profileable: its successful SELECT is rerun to calculate statistics over the full result rather than only the displayed row limit.

The pane has three tabs, all following the active cell:

  • Record reads the selected row down the page instead of across it, one labelled field per column — the view that makes a forty-column table legible. Step rows with the arrows beside the position, and click a field to jump the grid to that column.
  • Column profiles just the active cell's column, at full pane height.
  • All columns is the card grid: every column at once. Click a card to select that column in the grid.

Every card shows the column's missing, unique, minimum, and maximum values; numeric columns add the mean, the median, and a twelve-bin histogram, while other columns show their eight most frequent values as bars, each with its share of the values present. PNG saves the complete theme-aware card grid, including off-screen cards; it is offered on the All columns tab only.

SQLite has no approximate-distinct function, so unique counts are exact — and exact means a full scan with a sort or hash per column. They are computed every time regardless, so on a very large table expect the profile to take a while.

SQL and notebook export

Click SQL in the viewer toolbar, enter one read-only query (or, with changes allowed, one statement of any kind), and select Run or press Ctrl/Cmd+Enter. The editor shows line numbers, and the panel is resized by dragging its bottom edge.

Because SQLite offers no way to interrupt a running statement, Cancel and the pkSqlite.queryTimeoutSeconds timeout stop a query by destroying the thread running it and starting a fresh one. This is safe because nothing is being written, or, with changes allowed, because each change is one statement that either completes or never begins. It cannot disturb the grid, the tabs, or the explorer, which are served by a separate worker.

Suggestions appear as you type and can be requested with Ctrl+Space. What is offered first depends on the clause: relations after FROM and JOIN, columns after SELECT, WHERE, ON, GROUP BY, and ORDER BY, and keywords at the start of a statement. Typing alias. narrows to that relation's columns, and main. lists the database's relations. Use ↑ and ↓ to move, Enter or Tab to accept, and Escape to dismiss; Ctrl/Cmd+Enter still runs the query. Names that need quoting are inserted quoted. Set pkSqlite.sqlSuggestions to false to turn the popup off.

Prettify

Prettify in the SQL panel, Shift+Alt+F in the editor, right-click in the editor, or PK SQLite: Prettify SQL reformats the query in place:

SELECT prefs.purchase_purpose,
  prefs.preference,
  prefs.rating
FROM (
  SELECT b.purchase_purpose,
    p.preference,
    p.rating,
    RANK() OVER (PARTITION BY b.purchase_purpose ORDER BY p.rating DESC) AS rank
  FROM buyers AS b
  JOIN prefs AS p
  ON b.buyer_id = p.buyer_id
) AS prefs
WHERE prefs.rank <= 5
ORDER BY prefs.purchase_purpose,
  prefs.rating DESC;

Each clause starts a line. The first item stays on the clause's line and later items — separated by a comma, or by AND or OR — wrap to one extra level of indentation. A subquery's parentheses open an indent level and its closing parenthesis returns to the clause indent, so ) AS prefs reads as one line. Parentheses holding an expression never break, which keeps a window specification such as OVER (PARTITION BY x ORDER BY y) intact.

Keywords and type names are uppercased. Identifiers, quoted identifiers, string literals, function names, and comments keep the spelling you gave them, and the caret stays with the token it was on.

Expand SELECT *

Shift+Alt+E in the editor, right-click in the editor, or PK SQLite: Expand SELECT * writes each star in the statement out as the columns it stands for:

SELECT * FROM sales            -- becomes
SELECT id, region, channel, amount, "order date" FROM sales

It works on the selection if there is one, otherwise on the statement the caret is in, and nothing is run. s.* becomes s.id, s.region, …. A bare * over a join qualifies only the names both sides have, and a key merged by USING stays bare. Stars inside CTEs and subqueries are expanded where they stand. The list stays on the star's line while that line fits in 100 characters, and otherwise goes one column per line. A star SQLite cannot read is left as it is, and the status line says why. One Undo puts every star back.

Join Lines

Ctrl+J (Cmd+J on macOS) in the SQL editor joins the line the caret is on to the one below it, or, with several lines selected, joins them all into one. The first line's trailing spaces and the next line's indentation are dropped and one space goes between them, so SELECT id, over name becomes SELECT id, name. The caret stays at the join, so pressing it again keeps joining downward, and one Undo splits the lines back. The same keys run VS Code's own Join Lines in the cells of a SQL script notebook.

Select and delete lines

Ctrl+L (Cmd+L on macOS) selects the caret's whole line, line break included, as VS Code's Expand Line Selection does; press it again to take in the next line, and with a selection it widens to whole lines. Ctrl+U deletes from the caret back to the start of its line and Ctrl+K from the caret to its end; at the very start or end of a line the line break goes instead, joining the two lines, and with a selection both delete the selection. Ctrl+U and Ctrl+K are Ctrl on macOS too. One Undo puts the text back. In a SQL script notebook's cells these keys keep VS Code's own meanings.

Alt+Up and Alt+Down (Option on macOS) move the caret's line, or every line the selection touches, one line up or down, as VS Code's Move Line Up and Move Line Down do; the selection moves with the lines, and one Undo puts them back.

Shift+Alt+Down (Shift+Option on macOS) duplicates the caret's line, or every line the selection touches, below itself and moves the selection to the copy, as VS Code's Copy Line Down does; pressing it again copies the copy.

Ctrl+/ (Cmd+/ on macOS) comments the caret's line, or every line the selection touches, out with --, as VS Code's Toggle Line Comment does; when every one already is, it comments them back in. The -- goes in at the lines' smallest indentation, so an indented block stays aligned, and blank lines are left alone. One Undo puts the text back. In a SQL script notebook's cells the same keys run VS Code's own toggle.

Right-clicking the SQL editor opens a menu with Prettify SQL, Expand SELECT * and Run SQL alongside Cut, Copy, Paste, and Select All. Each entry shows its keyboard shortcut, which remains available if your platform refuses clipboard access to the viewer.

Ctrl+wheel (Cmd+wheel on macOS) over the SQL editor zooms its text, a pixel per notch, between 6 and 48 pixels; a trackpad pinch works too. The line numbers and suggestions zoom with it. The size lasts until the panel closes, and a new panel starts at VS Code's editor.fontSize.

The History panel records each command, status, duration, and displayed row count until the document closes. The history export button in the toolbar opens a menu of three formats; each asks which queries to include, with the successful ones preselected:

  • Jupyter notebook (.ipynb) creates a JupySQL notebook that reopens the database read-only through a SQLite URI. Python's sqlite3 is in the standard library, so the notebook runs without installing a database driver. It needs a local SQLite file, so it is refused for a CSV or Excel file.
  • SQL script (.sql) writes the queries one after another, each ending in a semicolon, under a comment giving its number, status, time, and row count.
  • Markdown with SQL blocks (.md) gives each query a heading and its own ```sql block, ready to paste into notes or a README.

Each file records when it was exported, in local time with its UTC offset. The button is disabled until the session has at least one query to export.

Running a SQL script as a notebook

The scroll button beside the history buttons in the toolbar, or PK SQLite: Open SQL Script as Notebook…, opens a notebook that runs against the file in that viewer. It takes either:

  • a .sql file, with a cell for each statement
  • a Markdown file of ```sql blocks, with a cell for each block and the text between them shown as Markdown

After a .sql or .md history export, Open as Notebook in the export message does the same for the file you just wrote. In a .sql file, a comment directly above a statement stays in its cell. A comment set apart by a blank line becomes a text cell.

Run a cell with Ctrl/Cmd+Enter or Shift+Enter, or use Run All, which stops at the first cell that fails. Each cell shows its result under it, up to 1,000 rows. A cell holding several statements runs them in order and shows the last one's result.

Cells run exactly as the SQL editor runs them:

  • read-only unless you allow changes
  • under the same timeout
  • recorded in the same history

The notebook's kernel is the file it runs against. It is named after that file and stays with the notebook when you switch viewers. To run the script against another open file, pick that file's kernel.

Save writes the script back in its own format, line endings, and byte order mark, so an unedited script saves unchanged.

Save also writes each cell's last result under it, as a Jupyter notebook keeps its outputs: the first 20 rows as a table with the row count, or the error. In Markdown the table renders in any preview; in a .sql file it is written as comments. Reopen the script and each cell shows its saved result until you run it again. Re-running unchanged SQL leaves the file unchanged, since no timings are saved. The pkSqlite.scriptOutputRows setting sets how many rows are kept; 0 saves no results. DuckDB sidekick and SQLens save results the same way, so a script opens with its results in any of them.

.sql and .md files still open as text unless you use the button, the command, or Reopen Editor With….

Opening a tab in its own window

To see two tables side by side, or to put one on another monitor, pull its tab off the strip: drag it up, clear of the tab strip, and let go. A chip under the pointer says Release to open in a new window once it will open. Dragging along the strip, or pressing Escape, leaves the tab where it is. The Query Result tab comes off the same way, with the rows it already has — the query is not run again. You can also right-click a tab → Open in New Window, or run PK SQLite: Open Current Tab in New Window from the Command Palette.

The new window is a complete second viewer on the same file, opened on that tab. It has its own grid, filter, profile, and SQL editor, and it shares the first viewer's session: the same SQL history and the same added tables. Adding, renaming, or closing an added tab in either window reloads both, and each keeps the tab it was showing. The tab stays in the window it came from too, because tearing it off opens a second view of the file, not a move. VS Code for the Web has no separate windows, so there the new viewer opens beside the current one.

Commands

  • PK SQLite: Toggle Explorer
  • PK SQLite: Toggle SQL Editor
  • PK SQLite: Run SQL
  • PK SQLite: Cancel SQL
  • PK SQLite: Allow Changes / Make Read-Only
  • PK SQLite: Prettify SQL
  • PK SQLite: Expand SELECT *
  • PK SQLite: Show SQL History
  • PK SQLite: Export SQL History as Jupyter Notebook
  • PK SQLite: Export SQL History as SQL Script
  • PK SQLite: Export SQL History as Markdown
  • PK SQLite: Clear SQL History
  • PK SQLite: Open SQL Script as Notebook…
  • PK SQLite: Open URL…
  • PK SQLite: Reload from Link
  • PK SQLite: Export Active Table or Query Result as CSV
  • PK SQLite: Export All Tables to Excel Workbook
  • PK SQLite: Export All Tables as CSV Files
  • PK SQLite: Export All Tables to a SQLite Database
  • PK SQLite: Auto-fit All Columns
  • PK SQLite: Freeze or Unfreeze Panes at Active Cell
  • PK SQLite: Toggle Data Profile
  • PK SQLite: Filter Rows
  • PK SQLite: Write Row Filter as SQL
  • PK SQLite: Find in Database
  • PK SQLite: Open Current Tab in New Window
  • PK SQLite: Switch Viewer Theme (VS Code, Light, Dark)
  • PK SQLite: Open File as SQLite Database
  • PK SQLite: Open CSV, TSV, PSV, or Excel in SQLite Sidekick
  • PK SQLite: Save as SQLite Database
  • PK SQLite: Add Data Files to Viewer…

Not supported

Editing cells or rows in the grid (changes are made with SQL, when allowed); multi-statement scripts and transactions; charting; encrypted databases (SQLCipher, SEE); opening a database over HTTP or an object store; and FTS5 or R*Tree shadow tables presented as first-class relations. Parquet, CSV, and Excel are a different product — see DuckDB sidekick.

Contributing

Building the extension from source, running tests, and packaging a release are covered in DEV.md.

License

MIT

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft