# Load steps
URL: https://lsd.tools/docs/tools-load
Updated: 2026-07-17

> Decorate a function with @tool.load to make a node that produces data. It takes no Table ports (it is the source of data). Add a Source parameter to let the user pick files or a database; add outputs=[...] to emit several tables at once.

A **load step** is where data enters a pipeline. It produces a [`Table`](/docs/api-table) and takes
**no Table input ports** — it is the source, so there is nothing upstream to read.

## Usage

The smallest load step builds a table from its parameters:

```python
from lsdtools import Tool, Table

tool = Tool("demo")

@tool.load
def rows(n: int = 3) -> Table:
    """Emit a tiny table of n rows."""
    return Table({"i": list(range(n)), "sq": [i * i for i in range(n)]})
```

`n: int` becomes a number field on the node. Run it → a 2-column table. `# → i:[0,1,2] sq:[0,1,4]`

## Reading files with `Source`

To let the user pick real data, add a parameter annotated [`Source`](/docs/api-source). LSD injects a
reader bound to whatever the user selects (a file, a folder + glob, or a SQL query); call
`source.read()` to get a `Table`:

```python
from lsdtools import Tool, Source, Table

tool = Tool("io")

@tool.load(source_kinds=("file", "sql"))
def load_data(src: Source) -> Table:
    """Load a CSV/Parquet file or a SQL query the user picks."""
    return src.read()
```

`source_kinds` narrows the picker (default is file). A `Source` parameter is only valid on
`@tool.load` — using it on a shape/deliver step raises at import time.

## Emitting several tables

Pass `outputs=[...]` to produce named output ports; return a dict. Downstream nodes wire to a
specific port with `step.output("name")`:

```python
@tool.load(outputs=["nodes", "edges"])
def load_graph(src: Source) -> dict:
    tbl = src.read()
    return {"nodes": nodes_of(tbl), "edges": edges_of(tbl)}
```

## Parameters vs ports — the rule

Inside any step signature:

- a **`Table`** annotation → an **input port** (load steps may not have these);
- a **`Source`** annotation → an injected reader (load only);
- a **`Context`** / **`StepContext`** annotation → injected capabilities, excluded from the cache key;
- **anything else** → a typed **parameter** (a form field; no default ⇒ required).

These are checked at **import time**, so mistakes surface when you load the package, never mid-click.

## Related

**Learn** — [Shape steps](/docs/tools-shape) · [Deliver steps](/docs/tools-deliver) · [Params vs ports](/docs/tools-params-ports)

**API** — [Tool](/docs/api-tool) · [Source](/docs/api-source) · [Table](/docs/api-table)

**Examples** — [File loader](/docs/ex-file-loader) · [Multi-output mesh](/docs/ex-multi-output)

## FAQ
### Why can't a load step take a Table input?

A load step is the START of a pipeline — it produces the first Table, so it has nothing upstream to read. Passing a Table parameter to @tool.load raises an error at import time, not at click time.

### How do I let the user choose a file?

Add a parameter annotated Source. LSD injects a reader bound to the file/folder/query the user picks in the node's form, and source.read() gives you a Table.
