Skip to content
Guide

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.

terminal
# 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:

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.

Haybarn shell
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;
result
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.

Open in Cupola ↗

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.

Haybarn shell
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;
result
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.

Haybarn shell
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;
result
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.

one SELECT, two catalogs plans across both, joins the results DELIM_JOIN VGI_TABLE_SCAN Filters: mag >= 6.0Projections: mag, place, latitude, longitude filter + columns pushed down earthquakes worker wraps the USGS feed VGI_LATERAL_BATCH Function: forecast_currentProjected: 2 called per row, batched open-meteo worker wraps the forecast API

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:

Haybarn shell
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;
result
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:

Haybarn shell
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.

Start building →

Where to from here

Stuck or want a hand?

We help teams design and ship VGI-based extensions.

Talk to us