One dropped file becomes a table before you write a single query
Dropping a .json, .ndjson, .jsonl, .csv, .tsv, or .parquet file registers its raw bytes into a DuckDB instance running as WebAssembly inside a dedicated Web Worker, then runs one statement — CREATE TABLE data AS SELECT * FROM read_json_auto('yourfile') (or read_csv_auto, or read_parquet, chosen from the file's extension) — and that table, named data, is what every query runs against. A .tsv file runs through the same CSV reader with the separator forced to a tab.
The reader infers column names and types from a sample of the file in that same statement, and the file's bytes stay inside the browser tab's own memory the entire time. The DuckDB WASM engine is fetched once from a CDN on first load — that is the query engine, not your data; your uploaded file is never sent anywhere over the network.
Schema inference, and the editor built for when it guesses wrong
DESCRIBE data, run automatically after ingestion, populates the Schema panel: nested values come back as STRUCT, MAP, LIST, or UNION, and a list of structs reports as LIST rather than STRUCT so it is not mistaken for a plain nested object.
When inference calls a column the wrong type — a numeric-looking field that is actually mixed, or a date format DuckDB does not recognize — ingestion fails, and this tool parses the raw DuckDB error to extract the offending column and opens the Schema Editor with that field highlighted. From there you can override individual columns' types, adjust the sample size DuckDB reads before guessing, turn on union_by_name to merge column sets across an NDJSON file with inconsistent line shapes, or raise the maximum nesting depth — options scoped to JSON only, so the editor offers only what your file's format supports. Applying an override re-reads the whole file with the corrected schema in the same CREATE TABLE statement, not a cast layered on afterward.
Querying nested JSON: dot notation, list indexing, and when neither works
A plain STRUCT column reads with ordinary dot notation — address.city — because DuckDB treats a nested object as a first-class row type. A list of structs is different: it is a LIST, not a STRUCT, so dot notation fails, and the fix is indexing first — orders[1].total reads the first element's total (DuckDB lists are 1-indexed, not 0-indexed). A column left as raw JSON rather than exploded into STRUCT or LIST needs json_extract(column, '$.field') or json_extract_string(column, '$.field') instead.
Getting this wrong produces a real DuckDB error — "Cannot extract field," "not a struct," and similar — and this tool watches for exactly those shapes, plus a bare "column not found" case, and prints the matching tip under the query editor rather than leaving you to work out which access pattern applies from the raw error alone.
The data profile: a full-table SUMMARIZE you can cancel mid-scan
The Profile view runs DuckDB's own SUMMARIZE over the active table in one pass: per-column min, max, an approximate distinct-value count (a HyperLogLog estimate, not exact, so it stays cheap on a huge column), mean, standard deviation, the three quartiles, and a null percentage, each shown as a colored density bar so a fully-null or single-valued column stands out at a glance.
SUMMARIZE can refuse a nested STRUCT, MAP, LIST, or UNION column on some DuckDB builds; when that happens this tool retries with only the scalar columns projected in and reports which columns it skipped, rather than dropping them silently or failing the whole profile. It runs through the same cancellable path as a query, so a profile started on a huge table by mistake can be stopped mid-scan.
Focus Datasets: materializing a result and freeing the original file's memory
Extract turns whatever query produced the rows on screen into a brand-new named table — a genuine CREATE TABLE ... AS (your query), not a saved filter — so a working subset of a huge file becomes its own lightweight table. Extractions chain: extracting from a subset creates a second subset built on the first, and every subset from the session is dropped together the moment you load a new file.
Because WASM memory can only grow, extracting a subset optionally offers to release the original file's table and its registered bytes from memory — worth doing once you have narrowed a multi-hundred-megabyte file down to what you need, since the original's memory otherwise sits held for the rest of the session.
A worked query, and the real size limits behind "massive"
Say the file is NDJSON where each line looks like {"user": {"name": "Ana", "tier": "pro"}, "amount": 42.5}. DESCRIBE data reports user as a STRUCT and amount as a DOUBLE. SELECT user.name, amount FROM data WHERE user.tier = 'pro' AND amount > 20 ORDER BY amount DESC runs the dot-notation path directly, no json_extract needed, because the reader already exploded that object into a real STRUCT column.
Two hard limits define "massive" here: a file over 1GB is rejected by a size check before it reaches DuckDB, with a message to split or pre-filter it; and 50,000+ result rows trigger a dialog to render everything or cap the grid at 10,000, since rendering — not the SQL engine — is what strains the tab. Sharing a query shares a URL, not the data: the SQL text is compressed into the query string, so a shared link parses to an empty result until you drop the same file back in yourself.