---
title: "Building a Dashboard with Charts · RUAL Documentation"
description: "Aggregate storage data and render it as a live dashboard."
canonical: https://docs.rual.nl/tutorials/dashboard-charts
language: en
---

# Building a Dashboard with Charts

Aggregate your storage data, render it live on a page, and keep it fast. The dashboard pattern end to end.

A dashboard is a page backed by a few well-chosen queries: counts, sums, recent items, and grouped stats. This tutorial assembles one: data first, then rendering, then keeping it fast.

### Step 1, Get the Data

Dashboards read. Never write. Build a function per metric (or one function returning an object with all of them):

- **Counts**: `function_search` with the `hits` out-pin (total matching documents), one per filtered query: open tasks, shipped orders, this week's signups.

- **Recent items**: `function_search` + [`query_sort_field`](https://docs.rual.nl/block-types/query/query_sort_field) on `_meta.created` descending + `limit` 10. See [search tips](https://docs.rual.nl/blueprints/storage#search-tips).

- **Grouped stats**: aggregations where the storage supports them; for anything heavy, precompute counters on write instead (see performance below).

Return everything from one function as an object built with [`object_new_fields`](https://docs.rual.nl/block-types/object/object_new_fields). One call, one payload for the page.

### Step 2, Render the Page

- A `state_page` for the dashboard URL, scoped for your team (`*loggedin` or a custom scope).

- On load, run the metrics function from the render flow and pass results into state elements: number tiles (styled elements), a recent-items list (iterate the array), and your chart component.

- For real charts, mount a custom React component in the page and feed it the metrics object. See [Components](https://docs.rual.nl/interfaces/components) and [RUAL Library](https://docs.rual.nl/interfaces/rual-library) for passing data to components.

- Want it live? Real-time search updates the metrics as documents change: worth it on shared boards, overkill on quiet data (see [real-time vs polling](https://docs.rual.nl/architecture/choosing-approaches#realtime-vs-polling)).

### Step 3, Keep It Fast

- **Cache the metrics function** with a `cache_key` on `function_search` and a short TTL via Redis. The dashboard loads instantly even with thousands of documents. See [caching patterns](https://docs.rual.nl/blueprints/common-patterns#caching-strategies).

- **Precompute counters on write**: increment a stats document on each create ([`mutations_increment_by_field`](https://docs.rual.nl/block-types/mutations/mutations_increment_by_field)) instead of counting at read time; reads become a single [`function_get_document`](https://docs.rual.nl/block-types/storage/function_get_document).

- **Avoid dashboard-side loops**. If the page needs per-item computation, do it in the function with `array_map`, not in the render.

- Watch out for the aggregation traps in [Common Pitfalls](https://docs.rual.nl/blueprints/common-pitfals).

### Checklist

- One metrics function returning one object.

- Sort + limit on every "recent" list.

- Cache key on expensive searches; counters for hot stats.

- Scope set intentionally (no accidental `*public` dashboards).

### Next Steps
