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

DuckDB sidekick

PrasertKana

|
216 installs
| (1) | Free
DuckDB, Parquet, CSV, TSV, PSV, and Excel viewer — read-only unless you allow changes — with SQL history and export to CSV, Excel, DuckDB, and Jupyter
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

DuckDB sidekick

A DuckDB, Parquet, delimited-text, and Excel data viewer for Visual Studio Code: read-only unless you turn on Allow Changes for a DuckDB file.

Open a .duckdb, .parquet, or .parq file normally. For .csv, .tsv, .tab, .psv, or .xlsx, right-click the file in the Explorer and choose Open with DuckDB Sidekick, run PK DuckDB: Query Tabular File with DuckDB, or use Open With > Query Tabular File with DuckDB. The tabular editor is optional so it does not replace an installed CSV or Excel viewer.

Features

  • Explorer panel listing databases, schemas, tables, views, and their columns. Right-click a database, schema, table, or view to Generate DDL.
  • Find in database searches every column of every table for a value and opens the matching rows as a query.
  • Text compares ignoring case by default; the Match Case toolbar button makes SQL, filters, sorting, and charts case-sensitive.
  • DuckDB tables and views appear as workbook-style tabs. Drag a tab off the strip to open it in a window of its own.
  • SQL suggestions for keywords, types, DuckDB functions, tables, views, and columns, aware of the clause and of FROM aliases.
  • Parquet files appear as one logical table.
  • CSV, TSV, TAB, and PSV files are exposed to SQL as data.
  • Excel sheets appear as workbook-style tabs and keep their sheet names as SQL relation names. Sheets that aren't tables — a chart sheet, a pivot-only sheet, an empty one — are skipped so one bad tab can't stop the workbook opening.
  • Virtualized rows and columns for large datasets.
  • Column names and DuckDB logical types remain visible while scrolling.
  • Repeating 16-color, color-blind-friendly column palette.
  • A Theme toolbar button forces a colorblind-friendly light or dark theme, independent of the VS Code theme; the choice is one setting shared by every open viewer and survives a reload.
  • Float columns show one consistent number of decimals so the decimal points line up, without the noise digits binary floating point prints.
  • A compact comma toggle adds thousands separators without converting exact integers to JavaScript numbers.
  • On-demand, wrapping data-profile cards with key statistics, missing and approximate unique counts, distributions, and complete PNG, WebP or SVG download.
  • Descriptive statistics for any tab from its right-click menu: one row per column, with count, nulls, distinct, extremes, mean, std, quartiles, skewness, and kurtosis.
  • Interactive charts — distribution, relationship, comparison, part-to-whole, correlation, trend, time pattern, calendar, butterfly, dumbbell, radar, and pivot, among others — with global filters. Every chart is aggregated by DuckDB, so it opens a huge file as cheaply as a small one.
  • Excel-style column filters: pick values from a list with their counts, match text, or set a number or date range. DuckDB does the filtering, so it works on a table of any size, and copy and export follow what the grid shows.
  • Fix a column from its right-click menu — trim, change case, extract numbers, blank or N/A to NULL, fill NULL, or anonymize personal data — with the rows it would change counted and shown before anything is changed.
  • Find & Replace in a column, with Match Case, Whole Word, and regular expressions, as VS Code's find widget works.
  • Set Column Type reads a column as a number, date, decimal, and more, counting the values that would not convert before it does.
  • Fixes, replacements, and column types change what the viewer shows, never the file — until you Save a CSV, Excel, or Parquet file with changes allowed.
  • Sortable columns, Excel-style cell/range/row/column selection and keyboard navigation, Tail 10, selection-aware copy, Focus Cell, resizing, and auto-fit.
  • Grid right-click menus provide Copy Cell and Copy Column in regular and frozen panes.
  • Excel-style freeze panes pin rows and columns while the rest of the grid scrolls.
  • 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.
  • Read-only SQL guard blocks mutating and multi-statement commands, and an opt-in Allow Changes mode lets the SQL editor change a DuckDB file while COPY, ATTACH, transactions, and settings stay blocked. On a CSV, Excel, or Parquet file it lets Save write your fixes back instead.
  • Typed query results appear in the same colored grid.
  • SQL history remains in memory for the current open-document session.
  • Selected history exports to a Python/JupySQL .ipynb notebook, a plain .sql script, or Markdown with one ```sql block per query.
  • Every exported SQL cell starts with %%sql followed by a newline.
  • Active tables and the active SQL query result export as CSV, a typed JSON array, a Markdown table, Excel, or a DuckDB database. One Export… button opens a panel that picks the columns, format, and options. A query result export reruns the query so the file holds every row, not only the rows shown under the display limit.
  • CSV is written as UTF-8, with an optional byte order mark for double-clicking into Excel on Windows; Excel exports are UTF-8 by construction.
  • A DuckDB export keeps column types instead of flattening them to text, and several exports accumulate in one file.
  • Open files reload after external changes.
  • Explorer right-click entry for opening a data file in the viewer.

Source files are never modified unless you turn on Allow Changes for that file: then SQL you run changes a DuckDB file, and Save overwrites a CSV, Excel, or Parquet file.

Explorer context menu

Right-clicking a .csv, .tsv, .tab, .psv, or .xlsx file in the VS Code Explorer adds Open with DuckDB Sidekick, which opens it in the read-only viewer, the same as Open With > Query Tabular File with DuckDB. It acts on the whole selection, so selecting several files and choosing the entry opens all of them.

Save as DuckDB appears on the same files and on .parquet/.parq too. It writes them into a DuckDB database without opening a viewer:

  • A CSV, TSV, TAB, PSV, or Parquet file becomes one table named after the file.
  • An Excel workbook becomes one table per sheet, named after the sheet. Sheets that are not tables (a chart, a cover page, an empty tab) are skipped, as they are in the viewer, and whole-number columns are stored as BIGINT (see the DuckDB export below).
  • Selecting several files saves them all into the one database you choose.

Table names keep letters of any script, so a Thai sheet name stays Thai; anything else becomes _, and a name used twice gets _2, _3. Saving into an existing database adds to it, and if any of the tables already exist you are asked once whether to replace them. The source files' own loading settings (pkDuckDb.tabular*, pkDuckDb.excelStopAtEmpty) apply. The command is also in the palette as PK DuckDB: Save as DuckDB, where it asks for the files.

Remote repositories

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

For a virtual repository, the extension reads the resource through VS Code and opens a disposable local snapshot with DuckDB. The snapshot is refreshed when the source changes and removed when the viewer closes. Read-only repositories still support viewing, SQL, profiles, and CSV or DuckDB export to a separate writable location.

Notebook export requires a local source because the exported notebook must reopen the data file outside VS Code. The .sql and Markdown history exports connect to nothing, so they work from any source. The browser-only editors at vscode.dev and github.dev are not supported because this extension uses DuckDB's native Node.js library.

Opening a file from a link

PK DuckDB: 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:

  • Excel workbooks (a Google Sheet opens as a workbook, with every tab)
  • CSV, TSV, and PSV
  • Parquet
  • DuckDB databases

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. 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. A CSV saved in UTF-16, or in your computer's legacy code page such as Windows-874 for Thai, is converted to UTF-8 as it is downloaded.

The tab is named after the file. The file stays read-only (Allow Changes needs a file on disk) and cannot be exported as a notebook, which would need to reopen it outside VS Code. PK DuckDB: Reload from Link downloads it again, and every viewer on it shows the new copy. 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.

Grid interaction

Click a column header to sort it. Repeated clicks cycle through ascending, descending, and the relation's original browsing order; ▲ or ▼ beside the column name shows the active direction. Each table or query result remembers its own sort while you switch tabs. Table pages are sorted by DuckDB, with rowid as a secondary key for stable paging on physical tables. SQL query results are sorted in the viewer from an untouched snapshot, so the third click can restore their original row order exactly.

Clicking a header also selects it. Shift+click selects a contiguous header range, Ctrl/Cmd+click toggles one row or column, and Ctrl/Cmd+Shift+click adds a range without changing the sort. Right-click selects without sorting. The top-left corner or Ctrl/Cmd+A selects all displayed cells; Escape or an empty grid click clears the selection.

Focus the grid and use arrows, Home, End, Page Up, Page Down, Tab, Enter, and their Shift or Ctrl/Cmd variants for Excel-style movement and range extension. Ctrl/Cmd+Space selects the active column, Shift+Space the active row, and both modifiers select all cells. After selecting a whole row or column, Shift+arrow extends or shrinks that selection along its axis; add Ctrl/Cmd to jump to the edge. Tail 10 switches to the final ten rows in the current sort order; Show all restores the full relation.

Ctrl/Cmd+C copies a rectangular cell range or one or more selected rows or columns as TSV. Whole columns include their headers and all rows, including uncached table pages. Ctrl/Cmd+Shift+C prepends column headers to selected rows. Tabs, line breaks, and quotes are escaped, while nulls remain empty and values keep their exact raw text rather than display-only formatting.

The right-click menu labels the active operation as Copy Cell, Cells, Row(s), Column(s), or All Cells. Right-clicking the top-left corner selects the entire displayed grid and opens that menu. The same selection and copy behavior works in frozen panes. Right-clicking inside the SQL editor keeps its editor menu.

Drag the right edge of a header to resize that column, down to a minimum of 40px. Double-click the edge to fit that column to its widest cached or displayed value, capped at 400px. Auto-fit all applies the same measurement to every column.

Focus Cell highlights the active row and column, including their headers. While it is on, Column colors is turned off and disabled so the two background treatments do not compete. Leaving Focus Cell restores Column colors to exactly the setting it had before, whether that setting was on or off.

Filtering rows

Point at a column header and click its funnel to filter that column. 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 most frequent ones; tick Every value containing … to keep all of them at once, matched without regard to case. Number and date columns take a From and To instead, either of which can be left open; a range never keeps an empty cell. Clear filter removes that column's filter.

Filters on several columns combine: a row must pass all of them. A filtered column's funnel stays lit, and a strip above the grid shows how many rows are left (1,204 of 50,000 rows) with one chip per filter. Click a chip to change that filter, or its × to remove it. Right-click a cell for Filter to This Value, Filter Column…, and Clear All Filters.

  • Table tabs are filtered by DuckDB, page by page, as they are sorted, so a filter costs about what a sort does however large the table is.
  • The Query Result tab is filtered in the viewer, over the rows it holds.
  • Sorting, Tail 10, copying, and Export Selected work on the filtered rows. The Export panel says when a tab is filtered and writes only the rows the filters keep; a query result's export re-runs the query, so it holds every matching row, not just those under the display limit. An all-tabs export writes every tab whole.
  • Open as SQL writes what the grid shows — the tab, its filters, and its sort — into the SQL editor as a query, to edit or run. Ctrl+Z brings back what the editor held.
  • The data profile and the charts describe the whole tab, not the filtered rows; the chart panel has filters of its own.
  • Each tab keeps its own filters while you switch between them. They are cleared when the file reloads, as the sort is.

Fixing a column

Right-click a cell or a column header and point at Fix. The fixes act on the column you clicked, or on every selected column when whole columns are selected:

  • Text: Trim whitespace, Collapse extra spaces, Remove accents, Remove HTML tags, and Change case ▸ lower case, UPPER CASE, or Title Case.
  • Numbers: Extract number reads the one number in a value such as 25.20 SqM, $1,200, or (1,234); Expand K/M/B/T reads 1.5K as 1500.
  • Missing values: Blank to NULL; N/A, NA, null and - to NULL; and Fill NULL ▸ from the row above, with the mean, with the median, or with the most common value.
  • Privacy: Anonymize ▸ Mask partly (a***@example.com, a phone's last digits, a name's initials, a date's year), Replace with codes (Email 0001, numbered in the order values first appear, the same value always getting the same code), or Hash (a short salted SHA-256, the same for the same value until the file is reopened). Emails, phone numbers, Thai national ID numbers, and card numbers are recognized from the values; names, addresses, and birth dates from the column's header.

Before anything changes, a confirmation says how many rows would change, shows a few values as they are and as they would be, and names any value that could not be converted. A fix that would change nothing just says so. Each fix applies to every row of the table, not only the rows a filter shows, and the confirmation says so when the grid is filtered. Fixes build on one another; Fix ▸ Revert to Original Data undoes all of them together.

Fixes work on table tabs, not on a query result. The fixed rows live in the viewer: the grid, filters, profile, charts, search, and exports all see them, and so does the SQL editor for a CSV, Excel, or Parquet file's own tabs. A DuckDB file's table, or a table added from another file, keeps its own name in SQL, so there the editor still reads the original. The file is not changed, and fixes are gone when it is reopened — unless you save them.

Find & Replace

Right-click a cell or a column header and choose Find & Replace…. A bar like VS Code's find widget opens over the grid, on that column; its drop-down picks another.

  • Aa, ab, and .* turn on Match Case, Match Whole Word, and regular expressions (Alt+C, Alt+W, Alt+R). Regular expressions are DuckDB's: there is no lookahead, lookbehind, or backreference inside a pattern. A pattern DuckDB cannot read says why under the bar.
  • Matching cells are tinted, the counter reads 3 of 120, and Enter and Shift+Enter, or ↑ and ↓, move between matches in the grid's own order — its filters and its sort.
  • In a replacement, $1, $2, … and $& insert what a regular expression captured, as in VS Code.
  • Replace (Enter in the Replace box) changes the match you are on and moves to the next. Replace All (Ctrl+Alt+Enter) first shows how many cells would change, with examples, and asks.

A replacement is a fix: Fix ▸ Revert to Original Data undoes it, and the file is not changed. The column comes out as text.

Setting a column's type

Right-click a cell or a column header and point at Set Column Type to read that column as VARCHAR, BIGINT, INTEGER, DOUBLE, DECIMAL(18,2), BOOLEAN, DATE, TIMESTAMP, TIMESTAMPTZ, or TIME; its current type is ticked. When some values cannot be read as the new type, you are told how many and asked first; they become empty. Reading a number or text as a date also understands Excel's date serials, so 45291 becomes 2023-12-31.

Set Column Type ▸ Original Type puts the column back. On a tab that already shows fixed rows, a type change is one more fix, and Fix ▸ Revert to Original Data undoes it. The file is not changed, and the types are gone when it is reopened. For a CSV whose type should stick for the session, PK DuckDB: Configure Tabular Source still sets it as the file is read.

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 and removes the split; to move a split, unfreeze, select a new cell, and freeze again. Both actions are also available from the command palette as PK DuckDB: Freeze Panes at Active Cell and PK DuckDB: Unfreeze Panes.

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

Each relation remembers its own split, so switching tabs and coming back keeps it. The split is never written to the source file, and it is dropped when the file changes on disk, because a relation's shape may have changed with 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.

Theme

The Theme toolbar button cycles the viewer through three states:

State What it does
Theme: VS Code Follow the current VS Code color theme (the default).
Theme: Light Force a colorblind-friendly light theme, whatever VS Code is using.
Theme: Dark Force a colorblind-friendly dark theme.

The two forced themes are built from the Okabe-Ito colorblind-safe palette, so nothing in the chrome — SQL syntax highlighting, the correlation chart, the explorer tree, status marks — rests on a red/green distinction. Text stays at WCAG AA contrast and other marks at 3:1.

The choice is stored in the pkDuckDb.theme setting, not per file: it is a vision preference, so every open viewer follows it, a new file opens with it, and it survives a window reload. Changing the setting directly, or running PK DuckDB: Switch Viewer Theme from the command palette (works with no viewer focused), has the same effect.

Number display

Numeric columns are right-aligned in the grid, and DATE, TIME, and TIMESTAMP columns are right-aligned with them: they read as ordered quantities rather than as prose, so their digits line up down the column. Everything else stays left-aligned.

FLOAT and DOUBLE 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.3333333432674408 (a FLOAT) 0.3333
0.0000012345678 0.000001235

Two different things are being removed. A FLOAT holds about 7 real digits but is printed with 16, and 0.1 + 0.2 famously prints as 0.30000000000000004 — those trailing digits are an artifact of binary floating point, not your data, and are always dropped. Genuine precision beyond that is capped by pkDuckDb.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.

The comma button in the toolbar toggles thousands separators for numeric cells:

Raw value Shown with separators
1234567.50 1,234,567.50
9007199254740993 9,007,199,254,740,993

Grouping is performed directly on the value's text, so integers larger than JavaScript's safe-number range keep every digit. The separator pass leaves exponent notation and non-numeric text unchanged.

DECIMAL columns are left exactly as DuckDB renders them, at the scale your schema declares — DECIMAL(6,3) keeps its three places. Integers, including HUGEINT, never pass through the floating-point formatter.

All number formatting is display only. Ctrl/Cmd+C and the grid context menu copy exact stored values, hovering a cell shows the raw value, and CSV, Excel, and DuckDB exports are unaffected.

Explorer

Click Explorer in the viewer toolbar to show or hide the tree of databases, schemas, 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 DuckDB's catalog, so expanding a table never scans it. Columns for a Parquet, delimited, or Excel source are read when the relation is first expanded, which keeps opening a many-sheet workbook fast.

Generate DDL

Right-click a database, schema, table, or view in the tree and choose Generate DDL. The CREATE statements open in a new, unsaved SQL editor tab, ready to read, copy, or save:

  • A database gives its schemas, sequences, tables, views, and indexes, ordered so the script runs cleanly on an empty database. Tables come after the tables their foreign keys reference, and views after the views they read.
  • A schema gives everything in that schema.
  • A table gives its CREATE TABLE and its indexes. A view gives its CREATE VIEW.

Macros are not included, because DuckDB does not keep their parameter defaults. A CSV, Excel, or Parquet file stores no DDL of its own, so for those the viewer writes a CREATE TABLE from the columns as it read them.

Find in database

Click Find in database in the toolbar, press Ctrl+Shift+H (Cmd+Shift+H on macOS), or run PK DuckDB: Find in Database. Type a value and press Enter. Every column of every tab is searched, case-insensitively, for the text anywhere in the value. Numbers and dates match by their text too, and % and _ are plain characters.

Results appear table by table as they are found. Each column with a match shows how many rows match. Click one to run a query in the SQL editor that returns exactly those rows, and edit it from there. Stop ends a long search, and a search never holds up your own SQL. Binary (BLOB) columns are skipped.

Click Data profile in the viewer toolbar to open a resizable pane beside the grid. It has three tabs:

  • Record — the selected row read down the page, one field per line, with a stepper to move between rows. Clicking a field selects that column in the grid.
  • Column — a full-height profile card for the active cell's column.
  • All columns — a responsive grid of one card per column. Clicking a card selects that column in the grid.

Profiles are calculated lazily by DuckDB and cached for each relation until the source file changes. The Query Result tab is also profileable: its successful SELECT is rerun once to calculate statistics over the full result rather than only the displayed row limit. PNG saves the current tab as a theme-aware image (or a transparent-background one), WebP saves the same picture as a smaller lossless file, and SVG as a vector you can scale or restyle, each with the same two choices.

Charts

Click Chart in the viewer toolbar, or run PK DuckDB: Toggle Chart, to chart the active table. The panel is resized by dragging its bottom edge, and expanded out of the split altogether by the last icon over the chart — the rest of the viewer steps back behind it, and the same icon, Escape, or a click outside brings it back to the height you had left it at. Fifteen views are available:

View What it answers
Distribution How are one column's values spread? Histogram, box plot with outliers, boxenplot, or Pareto.
Comparison Which categories are biggest? Bar, upright or turned on its side, with how many values each bar is made of beside its name.
Part-to-Whole How does each category divide? Stacked or 100% bar; a 100% bar writes how many values it holds under its name.
Relationship Do two numeric columns move together? Scatter, or a bubble chart sized by a third column, with a shape per colour and a fitted line or curve per group with its confidence band.
Correlation Which numeric columns relate? Pearson heatmap.
Trend How does a measure move over time? Line or area.
Card Where does a measure stand now? Its latest value, its change against the previous period, the first or a target, and a sparkline of the series — as XFN.SPARKLINECARD draws it, one card per group.
Bump How do items move up and down a ranking, period by period? Named pills, numbered dots on lines, bands of values, or numbered circles — as XFN.BUMP draws them.
Pattern When do things happen? One date column folded onto two cycles — weekday by hour, month by weekday, month by day, or year by month — as a heatmap.
Calendar What did each day hold? A shaded square per day — or per hour of each day — laid out by hours, weeks, months, a year planner, or GitHub's contribution graph.
Bullet Did each group hit the target it was set? One row per group.
Range How wide is each group's spread? A dot at each end of its range — worked out from a column of values, or read from a low and a high column.
Butterfly How does each side's shape run down the rows? Two sides' bars back to back from a centre line — a population pyramid.
Dumbbell How do two groups compare, label by label? Two dots a row — two values of a side column, or two columns — with the gap between them.
Radar How does each group score across several measures? One polygon per group round a spoke per measure — or per step of a cycle, such as months.
Facets Does the shape hold in every slice? A grid of small charts on shared scales, or of KPI cards.
Pivot Cross-tabulate two dimensions. Bar, heatmap, and a pivot table.

Four filter slots above the controls narrow every view at once. Pick a column and either tick the values you want or type a range.

All aggregation happens inside DuckDB and only the finished series is sent to the chart, so charting does not load your data into memory. Where a view would otherwise produce an unreadable chart it is bounded and says so: the top categories are kept, and a scatter plots a random sample whose size is reported next to the chart. The caps are configurable through the pkDuckDb.lens* settings.

A trend shows every interval between its first and its last, including the ones nothing happened in, so a quiet month is a gap in the line rather than a month the axis skips. Dates along the bottom are labelled the short way: "Jan" over "2000", then "Apr", "Jul", "Oct", with the year written only where it changes.

Set a trend's Scale to Change since start to compare lines of very different sizes, the way stock prices are compared. Every line starts at 0% and shows its percent change from its own first value. Hover a point to see both the change and the actual value. A line that is zero throughout has nothing to measure a change from, so it is left off and the note under the chart says so. When a trend or a scatter carries more points than fit across the panel, a slider appears under it: drag the slider, or scroll and drag on the chart itself, to narrow the range. Changing any control resets it.

Cards

Card is the chart add-in's XFN.SPARKLINECARD, Power BI's card with a sparkline: a measure bucketed by a date at Every, its latest period's value in display units (1.25M), the change — a green ▲ or red ▼ — and an Area, Line or Column sparkline of the series along the bottom, the latest 400 periods. Cards by draws one card per group, the largest 12. Compare measures the latest value against the Previous period (written "vs previous month", or the period's name where the one just before is empty), the First, or a typed Target, also drawn as a dashed line. Units writes display units, the plain number, or a share as a percent, with a Prefix and Suffix around it ($), and Show › Period writes the latest period's name at each card's top right. A sum's or a count's empty period is a zero; a mean's is a gap that breaks the line. Below zero the line or area is drawn in vermillion.

Bump charts

Bump ranks an item column period by period, largest first, and joins each item's places so a rise or fall shows as a bump and two items that swap places cross — the chart add-in's XFN.BUMP, in its four styles: Pill (named pills joined by ribbons), Line (numbered dots on thin lines, names at both ends), Band (thick bands with each value written) and Circle (numbered circles on tinted ribbons). Long data ranks a value (or a count) within each period of a column, a date cut by Every, the latest 8 periods kept; Wide data ranks each of several numeric columns on its own, in the table's order. Top draws 3 to 15 places, ties taking consecutive places by name, and an optional Group column colours items by the group most of their rows have, with a legend. At most 64 items.

Palettes

Charts of several series are drawn in the viewer's own colour-blind-safe palette, or in one of the XFN chart functions' palettes — Deep, Okabe-Ito, Tableau, Muted, Pastel, Dark, Colorblind, Viridis, Corporate, Earth, Bright, Teals, Burgundys or Blacks. Set the default with pkDuckDb.lensPalette (read when a viewer opens), or pick one per chart from its Palette control, which redraws without querying again. A series keeps its place in whichever palette, and a five-colour palette is taken again in turn. Colours that mean something — a rise and a fall, the mean's red dot — stay as they are, and the grid's column colours keep their own palette.

Chart settings

Settings ▾ at the end of the chart tabs saves every chart's picks — each tab's columns and options, its palette, and the panel's filters — to a JSON file you choose, and loads them back: to return to a set of charts after the file is closed, hand them to someone else, or draw the same charts over next month's export. A table's charts load back onto that table where the file has it, and onto the tab showing otherwise. A query's charts carry their query: loading them puts it in the SQL editor and runs it — as a SELECT only, whether or not changes are allowed — after asking. A pick naming a column the data does not have, or one that was a number when saved and is not now, is cleared and named in the note the load leaves; Undo there, or in the menu, brings back the charts from before the load.

Histograms

A histogram is read on two scales: the count of values in each bar on the left, and the same heights as a proportion of every value counted on the right, so "how many" and "what share" are both read off one bar. A grouped histogram's proportions are of every group together, and hovering a bar gives its count and its share of that same total. The data profile's histograms carry the same two scales, as a share of the column's non-missing values.

Box plots

The whiskers stop at the furthest value still within 1.5 IQR of the box, and anything past that is drawn as its own small circle — the usual reading of a box plot, and the one that stops a single wild value from stretching a whisker until every box in the chart is squashed flat. The true minimum and maximum are still there: hover a box and it names them, along with how many values it holds and how many fell outside its whiskers. Show data gives the whiskers columns of their own beside them.

Each group's size is written under its name, as (n = 1,234), because a box drawn from eight values looks just as settled as one drawn from eight thousand. A red dot marks each group's mean: beside the median line it shows at a glance which way a box is skewed — a mean above the median is a long upper tail. The box plots in a Facets grid carry both too, with each size written over its box.

With a grouping, Stats offers the test ggstatsplot prints over the boxes. Test writes Welch's t with Hedges' g for two boxes, or Welch's ANOVA with ω² for more, with the effect size's 95% interval and its size in a word — Welch F(6, 23508.75) = 11.88, p = 2.33e-13, ω² = 0.0013 (negligible), … — and draws each mean's 95% interval as a thin whisker through its red dot. Pairs puts a bracket over each pair of boxes Games-Howell finds different, up to ten, labelled with its p and the difference between the two means. The figures are R's to the digits shown, except that an effect R would round to 0.00 is written to two significant figures: on large data every difference is significant, and the word, the digits and the whiskers are what say how small it is.

A box with a very long tail draws the furthest outliers from each end rather than all of them — 50 per box by default, through pkDuckDb.lensOutlierLimit — and the note under the chart says so whenever there were more.

Boxes are grey, like the bars of a histogram: every box is the same measurement of the same column, so a colour would tell you nothing. The one you point at turns amber, which is what says which box you are reading. Tick Color each box to fill each group's box in its own colour from the chart's Palette instead.

Inside a Facets grid, box plots keep their whiskers at the extremes and draw no outliers. A cell is a twelfth of the chart area, where a scatter of dots would be noise rather than a tail.

Boxenplots

A Boxenplot is seaborn's letter-value plot, for the many rows a box plot handles worst: past a few thousand, 1.5 IQR marks hundreds of ordinary tail values as outliers. It draws the box plot's box, then a box half as wide out to the eighths, another to the sixteenths, and so on into the tails, each a shade paler, with the median across the widest and the mean as the red dot. What lies past the last box is drawn as a box plot's outliers are. Depth picks seaborn's rule for how far the boxes go — Tukey (the default), Proportion, Trustworthy, or Full, which reaches the minimum and maximum. Log draws it on a log axis, built from the logs of the values, so a long right tail no longer squashes every box into the floor; the dot is then the geometric mean, and the note says how many rows at or below zero were left out. Hover a box and it names the share of the values it holds. Color each box works here too: each group's boxes fade from its palette colour rather than from grey. A Facets grid offers boxenplots too, with no values drawn past the last box, as its box plots draw none.

Reference lines

Distribution takes any of Mean, Median and Quartiles at once — comparing them is the point, and a mean pulled away from a median is the whole story of a skew. Mean and median are on by default; the quartiles are left to be asked for. Because the median is the 50th percentile, asking for both still draws one median rather than stacking two lines on a spot.

Comparison draws its line across the bars instead, at their mean, their median, both, or a number you type — off by default, a bar chart being read against its own bars. A box plot is offered none: it already draws its median, and its axis is the values themselves. A Pareto is offered none for the opposite reason: its axis is its categories, so a line down it would stand at no value at all. Both withdraw the control rather than showing it to no effect.

Range marks the same three statistics on each row's own line, where they answer a different question — where a group sits inside its own spread, rather than where the whole chart's middle is. A quartile mark is withdrawn when the dots are already at Q1 and Q3, since it would be drawn on top of itself. Ticking Quartiles draws the median too, so the marks show 25%, 50% and 75%.

Each line is told apart three ways — a colourblind-safe colour, a dash, and a weight — so four crossing one chart stay distinguishable to a reader who cannot separate the hues, and under a forced palette that drops colour entirely.

Pareto, Bullet, Range and Facets

A Pareto ranks a column's total by category, largest first, with the running share of the whole climbing over it up a right-hand percentage axis and a dashed guide at 80% — dashed because 80% is a rule of thumb rather than anything your data said. Where the curve crosses the guide is how few categories account for most of the number. The shares are divided by the total over every category, including the ones the cap dropped, so a capped chart's last bar reads below 100% rather than claiming the cap was the whole.

The bars say it too: the ones that carry the running share up to 80% — the crossing bar included — are drawn in a solid mid grey, and the tail behind them takes the fainter grey a histogram bar has. Hovering any bar turns it amber, as everywhere else in the panel. Where the 80% point lies off the right of a capped chart every bar drawn is inside it, and where there is no positive total to take a share of there is no 80% at all, so no bar is singled out and no guide is drawn.

A Bullet gives each row a target of its own instead of one line across them all: the target is a wide quartered bar, the actual a narrower one centred inside it, and a green tick stands at the goal so it stays readable when the actual overshoots. Both columns are reduced by the same aggregate. Show as switches between the measured values and each row's share of its own target, which is what makes a small group that beat its quota comparable to a large one that missed.

A Range draws one row per group with a dot at each end of its span — min–max, or the middle half between Q1 and Q3, which is what a single outlier stops min–max from showing. Unlike a box plot a single-value group is kept: a range of one value is a real answer, and it draws as one dot.

Facets answers whether a shape holds in every slice without setting a filter, reading, changing it, and holding the last one in your head: the same chart drawn once per slice. Give it a value and a facet row, a facet column, or both. Nothing a cell draws is decided by that cell — the bins are measured once over the whole column, the categories are ranked once over every row, and every cell is given the same ceiling, so a taller bar means more wherever it is. An empty slice still gets its cell, because a gap in the grid is a fact about the data. A date is coarsened rather than truncated, through the Every control; anything else keeps the largest slices and gathers the rest into one quieter cell that says how many it stands for. The grid scrolls rather than squeezing its rows.

Chart › Card turns the grid into a dashboard's KPI cards, a tile per slice: a green or magenta edge and arrow for up or down, a grey sparkline of the Date's latest 24 periods (each to its own scale), "Current month" and its value, and that value against the period before at the Period picked — a week against last week ("vs. PW"), a month against last month ("vs. PM") — or, with Compare › Previous year, the same period a year before ("vs. PY"), in percent and in units. Every tile covers the same periods; a sum's empty period is a zero, a mean's a gap, and a period before the data starts leaves a dash.

A column whose name reads as a rate (conversion_rate, pct_churn, margin) and whose values sit in −1…1 is charted as a percentage: its axes and histogram edges read 0.0425 as "4.25%", and so does a mean, median, min or max of it — but not a sum or a count, which are no longer rates.

A whole-number column that holds codes rather than amounts — store_id, ProductCode, branch_no, or a rating with a handful of repeated values — is offered under Group by, Colour by, and a pivot's rows and columns, as well as remaining a measure. Its name decides first (store_count and unit_price stay amounts), and a quick sample of its values settles the rest. A code that is unique per row, such as order_id, can be picked but is never chosen for you, and a table with no text column opens grouped by its first code and measuring its first real amount.

Relationship and Trend carry a Log toggle — one per axis on the scatter, one for the value axis on a trend. It is what a measure spread over orders of magnitude needs: revenue by customer or rows by table otherwise piles into the bottom of the frame under its own largest value. A log axis cannot place a value at or below zero, so where the data reaches there the axis is drawn linear and the note under the chart says why. No bar chart offers it: a bar is read from zero, and logging one would leave its length saying something its number does not. The scatter is also never pinned to zero, since a point sits at a value rather than measuring a length from an origin.

Every chart also publishes its numbers as a table under Show data, which can be copied as TSV, and the chart itself can be saved next to your file. Hover the chart and four icons appear in its top corner — one saves a PNG, the next the same picture as a smaller lossless WebP, the next an SVG you can scale or restyle afterwards, and the last expands the chart over the whole viewer for a closer look; Save image in the footer is the same PNG for anyone who would rather not go looking. Either way the picture is captioned with what it shows, where it came from, anything you filtered, and the same note the panel was showing — so a chart of the top thirty categories still says so once it is pasted somewhere else. Under a high-contrast theme the table replaces the chart, since a canvas cannot follow a forced palette.

SQL and notebook export

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

The viewer accepts result-producing statements such as SELECT and EXPLAIN. Statements such as INSERT, UPDATE, DELETE, CREATE, COPY, ATTACH, INSTALL, and LOAD are blocked (INSTALL and LOAD unless you allow extensions).

EXPORT DATABASE also runs, read-only or not, since it only reads the file. It writes the schema, a load script, and one file per table into the directory you name, for example EXPORT DATABASE 'C:/exports/shop' (FORMAT parquet). Give an absolute path: a relative one resolves against VS Code's working directory, not your file's. The SQL status says Database exported when it finishes, and a large export may need a longer pkDuckDb.queryTimeoutSeconds. IMPORT DATABASE stays blocked.

SET and RESET run too, read-only or not, for the settings that only change how a query runs or reads: TimeZone, Calendar, default_collation, threads, memory_limit, max_temp_directory_size, and explain_output, for example SET TimeZone = 'UTC'. The SQL status says Setting applied. A setting lasts until the viewer reloads the file, and threads and memory_limit override pkDuckDb.queryThreads and pkDuckDb.queryMemoryLimit until then.

SET default_collation is what the Match Case button switches, and the button follows a SET or RESET of it typed here. Every other setting is blocked, as are SET search_path, SET schema, and SET VARIABLE: the charts re-run a query on a connection of their own, where those would not apply.

DuckDB extensions

By default the viewer loads no DuckDB extension but the Excel one it ships with, and downloads nothing. Turn on the pkDuckDb.allowExtensions setting (in your user settings: a workspace cannot turn it on) to let the SQL editor run INSTALL and LOAD, read-only or not:

INSTALL httpfs;
LOAD httpfs;
SELECT * FROM 'https://example.com/data/orders.parquet';

With the setting on, DuckDB also installs and loads a known extension by itself the first time a query needs it, as the DuckDB CLI does, so the SELECT above works without the first two lines. The setting takes effect the next time a file opens.

  • Only DuckDB's official, signed extensions (httpfs, spatial, json, icu, excel, and the rest of the core set). Community extensions (INSTALL … FROM community) and unsigned files are refused.
  • Extensions download from DuckDB's repository into ~/.duckdb/extensions, and run inside VS Code like the viewer itself.
  • An extension you LOAD stays loaded when the viewer reloads the file, after a change for example. The SQL status says Loaded httpfs.
  • An extension's own settings, such as s3_region, stay blocked, as does UPDATE EXTENSIONS. Public https:// URLs work; private S3 buckets need a secret, and CREATE SECRET needs changes allowed.

Match Case

Every file opens with text compared ignoring case, so WHERE name = 'alice' also finds Alice and ALICE. The Aa button in the toolbar, or PK DuckDB: Toggle Match Case, switches to case-sensitive comparisons and back, as VS Code's own Find does: pressed means case matters. Every viewer on the same file follows it, and nothing reloads.

It is DuckDB's default_collation = 'nocase', so it reaches everything that compares text: your SQL's =, IN, GROUP BY, ORDER BY, and joins, a column filter's value list (Alice and alice are one choice), the grid's sort, the data profile's top values, and the charts' categories. The Query Result tab filters its rows in the viewer by the same rule. Nothing is saved into the file: a table created while case is ignored keeps a plain text column.

With changes allowed, it reaches changes too: UPDATE ... WHERE name = 'alice' changes every Alice as well. Turn Match Case on first when only exact spellings should change.

Allowing changes

The lock button in the toolbar, or PK DuckDB: Allow Changes / Make Read-Only, lets the SQL editor change the open DuckDB file. (On a CSV, Excel, or Parquet file it allows Save instead.) 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, TRUNCATE, MERGE INTO, CREATE and DROP of tables, views, schemas, indexes, sequences, and macros, ALTER, COMMENT ON, ANALYZE, and VACUUM. 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, and each viewer stays on its tab.

  • 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 ROLLBACK are refused, so every statement is its own transaction.
  • Still refused: COPY, IMPORT DATABASE, ATTACH, DETACH, USE, SET beyond the settings above, PRAGMA, CALL (including CHECKPOINT), INSTALL and LOAD (unless extensions are allowed), and persistent secrets. The SQL box can change the file it shows, and can write no other except through EXPORT DATABASE.
  • Tables you added to the session stay unchanged. Each one is a copy of its file, made again on every reload, so a change to it would be lost. Copy the rows into the DuckDB file instead, as in CREATE TABLE saved_orders AS SELECT * FROM orders.
  • Only the SQL editor writes. The grid, the profile, the charts, and the exports stay read-only, and exporting, profiling, or charting a result never runs its statement again.
  • Other programs cannot open the file while changes are allowed: DuckDB holds it exclusively. If another program already has it open, turning changes on fails and the viewer stays read-only. Making the viewer read-only again releases the file.
  • Cancel and the timeout roll a change back unless it had already finished.
  • Not available for a file in a remote or virtual workspace, or one opened from a link: a change would land in a temporary copy rather than your file.

Saving a CSV, Excel, or Parquet file

A CSV, TSV, PSV, Excel, or Parquet file is read into a temporary database, so SQL never changes it. With changes allowed on one, a Save button appears beside the lock, and Save — the button, Ctrl/Cmd+S in the viewer, or PK DuckDB: Save Changes to File — overwrites the file with what its tabs show: every fix, replacement, and column type.

  • Every row is written, whatever the grid's filters or sort. Tabs you added from other files are not.
  • A CSV keeps its delimiter and header row, and is written as UTF-8.
  • An Excel file is written as values, every sheet under its own name and in its order. Formatting, formulas, charts, merged cells, comments, and anything outside the range that was read are lost.
  • Parquet keeps its column types.
  • It asks every time, and there is no backup or undo. The file is written to a hidden copy beside it first and replaces the original only once complete, so a save that fails leaves the file as it was.
  • After a save the file is opened again, read-only: what you saved is now the original.

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 schema. or database. walks down the catalog. 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 pkDuckDb.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 DuckDB: 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 DuckDB: 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. EXCLUDE, REPLACE and RENAME are written out: excluded columns are left out, a replaced column keeps your expression, and a renamed one becomes amount AS total. Stars inside CTEs, subqueries and each branch of a UNION 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 DuckDB 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.

With nothing selected, Ctrl+X and Ctrl+C (Cmd on macOS) cut or copy the caret's whole line, line break included, as VS Code does. Pasting that line with nothing selected puts it above the caret's line rather than in the middle of it, so Ctrl+X, a move, and Ctrl+V moves a line. With a selection they cut and copy the selection as usual.

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. The selection is captured when the menu opens, so Ctrl/Cmd+C still copies that selection even if opening the menu moved the caret.

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 (Export SQL history ▾) asks which entries to include, successful ones picked by default, then writes one of three files:

  • Jupyter notebook (.ipynb) — a JupySQL notebook that reconnects to the opened DuckDB database in read-only mode, so a change made with changes allowed stays in it as a cell that is refused rather than run again. Parquet exports create an in-memory DuckDB connection and register the file as a view. Delimited and Excel exports recreate the same source relations and parsing options.
  • SQL script (.sql) — every query under a comment giving its number, status, duration, and row count, or its error. Each statement ends in exactly one semicolon, so the file runs as a script in DuckDB.
  • Markdown (.md) — a heading per query, then a ```sql block, ready to paste into notes or a README.

All three record when they were exported, in local time with its UTC offset, such as 2026-09-23 14:05:12 +07:00.

Running a SQL script as a notebook

The scroll button beside the SQL history buttons in the toolbar (or PK DuckDB: Open SQL Script as Notebook…) opens a .sql file, or a Markdown file of ```sql blocks, as a notebook that runs against the file in the focused viewer. After a .sql or .md history export, Open as Notebook in the export message does the same for the file you just wrote.

  • A .sql file has a cell for each statement. A comment directly above a statement stays in its cell, and a comment set apart by a blank line becomes a text cell.
  • A Markdown file has a cell for each SQL block, with the text between them shown as Markdown.

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, and 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 pkDuckDb.scriptOutputRows setting sets how many rows are kept; 0 saves no results. SQLens saves results the same way, so a script opens with its results in either.

Both notebook types are optional editors: .sql and .md files still open as text unless you use the command or Reopen Editor With….

Export

With one or more column headers selected, Export selected ▾ offers CSV, TSV, PSV, and a typed JSON array. These quick exports contain every row in the current sort order and only the selected columns. Delimited formats include a header and UTF-8 BOM; JSON remains typed and has no BOM. The quick menu requires non-empty, unique column names. Query-result quick exports rerun the successful SELECT so they are not limited to displayed rows.

Export… in the viewer toolbar opens a panel over whatever the grid is showing — the active table, or the active SQL query result. A query result export reruns the query, so the file holds every row rather than the display-limited page.

The panel chooses:

  • Which columns. Every column starts checked. Filter the list by name, use All / None / Invert on whatever the filter left showing, and shift-click to check or uncheck a run of columns at once. Each column carries the colour it has in the grid and its DuckDB type. Leaving everything checked exports everything, and the export then keeps a plain SELECT * rather than a column list, so nothing gets reordered or renamed on the way out.
  • Which format — CSV, JSON Array, Markdown table, Parquet, Excel, or DuckDB.
  • Include header row, for CSV and Excel. JSON has no header, Markdown requires one, and a Parquet file or a DuckDB table has no header row, so the option is dimmed for those formats.
  • UTF-8 BOM, for CSV, off by default. Dimmed for every other format.

Column picks last while you stay on the same table, and reset when you switch to another one.

All tabs at once

With more than one table tab open, the panel shows This tab / All tabs. All tabs writes every table in one go — including files you added to the session — in one of four shapes:

  • DuckDB database: one database, one table per tab, named after the tab. An existing database keeps the tables it already holds, and anything a tab would replace is listed and confirmed once.
  • Excel workbook: one .xlsx, one sheet per tab. Sheet names follow Excel's rules: at most 31 characters, and a clash gets a (2) suffix.
  • CSV or Parquet files: you pick a folder, and each tab is written to <table>.csv or <table>.parquet in it. Files already there are listed and confirmed once, and the export can be cancelled between files.

Every column goes out, since the tabs do not share a column list, so the column picker is replaced by the list of tables to be written. JSON and Markdown stay single-tab. The SQL Query Result tab is not included: it is a re-run of your query rather than part of the source. Export it on its own from This tab.

The file being viewed can never be the destination, whichever format you pick, changes allowed or not.

Each format is also a command, which opens the same panel with that format already chosen: Export Active Table or Query Result as CSV, … as Excel, … as Parquet, and … to DuckDB.

CSV

Written as plain UTF-8. Check UTF-8 BOM to lead the file with a byte order mark instead.

The mark matters when you intend to double-click the file: without one, Excel on Windows decodes a CSV with the ANSI code page, which turns Thai, Japanese, and accented text into mojibake. It is off by default because most other tools — pandas, duckdb, git, a text editor — would rather see the bare bytes, and Excel's own Data → From Text import reads UTF-8 correctly without it.

JSON Array

Written as a typed JSON array of row objects. Numbers and booleans remain JSON values rather than being flattened to display text, and the file has no BOM.

Markdown table

Written as a GitHub-flavoured Markdown table. Pipes, backslashes, and line breaks in values are escaped so they cannot change the table shape. Every numeric, DATE, TIME, and TIMESTAMP DuckDB column uses a trailing-colon alignment marker (---:), making the whole column right-aligned even when its values are empty or NULL, matching how the grid aligns the same columns.

Parquet

A single .parquet file. Parquet stores its own schema, so column names and types survive: a DATE stays a DATE, a DECIMAL keeps its scale, and there is no header or encoding to choose. As with the DuckDB export, exporting an Excel sheet narrows whole-number columns from DOUBLE to BIGINT, while a query result keeps the types the query produced.

Excel

Written as a single-sheet .xlsx workbook, named after the table or query it came from. Excel's own rules for a sheet name are applied to that name — 31 characters, and none of : \ / ? * [ ] — so the workbook always opens. The format is UTF-8 throughout, so text needs no special handling.

Excel stores no column types, so this is the format to pick when the numbers matter more than the types. It also caps a sheet at 1,048,576 rows; a larger export reports that rather than truncating.

DuckDB

Written into a DuckDB database as a table you name. Column types survive, so a DATE stays a DATE and a DECIMAL keeps its scale.

Choosing a database that already exists adds the table to it and keeps the tables already there, which is how several exports collect into one file. Your operating system's save dialog still asks whether to replace the file; the export never does. The one case that overwrites is a name already used inside that database, and it asks first.

Excel is the one source whose types need help here. The format has no integer type — every numeric cell is a floating-point number — so a column of whole numbers would otherwise be stored as DOUBLE. Exporting a sheet checks each numeric column and narrows the ones holding only whole numbers to BIGINT, leaving genuinely fractional columns as DOUBLE. This applies to exporting a sheet; exporting a query result over a sheet keeps the types the query produced, since a computed value such as an average is a real DOUBLE even when it lands on a whole number.

Adding files to a viewer

Any open viewer (DuckDB, Parquet, CSV, or Excel) 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, TXT, XLSX, or Parquet files from the VS Code Explorer onto the viewer. VS Code only lets a drop reach a viewer while Shift is held.
  • Or use Add files… in the toolbar, or PK DuckDB: Add Data Files to Viewer… from the Command Palette.

Each file becomes a table named after it, and each readable sheet of a workbook becomes its own table (book_Sheet1). The tables sit in a scratch database named added, which the Explorer lists beside the source, and SQL can use them by their bare names:

SELECT b.name, sum(o.amount) FROM buyers b JOIN orders o ON o.buyer_id = b.id GROUP BY ALL

If a name is already taken, the new table gets a suffix (orders_2), and the source's own table keeps the bare name.

Double-click an added tab to rename its table — or press F2 with the tab focused. Enter commits, Escape puts it back. The name is what SQL calls the table, so SELECT * FROM sales_2024 follows the tab, and a name needing quotes works quoted. A name another tab already answers to is refused, since that table would keep the name and this one would be reachable only by its full path. The new name survives a reload of the source.

Added tables last for the session only. The rows are copied in when you add the file, and adding one never writes to the file you opened. With changes allowed, SQL can change an added table too, but not for long: every change reloads the viewer, which reads the added files again. They come back if the source is reloaded, and they go away when you close the viewer or a tab's ×. To keep one, export it from its tab with Export… → DuckDB. Dragging files in from the operating system's file manager does not work, because a webview is not given the path. Drag them from the VS Code Explorer instead, or use Add files….

Descriptive statistics

Right-click a tab → Descriptive Statistics to open a table of its columns in an editor beside the viewer: one row per column, with its type, count, nulls, null %, approximate distinct count, min, and max, and for a numeric column the mean, standard deviation, quartiles, median, skewness, and excess kurtosis — the figures pandas.describe() gives. Quartiles are interpolated, as pandas' are, rather than approximated like DuckDB's SUMMARIZE.

A table tab is described under its own column filters. The Query Result tab is described by running its SQL again, so the figures cover every row, not just the rows the grid loaded; only a SELECT result can be described. Everything is computed in one scan inside DuckDB, off to the side, so the grid and the SQL editor stay usable while it runs. The table is a snapshot: select it and copy to paste it into a spreadsheet.

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 or down, 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. You can also right-click a tab → Open in New Window, or run PK DuckDB: 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, profile, chart, 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 changes it in both. 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.

Tabular source loading

The viewer uses a scan-then-materialize strategy:

  • Auto materializes delimited files up to 128 MB in memory, uses a temporary on-disk DuckDB cache for larger files, and uses disk for Excel.
  • Memory and Disk force materialization to the selected session store.
  • Direct leaves relations as file scans, which opens quickly but rescans the source for repeated queries and profiles.

The cache lives in the operating system temporary directory and is removed when the viewer worker closes. Excel sheets are registered immediately and materialized individually when first browsed, profiled, or exported. SQL can query any registered sheet before materialization.

Trailing delimiters

Several systems write a delimited file with the delimiter at the end of every line, header included — id,name,updated,. That gives each row one more field than the file has named columns, and DuckDB reads the extra one as a column it names itself, with nothing in it.

The viewer drops that column, so the grid, the profile, and every export show the file as it was meant. It is dropped only when both signs agree: the header cell was blank, and the column holds no value in any row. A column the file named is always kept, and so is an unnamed one that turns out to carry data — this can hide a column, but never loses a value. Setting Header to no in Source settings keeps every column, since a file read without a header has no named columns to compare against.

Use Source settings in the viewer toolbar to change storage, delimiter, header, encoding, type inference, malformed-row handling, or Excel empty-row behavior for the current open session. Workspace defaults are available under pkDuckDb.tabular* settings. TSV/TAB defaults to tab, PSV defaults to pipe, and Excel treats the first row as a header unless No header row is selected.

Commands

  • PK DuckDB: Toggle Explorer
  • PK DuckDB: Find in Database
  • PK DuckDB: Toggle SQL Editor
  • PK DuckDB: Run SQL
  • PK DuckDB: Cancel SQL
  • PK DuckDB: Allow Changes / Make Read-Only
  • PK DuckDB: Save Changes to File
  • PK DuckDB: Toggle Match Case
  • PK DuckDB: Prettify SQL
  • PK DuckDB: Expand SELECT *
  • PK DuckDB: Show SQL History
  • PK DuckDB: Export SQL History as Jupyter Notebook
  • PK DuckDB: Export SQL History as SQL Script
  • PK DuckDB: Export SQL History as Markdown
  • PK DuckDB: Clear SQL History
  • PK DuckDB: Open SQL Script as Notebook…
  • PK DuckDB: Open URL…
  • PK DuckDB: Reload from Link
  • PK DuckDB: Export Active Table or Query Result as CSV
  • PK DuckDB: Export Active Table or Query Result as Excel
  • PK DuckDB: Export Active Table or Query Result to DuckDB
  • PK DuckDB: Export Active Table or Query Result as Parquet
  • PK DuckDB: Auto-fit All Columns
  • PK DuckDB: Freeze Panes at Active Cell
  • PK DuckDB: Unfreeze Panes
  • PK DuckDB: Query Tabular File with DuckDB
  • PK DuckDB: Configure Tabular Source
  • PK DuckDB: Add Data Files to Viewer…
  • PK DuckDB: Open Current Tab in New Window
  • PK DuckDB: Save as DuckDB
  • PK DuckDB: Switch Viewer Theme (VS Code, Light, Dark)

Open with DuckDB Sidekick is an Explorer right-click entry rather than a palette command, because it acts on the file you clicked.

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