dlt is a free, open source data warehousing & processing project written in Python and released under Apache-2.0. It has 5,871 GitHub stars, 605 forks and 434 open issues, and was last pushed 10 hours ago. On this registry it ranks #6 of 9 tracked projects in Data Warehousing & Processing, with 5 head-to-head comparisons available.

What is dlt?

dlt (data load tool) is an open-source Python library, licensed under Apache-2.0, that automates tedious data loading by moving data from messy, often unstructured sources into well-structured, typed datasets, and it is built for Python developers and data engineers who want to load data without adopting a platform.

What it is

dlt is a library, not a platform. It installs with pip install dlt into code that already exists, so a pipeline keeps the surrounding workflow and the other tools already in use. It lives in the Python data ecosystem alongside the warehouses and file stores it writes to, and it targets the extract-and-load half of ELT work: requests, pagination, schema inference, and typing are handled by the library rather than by hand. The project supports Python 3.10 through 3.14, with some optional extras not yet available for 3.14 and that version's support considered experimental.

The concrete problem it solves is the repetitive loading code that would otherwise be written for every new source and destination pair. A REST API is described declaratively through rest_api_source, a SQL database is reflected through dlt[sql_database], and any Python iterable becomes a table through a resource generator. Instead of a hosted black box, dlt produces human-readable file formats and schemas that can be inspected, with no hidden side effects. It replaces the bespoke loading scripts and the platform-shaped service a team would otherwise stand up around them.

Key capabilities

  • Declarative REST API extraction through dlt.sources.rest_api.rest_api_source, including paginators such as {"type": "cursor", "cursor_path": "next_cursor"} and processing_steps with filter, map, and flatten functions applied at the source.
  • Resources as plain generators, declared with the @dlt.resource decorator and parameters such as table_name, primary_key, and write_disposition="merge".
  • Destination extras installed on demand: dlt[duckdb], dlt[bigquery], plus snowflake, postgres, redshift, databricks, and athena.
  • Cloud filesystem support through dlt[s3], with gs and az as alternatives.
  • SQL database sources through dlt[sql_database], which reflect tables and types directly from the database.
  • Schema inference and column typing without a separate declaration step, and results read straight back as a DataFrame via pipeline.dataset().playlist_tracks.df().
  • Tooling aimed at LLMs and coding agents, including dlthub.com/context, an LLM-native workflow, and a workspace covering 5000+ sources, plus the dlt[hub] extra for data quality, transformations, and AI.

Who uses it and how

  • Developers running pipelines in a Google Colab notebook, a local laptop, or an AWS Lambda function, where a library drop-in suits a short-lived or interactive environment.
  • Teams that schedule loads inside an existing Airflow DAG rather than adopting a separate orchestrator.
  • Engineers pointing one pipeline definition at different destinations by changing the destination argument, for example destination="duckdb" for local work and a warehouse extra for production.
  • Analysts and developers who need typed data back in-process, reading a loaded table as a DataFrame without leaving Python.
  • AI coding agents and the developers driving them, using declarative primitives and dlthub.com/context to go from prompt to working pipeline.

Getting started

Install with pip install dlt, or uv add "dlt[duckdb]" for the uv workflow, and add the extra matching the source or destination, for example dlt[bigquery] or dlt[s3]. Docs and context live at https://dlthub.com/docs and https://dlthub.com/context.

How it compares

No list of paid products this project replaces is provided in the facts, and no similar loading tool is named alongside it, so dlt stands alone in this registry.

When to use it β€” and when not to

A self-hoster must operate the destination itself, whether that is a local DuckDB file or a warehouse reached with the relevant extra and its credentials, so the library removes platform overhead but not destination operations. It is a poor fit for anyone who wants a fully managed service, and the 3.14 extras gap plus 434 open issues are worth weighing against a project that is otherwise actively pushed as of September 2026. The README excerpt available here is truncated mid-sentence, so the hosted docs at dlthub.com/docs should be treated as the authoritative reference.

project readme (upstream, from github) β€” read inline

data load tool (dlt) β€” the open-source Python library that automates all your tedious data loading tasks

Be it a Google Colab notebook, AWS Lambda function, an Airflow DAG, your local laptop,
or an AI coding agentβ€”dlt can be dropped in anywhere.

πŸš€ Join our thriving community of likeminded developers and build the future together!

Installation

dlt supports Python 3.10 through Python 3.14. Note that some optional extras are not yet available for Python 3.14, so support for this version is considered experimental.

pip install dlt

Add the extras you need for your sources and destinations, for example:

pip install "dlt[duckdb]"        # local DuckDB destination
pip install "dlt[bigquery]"      # or snowflake, postgres, redshift, databricks, athena, ...
pip install "dlt[s3]"            # or gs, az for cloud filesystems
pip install "dlt[sql_database]"  # read from any SQL database
pip install "dlt[hub]"           # data quality, transformations, and AI (see below)

Prefer uv? uv add "dlt[duckdb]".

Quick Start

Describe an API declaratively and load it into DuckDB β€” dlt handles requests, pagination, schema inference, and typing for you:

import dlt
from dlt.sources.rest_api import rest_api_source

# 1. Describe the API declaratively
source = rest_api_source({
    "client": {"base_url": "https://api.spotify.com/v1"},
    "resources": [
        {
          "name": "playlist_tracks",
          "endpoint": {"path": "playlists/{playlist_id}/tracks"},
        },
    ],
})

# 2. Point a pipeline at any destination
pipeline = dlt.pipeline(
    pipeline_name="spotify",
    destination="duckdb",
    dataset_name="spotify_data",
)

# 3. Extract, normalize, and load
pipeline.run(source)

# 4. ...and read it straight back as a DataFrame
pipeline.dataset().playlist_tracks.df()

...or load any Python iterable β€” a resource is just a generator, and dlt infers the schema, types the columns, and writes the table:

import dlt

@dlt.resource(table_name="tracks", primary_key="id", write_disposition="merge")
def tracks():
    yield {"id": 1, "title": "Yellow",       "artist": "Coldplay",   "streams": 4_200_000_000}
    yield {"id": 2, "title": "Shape of You", "artist": "Ed Sheeran", "streams": 3_900_000_000}

dlt.pipeline(
    destination="duckdb",
    dataset_name="spotify_data",
).run(
  source=tracks(),
)

Check out a basic in Colab or a more advanced Hugging Face demo with Marimo notebooks.

Why dlt

dlt loads data from messy, often unstructured sources into well-structured, typed datasets. It's a library, not a platform β€” you pip install it into your existing code and keep your workflow and the other tools you already use. No black boxes: clean Pythonic interfaces, human-readable file formats, schemas you can inspect, no hidden side effects.

dlt and its docs are built from the ground up for LLMs and coding agents. Pair the typed, declarative primitives below with dlthub.com/context and the LLM-native workflow to go from prompt to working pipeline β€” across 5000+ sources β€” often in a single shot.

Extract from any source

REST APIs β€” describe the endpoints declaratively; filter, map, and flatten records right at the source (docs):

from dlt.sources.rest_api import rest_api_source

source = rest_api_source({
    "client": {
        "base_url": "https://api.spotify.com/v1",
        "paginator": {"type": "cursor", "cursor_path": "next_cursor"},
    },
    "resources": [
        {
            "name": "playlist_tracks",
            "endpoint": {"path": "playlists/{playlist_id}/tracks"},
            "processing_steps": [
                {"filter": lambda r: r["track"]["duration_ms"] > 0},
                {"map": flatten_track},
            ],
        },
    ],
})
def flatten_track(record: dict[str, Any]) -> dict[str, Any]:
    ...

SQL databases β€” reflect tables and types straight from the database (docs):

from dlt.sources.sql_database import sql_database

source = sql_database("mysql+pymysql://user:pass@host/spotify")

Files in any bucket β€” list, then parse CSV / JSONL / Parquet from local disk, S3, GCS, or Azure (docs):

from dlt.sources.filesystem import filesystem, read_csv_duckdb

source = (
    filesystem(
        bucket_url="s3://my-bucket/spotify",
        file_glob="tracks_*.csv",
    ) | read_csv_duckdb()
).with_name("tracks")

DataFrames & Arrow β€” pandas, Polars, and Arrow tables load directly; Arrow-backed frames move with zero copies:

import dlt
import pandas as pd

df = pd.DataFrame({
  "track": ["Yellow",        "Shape of You"],
  "streams": [4_200_000_000, 3_900_000_000],
})
dlt.pipeline(
    destination="duckdb",
    dataset_name="spotify_data",
).run(
    df,
    table_name="tracks",
)

See many more sources in the ecosystem.

Load to 20+ destinations β€” swap one string

The same resource runs anywhere. Change the destination string and dlt takes care of credentials, DDL in the target dialect, staging, and schema drift:

pipeline = dlt.pipeline(
    pipeline_name="spotify",
    destination="duckdb",         # β†’ snowflake, bigquery, postgres, redshift, databricks,
    dataset_name="spotify_data",  #   athena, clickhouse, motherduck, filesystem (S3/GCS/Azure),
)                                 #   iceberg, delta, ... and custom reverse-ETL destinations
pipeline.run(source)

dlt handles the parts you'd rather not:

  • Credentials β†’ secrets.toml / env vars, injected automatically
  • DDL β†’ CREATE TABLE in the target's dialect
  • Type mapping β†’ source types converted to the destination's types
  • Staging β†’ S3 / GCS for warehouses that need it
  • Schema drift β†’ ALTER TABLE on the fly

Browse all supported destinations, or build a custom one.

Declare intent with decorators

Decorators let you declare what you want β€” incremental loading, merge strategies, schema contracts, column hints β€” instead of hand-rolling it. Every knob can be overridden at runtime (docs):

import dlt

@dlt.resource(
    primary_key="id",
    write_disposition="merge",                        # upsert on the primary key
    columns={"artist": {"x-annotation-pii": False}},  # type and annotate columns
    schema_contract={"columns": "freeze"},            # reject unexpected columns
)
def tracks(
    updated_at=dlt.sources.incremental("updated_at"),  # load only new/changed rows
):
    yield from fetch_tracks(since=updated_at.last_value)


@dlt.source
def spotify(api_key: str = dlt.secrets.value):
    return tracks(), playlists()   # group one or more resources behind shared config/auth

Schema contracts enforce the shape at the gate, with three modes β€” evolve (accept and adapt the schema), freeze (reject the record), and discard (drop the offending row/column) β€” applied independently to tables, columns, and data_type. You also get schema inference, normalization of nested data, incremental loading, and [secrets & config injection](

readme truncated β€” read the full docs on github

Frequently asked questions

Is dlt free to use?

dlt is open source under the Apache-2.0 licence. There is no licence fee and no seat count β€” you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does dlt do?

data load tool (dlt) is an open source Python library that makes data loading easy πŸ› οΈ

What is dlt written in?

dlt is primarily written in Python. Its source is publicly available at https://github.com/dlt-hub/dlt, and it has 5,871 GitHub stars.