# Develop a package
URL: https://lsd.tools/docs/pkg-develop
Updated: 2026-07-17

> Run `lsd package new <name>` to scaffold a package, then add it to the desktop by reference with "Add local…" so your working folder stays editable. Edit the tool, reload the package, and the nodes update — no reinstall.

The tight authoring loop is: **scaffold**, **add your folder by reference**, then **edit and
reload**. Nothing gets copied or reinstalled while you work.

## Scaffold

Generate a fresh package skeleton from the CLI:

```bash
lsd package new my-pkg
# → creates my-pkg/ with lsd_package.json + tool.py
```

The scaffold is a complete, working package — a manifest (schema v1) and a `tool.py` with a
`tool = Tool(...)` and one example step. The same scaffold is available in the desktop Package
Manager, which calls [`PackageManager.scaffold(name)`](/docs/api-package-manager).

## Add it by reference (editable)

In the desktop Package Manager, choose **Add local…** and point at your `my-pkg/` folder. This
registers the folder *by reference* — the code stays on disk where you are editing it, rather than
being copied into `~/.lsd/packages/`. Under the hood this is
[`PackageManager.add_local(source)`](/docs/api-package-manager).

Because the folder is editable, your changes are the source of truth: there is no reinstall step in
the loop.

## Edit, then reload

Edit `tool.py` — add a `@tool.shape` step, tweak a parameter, adjust a dialog — and **reload** the
package (toggle it off and on in the Package Manager, or restart the app). The palette and node
forms pick up the new code. This is the write → reload → see loop that makes package authoring fast;
see for the full workflow with an editor open.

```python
from lsdtools import Tool, Table

tool = Tool("my_pkg", label="My Package")

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

## Loose project scripts

For quick, project-local experiments you don't even need a package. A loose Python script dropped
inside a project folder is loaded automatically when the project opens — handy for one-off code that
belongs to a single project. When the code becomes something you want to reuse or share, promote it
into a manifest-backed package (a `lsd_package.json` plus your `tool.py`) so it can be installed,
versioned, and depended on. See [Package anatomy](/docs/pkg-anatomy).

## Related

**API** — [PackageManager](/docs/api-package-manager) · [CLI commands](/docs/api-cli) · [Tool](/docs/api-tool)

**Examples** — [A complete package](/docs/ex-full-package)

## FAQ
### How is "Add local…" different from installing?

Add local registers your folder by reference — the package stays where it is on disk and your edits are live. Installing copies a package into ~/.lsd/packages/ instead. Use Add local while you author.

### Do I need a package to try out a tool?

Not for throwaway code — a loose Python script inside a project folder is picked up automatically. Move it into a manifest-backed package once you want to reuse or ship it.
