The whole language fits on one page.
A SmashGL card has three concerns: goal (what success looks like), data (where values come from), and presentation (how to display it). Clauses can appear in any order. The runtime parses them into a canonical IR, evaluates against your data, and renders the result.
Three concerns — any order.
// Goal — what success looks like GOAL <verb> <metric> [to|by] [<op>] <value>[<unit>] <time-phrase> [SOFT|HARD] [AND|OR <verb> <metric> ...] [COMPARE <mode>] [TYPE cumulative | ratio | level] [DIRECTION higher | lower] [FORMAT percent | currency | number] [SMOOTH rolling7 | rolling14 | rolling30 | ema7 | ema14] [WINDOW <time-phrase>] // standalone — only when GOAL is absent // Data — live binding SOURCE <namespace>.<table>.<column> [AS <alias>] [, ...] [WHERE <field> <op> <value> [AND ...]] [GROUP BY <dimension>] [ORDER BY <field> [ASC|DESC]] [LIMIT <n>] // — OR inline static data — DATA <alias> = <json-value> [PREVIOUS <json-value>] // Presentation — visualization type, flags, title SMASH <viz> [WITH <flag>, <flag>, ...] [TITLED "<title>"]
Ten keywords. That's the whole language.
| viz | renders as |
|---|---|
| metric | Hero number with OKR goal block and vs-prior pill. |
| sparkline | Hero number + delta pill + small trend line. |
| chart | Full-width chart variant (alias for sparkline). |
| bar | Horizontal bar chart with optional goal line. |
| gauge | Arc / dial showing progress toward a goal. |
| table | Ranked rows. |
| badge | Simple large-number display. |
FLAGS · TREND · FORECAST · ALERT · RISK · GAP
No WITH clause → all applicable flags active by default. WITH <flag>, <flag> → only listed flags active. Flags requiring a GOAL are silently inert without one.
Custom ALERT colors — provide three hex values inline: SMASH metric WITH alert(#dc2626, #d97706, #16a34a). Fewer than 3 valid values → default palette.
SHOW AS is accepted as a verbose synonym for SMASH and produces identical IR.
// canonical verbs GOAL grow revenue by +15% this month GOAL hit revenue >= $50,000 this month GOAL reduce refund_rate to <= 2% this month GOAL hold uptime >= 99.98% this month GOAL cap ad_spend <= $5,000 this week // SOFT / HARD commitment suffix GOAL grow sessions to >= 50000 this month SOFT GOAL hit revenue >= $50,000 this month HARD // compound — AND requires all; OR requires any GOAL hit revenue >= $15,000 AND hit orders >= 300 this month GOAL grow revenue by +20% OR hit gross_profit >= $20,000 this month
| verb | direction | typical use | implied operator |
|---|---|---|---|
| grow | higher-is-better | Revenue, sessions, orders | >= |
| reduce | lower-is-better | Actively shrinking: refund rate, churn | <= |
| hold | maintain | Pass/fail thresholds: uptime SLA, conversion floor | >= |
| hit | reach an absolute | Point-in-time targets: revenue ≥ $50K | >= |
| cap | enforce a ceiling | Budget caps: ad spend ≤ $5K | <= |
Surface synonyms are accepted at parse time and normalized to canonical: increase/raise/boost/drive/lift/scale → grow · decrease/lower/cut/shrink/minimize/trim/drop → reduce · maintain/sustain/keep/stay/ensure → hold · reach/achieve/target/attain → hit · ceiling → cap.
Time phrases are embedded directly in the GOAL clause:
| phrase | window |
|---|---|
| this week | Current calendar week (week-start configurable; default Sunday). |
| this month | First of current month → today. |
| this quarter | Start of current fiscal/calendar quarter → today. |
| this year | January 1 → today. |
| last N days | Rolling N-day window ending today. over last N days is identical. |
| last N weeks | Rolling N-week window. |
| from <date> to <date> | Fixed ISO date range. |
| since <date> | From ISO date to today. |
Values accept: $50,000 (currency), 99.98% (percent), +15% (relative, +N% above comparison), 50000 (plain number). Comma thousand-separators are accepted.
// single column with alias SOURCE shopify.sales.net_sales AS revenue // multiple columns (comma-separated) SOURCE shopify.sales.net_sales AS revenue, shopify.sales.gross_sales, shopify.sales.orders // continuation clauses refine the source SOURCE shopify.sales.gross_sales AS products WHERE channel = "online_store" GROUP BY product_title ORDER BY gross_sales DESC LIMIT 10
| clause | purpose |
|---|---|
| WHERE <field> <op> <value> | Filter rows. Operators: =, !=, >, >=, <, <=, IN, NOT IN, CONTAINS. Chain with AND. |
| GROUP BY <dimension> | Group by a categorical dimension (for bar, table) or a time granularity (for sparkline, chart). |
| ORDER BY <field> ASC|DESC | Sort results. |
| LIMIT <n> | Cap row count — used by table. |
Confirmed namespaces: shopify.sales (net_sales, gross_sales, gross_profit, orders, net_items_sold, refund_rate) · shopify.sessions (online_store_visitors, uptime_pct). Other namespaces work if your runtime is configured with the appropriate emitter.
IR note (v1.0-rc1): GROUP BY day|week|month|quarter|year is the surface form for the IR's granularity field on SourceBinding. SQL emitters pair it with timeField (defaults to created_at) to render DATE_TRUNC(<granularity>, <timeField>).
// single-row — object DATA revenue = {"net_sales": 47300, "gross_sales": 50600, "orders": 847} PREVIOUS {"net_sales": 41800, "gross_sales": 44200, "orders": 723} // multi-row — array (bar charts) DATA returns_total = [ {"period": "Week 1", "returns_total": 2650}, {"period": "Week 2", "returns_total": 2300}, {"period": "Week 3", "returns_total": 3100}, {"period": "Week 4", "returns_total": 1890} ]
| syntax | period |
|---|---|
| WINDOW this week | Named — from start of current week to today. |
| WINDOW this month | Named — from the 1st to today. |
| WINDOW this quarter | Named — from start of current quarter to today. |
| WINDOW this year | Named — from January 1. |
| WINDOW last N days | Rolling — last N days, ending today. |
| WINDOW over last N weeks | Rolling — last N weeks. |
| WINDOW since 2026-03-01 | Fixed start — rolls to today. |
| WINDOW from 2026-03-01 to 2026-03-15 | Fixed range — both ends defined. |
FORECAST · RISK · GAP (time component) — require a window with a defined end. Inactive for rolling windows.
| mode | comparison period |
|---|---|
| previousPeriod | Equal-length window immediately before — default; no clause needed. |
| previousYear | Same date range, twelve months earlier. Best for seasonal businesses. |
| sameDayLastWeek | Same dates, shifted back seven days. Best for short rolling windows. |
| samePeriodLastYear | Same named period last year — Feb 2026 → Feb 2025. Named windows only. |
| samePeriodLastMonth | Same named period last month. Named windows only. |
Default alignment: elapsed. For in-progress periods, the comparison window is auto-truncated to match the elapsed portion. "This month vs last year" on the 13th compares the 1st–13th both years — not the full prior month.
IR alignment override: the IR exposes alignment: "calendar-full" as an alternative — useful when a card should compare against the full prior calendar period regardless of how much of the current window has elapsed. Surface authoring stays on the elapsed default.
| value | meaning | forecast |
|---|---|---|
| cumulative | Period total that grows over time. | Linear extrapolation — currentValue / periodProgress. |
| ratio | Running value — the metric IS the final value at any point. | Current value — no extrapolation. |
| level | Snapshot value at a point in time. | Current value — no extrapolation. |
Inferred when omitted: column names containing rate, uptime, pct, percent, aov, cpa, cpc, roas → ratio. Everything else → cumulative. Declared TYPE always overrides.
| value | meaning | attainment formula |
|---|---|---|
| higher | Higher is better — default. | currentValue / goal × 100 |
| lower | Lower is better. Use with <= or < goal operators. | goal / currentValue × 100 |
Inferred when omitted: column names containing refund, return, churn, abandon, cost, bounce → lower. The reduce and cap verbs also imply lower. TREND pill colour is inverted for lower — green means decreased.
| value | display | notes |
|---|---|---|
| percent | Appends %, 2 decimal places. | Use with TYPE ratio or DIRECTION lower. |
| currency | Tenant's currency symbol. | Same as auto-inferred for net_sales, gross_sales, etc. |
| number | Plain number — 1,200 or 1.2k. | Forces plain display on an ambiguous column. |
Most commonly needed when a rate metric returns a raw decimal but should display as a percentage — e.g. 0.018 → 1.80%.
| value | algorithm |
|---|---|
| rolling7 / 14 / 30 | N-day rolling average — even weighting. |
| ema7 / ema14 | Exponential moving average — recent days weighted more heavily. |
SMOOTH affects the trend line only — never the headline KPI. Only effective when the card renders a sparkline.
Nine queries that exercise the full language.
Revenue · monthly target, vs. last year
Ex. 01 · metric + ALERT
GOAL hit revenue >= $50,000 this month COMPARE previousYear SOURCE shopify.sales.net_sales AS revenue, shopify.sales.gross_sales SMASH metric WITH alert TITLED "March Revenue"
Orders · rolling 30 days, grow 15%
Ex. 02 · metric + TREND + ALERT
GOAL grow orders by +15% over last 30 days COMPARE previousPeriod SOURCE shopify.sales.orders AS orders SMASH metric WITH trend, alert TITLED "Orders"
Top products · ranked table
Ex. 03 · table + GROUP BY + ORDER BY + LIMIT
WINDOW last 90 days SOURCE shopify.sales.gross_sales AS products GROUP BY product_title ORDER BY gross_sales DESC LIMIT 10 SMASH table TITLED "Top Products (90d)"
Refund rate · lower is better, percent display
Ex. 04 · TYPE ratio + DIRECTION lower + FORMAT
GOAL reduce refund_rate to <= 2% this month TYPE ratio SOURCE shopify.sales.refund_rate AS refund_rate SMASH metric WITH trend, alert TITLED "Refund Rate"
Compound goal · revenue AND orders
Ex. 05 · compound GOAL with AND
GOAL hit revenue >= $15,000 AND hit orders >= 300 this month SOURCE shopify.sales.net_sales AS revenue, shopify.sales.orders AS orders SMASH metric WITH alert, forecast TITLED "Monthly OKR"
Uptime SLA · hold threshold
Ex. 06 · hold verb + TYPE ratio
GOAL hold uptime >= 99.98% this month TYPE ratio SOURCE shopify.sessions.uptime_pct AS uptime SMASH metric WITH alert TITLED "Uptime SLA"
Static / demo data
Ex. 07 · DATA with PREVIOUS
GOAL hit revenue >= $15,000 this month DATA revenue = {"net_sales": 47300, "gross_sales": 50600, "orders": 847} PREVIOUS {"net_sales": 41800, "gross_sales": 44200, "orders": 723} SMASH metric WITH alert TITLED "Revenue (Demo)"
Sparkline · 30-day trend with smoothing
Ex. 08 · sparkline + SMOOTH + GROUP BY day
WINDOW over last 30 days SMOOTH rolling7 SOURCE shopify.sales.net_sales AS revenue GROUP BY day ORDER BY day ASC SMASH sparkline WITH trend TITLED "Revenue Trend"
Ad spend · weekly budget cap
Ex. 09 · cap verb + gauge
GOAL cap ad_spend <= $5,000 this week SOURCE shopify.marketing.ad_spend AS ad_spend SMASH gauge WITH alert TITLED "Weekly Ad Budget"
When the parser refuses, and when it just whispers.
A query is valid when
- Either a SOURCE or DATA clause is present.
- A SMASH clause is present.
- Some time phrase is resolvable — either embedded in GOAL (recommended) or in a standalone WINDOW clause.
- If GOAL is present, every metric it references must be available in the data binding.
- <viz> in SMASH is one of metric · sparkline · chart · bar · gauge · badge · table.
- DATA requires a value (object or array); the alias on the left of = is the binding name.
- Compound goals must use either all AND or all OR — mixing is rejected.
- FORMAT must be one of percent, currency, number — any other value is a parse error.
Warnings (the query still runs)
- FORECAST with a rolling window — ignored.
- RISK with a rolling window — ignored.
- GAP time component with a rolling window — value remnant only.
- TREND on sparkline / chart without day-level GROUP BY in SOURCE — trend line omitted; comparison pill still shown on metric / badge.
- TREND with DATA (no time-series available) — trend line omitted.
- TYPE ratio + FORECAST — forecast renders as current value; a warning is surfaced.
- DIRECTION lower with a relative +N% goal — prefer absolute goals (reduce X to <= N) for lower-is-better cases.
- samePeriodLastYear / samePeriodLastMonth with rolling windows — falls back to previousPeriod.
- DIRECTION lower with a relative +N% goal — the sign-vs-direction interplay is ambiguous; prefer absolute goals (reduce X to <= N) for lower-is-better cases.
That's the whole language. The rest is rendering.