Block Development Libraries
Spade provides official libraries for five languages. Each library handles the runtime details — reading inputs, loading parameters, writing outputs — so you can focus entirely on your processing logic.
| Language | Runtime | Install |
|---|---|---|
| Python | Python 3.12+ with uv | pip install spadelib |
| R | R 4.0+ with pak | pak::pkg_install("krbundy/spadelib-cran") |
| TypeScript | Bun 1.0+ | bun add spadelib |
| Go | Go 1.25+ | go get github.com/krbundy/spadelib |
| Rust | Rust stable | cargo add spadelib |
All five libraries share the same concepts and workflow:
- Define a handler function that accepts typed inputs and parameters
- Process the data using your domain tools and libraries
- Return a typed output — the library writes it to the correct location
- Call
run(handler)— the library wires everything together
The libraries also provide a manifest builder that generates block manifest YAML from your handler's type annotations, keeping the manifest and implementation in sync.
Optional inputs and any inputs🔗
From spadelib 0.3, a block can declare a wired input optional -- it may be left unconnected -- and an input of type any, which takes whatever output is connected to it (a file, a JSON result, a directory or a collection). Both are declared in the manifest:
inputs:
raster: { type: file, format: GeoTIFF }
hillshade: { type: file, format: GeoTIFF, optional: true }
member: { type: any, optional: true }An optional input that nothing is connected to is absent: there is no inputs/<name>/ directory at all, and every library hands the handler its language's null. That is different from a connected collection that happens to be empty, which arrives as an empty collection.
An any input arrives as an AnyInput: kind (file, json, directory or collection) and format say what was connected; path is the file itself (or the directory, for a directory or collection); paths lists the top-level files.
| Language | Optional input | any input |
|---|---|---|
| Python | annotate Optional[RasterFile] (or RasterFile | None); receives None | annotate AnyInput |
| R | give the parameter a default (hillshade = NULL) and a wired type in spade_types; receives NULL | spade_types(h) <- list(member = "AnyInput") |
| TypeScript | inputs: { hillshade: optional(RasterFile) }; receives undefined | inputs: { member: AnyInput } |
| Go | spadelib.OptionalInput[*spadelib.RasterFile](args, "hillshade"); returns nil | spadelib.InputAny(args, "member") / OptionalInputAny |
| Rust | args.optional_input::<RasterFile>("hillshade")?; returns None | args.any_input("member")? / optional_any_input |
from typing import Optional
from spadelib import RasterFile, run
def handler(raster: RasterFile, hillshade: Optional[RasterFile]) -> RasterFile:
if hillshade is None:
...Because several optional or any inputs of the same type are interchangeable to type matching, a pipeline connecting into them must name the input with as (see Input References); the web editor does this for you.
Python
Build Spade blocks in Python using familiar tools and type hints.
Explore →R
Build Spade blocks in R using familiar R idioms and packages.
Explore →TypeScript
Build Spade blocks in TypeScript using the Bun runtime.
Explore →Go
Build Spade blocks in Go with generics and strong type safety.
Explore →Rust
Build Spade blocks in Rust with trait-based type safety.
Explore →