# Write your first tool
URL: https://lsd.tools/docs/start-first-tool
Updated: 2026-07-17

> Scaffold a package with lsd package new, or just drop a tool.py in a project folder. Decorate a function with @tool.shape and it appears in the add-node palette with a generated form, live caching, and an inspector. Editing the file hot-reloads the node.

A node is just a decorated Python function. Here is the whole thing.

## Scaffold a package

The quickest start is a scaffold — it writes a working `tool.py`, an `lsd_package.json`, and a
README:

```bash
lsd package new my-pkg
```

## Or write `tool.py` by hand

```python
# tool.py
from lsdtools import Tool, Table

tool = Tool("my", label="My Tool")

@tool.shape
def top(t: Table, limit: int = 10) -> Table:
    """Keep the highest-scoring rows."""
    return t.sort("score", descending=True).head(limit)
```

- `tool = Tool("my")` — one `Tool` per package, at module scope.
- `@tool.shape` — makes a *transform* node (`my.top`).
- `t: Table` — an **input port** (wire it from an upstream node).
- `limit: int = 10` — a **parameter**; it renders as a number field.

## See it on the canvas

In the desktop app, use the Package Manager's **["Add local…"](/docs/pkg-develop)** and pick the
folder. Your node appears in the add-node palette with a generated form. Edit `tool.py` and it
**hot-reloads** — the tight loop is: change the function, see the node update.

## Run it without the desktop

You don't even need the app to try it:

```python
from lsdtools import Engine, Table

data = Table({"name": ["a", "b", "c"], "score": [3, 9, 5]})
result = Engine().run(top(data, limit=2))
result.output.print()
# → name:["b","c"] score:[9,5]
```

## Next

- **[Where to go next](/docs/start-where-next)** — the map of these docs.
- Go deeper on the [Tool object](/docs/tools-tool-object) and [parameters vs ports](/docs/tools-params-ports).

## Related

**Learn** — [The Tool object](/docs/tools-tool-object) · [Load steps](/docs/tools-load)

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

**Examples** — [Your first tool](/docs/ex-hello-tool)

## FAQ
### Where does tool.py go?

Anywhere on your import path. In the desktop, use "Add local package…" to point at a folder — its steps hot-reload as you edit. For a shareable unit, scaffold a package with lsd package new.

### How does a function become a node?

The @tool.load/@tool.shape/@tool.deliver decorators register the function as a node type. A Table parameter becomes an input port; every other parameter becomes a form field.
