Grid Quickstart
Grid Quickstart
Goal: write your first Grid model and see it compute. About 10 minutes.
Prerequisites
You need:
- Node.js 20+
- A clone of the Grid repo
npm installalready run in the repo root
You do not need Redis for this walkthrough — we'll use the in-process TypeScript runtime which keeps state in memory.
1. Write Your First Cells
Create a file hello.grid anywhere:
A1 = 10
A2 = 20
A3 = A1 + A2That's already a valid model. A3 evaluates to 30 whenever A1 or
A2 change.
Compile it to see what Grid produces:
npm run compile:model hello.gridYou'll see a generated Lua script printed to stdout. The compile step parses, builds the dependency graph, and emits a Redis Functions library. Don't worry about the Lua — just confirming it parsed.
2. Add A Header
Real models start with a metadata header. Update hello.grid:
MODEL "Hello"
DESCRIPTION "A first Grid model."
RUNTIME "lua_generated"
VERSION "0.1.0"
AUTHOR "you@example.com"
A1 = 10
A2 = 20
A3 = A1 + A2
END MODELThe header has no semantic effect on cell values, but it's required by
linters and the canonical style guide. RUNTIME "lua_generated" says
this model must work in both runtimes.
3. Use Functions And Type Tags
Replace the body with this:
A1 as currency = 125000
A2 as currency = 86000
B1 as currency = A1 - A2
B2 as percentage = B1 / A1
B3 = ROUND(B2 * 100, 1)What just happened:
as currencyandas percentageare type tags. They tell consumers (formatters, validators, the UI) what the value means semantically.ROUNDis one of 496 built-in functions. Seefunctions.mdfor the full catalog.B2will format as a percentage in the UI;B3will format as a plain number rounded to 1 decimal place.
4. Branch With THEN ... ELSE
Grid's preferred conditional is THEN ... ELSE:
C1 = B1 > 50000 THEN "strong" ELSE "watch"For multiple tiers, chain right-to-left from most-specific to
least-specific (chained THEN ... ELSE must be on one line):
C2 = B2 > 0.5 THEN "great" ELSE B2 > 0.3 THEN "good" ELSE "weak"If you're matching the same value against several alternatives, use
MATCH:
C3 = MATCH(C1, "strong" -> :green, "watch" -> :amber, _ -> :red)MATCH is a function call, so it can also wrap across lines:
C3 = MATCH(C1,
"strong" -> :green,
"watch" -> :amber,
_ -> :red
)Multi-line rule. Grid is line-oriented at the top level, but any expression inside
(...),[...], or{...}may span multiple lines. Block forms —DO,CASE WHEN,WHEN/EVERY/AT,WITH— also span lines. Seereference.md.
The :green, :amber, :red are symbol literals — enum-like labels.
5. Local Bindings With DO
When a formula gets long, name intermediate values with DO ... END:
B4 as currency = DO
margin = B1
deduction = 2500
MAX(margin - deduction, 0)
ENDThe last expression in a DO block is the value of the block.
6. Handle Missing Or Failing Values
Use DEFAULT for blank-or-error fallback:
A5 = BLANK
B5 = A5 DEFAULT 0 # → 0For division-by-zero or other errors, use IFERROR or TRY:
B6 = IFERROR(A1 / 0, "n/a") # → "n/a"
B7 = TRY 10 / 0 ELSE 0 # → 0See errors.md for the full error model.
7. Try Arrays
Grid arrays spill — a formula returning an array writes a rectangle of cells anchored at its target.
D1 = [120, 135, 142, 150, 168] # spills into D1:D5 horizontally
E1 = D1 * 1.1 # broadcasts; spills into E1:E5
F1 = SUM(D1#) # D1# is the entire spilled arrayHigher-order helpers work with arrays:
G1 = MAP(D1#, x => ROUND(x * 1.05, 2))
G2 = REDUCE(0, D1#, (acc, x) => acc + x)
G3 = SCAN(0, D1#, (acc, x) => acc + x)8. Call An External Function
External functions cross the runtime boundary — they enqueue a job and
write back asynchronously. Three are built in: FX_RATE, HTTP_JSON,
ML_SCORE.
H1 = FX_RATE("EUR", "USD")
H2 = ROUND(125000 * (H1 DEFAULT 1.08), 2)While the worker is fetching the rate, H1 is queued or running,
not ready. The DEFAULT 1.08 keeps H2 computable in the meantime.
For a value you only want to compute on demand, use ~=:
H3 ~= ML_SCORE([0.12, 0.18, 0.27, 0.43])H3 will not enqueue work until something reads it.
See external-functions.md for the full
async semantics.
9. Add A Rule
Rule blocks run on every runtime. The TS GridService orchestrates rule
waves and arms a smart-wakeup setTimeout for EVERY/AT schedules,
so the same model deploys identically on ts, lua_generated, and
lua_interpreter:
MODEL "Hello With Rules"
RUNTIME "lua_generated"
VERSION "0.2.0"
A1 = 0
WHEN A1 > 100 THEN
C1 = "over-limit"
C2 = NOW()
ENDWhenever A1 becomes greater than 100, the rule fires atomically: both
C1 and C2 are set in the same wave.
For periodic actions:
EVERY duration"PT15M" SKIP MISSED THEN
E1 = E1 + 1
ENDFor a one-shot action at a specific time:
AT dt"2026-12-31T23:59:00Z" BACKFILL THEN
G1 = TRUE
ENDSee rules-and-schedules.md for the full
rule semantics, including missed-run policy.
10. Run It For Real
To deploy the model into a running Grid backend (Redis required):
docker compose up -d redis
npm run dev:backendThen in another shell:
curl -X POST http://localhost:3000/api/models \
-H "Content-Type: application/json" \
-d "{\"id\": \"hello\", \"source\": $(jq -Rs . < hello.grid)}"
curl http://localhost:3000/api/models/helloTo write an input value:
curl -X PUT http://localhost:3000/api/models/hello/input \
-H "Content-Type: application/json" \
-d '{"symbol": "A1", "value": 200000, "typeTag": "currency"}'See docs/api/http_api.md for the full HTTP
contract.
What To Read Next
reference.md— every literal, operator, and expression form.assignments.md— every assignment shape.style-guide.md— the canonical authoring style.cookbook.md— recipes by task.
If you're an LLM agent generating Grid code from natural language,
start at ai-agent-guide.md.