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:
- Paths on the command line —
ducklocal ./logs/ billing.parquet - 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
- Dropping files or folders anywhere on the window
- 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.
| Extension | How it is read |
|---|---|
.csv, .tsv, .txt | read_csv_auto view |
.parquet | read_parquet view |
.json, .ndjson, .jsonl | read_json_auto view |
.xlsx, .xls, .xlsb, .ods | imported 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:
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:
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 theSELECTlist comes too late — the read itself errors. Read every column as text and cast in SQL instead:sqlSELECT try_cast(amount AS DOUBLE) AS amount FROM read_csv('data/*.csv', union_by_name = true, all_varchar = true)union_by_name = truealso tolerates files whose column sets differ.The string
'NULL'is notNULL. A literal'NULL'(or'N/A','null') in a CSV is ordinary text, soWHERE amount IS NOT NULLdoes not filter it. UseNULLIF(amount, 'NULL'), orWHERE 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
.duckdband 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 asFile 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 is | What DuckLocal tells you |
|---|---|
| A DuckDB file written by a newer DuckDB than the one built into DuckLocal | Both 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 earlier | Run EXPORT DATABASE 'dir' with the DuckDB that wrote it, then IMPORT DATABASE 'dir' in DuckLocal |
| A SQLite database | It is not a DuckDB file; attach it instead — with Attach database…, or INSTALL sqlite; ATTACH '…' (TYPE sqlite); |
| Something else that is not a DuckDB file | Open 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:
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.
- Open the Open menu in the title bar and choose Attach database….
- Type the path, or click Browse to pick the file.
- Leave Read-only checked unless you mean to change the file. It is checked by default.
- Click Attach.
The database gets an alias from its file name, and you query its tables as alias.table (or alias.schema.table):
-- 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.duckdbbecomesdb_2026_10_logs.- A counter when the name is taken:
snap, thensnap_2.mainis 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
sqliteextension. - 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:
| File | Double-click | Open With, or drop on the Dock icon |
|---|---|---|
.parquet | Opens in DuckLocal | Yes |
.duckdb, .ddb | Opens in DuckLocal | Yes |
.dash | Opens in DuckLocal | Yes |
.csv, .tsv, .json, .jsonl, .ndjson, .xlsx, .xls | Stays 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.
| File | What the preview shows |
|---|---|
.parquet | Columns and types, the first 50 rows, and from the file's footer: row count, row groups, compression, and the writer |
.duckdb, .ddb | The 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, .tsv | Columns with their sniffed types, and the first 50 rows — a table instead of raw text |
.jsonl, .ndjson | Columns 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:
- Make sure DuckLocal is in Applications, and launch it once. macOS registers the preview when it first sees the app.
- Reset Quick Look in Terminal:
qlmanage -r. Then try again. - 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.
