Skip to content

Data sources ​

中文

DuckLocal turns files into views in an in-memory DuckDB connection, or in a database file if you opened one. Other databases can sit beside it. There is nothing to configure. See the local data analysis workflow for a concise path from files to SQL, charts and export.

Ways to open data ​

All of these go through the same resolution rules:

  1. Paths on the command line — ducklocal ./logs/ billing.parquet
  2. The Open menu in the title bar: Open files, Open folder, or Enter a path or glob, which opens the Open data dialog for a typed path or one from the native picker
  3. Dropping files or folders anywhere on the window
  4. On a Mac, files Finder hands to DuckLocal — see Open from Finder

To add a database beside the open one rather than replace it, use Open → Attach database… — see Attached databases.

On macOS, you can double-click Parquet and DuckDB files in Finder to open them, or press the Space bar to preview the initial rows of Parquet, DuckDB, CSV, TSV, and JSON Lines files with Quick Look without launching DuckLocal.

Supported file formats ​

Extensions are matched case-insensitively.

ExtensionHow it is read
.csv, .tsv, .txtread_csv_auto view
.parquetread_parquet view
.json, .ndjson, .jsonlread_json_auto view
.xlsx, .xls, .xlsb, .odsimported as a table per sheet

Gzipped text such as .json.gz and .csv.gz counts as a data file too, in schema, source blocks and when you attach a file; logs.json.gz becomes a view named logs. Before 0.4.0, ducklocal schema tried such a file as a database; on an older copy, read it in SQL with read_json or read_csv.

Anything else is not a data file — see Database files.

A workbook is the one format that becomes tables, not views, and it imports every sheet: the first sheet takes the file stem (the file name without its extension) as its name, the others are named {stem}_{sheet name}. An empty sheet is skipped. Column types are inferred from the cells — integers become BIGINT, an integer/float mixture DOUBLE, dates TIMESTAMP, and anything mixed or unrecognized VARCHAR; empty cells import as NULL.

Folders and patterns ​

A directory is walked recursively, and every data file inside becomes a view. Two rules apply to the walk:

  • Hidden entries (names starting with .) are skipped, and their subtrees are not descended into.
  • Symlinks are not followed. Following a link back up the tree would never terminate, and the linked-to directory is normally walked where it actually lives.

Files are attached in sorted order, so the same folder always produces the same workspace.

A pattern may contain * (any run of characters) and ? (exactly one). Matching happens per path component, against the filesystem, so a pattern can span directories:

bash
ducklocal './data/2026-0*/*.parquet'

There is no escape character. ** is not special — it behaves like a single * and does not recurse. A pattern that matches a directory skips it; only files are attached. Hidden entries are skipped here too.

Unquoted patterns are expanded by your shell before DuckLocal sees them. That is fine. Quote a pattern only when you want DuckLocal to expand it — for example, when the shell finds no match and would otherwise error out.

Many CSV files at once ​

Each file DuckLocal attaches becomes its own view with its own sniffed schema, so two CSVs that disagree never break each other. The pitfalls show up when one SQL read spans many files:

sql
SELECT * FROM read_csv('data/*.csv', union_by_name = true)
  • Type sniffing can disagree across files. The reader infers a column's type from a sample; a column that is numeric everywhere but holds a stray 'NULL' string in one file fails the whole read with a conversion error, and a cast in the SELECT list comes too late — the read itself errors. Read every column as text and cast in SQL instead:

    sql
    SELECT try_cast(amount AS DOUBLE) AS amount
    FROM read_csv('data/*.csv', union_by_name = true, all_varchar = true)

    union_by_name = true also tolerates files whose column sets differ.

  • The string 'NULL' is not NULL. A literal 'NULL' (or 'N/A', 'null') in a CSV is ordinary text, so WHERE amount IS NOT NULL does not filter it. Use NULLIF(amount, 'NULL'), or WHERE amount <> 'NULL'.

The two fixes compose: try_cast(NULLIF(amount, 'NULL') AS DOUBLE).

Database files ​

DuckLocal has one main database at a time: in memory, or a DuckDB file you opened. Data files become views in the main database. You can also attach other databases beside it.

Any path that exists and is not a data file is opened as the main database instead of being attached as a view. The extension does not matter: a .db, .duckdb, or even a .md path is treated as a database, and DuckDB decides whether it can actually be opened.

A few consequences are worth knowing:

  • A new database comes only from the Open data dialog. Type a path that ends in .duckdb and does not exist yet, and DuckLocal creates the database, along with any missing parent folders. Everywhere else — the command line, the pickers, a drop, Finder — a path that does not exist is reported as File not found.
  • Opening a database replaces the main one. Your registered files and attached databases are attached again into the new connection. S3 credentials are not; configure S3 again.
  • If the file cannot be opened, DuckLocal tells you why (below). At launch it falls back to an in-memory workspace rather than failing to start. While it is running, the workspace you had stays as it was.
  • A file that is already attached beside the main database is detached first, then opened as the main database. If that open fails, the attachment comes back. Opening the file that is already the main database reuses it.

Attaching data files while a database file is open writes those views into that database file — they persist there, not merely in the session.

When a database will not open ​

When DuckDB refuses a file, DuckLocal reads the file's first bytes itself and, where they explain the failure, puts a reason and a way out above DuckDB's own message. DuckDB's message stays underneath for the exact error. The explanation is in the interface language.

What the file isWhat DuckLocal tells you
A DuckDB file written by a newer DuckDB than the one built into DuckLocalBoth versions, and SQL to run in the newer DuckDB that copies the file into a format DuckLocal reads (below)
A DuckDB file from v0.9 or earlierRun EXPORT DATABASE 'dir' with the DuckDB that wrote it, then IMPORT DATABASE 'dir' in DuckLocal
A SQLite databaseIt is not a DuckDB file; attach it instead — with Attach database…, or INSTALL sqlite; ATTACH '…' (TYPE sqlite);
Something else that is not a DuckDB fileOpen CSV, Parquet, JSON or Excel files as data files instead

For a file from a newer DuckDB, the message fills in the file's path and DuckLocal's DuckDB version. Run it in the DuckDB that created the file, then open compat.duckdb:

sql
ATTACH '/path/to/file.duckdb' AS src (READ_ONLY);
ATTACH 'compat.duckdb' AS dst (STORAGE_VERSION 'v2.0.0');
COPY FROM DATABASE src TO dst;

Files written before DuckDB v1.2 do not record which release created them. Any other failure — a file another program has locked, one you may not read — shows DuckDB's message alone. See Troubleshooting.

Which DuckDB can read a database ​

When a database file is open, the status bar shows its path. Hover over it to see the file's storage format — the oldest DuckDB that reads it, such as Storage format: readable by DuckDB v1.0.0+ — and, when the file records it, the release that created it (Created by DuckDB v1.4.3). Check this before you hand a file to someone on an older DuckDB.

DuckLocal runs DuckDB 2.0, but a database file it creates is written in the format DuckDB 1.x wrote (readable by DuckDB v1.0.0+), so the duckdb CLI, Python or DBeaver on DuckDB 1.x still open it. A file another program wrote in DuckDB 2.0's own format (v2.0.0+) opens too, and keeps that format: 1.x cannot read it.

Attached databases ​

Attach another DuckDB or SQLite file beside the main database to query across them — a snapshot next to today's data, say.

  1. Open the Open menu in the title bar and choose Attach database….
  2. Type the path, or click Browse to pick the file.
  3. Leave Read-only checked unless you mean to change the file. It is checked by default.
  4. Click Attach.

The database gets an alias from its file name, and you query its tables as alias.table (or alias.schema.table):

sql
-- snap-2026-10-01.duckdb attached as snap_2026_10_01
SELECT t.id, t.amount, s.amount AS amount_last_week
FROM orders t
JOIN snap_2026_10_01.orders s USING (id);

How the alias is made:

  • The file stem, with every character that is not an ASCII letter or digit replaced by _.
  • db_ in front when it does not start with a letter: 2026-10 logs.duckdb becomes db_2026_10_logs.
  • A counter when the name is taken: snap, then snap_2. main is never used.
  • Attaching a file again later gives it back the alias it had, so SQL written against it keeps working.

Details:

  • SQLite files are recognized by their contents and attached through DuckDB's sqlite extension.
  • Unchecking Read-only attaches the file for writing. A DuckDB path that does not exist is then created as a new, empty database; read-only, the attach fails instead.
  • The same file is never attached twice. Attaching a file that is already open — as the main database or as an attachment — says This database is already open, as ….

In the sidebar ​

Every database appears in the schema tree, the main one and attached ones alike, empty ones included. A database shows read-only or sqlite beside its name where that applies, and its file as a tooltip.

Right-click a database for:

  • Use as default database — runs USE, so table names without a database resolve there. The status bar shows where they resolve now.
  • Detach database — detaches it and forgets it, so it does not come back next time. If it was the default database, the session moves back to the main database first. The file itself is untouched.

The rest of that menu is described in Schema browser and history.

Kept across connections ​

Attached databases are stored in DuckLocal's own history database, with their alias and read-only setting, and attached again into every new connection: on the next launch, after you open another database, and after New in-memory database.

  • A database whose file is missing is skipped quietly. Its registration is kept, so it comes back once the file does — DuckLocal never creates an empty database in its place.
  • A database that is already open, as the main database or otherwise, is not attached a second time.
  • If the new main database already has a database by that alias, DuckLocal says Could not attach …: the name … is taken in the open database.

Open from Finder ​

On a Mac, Finder can hand files to DuckLocal directly:

FileDouble-clickOpen With, or drop on the Dock icon
.parquetOpens in DuckLocalYes
.duckdb, .ddbOpens in DuckLocalYes
.dashOpens in DuckLocalYes
.csv, .tsv, .json, .jsonl, .ndjson, .xlsx, .xlsStays with the app that already opens them (Numbers, Excel, a text editor)Yes

DuckLocal registers itself as the default only for formats nothing else on a Mac claims. If another app already took over Parquet or DuckDB files, choose DuckLocal under Open With — or use Finder's Get Info → Open with → Change All… to make it the default.

A file from Finder is handled exactly like a drop on the window: a data file becomes a view in the current workspace, a database file is opened as the main database, and a .dash file opens as a dashboard tab. If DuckLocal is not running, it launches first and opens the file once its own startup has finished.

Quick Look previews ​

On a Mac, select a data file in Finder and press Space to see its columns and first rows without opening anything. The preview ships inside DuckLocal.app; there is nothing to install separately. It needs macOS 12 or later, like the app.

FileWhat the preview shows
.parquetColumns and types, the first 50 rows, and from the file's footer: row count, row groups, compression, and the writer
.duckdb, .ddbThe number of tables, the storage format (Readable by DuckDB …), and the five largest tables, each with its estimated row count and first 50 rows
.csv, .tsvColumns with their sniffed types, and the first 50 rows — a table instead of raw text
.jsonl, .ndjsonColumns and the first 50 rows

Plain .json and Excel files are left to macOS's own previews.

What the preview does and does not do:

  • It never writes. A database is attached read-only.
  • It never scans a whole file: rows are a LIMIT, and a row count appears only where the format stores one (Parquet's footer, a database's estimate).
  • It downloads nothing: DuckDB extensions are neither installed nor loaded.
  • Cells longer than 200 characters are cut off. The page text is in English.
  • A file it cannot read gets a page saying why.

If pressing Space shows no DuckLocal preview:

  1. Make sure DuckLocal is in Applications, and launch it once. macOS registers the preview when it first sees the app.
  2. Reset Quick Look in Terminal: qlmanage -r. Then try again.
  3. Check that DuckLocal Preview is turned on under Quick Look in System Settings' extension settings.

Naming ​

A file's view is named after its file stem, cleaned up into a valid identifier. When that name is already taken in the connection, DuckLocal appends a counter: events, then events_2, events_3. Two files with the same stem in different directories therefore get distinct views.

Duplicate detection is by the path text as written, so ./a.csv and a.csv are treated as two different files.

Limits ​

  • 256 data files per open request. The cap covers all the paths in one request together, not each folder separately — it exists so that dropping a home directory by accident does not tie up the connection for minutes. When the cap is hit, DuckLocal says so and attaches the first 256 files.
  • Files re-attached at startup from the previous session are not subject to that cap.

Managing registered sources ​

Registered files appear in the sidebar's Local files group, each with its view name, file name, and row count. Selecting a file generates a SELECT statement for it.

The × on a file row removes it, after a confirmation dialog. Removal drops the view and forgets the registration; the underlying file is untouched.

Refresh schema reloads the catalog and the registered files.

What is remembered ​

The list of registered files — their paths, view names, and kinds — is stored in DuckLocal's own history database and re-attached on the next launch, so the workspace you left is the workspace you get back. So are attached databases. The main database file is not remembered: each launch starts in memory unless you name a database.

See Settings and app data for the on-disk locations.

Released under the Apache-2.0 License.