# Visualize MotherDuck Data in Grafana
> Runs a local Grafana instance in Docker with the MotherDuck DuckDB datasource plugin auto-provisioned, plus example dashboards that query MotherDuck with SQL. Use when you want to build Grafana dashboards or time-series panels on top of MotherDuck data.
<ExampleHeader
  githubUrl={"https://github.com/motherduckdb/motherduck-examples/tree/main/motherduck-grafana"}
  features={[]}
  tags={["grafana","docker"]}
/>

A local Grafana setup that connects to MotherDuck through the `motherduck-duckdb-datasource` plugin. The `setup.sh` script downloads the latest plugin release from `motherduckdb/grafana-duckdb-datasource`, starts a Grafana container, and mounts a `provisioning/` directory so the datasource and example dashboards are configured on boot. It shows the MotherDuck pattern of using an access token as a Grafana datasource credential, attaching a MotherDuck database (here the public `sample_data` share) on connect, and running DuckDB SQL directly in panel queries with results formatted as time series.

## How it works

- `setup.sh`:
  - Detects the OS and resolves the plugin directory. Windows (`CYGWIN`/`MINGW`/`MSYS`) and unknown systems exit early; macOS and Linux are supported.
  - Queries the GitHub API for the latest `motherduck-duckdb-datasource` release, downloads the `.zip`, and unzips it into `plugins/` (gitignored, so the plugin is fetched fresh and never committed).
  - Stops and removes any prior Grafana container on port 3000, then fails fast if the port is still in use:

```bash
    if lsof -Pi :3000 -sTCP:LISTEN -t >/dev/null; then
      echo "❌ Port 3000 is already in use. Please free up the port and try again."
      exit 1
    fi
```

  - Validates the token before starting the container, so a missing credential fails loudly instead of producing a half-broken datasource:

```bash
    if ! printenv motherduck_token >/dev/null 2>&1; then
      echo "❌ Required environment variable 'motherduck_token' is not set." >&2
      exit 1
    fi
```

  - Runs the container with the plugin and `provisioning/` mounted, allows the unsigned plugin using `GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=motherduck-duckdb-datasource`, and passes the token through as an environment variable. It then waits up to 5 seconds for the container to report `running`.
- `provisioning/datasources/sample_data.yaml`: defines the datasource. `database` is blank (MotherDuck infers it), `initSql` attaches `md:sample_data` on connect, and the token is read at runtime from the `motherduck_token` environment variable using `$__env{motherduck_token}`, so no secret is written into the file.
- `provisioning/dashboards/dashboards.yaml`: registers the file provider that loads every dashboard from `/etc/grafana/provisioning/dashboards/json`.
- `provisioning/dashboards/json/`: holds the dashboard definitions. `nyc_services.json` groups `sample_data.nyc.service_requests` by `created_date`; `nyc_rideshare.json` groups `sample_data.nyc.rideshare` by `request_datetime`.

## What you'll adjust

| Setting | Purpose | Options / example |
| --- | --- | --- |
| `motherduck_token` env var | MotherDuck auth token passed to Grafana and the datasource. `setup.sh` requires it (it exits if unset) and the datasource reads it as `$__env{motherduck_token}` | `export motherduck_token=<your-token>` |
| `provisioning/datasources/sample_data.yaml` `initSql` | DuckDB SQL run when the datasource connects, used to attach databases | `ATTACH IF NOT EXISTS 'md:sample_data'`, or `ATTACH IF NOT EXISTS 'md:my_db'` |
| `provisioning/datasources/sample_data.yaml` `database` | Default database for the connection. Leave blank for MotherDuck | `""` |
| `provisioning/datasources/sample_data.yaml` `name` / `isDefault` | Datasource display name and default flag panels reference | `MotherDuck-Sample-Data`, `isDefault: true` |
| Panel `rawSql` in `provisioning/dashboards/json/*.json` | The DuckDB query each dashboard panel runs | `SELECT created_date, COUNT(status) FROM sample_data.nyc.service_requests GROUP BY created_date LIMIT 50` |
| `provisioning/dashboards/json/*.json` | Dashboard definitions auto-loaded from this folder | `nyc_services.json`, `nyc_rideshare.json`, or your own exported dashboard |
| `provisioning/dashboards/dashboards.yaml` `path` | Where Grafana looks for dashboard JSON inside the container | `/etc/grafana/provisioning/dashboards/json` |
| Grafana port mapping in `setup.sh` | Host port Grafana is published on | `-p 3000:3000` |
| Grafana image tag in `setup.sh` | Grafana version that runs in the container | `grafana/grafana:latest-ubuntu` |

## Questions to answer

- Which MotherDuck database(s) and schema(s) should be attached (the `initSql` `ATTACH` target)?
- Which tables or queries power the dashboards, and what is the time column for time-series panels?
- Is a MotherDuck access token available, and should it be a read scaling token (recommended for read-only dashboard traffic)?
- Should this stay a local Docker setup, or be adapted for a hosted Grafana deployment?
- Are there existing dashboard JSON exports to drop into `provisioning/dashboards/json/`?

## Run it

Prerequisites: Docker installed and running, plus a MotherDuck access token. macOS and Linux only (see Caveats for Windows).

```bash
export motherduck_token=<your-motherduck-token>
cd motherduck-grafana
./setup.sh
```

Then open Grafana at `http://localhost:3000` (default login `admin` / `admin`). The MotherDuck datasource and the example NYC dashboards (`NYC Services` and `NYC_rideshare`) are already provisioned.

When building a new panel:

1. Create a new dashboard and add a panel.
2. Select `MotherDuck-Sample-Data` as the data source.
3. Set the panel format to **time series** at the top of the query (panels expect a time column).
4. Switch the query editor from **builder** to **code** to enter DuckDB SQL manually, for example:

```sql
SELECT created_date, COUNT(status)
FROM sample_data.nyc.service_requests
GROUP BY created_date LIMIT 1000
```

`image.png` shows where to set the time-series format and the code-mode query editor.

To add your own dashboard, export it from the Grafana UI (the **Export** button, or copy the JSON model from dashboard settings under **JSON model**), save it as `provisioning/dashboards/json/<name>.json`, then commit it. It is auto-loaded on the next `setup.sh` run.

## Files

- [`setup.sh`](https://github.com/motherduckdb/motherduck-examples/blob/main/motherduck-grafana/setup.sh) - the runner: detects the OS, downloads the latest MotherDuck DuckDB datasource plugin from GitHub, validates the `motherduck_token` env var, then starts a Grafana Docker container with the plugin and `provisioning/` mounted.
- [`provisioning/`](https://github.com/motherduckdb/motherduck-examples/tree/main/motherduck-grafana/provisioning/) - the Grafana provisioning tree mounted into the container, holding the datasource and dashboard config:
  - [`provisioning/datasources/sample_data.yaml`](https://github.com/motherduckdb/motherduck-examples/blob/main/motherduck-grafana/provisioning/datasources/sample_data.yaml) - defines the `MotherDuck-Sample-Data` datasource, attaching `md:sample_data` using `initSql` and reading the token from `$__env{motherduck_token}`.
  - [`provisioning/dashboards/dashboards.yaml`](https://github.com/motherduckdb/motherduck-examples/blob/main/motherduck-grafana/provisioning/dashboards/dashboards.yaml) - registers the file provider that auto-loads every dashboard JSON from `/etc/grafana/provisioning/dashboards/json`.
  - [`provisioning/dashboards/json/`](https://github.com/motherduckdb/motherduck-examples/tree/main/motherduck-grafana/provisioning/dashboards/json/) - the example dashboard definitions (2 files): `nyc_services.json` (NYC Services, service requests by `created_date`) and `nyc_rideshare.json` (NYC_rideshare, rideshare totals).
- [`image.png`](https://github.com/motherduckdb/motherduck-examples/blob/main/motherduck-grafana/image.png) - screenshot showing where to set the time-series format and the code-mode query editor when building a panel.
- `.gitignore` - excludes the downloaded `plugins/` directory and `.DS_Store`, so the plugin is fetched fresh by `setup.sh` and never committed.

## Caveats

- **Token in the environment, not the config.** The token is injected through `$__env{motherduck_token}` and the Docker `-e` flag at runtime. Do not hardcode it into `sample_data.yaml`, which is committed to the repo.
- **Token must be exported before `setup.sh`.** If `motherduck_token` is unset the script exits before starting Grafana. If you start Grafana some other way without the variable, the datasource provisions but every query fails to authenticate.
- **Windows is not supported by the script.** It exits on `CYGWIN`/`MINGW`/`MSYS`. On Windows, download and unzip the plugin into the Grafana plugin folder manually and run the container yourself.
- **Port 3000 must be free.** The script stops a prior Grafana container on that port, but if anything else is listening on 3000 it aborts. Change the `-p` mapping if 3000 is taken.
- **Unsigned plugin.** The datasource is loaded using `GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS`. Grafana will refuse to load it without that allow-list entry.
- **Time-series panels need a time column.** The panel format defaults to a table view; switch it to **time series** and make sure the query returns a timestamp/date column (`created_date`, `request_datetime`), or the panel renders nothing useful.
- **Builder mode does not write DuckDB SQL for you.** Switch the query editor to **code** mode to run DuckDB syntax; the visual builder will not produce the MotherDuck-specific queries.
- **`plugins/` is downloaded, not committed.** A fresh clone has no plugin until `setup.sh` runs and reaches GitHub; an offline machine cannot provision the datasource.
- **`sample_data` is a public MotherDuck share.** The example dashboards read from it. To query your own data, change the `initSql` `ATTACH` target and the panel `rawSql` to your database and tables.

## Learn more

- For DuckDB SQL syntax, attaching databases, or read scaling tokens, use the `ask_docs_question` MCP tool or the MotherDuck docs.
- MotherDuck DuckDB datasource plugin: `motherduckdb/grafana-duckdb-datasource` on GitHub.


---

## Docs feedback

MotherDuck accepts optional user-submitted feedback about this page at `POST https://motherduck.com/docs/api/feedback/agent`.
For agents and automated tools, feedback submission should be user-confirmed before sending.

Payload:

```json
{
  "page_path": "/cookbook/motherduck-grafana/",
  "page_title": "Visualize MotherDuck Data in Grafana",
  "text": "<the user's feedback, max 2000 characters>",
  "source": "<optional identifier for your interface, for example 'claude.ai' or 'chatgpt'>"
}
```

`page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": "<uuid>"}`, `400` for malformed payloads, and `429` when rate-limited.
