Public testing phase We’re in a public testing phase — feel free to look around, but we’re not taking orders yet. Ordering opens 1 October 2026. See plans →

Dashboards: charts over your data

Dashboards turns the tables in your site databases and your warehouse into charts, stats and tables that everyone on your account can open — Grafana-like, with a time picker, auto-refresh and variables, but with nothing to install. Open it from the sidebar, next to Databases.

What a dashboard is

A dashboard is a 24-column grid of panels. Each panel is one read-only SQL query against one of your databases (a site's MariaDB or PostgreSQL database, or a warehouse database that a job or an import fills) and one way of showing the result:

  • Time series — lines, areas or bars over time; several series from several numeric columns, or from a group column;
  • Bar — categories side by side, horizontal or vertical;
  • Stat — one number with a sparkline and threshold colours;
  • Gauge — a value on an arc between a minimum and a maximum;
  • Table — sortable rows, with units per column;
  • Pie — shares of a whole, as a pie or a donut.

Press e to edit and drag panels around, v to go back to viewing, t for the time picker, f for a panel full-screen and Esc to close the panel editor. Every dashboard keeps its last 20 saved versions, can be starred, duplicated, and exported as a JSON file (veldhost.dashboard/v1) that imports into any account.

Time and refresh

The time picker offers the usual presets (last 5 minutes to last 90 days), an absolute range, and the now-… grammar: now-24h, now-7d, now/d (since midnight), now-1d/d (yesterday, whole day), with units s m h d w M y. The range is in the URL (?from=now-7d&to=now), so a link opens exactly what you saw. Auto-refresh is off, 30 s, 1, 5 or 15 minutes, and pauses while the tab is hidden. Dashboards are UTC: every query runs with the session time zone set to UTC, and the time macros are epoch-based, so the same query gives the same buckets on MariaDB and PostgreSQL.

Macros

Panel SQL may use these macros. They are expanded on the server, for the current time range, before the read-only guard checks the statement — so the engine only ever sees plain SQL. F and T are the range's start and end as epoch seconds; N is the automatic bucket width in seconds (the smallest step from 1 s to 30 d that keeps a series under the panel's max points, never below its min interval).

MacroMariaDBPostgreSQL
$__timeFilter(col)(col >= FROM_UNIXTIME(F) AND col < FROM_UNIXTIME(T))(col >= to_timestamp(F) AND col < to_timestamp(T))
$__timeFrom() / $__timeTo()FROM_UNIXTIME(F) / FROM_UNIXTIME(T)to_timestamp(F) / to_timestamp(T)
$__unixEpochFilter(col)(col >= F AND col < T)
$__unixEpochFrom() / $__unixEpochTo()F / T
$__intervalINTERVAL N SECONDINTERVAL 'N seconds'
$__interval_s, $__range_sthe bucket width / the range length, in seconds
$__timeGroup(col[, iv])FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(col) / N) * N)to_timestamp(floor(extract(epoch from col) / N) * N)
$__timeGroupAlias(col[, iv])… AS `time`… AS "time"

iv is $__interval (the default) or a literal like 5m, 1h, 1d; a bucket narrower than the panel's min interval is widened to it. The column must be a plain or quoted column name — an expression is refused. A time series panel expects a first column named time:

-- events per bucket (MariaDB or PostgreSQL alike)
SELECT $__timeGroupAlias(created_at, $__interval), count(*) AS events
FROM analytics_events
WHERE $__timeFilter(created_at)
GROUP BY 1 ORDER BY 1
-- top pages in the range, for a bar panel
SELECT path, count(*) AS hits
FROM analytics_events
WHERE $__timeFilter(created_at)
GROUP BY 1 ORDER BY 2 DESC LIMIT 10

Variables

A variable is a dropdown (or a text box) under the top bar whose value goes into every panel that references it, and into the URL (?var-site=shop). Three kinds:

  • custom — a fixed list of value/label pairs;
  • query — the first column of a query is the value, the second (if any) the label; the query may use the macros and other variables;
  • textbox — free text; with validate: number only a number is accepted.

Reference a variable as ${site} or $site. The value is always inserted as a complete string literal, quoted for the engine — WHERE site = ${site}, never WHERE site = '${site}'. A multi-select becomes a comma-separated list of literals (WHERE site IN (${site})), "All" expands to every option (up to 500), an empty selection becomes NULL, and a number text box inserts bare digits. There is no raw mode: a value can never change the shape of the query. To match part of a string, build it in SQL: CONCAT('%', ${q}, '%') on MariaDB, '%' || ${q} || '%' on PostgreSQL.

Who sees what

Everyone on the account sees every dashboard — viewers and billing included — because a panel runs with the right of the person who last edited its SQL, never the viewer's. Someone who could not query the panel's database themselves sees the chart and its rows, but not the SQL and not the engine's error text (only "this panel's query failed"). Owners, admins and developers create and edit dashboards; the author, or an owner or admin, deletes one. If the editor of a panel leaves the account, that panel stops until someone with access saves it again.

Caching and limits

Every panel query is answered from the most recent identical run while it is fresh: five people looking at the same dashboard, or two tabs of yours, share one query per panel per refresh. The cache lasts for the refresh interval (at least 30 s) or, without auto-refresh, 5 minutes; a manual refresh runs the query again. Relative ranges are snapped to the refresh interval so that identical requests really are identical.

The published limits are the standard offer:

  • 50 dashboards per account, 24 panels and 10 variables per dashboard;
  • 1,000 rows per panel, 500 points per series (the automatic bucket keeps series under it), 30 seconds per query;
  • 8 dashboard queries running at the same time and 30,000 a day per account, counted separately from the editor's limits — a busy dashboard never blocks the SQL editor;
  • auto-refresh at least every 30 seconds; when the daily budget is 80 % used the page drops to a 5-minute refresh and says so.

Need more? Contact us — larger limits are available on request, for a fee.

From a script or your AI assistant

Dashboards are in the API and the MCP server, behind the same opt-in data scope as Databases:

  • GET /api/v1/dashboards, POST /api/v1/dashboards — list and create (with panels and variables in one go);
  • GET / PUT / DELETE /api/v1/dashboards/{uid} — the full model (send back the version you read; a stale one is a 409);
  • POST /api/v1/dashboards/{uid}/panels, PUT / DELETE …/panels/{key} — one panel at a time;
  • POST /api/v1/dashboards/{uid}/panels/{key}/data — the rows behind a panel for a time range, from the same cache.

An assistant gets list_dashboards, get_dashboard, create_dashboard, add_panel and render_panel_data. A token limited to particular sites sees a panel's SQL only when the panel's database belongs to one of those sites.