dlt 1.32.0 Release Notes
Breaking Changes
- Naive scd2 timestamps are no longer read as UTC (#4547 @rudolfix)
- A naive
boundary_timestamporactive_record_timestampis sent as it is, so the database session timezone reads it. A naiveactive_record_timestampmay stop matching existing active records on servers whose session timezone is not UTC. - On destinations without timezone support, scd2 timestamps are naive UTC instead of the wall clock of the context timezone. Stored values change only if you set a non-UTC context timezone.
- A naive
refresh="drop_resources",pipeline.drop()anddlt pipeline dropattempt to drop all tables belonging to resources (#4582 @rudolfix) — They drop every table of the dropped resources that exists in the destination. Before, only tables that were marked as saw data were dropped.- Arrow extract shares the resolved schema contract per table (#4582 @rudolfix) — Two resources with different
schema_contractthat write the same table share the contract resolved for the first one, as object items always did. - jira and salesforce verified sources are rewritten from scratch (dlt-hub/verified-sources#717, dlt-hub/verified-sources#718 @rudolfix) — If you use the old sources (versions before 1.0.0), keep using them or refresh the pipeline.
Other Behavioral Changes
- scd2
boundary_timestampapplies only to the load that declares it (#4547 @rudolfix) — The boundary is stored in the load package, not in the schema. dlt no longer writes or reads thex-boundary-timestamptable hint, and a leftover hint in an existing schema is ignored. This is actually a bugfix -boundary_timestampshould be ephemeral. - Load package state and trace shape (#4547 @rudolfix)
nested_hintsof a resource are a list in the trace, each entry with itspathas a list of identifiers, instead of a dict keyed by path. Each entry carriesload_id,extract_idxandresource_name, and nested hintcolumnsare YAML text. A trace loaded to a destination puts them in..._hints__nested_hintsand..._hints__nested_hints__path.
- Relation schema raises when lineage fails (#4538 @rudolfix)
Relation.columns,columns_schemaandschemaraiseLineageFailedExceptionwhen a column cannot be resolved (an unqualified column of a table outside the dataset schema,*over such a table, a non-SELECT query). Before, they returned a guessed or empty schema while execution failed anyway. Raw query relations and relations with_allow_partial_lineagereturn columns without data type instead.dlt.dataset.lineage.compute_columns_schemano longer acceptsinfer_sqlglot_schemaandallow_anonymous_columns.- Relations derived from a raw query relation (ie.
.limit()) execute as raw query. Before, they were qualified and bound. - On sqlglot < 27.14, a
NULLliteral column has no data type (wastext), like on newer sqlglot.
add_limiton transformers and unselected parents (#4582 @rudolfix) — A limit on a transformer is set on the resource at the top of its chain, so that resource and all its transformers get the limited pages.source.add_limit()also limits parents that are not selected, for example thefilesystemresource behindreaders()or a REST API parent withselected: False. Before, a limit on a transformer had no effect and unselected parents ran unlimited.sql_table(defer_table_reflect=True).add_limit(n)loadsnpages instead ofn - 1.- Traces written by 1.32 cannot be read by older dlt (#4582 @rudolfix) — Older versions report no last trace. 1.32 reads older traces, with file counts and load sizes shown as unknown.
Highlights
- Placeholders in merge conditions and partition pruning on nested tables (#4547 @rudolfix)
source_filteranddestination_scopeaccept value placeholders:{load_id},{table_name},{load_package_timestamp},{boundary_timestamp},{curr_date}, date parts ({YYYY},{MM}, ...) and your ownextra_placeholders. These can be constants, or functions called after extract with the resource and the table name.- Values are stored in the load package, not in the schema, so a partition that changes on every load no longer creates a new schema version.
- Values render as escaped destination literals. A list expands for
IN, e.g.event_day IN {days}, so you can compute the partitions to replace from the extracted data. - A nested table with its own
destination_scopeinnested_hintsdeletes by that scope instead of a subquery on the root table, so its partitions can be pruned. With every nested table scoped,root_keycan be disabled. - New cookbook page: "Prune BigQuery partitions when merging data".
- Faster extract, balanced normalize and
trace metrics(#4582 @rudolfix)dlt pipeline <name> trace metricsshows files, rows and sizes of each step of the last run: by resource and table for extract, by table for normalize, and files, sizes, job time and failed jobs by table for load.- Normalize gives each worker a contiguous group of files with a similar total uncompressed size. It used to split by file count, so one worker could end up with all the files of a large table.
- Extract warns when the files of a load package would leave at least half of the normalize workers idle, and tells you which
file_max_items/file_max_bytesto set. - Arrow extract caches the computed table. Extract of small arrow items is up to ~3x faster.
replacecan keep the resource state (#4575 @rudolfix) — The dict form{"disposition": "replace"}keeps the resource state by default, so incremental cursors survive a full table replace. Setexclude_resource_state: Falseto reset the state like the"replace"shorthand does. Closes the long-standing #2252.- dbt runner: microbatch intervals, load id and row counts (#4562 @rudolfix)
run_all()andrun()acceptinterval, e.g.dlt.current.interval(), and pass it to dbt as--event-time-start/--event-time-end.load_idis passed to the package as a variable, so models can write it to_dlt_load_id.- Results carry row counts.
- salesforce: load any sObject with
salesforce_table(dlt-hub/verified-sources#717, dlt-hub/verified-sources#725 @rudolfix)salesforce_table(sobject=...)loads any sObject, standard or custom, likesql_tabledoes for tables.salesforce_sourcekeeps its 15 resources and table names and runs them in parallel.- Bulk API 2.0 query jobs replace Bulk API 1.0. sObjects that the Bulk API does not support are read with the REST API (
api="auto" | "bulk" | "rest"). - Column types come from the sObject describe: decimals with precision and scale for currency,
date,time,timestamp,bigintandjson. incremental="auto"picks the cursor per sObject,start_datesets its initial value, and anydlt.sources.incrementalis translated to SOQLWHERE/ORDER BY.add_limitis pushed down as SOQLLIMIT.- Deleted records are read with
queryAlland removed from the destination (include_deleted). pyarrowandpythonbackends.
- jira: incremental issues with comments, changelogs and worklogs (dlt-hub/verified-sources#718 @rudolfix)
issuesload incrementally fromstart_dateand merge onid. Pages of issues are bulk fetched in parallel.- Every field is a top level column typed from
/field.included_columnsandexcluded_columnsselect fields. - New resources:
issue_comments,issue_changelogs,worklogsandissue_fields.
- pg_replication: reliable runs, direct snapshots and an arrow backend (dlt-hub/verified-sources#719, dlt-hub/verified-sources#722 @rudolfix)
- Changes stream over one connection per run, and a run stops at the WAL position flushed when it started. The position is stored with every batch, so runs bounded with
add_limitmake progress. init_replication(initial_snapshot="direct")reads the tables withsql_tablein the snapshot exported by the slot, without copies."unlogged_table"makes unlogged copies.backend="pyarrow"decodes inserts column-wise: 173k rows/s against 23k before, for 500k rows from Postgres 17 intofilesystem.- The snapshot and replicated changes load the same column types, and columns loaded before keep their type.
- A role with only REPLICATION and SELECT can replicate tables it does not own.
hard_delete=Falsekeeps deleted rows with_dlt_deleted_tsset.
- Changes stream over one connection per run, and a run stops at the WAL position flushed when it started. The position is stored with every batch, so runs bounded with
Core Library
- Merge condition placeholders and nested partition pruning: see Highlights (#4547 @rudolfix)
- New
propagate_to_nestedmap step and merge staging options (#4547 @rudolfix)propagate_to_nestedindlt.sources.helpers.transformcopies root keys into nested lists of dicts, with a nesting cutoff.load.truncate_staging_merge_tables_before_loadkeeps the staging tables ofmergetables between loads.- Fixes: with a
source_filter, dedup ranks only the merge source; the trace serializes to JSON whennested_hintsuse tuple paths; themergewarning on a nested table no longer fires when the root uses a merge dict.
trace metrics, balanced normalize and faster arrow extract: see Highlights (#4582 @rudolfix)replacekeeps resource state: see Highlights (#4575 @rudolfix).replaceon filesystem also deletes files in parallel.- dbt runner context: see Highlights (#4562 @rudolfix)
add_limiton transformers and on unselected parents (#4582 @rudolfix) — Derived REST API endpoints and filesystem readers can now be limited. dlt warns when this replaces a different limit.table_namefunction dispatches arrow, pandas and polars items (#4582 @rudolfix)- Unified lineage computation (#4538 @rudolfix) — Relation columns and query binding use the same options.
_allow_partial_lineageworks. Raw query relations compute partial lineage forcolumns/schema. dlt warns when untyped columns are dropped in the normalizer. Supports sqlglot >= 30.22. - connectorx 0.4.6 for decimals (#4567 @rudolfix) — dlt warns when connectorx rescales decimals in a way that loses precision. MSSQL
MONEYis handled. - Default code input for resources without recorded inputs (#4609 @sh-rp) — Resources that record no input with
add_input()get acodeinput pointing at their source or resource function, so custom sources show an upstream in workspace lineage. Also fixes inputs lost when a resource is renamed or cloned, e.g.sql_table(...).with_name(...). - Fix: compound SCD2 merge keys are matched exactly (#4245 @mattfaltyn) — Keys were concatenated, so
("a", "bc")and("ab", "c")collided and retired an unrelated active record. - Fix: existing tables get
ALTERwhen a new schema hash is first stored (#4542 @raunak2007) — A column added to a table without data in that load was never applied, and later loads failed with a missing column. - Fix: changing a column data type drops the old type modifiers (#4467 @developer-rpai) —
precision,scale,timezoneandx-nested-typeof the previous type no longer stay in the schema. FixesTypeError: not all arguments convertedon SnowflakeVARCHARafterdecimal->text. - Fix: descending incremental keeps extracting after a deduplicated boundary row (#4525 @bunnysayzz) — New rows with the same cursor value on later pages were skipped.
- Fix: incremental step keeps its position when hints are re-applied or the resource is cloned (#4540 @rudolfix) — Steps added with
insert_atafter theIncrementalstep were moved before it inpipeline.run(). - Fix:
where()edge cases onRelation(#4570 @rudolfix) — Filters on aggregating, grouping,DISTINCTand window relations wrap the query instead of producing invalid SQL. Filters on aliases that shadow a source column filter the alias. Typedwhere()on a column without data type no longer raisesKeyError. Explicit joins work on table functions. - Fix: iceberg tables with nested columns can be partitioned (#4558 @nanjeshramesh)
- Fix: Oracle
TIMESTAMP WITH LOCAL TIME ZONEmaps totimezone: true(#4343 @Aryan-Pardeshi) - Fix: filesystem
destination_fingerprintusesscheme://netlocof the bucket url (#4576 @rudolfix) — Fingerprints change for buckets whose name repeats in the path (ie.s3://data/data), bucket roots with a trailing slash, urls with a query string or inline password, upper case schemes andhf://namespaces.s3://bucketands3://bucket/pathdo not change. - Fix: workspace launchers set a lower bound on every dependency they add (#4521 @tetelio) — Without bounds,
s3fscould resolve to 0.4.2 from 2020. - Fix: release fixes collected in the version bump (#4582 @rudolfix)
- extract: contract filters of the previous arrow item no longer apply to the next one, so
discard_rowdoes not raiseKeyErroranddata_typecontracts keep valid values. - extract:
add_limitno longer counts hints-only items as pages (sql_table(defer_table_reflect=True).add_limit(1)loaded no rows). - extract:
dlt.transformeraccepts the dict form ofwrite_dispositionin its type hints. - pipeline: refresh with
drop_sourcesordrop_resourcesrecreates dropped tables from the import schema. Importing a schema keeps the normalizer hints of existing tables. - destinations: the nested destination scope is verified on the prepared root table. Delta merge checks ignore nested tables without data.
- workspace: the entry point
profileis active before launchers resolve config, the workspace.gitignoreno longer ignores lock files, and agent loops recognize retried tool calls on newer pydantic-ai.
- extract: contract filters of the previous arrow item no longer apply to the next one, so
Verified Sources
- salesforce: load any sObject with
salesforce_table: see Highlights (dlt-hub/verified-sources#717 @rudolfix) - salesforce:
start_datefor incremental loading (dlt-hub/verified-sources#725 @rudolfix) — Also fixes changes skipped during long jobs and very large fields and records. - jira: incremental issues with comments, changelogs and worklogs: see Highlights (dlt-hub/verified-sources#718 @rudolfix)
- jira: workflows use the Jira Cloud v3 endpoint (dlt-hub/verified-sources#714 @lis365b) — Jira removed the old endpoint on June 1, 2026.
- pg_replication: reliable runs, direct snapshots and an arrow backend: see Highlights (dlt-hub/verified-sources#719, dlt-hub/verified-sources#722 @rudolfix). Includes fixes from @fengjikui, @mskrip, @jkasireddy and @developer-rpai.
- Fix: jira
replacereloads all issues (dlt-hub/verified-sources#720 @rudolfix) — Issues were lost from the second run on.
Docs
- Parallel loads to one dataset from multi-tenant pipelines (#4536 @rudolfix). Also fixes ClickHouse to derive the primary key from the sort key when no primary key hint is set.
- Adding the dlt root key to existing nested tables (#4569 @rudolfix)
- Release highlights page for dlt 1.31 (#4533 @dlt-oss-docs-agent)
- Generated dltHub CLI, REST API and Python SDK references (#4550 @sh-rp)
- Syntax highlighting for CLI, tree and endpoint code blocks, and a light-mode code theme (#4616 @sh-rp)
- Docs search moved from Algolia to
pagefind(#4515 @zilto) - Background agents marked as public preview, with updated setup dependencies (#4522 @lis365b)
- dltHub section in the README (#4593 @ShreyasGS)
- Links to dltHub product pages from the dltHub docs pages (#4612 @njaltran)
- Cross-links between related destination and source pages (#4492), distinct page titles (#4493), "Go further" links on the most-linked pages (#4494), direct
/contextlinks and aligned source counts (#4491) (@martinibach) - Docs worker: single permanent redirects and real 404s (#4489 @martinibach); request logs exported to the
traffic-logsdataset (#4561 @djudjuu)
Chores
- Rename "workbench" to "dltHub AI Harness" in CLI text and docs (#4563 @AstrakhantsevaAA)
New Contributors
- @Aryan-Pardeshi made their first contribution in #4343
- @developer-rpai made their first contribution in #4467
- @raunak2007 made their first contribution in #4542
- @nanjeshramesh made their first contribution in #4558
- @lis365b made their first contribution in dlt-hub/verified-sources#714