Query a VGI worker in a minute
You don't have to build anything to use VGI. Attach a worker somebody else already runs, and it behaves like any other SQL catalog.
A VGI worker is a program that exposes an API, dataset, model, or internal service as SQL.
You ATTACH it by URL and query it — no download, no ETL, no
connector to write, no custom extension to compile. Both workers used below are public
and need no credentials.
Attach your first worker
Run Haybarn
Haybarn is Query.Farm's DuckDB-compatible distribution, and it ships the VGI extension.
# Haybarn is Query.Farm's DuckDB-compatible distribution.
npx haybarn@latest
# Or with uvx / pipx:
uvx haybarn-cli
pipx run haybarn-cli
Platform binaries, provenance verification and more install channels are in the Haybarn install guide.
Load VGI
Once, inside the Haybarn shell:
INSTALL vgi FROM community;
LOAD vgi;
Already installed it before? FORCE INSTALL vgi FROM community
refreshes it.
Attach and query
This worker wraps the live feed from the USGS Earthquake Hazards Program — the same 30-day catalog seismologists watch — and serves it as a table.
ATTACH 'earthquakes' AS eq (
TYPE vgi,
LOCATION 'https://vgi-earthquakes.rusty-bb6.workers.dev'
);
-- eq is now an ordinary catalog. Query it with ordinary SQL.
SELECT round(mag, 1) AS mag,
place,
strftime(time, '%Y-%m-%d %H:%M') AS utc
FROM eq.main.recent
WHERE mag >= 6
ORDER BY mag DESC
LIMIT 6;
| mag | place | utc |
|---|---|---|
| 7.7 | 68 km NNW of Ende, Indonesia | 2026-08-14 21:58 |
| 7.4 | 5 km S of San José del Palmar, Colombia | 2026-08-10 12:34 |
| 6.9 | 15 km NNW of Pematangsiantar, Indonesia | 2026-08-15 10:54 |
| 6.8 | The 2026 Kumamoto Region, Japan Earthquake | 2026-07-28 07:27 |
| 6.3 | south of the Kermadec Islands | 2026-08-05 07:43 |
| 6.3 | 32 km SW of Sarangani, Philippines | 2026-08-05 04:14 |
That is the whole idea. The alias after AS becomes the catalog
name, and everything downstream is ordinary SQL — joins, aggregates, window functions,
CREATE TABLE AS.
Prefer not to install anything?
Cupola is a free SQL workbench that runs DuckDB in a browser tab — with charts, pivots and an AI analyst. This link opens it pointed at the same earthquake worker, no download and no setup.
More things you can query
Same two steps — ATTACH, then SQL — against very different
services. Neither of these is a dataset sitting in a bucket; both are live APIs a worker
is translating on the fly.
A weather API, as table functions
Open-Meteo is a free, open-source weather API. This worker exposes its geocoding and hourly-forecast endpoints as table functions, so a place name and a forecast join in one query.
ATTACH 'open_meteo' AS m (
TYPE vgi,
LOCATION 'https://vgi-open-meteo.rusty-bb6.workers.dev'
);
-- Geocode the place, then LATERAL-join its coordinates straight into the
-- forecast call — one round trip per row, no separate lookup step.
SELECT strftime(w.time, '%a %H:%M') AS hour,
round(w.temperature_2m, 1) AS temp_f,
m.main.weather_code_emoji(w.weather_code) AS icon,
m.main.weather_code_text(w.weather_code) AS conditions
FROM m.main.geocoding('Glen Allen, VA', count := 1, country_code := 'US') AS g,
LATERAL m.main.forecast_hourly(g.latitude, g.longitude,
forecast_days := 2,
temperature_unit := 'fahrenheit') AS w
WHERE w.time >= now()
ORDER BY w.time
LIMIT 5;
| hour | temp_f | icon | conditions |
|---|---|---|---|
| Mon 21:00 | 90.7 | ☀️ | Clear sky |
| Mon 22:00 | 89 | ☀️ | Clear sky |
| Mon 23:00 | 86.5 | ☀️ | Clear sky |
| Tue 00:00 | 83.9 | ☁️ | Overcast |
| Tue 01:00 | 79 | ☁️ | Overcast |
Workers compose
The part that pays off: attach more than one worker in the same session and query across them. Neither service knows the other exists, and you did not build a pipeline to put them in the same place — DuckDB plans across both catalogs.
Attach both, then ask what the weather is right now at the sites of the largest recent earthquakes — one query, two services, no pipeline in between.
ATTACH 'earthquakes' AS eq (TYPE vgi, LOCATION 'https://vgi-earthquakes.rusty-bb6.workers.dev');
ATTACH 'open_meteo' AS m (TYPE vgi, LOCATION 'https://vgi-open-meteo.rusty-bb6.workers.dev');
-- Two workers in one session: one wrapping the USGS feed, one the forecast
-- API. DuckDB plans across them.
SELECT round(q.mag, 1) AS mag,
q.place,
round(w.temperature_2m, 1) AS temp_c,
m.main.weather_code_text(w.weather_code) AS conditions
FROM (SELECT * FROM eq.main.recent WHERE mag >= 6 ORDER BY mag DESC LIMIT 5) AS q,
LATERAL m.main.forecast_current(q.latitude, q.longitude) AS w;
| mag | place | temp_c | conditions |
|---|---|---|---|
| 7.7 | 68 km NNW of Ende, Indonesia | 24.9 | Clear sky |
| 7.4 | 5 km S of San José del Palmar, Colombia | 20.8 | Slight rain showers |
| 6.9 | 15 km NNW of Pematangsiantar, Indonesia | 23.4 | Overcast |
| 6.8 | The 2026 Kumamoto Region, Japan Earthquake | 24.5 | Mainly clear |
| 6.3 | 32 km SW of Sarangani, Philippines | 28.2 | Mainly clear |
These are live feeds, so your numbers will differ from the samples above.
What the planner does with that
This is not two queries stapled together. DuckDB plans the whole thing at once, and pushes
what it can to the far side: the earthquake worker is asked for four columns with
mag >= 6 already applied, rather than for the table.
Put EXPLAIN in front of the query to see it yourself — the plan
names VGI_TABLE_SCAN and VGI_LATERAL_BATCH,
and prints the filter and projection list each worker was handed. Pushdown is opt-in per
worker; see Workers are first-class catalogs.
Not just data — functions too
It is easy to read all of the above as "VGI is a way to fetch remote tables." It is not. A
worker publishes functions, and a table is
only one of the shapes they come in. You have already been calling them: every
icon and conditions value above came from
a function the weather worker exposes, not from a column in a dataset.
Called on their own, with no FROM clause at all, they look like any
other SQL function — because to DuckDB that is exactly what they are:
ATTACH 'open_meteo' AS m (TYPE vgi, LOCATION 'https://vgi-open-meteo.rusty-bb6.workers.dev');
-- No table, no FROM — these functions belong to the worker.
SELECT m.main.weather_code_emoji(95) AS icon,
m.main.weather_code_text(95) AS conditions,
m.main.wind_compass(247) AS wind,
m.main.uv_index_category(8.2) AS uv;
| icon | conditions | wind | uv |
|---|---|---|---|
| ⛈️ | Thunderstorm | WSW | Very High |
That matters because the code behind a function runs in your worker, in your language, on your machines. So a VGI function can call a model, hit an internal API, apply a licensing rule, or reach a library that has no business being inside a database process — and to the person writing SQL it is just a function.
A worker can expose five shapes in total — scalar, table, table-in-out, aggregate and buffering — plus macros, and the tables and views you have already seen. Ask any worker what it publishes:
ATTACH 'open_meteo' AS m (TYPE vgi, LOCATION 'https://vgi-open-meteo.rusty-bb6.workers.dev');
-- Everything this worker publishes, by kind.
SELECT function_name, function_type
FROM duckdb_functions()
WHERE database_name = 'm'
ORDER BY function_type, function_name;
The shapes are laid out in Concepts, and in detail in Function lifecycle.
Want to build a VGI worker?
Everything above was consuming a worker. Writing one is the other half: you own an API, a dataset, a model or an internal service, and you want SQL access to it without building a pipeline first. A worker is an ordinary program in Python, Go, TypeScript, Rust or Java — no DuckDB internals, no C++, no rebuild per DuckDB release.
Building a Worker
Whether you should build one at all, what a worker can expose, picking a language, how it runs and gets hosted — then your language's tutorial.
Where to from here
Architecture
Transports, lifecycle, wire format — and what the process hop costs.
Connect to a worker
Every ATTACH option, transports, and settings.
SDK documentation
Concepts, tutorials and API reference for all five SDKs.
Stuck or want a hand?
We help teams design and ship VGI-based extensions.