how do I do X
Each recipe is a question, a short script, and a one-line note on why this is the canonical way. Try any of them in the playground; full examples that string several together live in examples/.
filter, map, sum a list
"Sum the amount field for orders over $10."
let orders = [ { id: 101, amount: 12.50 }, { id: 102, amount: 8.00 }, { id: 103, amount: 99.00 }, ] let total = orders | where amount is greater than 10 | map amount | sum _ # total: 111.5
whyBare names inside where and map auto-resolve to row fields. sum(xs) takes a list directly — the underscore in sum _ is just the implicit pipeline argument.
group by + summarize
"Total sales per product, sorted descending."
let sales = [ { product: "widget", amount: 12.50 }, { product: "gadget", amount: 8.00 }, { product: "widget", amount: 15.00 }, { product: "doodad", amount: 99.00 }, ] let by_product = sales | group by product | summarize { total: sum(amount), n: length(items) } | sort by total descending
whygroup by name produces records with a name field (the key) and an items list. Inside summarize, bare names resolve to fields of the grouped items.
safe field access
"What if the field might be missing?"
let r = { name: "Ada" } # r.age would error -- the field doesn't exist. # r.get("age") returns nothing safely. let age = r.get("age") if age is nothing then show "age unknown" else show f"age is {age}" end
whyDirect field access is strict on purpose — typos turn into hard errors with "did you mean" hints. r.get("key") opts into nullable lookup when the field really might be absent.
validate records
"Take a list of records, return { ok, errors } per item."
to validate(items) var errors = [] for each i in 1..length(items) let r = items[i] if r.get("name") is nothing then errors = errors + [{ index: i, field: "name", msg: "missing" }] end end return { ok: length(errors) is 0, errors: errors } end show json.stringify(validate(records))
whyThe agent-loop primitive: take upstream LLM output, return structured diagnostics another tool can parse. full example ↗
fetch JSON, handle non-2xx
"Hit an API and bail safely on errors."
let r = http.get("https://api.example.com/data") # r.ok is connection-level (true even on 4xx/5xx). # Decide success ourselves -- the language doesn't guess. if r.ok is false then show f"network error: {r.error}" else if r.status is less than 200 or r.status is at least 300 then show f"HTTP {r.status}" else let data = json.parse(r.body) show f"got {length(data)} records" end
whyhttp.get returns { ok, status, body, time_ms, error }. ok only flags network-level failure; the script decides what counts as success. (Note: http.get is disabled in the in-browser playground.)
parse and transform CSV
"Read CSV, filter, write CSV."
let input = "name,score\nAda,90\nBob,72\nCara,85" let rows = csv.parse(input) # csv.parse returns a list of records keyed by header row. let passing = rows | where to_number(score) is at least 80 show csv.write(passing)
whyCSV cells arrive as text. Cast explicitly with to_number — cr8 will never silently coerce "72" for you, which is the whole point.
emit a markdown report
"Structured tickets —> markdown standup."
show "# daily standup" show "" show f"_{length(tickets)} tickets_" show "" let by_owner = tickets | group by owner for each g in by_owner show f"### {g.owner}" for each t in g.items show f"- `{t.id}` ({t.status}) -- {t.title}" end show "" end
whyshow f"..." is the entire templating story. No Jinja, no templating engine. Pipe stdout to a .md file.
mermaid Gantt for an IT project
"Sprint tickets —> Gantt chart grouped by team, with milestones."
let tickets = [ # engineering { id: "PLAT-201", title: "Discovery + RFC", owner: "eng", status: "done", start: "2026-04-06", days: 5 }, { id: "PLAT-202", title: "Auth service", owner: "eng", status: "done", start: "2026-04-13", days: 7 }, { id: "PLAT-205", title: "API gateway", owner: "eng", status: "active", start: "2026-04-27", days: 6 }, { id: "PLAT-210", title: "Cutover", owner: "eng", status: "todo", start: "2026-05-11", days: 2 }, # data { id: "DATA-118", title: "Schema review", owner: "data", status: "done", start: "2026-04-13", days: 3 }, { id: "DATA-122", title: "Migration scripts", owner: "data", status: "active", start: "2026-04-20", days: 5 }, { id: "DATA-125", title: "Backfill + verify", owner: "data", status: "todo", start: "2026-04-27", days: 4 }, # devops { id: "OPS-077", title: "Terraform modules", owner: "devops", status: "done", start: "2026-04-06", days: 6 }, { id: "OPS-081", title: "Staging stack", owner: "devops", status: "active", start: "2026-04-20", days: 5 }, { id: "OPS-084", title: "Prod stack + DNS", owner: "devops", status: "todo", start: "2026-05-04", days: 4 }, # qa { id: "QA-040", title: "Test plan", owner: "qa", status: "done", start: "2026-04-13", days: 4 }, { id: "QA-046", title: "Load rig", owner: "qa", status: "active", start: "2026-04-27", days: 4 }, { id: "QA-051", title: "Smoke + soak", owner: "qa", status: "todo", start: "2026-05-04", days: 5 }, ] let milestones = [ { title: "RFC sign-off", id: "M1", on: "2026-04-10" }, { title: "Code freeze", id: "M2", on: "2026-05-08" }, { title: "Go live", id: "M3", on: "2026-05-12" }, ] to status_tag(s) if s is "done" then return "done, " else if s is "active" then return "active, " else return "" end end show "```mermaid" show "gantt" show " title Q2 platform migration" show " dateFormat YYYY-MM-DD" show " axisFormat %b %d" show " tickInterval 1week" show " weekday monday" show " excludes weekends" show "" # milestones lane first -- diamonds along the top show " section milestones" for each m in milestones show f" {m.title} :milestone, {m.id}, {m.on}, 0d" end # one swimlane per team let by_owner = tickets | group by owner for each g in by_owner show f" section {g.owner}" for each t in g.items let tag = status_tag(t.status) show f" {t.title} :{tag}{t.id}, {t.start}, {t.days}d" end end show "```"
whygroup by owner gives swimlanes and a tiny status_tag helper maps each row to mermaid's done / active tags. Milestones live in their own lane so the timeline keeps a clean top edge. Pipe stdout into a .md file and any GitHub issue, wiki, or Notion page renders the chart.
mermaid funnel for sales
"Stage counts —> flowchart with conversion rates."
let funnel = [ { stage: "leads", n: 12500 }, { stage: "mqls", n: 3100 }, { stage: "sqls", n: 1240 }, { stage: "opps", n: 410 }, { stage: "closed_won", n: 118 }, ] show "```mermaid" show "flowchart TD" # one node per stage, label = name + count for each i in 1..length(funnel) let s = funnel[i] show f" S{i}[\"{s.stage}<br/>{s.n}\"]" end # edges labeled with stage-to-stage conversion % let last = length(funnel) - 1 for each i in 1..last let cur = funnel[i] let nxt = funnel[i + 1] let pct = math.round(nxt.n / cur.n * 1000) / 10 show f" S{i} -->|{pct}%| S{i + 1}" end show "```"
why1-based indexing makes i + 1 the natural "next stage" reference. math.round(x * 1000) / 10 is the canonical one-decimal-percent trick — cr8 has no printf, but rounding to a tenth is one expression.
mermaid pie for channel mix
"Marketing spend by channel —> pie chart."
let spend = [ { channel: "paid_search", usd: 45000 }, { channel: "social", usd: 22000 }, { channel: "email", usd: 8500 }, { channel: "events", usd: 31000 }, { channel: "content", usd: 14000 }, ] show "```mermaid" show "pie title Q2 marketing spend (USD)" for each row in spend show f" \"{row.channel}\" : {row.usd}" end show "```"
whyThe simplest mermaid type — one record per slice, no math. Same shape works for budget allocation, headcount, deal-stage value, anything where you want a one-line summary visual.
return a value from if
"Conditional assignment without a temporary variable."
# if/then/else is also an expression in cr8. let label = if score is at least 90 then "A" else if score is at least 80 then "B" else "C" end show f"grade: {label}"
whyNo need for a var + reassign dance. The end closes the expression form just like the statement form.