# Package anatomy
URL: https://lsd.tools/docs/pkg-anatomy
Updated: 2026-07-18

> An LSD package is a directory with an lsd_package.json manifest (schema v1) and its code beside it. The manifest names the package, its version, dependencies, and the Tool to mount. The simplest package is a manifest plus a tool.py side by side.

A **package** is how LSD code ships and installs. It is just a folder with an `lsd_package.json`
**manifest** (schema version 1) at its root and the code beside it. The manifest names the package
and points at the one [`Tool`](/docs/api-tool) that mounts everything the package contributes.

## A minimal package

The smallest package is a manifest and a `tool.py` side by side:

```
my-pkg/
 lsd_package.json
 tool.py
```

```json
{
    "_schema_version": 1,
    "name": "lsd-pkg-hello",
    "display_name": "Hello",
    "version": "0.1.0",
    "description": "A tiny reference package.",
    "author": "LSD Project",
    "publisher": "lsd",
    "keywords": ["example", "template"],
    "min_lsd": "0.1.0",
    "dependencies": {},
    "tool": "tool"
}
```

`"tool": "tool"` tells the loader to read `getattr(module, "tool")` from the entry module — the
`tool = Tool("hello")` you declared at module scope. Mounting that Tool is the *single* way a
package contributes steps, commands, and views.

## The fields

| Field | Meaning |
|---|---|
| `_schema_version` | Always `1`. Identifies the manifest format. |
| `name` | Unique package id (pip-style, e.g. `lsd-gis`). |
| `display_name` | Human label shown in the Package Manager. |
| `version` | The package version (`X.Y.Z`). |
| `description` | One-line summary. |
| `author` | Who wrote it. |
| `publisher` | Publisher id (store namespace). |
| `keywords` | Search terms (a list of strings). |
| `min_lsd` | Minimum LSD version this package needs. |
| `dependencies` | `{ "other-pkg": ">=0.1.0" }` — see [Dependencies](/docs/pkg-dependencies). |
| `code` | *Optional.* Import-package subdirectory holding the code. Omit → code at the root. |
| `tool` | The `Tool` attribute to mount: `"tool"` or `"pkg.mod:tool"`. |

## Growing into a code directory

The `code` key is optional. When omitted, your code sits directly beside the manifest. As a package
grows, move the code into a named import-package subfolder and set `code` to it, so sibling packages
can `import` it under that canonical name:

```
lsd-gis/
 lsd_package.json # "code": "lsd_gis", "tool": "tool"
 lsd_gis/
 __init__.py
 tool.py
```

```json
{
    "_schema_version": 1,
    "name": "lsd-gis",
    "display_name": "GIS",
    "version": "0.4.0",
    "description": "Load and deliver spatial data",
    "author": "Acme Corp",
    "publisher": "acme",
    "keywords": ["geo", "spatial"],
    "min_lsd": "0.2.0",
    "dependencies": { "lsd-mim": ">=0.1.0" },
    "code": "lsd_gis",
    "tool": "tool"
}
```

## Where packages live

Installed packages land under `~/.lsd/packages/`, and the record of what is installed is
`~/.lsd/packages/installed.json`. You rarely touch these by hand — the
[Package Manager](/docs/pkg-manager) reads and writes them.

Sealed marketplace packages carry three extra manifest keys — `sealed`, `paid`, and `key_id` — and
ship their code as an encrypted `.lsdpkg` rather than a plain directory. See
[Publish to the store](/docs/pkg-publish).

## Related

**Learn** — [Develop a package](/docs/pkg-develop) · [Dependencies](/docs/pkg-dependencies) · [The Package Manager](/docs/pkg-manager)

**API** — [lsd_package.json](/docs/api-manifest) · [Tool](/docs/api-tool) · [PackageManager](/docs/api-package-manager)

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

## FAQ
### Where does the manifest point to my code?

The tool key names the Tool attribute to mount — "tool" reads getattr(module, "tool") from the entry module, or "pkg.mod:tool" imports that module and reads the attribute. The optional code key names a subdirectory holding the code; omit it and the code lives beside the manifest.

### Do I need a code directory?

No. The simplest package is lsd_package.json next to a tool.py. Add a code key only when you grow the package into its own import subfolder.
