Getting started
Write your first recipe and run it end-to-end in a few minutes.
Requirements
- macOS 14, Linux, or Windows 10+
- Rust 1.85 or newer
A visit drives a real browser through wry: WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux.
Install
Once releases ship, brew install foragelang/forage/forage or curl -fsSL https://foragelang.com/install.sh | sh. Until then, build from source:
git clone https://github.com/foragelang/forage.git
cd forage
cargo build --release --bin forage
./target/release/forage --versionSee the CLI reference for the full subcommand surface.
To use Forage as a library in your own Rust crate, point your Cargo.toml at the local checkout while we work toward a tagged release:
[dependencies]
forage-core = { path = "../forage/crates/forage-core" }
forage-static = { path = "../forage/crates/forage-static" }Write a recipe
Your workspace is the directory marked by forage.toml (by default ~/.forage/). Recipe files sit flat at the workspace root: one .forage file per recipe, named however you like. Scaffold one with forage new:
cd ~/.forage
forage new hello
$EDITOR hello.forageforage new <name> creates <workspace>/<name>.forage with a minimal recipe "<name>" header. Edit it to match a documented JSON endpoint:
// hello.forage
recipe "hello"
type Post {
externalId: String
title: String
body: String?
}
input userId: Int
step posts {
method "GET"
url "https://jsonplaceholder.typicode.com/posts?userId={$input.userId}"
}
for $p in $posts[*] {
emit Post {
externalId ← $p.id | toString
title ← $p.title
body ← $p.body
}
}Three things to notice:
type Post { … }: the shape of the records this recipe emits. File-scoped by default; prefix withshareto publish it to the workspace catalog.input userId: Int: a per-run parameter.step posts { … }: an HTTP request whose response is addressable as$posts.
Run it
forage run takes a recipe header name and resolves it against the surrounding workspace:
echo '{"userId":1}' > /tmp/hello-inputs.json
forage run hello --inputs /tmp/hello-inputs.jsonThe CLI parses the recipe, validates it against the type catalog, runs the HTTP graph, and prints the resulting snapshot (a JSON list of every emitted record) to stdout. --inputs <path> points at a JSON object of bindings; omit it for recipes without inputs.
Validation runs first
Unknown types, unbound path variables, missing required fields, and unknown transforms are caught before any HTTP request fires. The error format speaks the DSL's own terms, not stack traces from extraction code.
From here
- Read the syntax reference for the full set of constructs:
enum,share-visibility,authstrategies,caseexpressions, optional chaining, transforms. - Read engines & pagination when you need to gather from a paginated API or a JS-rendered SPA.
- Browse the canonical recipes on hub.foragelang.com for end-to-end examples that exercise the full DSL.