Block Collections
A block collection is a repository that groups related blocks together. All blocks in a collection share the same programming language and build system. Collections are the unit of distribution in Spade: you install, version, and publish collections, not individual blocks.
What collections are for🔗
Collections serve several purposes:
- Organization — Group related blocks by domain (e.g., raster processing, machine learning, data ingestion)
- Shared code — Blocks in the same collection can share utility functions, data types, and dependencies
- Consistent toolchain — All blocks in a collection use the same language and dependency management
- Versioning — The entire collection is versioned together, so all its blocks stay in sync
For example, you might have a raster collection containing blocks for reprojection, resampling, and mosaic operations, all written in Python and sharing a common set of GDAL bindings.
Directory structure🔗
A collection has this general layout:
my-collection/
blocks/
reproject.yaml # Block manifest
resample.yaml # Block manifest
mosaic.yaml # Block manifest
src/ # Language-specific source code
...
<project-file> # Language-specific project file (see below)The blocks/ directory contains one YAML manifest per block. Each manifest declares the block's interface (inputs, outputs, parameters) as described in Blocks. The rest of the repository contains the source code and configuration for the chosen language.
Language detection🔗
Spade determines the collection's language by looking for a project file in the repository root:
| Project file | Language detected |
|---|---|
Cargo.toml | Rust |
go.mod | Go |
pyproject.toml | Python |
package.json | TypeScript |
| (none of the above) | R (default) |
When you run spade init --language <lang>, Spade scaffolds the appropriate project file and directory structure. If you create a collection manually, just make sure the correct project file is present at the root.
Language-specific layouts🔗
Python collections use pyproject.toml and place source code under src/<package_name>/:
my-collection/
pyproject.toml
blocks/
summarize.yaml
src/
my_collection/
__init__.py
summarize.pyR collections are the default when no recognized project file is found. Source code lives under R/, and dependencies (CRAN packages plus spadelib) are declared in a DESCRIPTION file:
my-collection/
DESCRIPTION
blocks/
interpolate.yaml
R/
interpolate.RGo collections use go.mod:
my-collection/
go.mod
go.sum
blocks/
convert.yaml
cmd/
convert/
main.goRust collections use Cargo.toml:
my-collection/
Cargo.toml
blocks/
detect.yaml
src/
bin/
detect.rsTypeScript collections use package.json:
my-collection/
package.json
tsconfig.json
blocks/
transform.yaml
src/
transform.tsCollection name🔗
Every block has a unique ID in the form <collection>.<block>. The block name comes from the manifest filename (without the .yaml extension). The collection name comes from the language's own root manifest:
| Language | Root manifest | Name source |
|---|---|---|
| Rust | Cargo.toml | name field |
| Python | pyproject.toml | name field |
| Go | go.mod | last path segment of the module line |
| TypeScript | package.json | name field |
| R | (none) | the collection's directory name |
R is the odd one out: Spade never reads DESCRIPTION's Package field for naming purposes, it just uses whatever directory the collection lives in. This is why the built-in sae and stats collections resolve to sae/stats block IDs even though their DESCRIPTION files declare Package: sae.collection and Package: stats.collection — that field only names the R package for R's own tooling, it has no effect on the Spade collection name.
There is currently no supported way to override the resolved name for any language — whatever the table above produces is exactly what appears in block IDs, in ~/.spade/blocks/<collection>/, and in the registry. If you need a different collection name than your manifest's name field (or, for R, your directory name) produces, rename that directly rather than looking for a separate override setting.
This naming convention ensures block IDs are globally unique and immediately tell you which collection a block belongs to. When you reference a block in a pipeline, you use this full <collection>.<block> ID.
Versioning🔗
Collections use semantic versioning (e.g., 1.0.0, 0.3.2). The version is specified in each block's manifest file under the version field. All blocks in a collection should share the same version number, since they are built and installed together.
When you install a new version of a collection, it is stored alongside previous versions. This means multiple versions of the same collection can coexist, and pipelines can pin to a specific version if needed.
Installing collections🔗
The spade install command installs a collection in one of two ways: it fetches a prebuilt, signed artifact from the cloud registry by reference, or it builds from source — a Git repository or a local directory.
# From the registry: a signed artifact, verified before it is unpacked
spade install gdal@1.0.0
spade install gdal@latest
# From a Git repository (shallow-cloned into a temp directory, then built)
spade install https://github.com/example/gdal-blocks.git
# From a local directory (built in place)
spade install .
spade install ./my-collectionA registry install needs no build toolchain: the CLI downloads the artifact for this machine's platform, checks its signature against the registry's trusted keys and its content hash against the registry's metadata, and unpacks it. It uses your spade login session, which is what lets you install your own private collections (below).
For a source install, Spade:
- Clones the repo (or uses the local directory in place)
- Detects the language from the project file and reads the collection's name/version
- Builds the collection using the appropriate toolchain (
cargo build --releasefor Rust,go buildfor Go,uv syncfor Python,bun install+bun build --compilefor TypeScript,pak/Rscriptfor R) - Installs the built artifacts and manifests to
~/.spade/blocks/<collection>/<version>/and registers each block
See spade install for the full breakdown, including how each language's build step works.
The installed layout looks like:
~/.spade/blocks/
raster/
0.2.1/
blocks/
reproject.yaml
resample.yaml
mosaic.yaml
<built artifacts>
0.3.0/
...
ml/
1.0.0/
blocks/
classify.yaml
<built artifacts>Publishing collections🔗
spade publish submits the collection to the registry. It does not upload an artifact: it submits a reference to a commit you have already pushed — (repo_url, commit_sha, subdir, collection, version) — and the registry clones that exact commit, screens the source, builds the artifact, signs it, and stores it. Building from the same SHA that was screened is what makes the signature mean something; publishing unpushed changes would let the screened source and the deployed artifact diverge.
A version moves through submitted → screening → screened → building → available, and spade publish tracks that progress in your terminal until it settles. Three further states are reached by owner or operator action: deprecated (hidden from browsing, still installable), yanked (blocks new installs), and recalled (operator-only, irreversible).
Once a version is available, workers fetch and verify that signed artifact before executing it — that is how a cloud run gets its blocks — and spade install <collection>@<version> fetches the same artifact for a laptop.
Visibility and sharing🔗
Collections are private by default. A private collection is visible to its owner and to the people the owner names, and to nobody else: its blocks do not appear in the editor's block picker, in the block catalogue, in search, or in an MCP client's discovery for anyone without access, and its artifact cannot be downloaded by anyone but the owner.
There are three rights, and they do not all travel together:
| Right | Who has it on a private collection |
|---|---|
| See the collection and its blocks — browse, read manifests, wire them in the editor | the owner and the users it is shared with |
| Use the blocks in a cloud pipeline — run them, receive the outputs and logs | the owner and the users it is shared with |
Take the artifact — spade install it, run it locally | the owner only |
A public collection grants all three to everyone. spade publish --public makes a collection public; the flag is one-way (it never makes a public collection private again), and going private again is done from the web UI's Collections page.
Sharing is by email, one user at a time, from the Collections page in the web UI — the same control pipelines and data files use. A grant carries one permission, use, which includes see. A grantee sees the blocks in the editor and can run pipelines with them; the collection appears on their Collections page under Shared with you. What a grantee cannot do is install the artifact: spade install refuses with a message saying the collection is shared for cloud execution only, and spade run on a pipeline that uses it points at spade submit, which runs the pipeline where the grant applies.
What private does not hide. That a collection exists is not secret: someone who already knows its name learns, from a "not accessible" rather than an "unknown block" error, that it is there. And "private" is about distribution, not about the platform: the source is cloned, screened, and built by the registry, and an operator can read all of it and can recall a version. For Python and R collections the artifact is the source tree, so the owner-only take right is what keeps that source from being downloaded; for Rust, Go, and TypeScript the artifact is a compiled binary either way.
Public pipelines and private blocks. A pipeline that uses a block from a private collection cannot be made public — the library would otherwise advertise pipelines most people cannot run. Saving or flipping such a pipeline to public is refused with a message naming the blocks; make the collection public, or keep the pipeline private and share it with named users. Per-user sharing of a pipeline is unaffected: a person the pipeline is shared with who also holds a grant on the collection can run it, and one who does not sees the block reported as inaccessible when they open it.
Private source repositories. A private collection usually lives in a private repository. The registry clones with a read-only token you store as a cloud secret and bind to the collection at publish time — see spade publish. The token is never written to the collection; only the secret's name is.
Creating a new collection🔗
To create a new collection:
mkdir my-blocks && cd my-blocks
spade init --language pythonThis scaffolds the project structure for the chosen language. Then add blocks with:
spade add my-block-nameThis creates the manifest file at blocks/my-block-name.yaml and a starter source file in the appropriate location for the language.
Validate your collection at any time:
spade checkThis verifies that all manifests are well-formed, entrypoints exist, and there are no conflicting block IDs.