DuckDB 2.0.0 was released on 21 October 2026, and it is a long-term support release: the team will support it until at least October 2027. For most analysts, the SQL you wrote last week will still run. But a few changes can do two quiet kinds of damage: lock a colleague out of a shared database file, or turn a query that used to return NULL into one that stops with an error. This is a checklist for finding those before they find you.
If you read the preview here earlier this month, this is the follow-up it promised: the final release notes and the official migration guide are now out, so we can be specific.
First, a date to note
According to the DuckDB 2.0 announcement, version 1.4 is supported only until late November 2026 (the migration guide gives 17 November as its end of life), while 1.5 has been extended to March 2027. If you are on 1.4, you have a reason to move soon. If you are on 1.5, you have time to do it properly.
The decision that matters most: the file format
New database files created by 2.0 use a new storage format, storage version 69. Versions before 2.0 cannot read them. Opening one in 1.5.x fails with an IO error.
The good news is that compatibility runs one way. DuckDB 2.0 can read and write your existing files, and those files keep their old storage version, so older versions can still open them.
Think of it like a document saved in a new file format that a colleague's older software cannot open. The risk is not your laptop. It is the shared .duckdb file that a scheduled job, a teammate's notebook and a dashboard all touch, each running a different version.
If you need new files to stay readable by older versions, set the storage version explicitly. From the CLI:
duckdb my.db -storage-version v1.5.6 -c "CREATE TABLE t (x INT);"
There is also a storage_compatibility_version setting and a STORAGE_VERSION option on ATTACH. When everyone is ready to move to the new format, the guide's conversion route is export and import:
/older/duckdb mydata.old.db -c "EXPORT DATABASE 'tmp'"
/newer/duckdb mydata.new.db -c "IMPORT DATABASE 'tmp'"
Changes that can alter what your queries return
Integer division and modulo by zero now raise an error
This is the one most likely to surprise analysts. In 2.0, // and % by zero raise an error instead of returning NULL. Regular division with / still returns infinity.
Picture a weekly report that computes orders per active store with integer division. One region has zero active stores this week. Last month that row quietly showed NULL. Now the whole query fails. That is arguably better behaviour, because a silent NULL in a ratio is a classic way to get a misleading average, but it will break scheduled jobs.
You can wrap the risky expression:
SELECT TRY(orders // active_stores) AS orders_per_store
FROM weekly;
Or restore the old behaviour for all divisions while you audit:
SET error_on_division_by_zero = false;
I would use TRY on the specific expressions and fix the logic, rather than switching the check off everywhere. A denominator of zero usually means something about the data you want to know.
The old lambda arrow is off by default
If you use list functions, this one will hit you. The single-arrow form is disabled by default:
-- no longer works by default
SELECT list_transform([1, 2, 3], x -> x + 1);
-- use this
SELECT list_transform([1, 2, 3], lambda x : x + 1);
There is a temporary bridge, SET lambda_syntax = 'enable_single_arrow';, but the guide is clear that 2.1 removes both the setting and the old syntax. Fix it now rather than twice.
MERGE and UPDATE ... FROM with multiple matches
The announcement says MERGE and UPDATE ... FROM now have well-defined semantics when several source rows match one target row. If any pipeline relied on the old behaviour, even by accident, compare results before and after on a copy.
Changes that break connections and logging
S3 settings in URLs. Passing credentials or settings in the query part of an S3 URL is disabled by default, and that part is now treated as part of the object key. Use secrets instead. A legacy switch exists after loading
httpfs:SET s3_enable_deprecated_url_parameters = true;Removed settings.
enable_http_loggingandhttp_logging_outputare gone; useCALL enable_logging('HTTP');.custom_profiling_settingsis replaced bytracked_metrics.Identifier case.
preserve_identifier_caseis now a text setting.trueandfalsestill work, mapping to'preserve_case'and'lowercase'.
The new parser
DuckDB 2.0 replaces its PostgreSQL-derived parser with a new PEG-based one. It is designed to be compatible, and error messages now point to a more precise location in your SQL, which helps with long queries. If something that used to parse now fails, you can switch back while you report it:
INSTALL AND LOAD legacy_parser;
SET enable_legacy_parser = true;
A safe upgrade order
Pin your current version in your requirements file today, so nothing upgrades under you.
Create a separate environment for 2.0. On PyPI,
duckdb2.0.0 requires Python 3.11 or later, so check your Python version first.Search your code for
->inside list functions, for//and%, and for S3 paths with query strings.Run your real queries and notebooks against copies of your data, and diff the outputs, not just whether they run.
Agree as a team on the storage version for any shared file before anyone writes a new one.
If a pipeline is business-critical, the announcement itself suggests waiting for the first patch release, scheduled for November.
The headline features, such as the Quack client-server mode and asynchronous I/O for remote Parquet and CSV, are worth trying once you are settled. They are not reasons to rush. The file format and the two behaviour changes are the parts to plan for this week.



