A spreadsheet that fetches the world.
An external function is just a function. You write FX_RATE("USD","EUR") the way you write SUM(...). Behind the scenes, Grid queues a worker job, tracks a status lifecycle, caches the result, and writes back to your cell — while the rest of the model keeps computing.
Sixteen async functions, built in.
Each ships with a declared cache policy, a timeout, and a worker route. And the catalog isn't the ceiling: import an OpenAPI, GraphQL, AsyncAPI, WSDL, or gRPC schema as a versioned package — USE "api:example/catalog@1.0.0" AS catalog — and its operations become namespaced functions in your model.
One token, four steady states, two terminal-ish branches — stale on a newer revision and failedwhen an attempt didn't land.
Every external value has a status.
dirty → queued → running → ready on the happy path. stale when a newer revision is desired. failedwhen the latest attempt didn't land. Read it through the API or stream it over the websocket.
Pair the call with DEFAULT and the rest of the model keeps computing while the cell rides the lifecycle. Use ?= to write only on success — a transient failure won't blank a previously good value.
One operator decides when work happens.
= queues the work the moment the model deploys. ~= waits until something reads the cell. Same syntax everywhere else — but a single character chooses between a warm dashboard at deploy time and a cold pool of cells that fire only when asked.
Eager — at deploy
Worker starts immediately. Downstream cells use DEFAULT until ready. Best for hot paths and live dashboards.
Lazy — on demand
No work until something asks. Eliminates the "deploy creates a thundering herd of jobs" problem. Best for rare-read or expensive paths.
Each external call lowers to a deterministic synthetic symbol — so a cell that wraps FX_RATE in ROUND is cached at the boundary, not at the outer expression. One fetch, many reads.
The cell is the wire. The world is the value.
# eager: warm at deploy B7 = FX_RATE("USD", "EUR") last_good ?= B7 rate = last_good DEFAULT 1.08 # lazy: only when read P1 ~= ML_SCORE(features) risk = P1 DEFAULT 0 # defensive multi-step user_email = WITH u = HTTP_JSON("https://api.x/me"), THEN u.email ELSE "unavailable"
SELECT is a formula.
Native SQL is source syntax, not a string. Write SELECT with joins, CTEs, and window functions directly in a cell; Grid lowers it to a typed relational plan, pushes what it can down to BigQuery, Snowflake, Redshift, or Databricks, and computes the rest locally. Grouped aggregates stay maintained incrementally as rows change — the KPI tile never recomputes the world.
# native SQL, pushed down orders = SELECT id, amount FROM finance.public.orders WHERE amount >= 100 ORDER BY id DESC LIMIT 25 # a live, grouped KPI revenue = SELECT region, SUM(price * quantity) AS total FROM orders GROUP BY region