Quickstart

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 install already 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 + A2

That'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.grid

You'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 MODEL

The 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 currency and as percentage are type tags. They tell consumers (formatters, validators, the UI) what the value means semantically.
  • ROUND is one of 496 built-in functions. See functions.md for the full catalog.
  • B2 will format as a percentage in the UI; B3 will 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. See reference.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)
END

The 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          # → 0

For division-by-zero or other errors, use IFERROR or TRY:

B6 = IFERROR(A1 / 0, "n/a")   # → "n/a"
B7 = TRY 10 / 0 ELSE 0        # → 0

See 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 array

Higher-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()
END

Whenever 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
END

For a one-shot action at a specific time:

AT dt"2026-12-31T23:59:00Z" BACKFILL THEN
  G1 = TRUE
END

See 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:backend

Then 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/hello

To 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.

If you're an LLM agent generating Grid code from natural language, start at ai-agent-guide.md.