DuckDB documents how to identify a database file and its storage version (the
Storage page), but not
the layout beyond the first bytes; that is defined by DuckDB’s source code,
storage_info.hpp
(read 2026-11-12), which declares the fields but not their exact serialised order.
Fields taken from the source are marked so.
Main header
| Offset | Size | Field |
|---|---|---|
0 | 8 | Checksum of the main header (docs). The source calls these 8 bytes the block header. |
8 | 4 | Magic 44 55 43 4B, DUCK. |
12 | 8 | Version number: the storage version (see versions), or 999 in files written in DuckDB 2.0’s format. |
| … | … | Source: four 8-byte flags (flag 1 = encrypted database), 32-byte library version text, 32-byte library source hash, and for encrypted files the encryption metadata (8 bytes: cipher at byte 2, encryption version at byte 3), a 16-byte database identifier and an encrypted 8-byte canary (DUCKKEY) with its 12-byte nonce and 16-byte tag. Their order is not stated in the header file. |
The documentation suggests hexdump -n 20 -C file.duckdb to see the
magic and version. The version in the header can be older than the format the file
has since been upgraded to; the reliable way is to attach the file read-only and
read the storage_version tag from duckdb_databases().
Database headers
From the source. Headers are 4,096 bytes (FILE_HEADER_SIZE), kept small
so they are written almost atomically. The main header comes first, then two
database headers. The iteration count rises by 1 at each checkpoint.
| Field (source order) | Type | Meaning |
|---|---|---|
iteration | uint64 | Checkpoint counter. |
meta_block | idx_t | Pointer to the first meta block. |
free_list | idx_t | Pointer to the block holding the free list. |
block_count | uint64 | Blocks in the file as of this header; blocks beyond are free. |
block_alloc_size | idx_t | Block allocation size: 16,384 to 262,144 bytes; 262,144 by default. |
vector_size | idx_t | The vector size of the file. |
storage_compatibility | StorageVersion | The storage compatibility version. |
Each block has an 8-byte header by default (larger for encrypted files: 40 bytes). Table data is split into row groups of 122,880 rows by default, each column of a row group stored in segments with its own compression.
Storage versions
| Storage version | DuckDB releases |
|---|---|
| 70 | 2.1 (in the source as V2_1_0; not yet in the documentation’s table) |
| 69 | 2.0 |
| 68 | 1.5 |
| 67 | 1.4 |
| 66 | 1.3 |
| 65 | 1.2 |
| 64 | 0.9, 0.10, 1.0, 1.1 |
| 51 | 0.8 |
| 43 | 0.7 |
| 39 | 0.6 |
| 38 | 0.5 |
| 33 | 0.3.3, 0.3.4, 0.4.0 |
| 31 | 0.3.2 |
| 27 | 0.3.1 |
| 25 | 0.3.0 |
| 21 | 0.2.9 |
| 18 | 0.2.8 |
| 17 | 0.2.7 |
| 15 | 0.2.6 |
| 13 | 0.2.5 |
| 11 | 0.2.4 |
| 6 | 0.2.3 |
| 4 | 0.2.2 |
| 1 | 0.2.1 and earlier |
From 0.10, newer releases aim to read files from older ones; reading newer files in
older releases is best effort only. Releases 1.0 to 1.5 write version 64 by default,
so that older releases can still read the files, unless a newer
STORAGE_VERSION (1.2 and later) or storage_compatibility_version
is chosen. DuckDB 2.0 writes version 69 by default, which releases before 2.0 cannot
open.
Column compression
The documentation lists the compression methods: constant encoding, run-length
encoding, bit packing, frame of reference, dictionary encoding, FSST, ALP, Chimp,
Patas and Zstd. Which are available depends on the storage version; DuckDB 2.0
enables DICT_FSST by default.
Write-ahead log
Changes since the last checkpoint are kept in a separate file named after the
database with .wal added. Its layout is internal and not documented.