Home Dashboards and Data Viz Generating Vega-Lite Specs From a Plain-Language Data Description

Generating Vega-Lite Specs From a Plain-Language Data Description

Vega-Lite's declarative JSON grammar is one of the few chart formats an LLM can produce reliably — if you give it the schema and check three specific things.

By Hana Lindqvist, a data visualization designer · Published 27 May 2026 · 8 min read · Reviewed against our editorial standards

ADVERTISEMENT

Vega-Lite is unusually well suited to generation by a language model, and the reason is structural. It is a declarative grammar: you describe what you want encoded — this field on x, that field on y, aggregate by month, color by category — and the renderer figures out the pixels. There is no imperative drawing code, no loops, no state. The output is a single JSON object that either validates against a published schema or does not. For an LLM, that is close to an ideal target. Compare it to asking for D3, where the model has to reason about DOM manipulation, scales, and axis construction imperatively, and gets it subtly wrong far more often.

So the workflow of "describe your data and intent in plain language, get a working spec" genuinely works in 2026. But it works well only if you understand where it breaks. Most of the failures come from the same three places, and all three are checkable in under a minute.

Give it the schema, not just the intent

The single biggest determinant of a usable spec is whether the model knows your actual field names and types. If you say "chart my sales over time," it will invent field names like date and sales, guess that date is temporal, and hand you a spec that fails silently against your real columns.

Give it the schema explicitly. I paste a compact description plus two or three sample rows:

My data is an array of objects with these fields:
- order_date: ISO date string (e.g. "2026-03-14")
- region: string, one of North/South/East/West
- net_revenue: number, USD
- channel: string (direct, partner, marketplace)

Sample rows:
{"order_date":"2026-03-14","region":"North","net_revenue":4210.50,"channel":"direct"}
{"order_date":"2026-03-14","region":"South","net_revenue":1980.00,"channel":"partner"}

I want net revenue by month, as a stacked bar chart, stacked by channel,
for the year 2026 only. Vega-Lite v5 spec, please.

The sample rows do real work: they let the model infer that order_date is a parseable date and that net_revenue is continuous, so it picks "temporal" and "quantitative" encodings correctly instead of guessing.

What comes back, and the three things to check

For the prompt above you should get something close to this:

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {"url": "orders.json"},
  "transform": [
    {"filter": "year(datum.order_date) == 2026"}
  ],
  "mark": "bar",
  "encoding": {
    "x": {
      "field": "order_date",
      "type": "temporal",
      "timeUnit": "yearmonth",
      "title": "Month"
    },
    "y": {
      "field": "net_revenue",
      "type": "quantitative",
      "aggregate": "sum",
      "title": "Net revenue (USD)"
    },
    "color": {"field": "channel", "type": "nominal"}
  }
}

Before you trust it, check these three things — in order, because they are the three most common defects.

1. Encoding types

The type on each channel — temporal, quantitative, nominal, ordinal — is where models slip most. A field that should be ordinal (say, a T-shirt size or a survey scale) often comes back nominal, which throws away the ordering and can scramble your axis. A numeric ID that is really a category comes back quantitative and gets summed into nonsense. Read every type and confirm it matches what the field means, not just its storage format.

2. Aggregation and the aggregation trap

Confirm the aggregate is what you asked for and that it is present at all. The subtle bug: if you want a sum per month but the model omits "aggregate": "sum" and there are multiple rows per month, Vega-Lite will layer or overplot rather than total them, and the chart looks plausibly wrong. Also check for double aggregation — a transform that already aggregates plus an aggregate in the encoding will silently mislead.

3. Invented field names

Diff every field value against your real schema. Even with the schema pasted in, models occasionally normalize net_revenue to revenue or netRevenue. A wrong field name usually produces an empty chart, not an error, which is the most time-wasting failure mode of all.

The iteration loop that keeps it honest

Do not generate a spec and paste it into production. Round-trip it through a renderer that validates. The Vega-Lite online editor is the fastest: paste the spec, point it at real data, and it shows both the schema errors and the actual chart. Observable and the Python altair library are good if you want it in a notebook — altair will raise on an invalid spec, which turns silent failures into loud ones.

My loop:

  1. Generate the spec from the schema-rich prompt.
  2. Paste into the Vega-Lite editor with a real data sample.
  3. If it errors or looks wrong, paste the error (or a description of what's wrong) back to the model. It fixes schema errors reliably because the error messages are precise.
  4. Once it renders correctly, refine styling in plain language: "make it horizontal," "sort bars descending by total," "use a diverging scale centered on zero."

That styling refinement is where generation shines. Asking for "sort": {"field": "net_revenue", "op": "sum", "order": "descending"} by describing it in English is genuinely faster than remembering the exact sort-object syntax, and the model gets these small grammar details right almost every time because they are heavily represented in its training.

Where hand-writing still wins

Generation is a strong default for standard chart types with a clean schema. It gets weaker as you move toward the edges of the grammar.

The honest trade-off is this: for the seventy percent of charts that are a bar, line, area, or scatter over a well-described table, generation turns a five-minute lookup-and-type job into a thirty-second describe-and-verify job. For the interactive, multi-view dashboard centerpiece, you are better off writing the spec yourself and using the model only to fill in the syntax you would otherwise be looking up. Knowing which bucket a chart falls into — and always checking types, aggregation, and field names before you ship — is what separates a fast workflow from a subtly broken one.

vega-litellmspecscharting

A note on shelf life. AI products change fast. This guide deliberately focuses on the parts that stay true — how to judge a tool, what the trade-offs are — rather than ranking products that will have changed by the time you read it. Prices and feature claims should always be checked against the provider before you rely on them.