# WeWeb Dashboard: Build Analytics Dashboards with DataGrid and Charts

- Tool: WeWeb
- Difficulty: Intermediate
- Time required: 60-90 min
- Compatibility: WeWeb Essential plan and above (Charts plugin requires plugin installation; DataGrid is built-in)
- Last updated: March 2026

## TL;DR

WeWeb's DataGrid (AG Grid-based) and Charts plugin (Chart.js) let you build production-grade analytics dashboards connected to Supabase, Xano, or any REST API. Use virtual scrolling in DataGrid for large datasets, bind Chart.js datasets to collections, and add Supabase Realtime to push live updates — all without writing framework code.

## Build an Analytics Dashboard in WeWeb with DataGrid and Charts

Dashboards are one of WeWeb's strongest use cases — the platform ships with a DataGrid component (built on AG Grid) for table-based data display and a Charts plugin (built on Chart.js) for visual analytics. Combined with collection data from Supabase or any REST API, you can build fully interactive dashboards with filtering, sorting, real-time updates, and exportable data. This tutorial covers the full dashboard build: setting up data collections, configuring the DataGrid with column definitions and virtual scrolling, adding Chart.js visualizations in both Guided and Advanced modes, creating KPI cards with computed variables, and enabling Supabase Realtime for live data updates.

## Before you start

- A WeWeb project with the Supabase data source plugin installed (or another backend connected via REST API)
- A Supabase database table with at least 50+ rows for meaningful dashboard data
- Basic understanding of WeWeb collections and the Data panel
- WeWeb Essential plan or above for production deployment

## Step-by-step guide

### 1. Create Dashboard Data Collections

In the WeWeb editor, open the Data panel (database icon in the left sidebar) and click + New to create your first collection. Select Supabase as the source, choose your analytics table, and configure the query. For a dashboard showing orders over time, select the 'orders' table, set the fetch mode to Dynamic (fetches on page load), and apply any date filters using the Filter section. Create a second collection for summary statistics — use a Supabase view or RPC (stored procedure) that returns pre-aggregated data (e.g., total revenue, order count by status). In the Supabase SQL editor, create a view: CREATE VIEW dashboard_summary AS SELECT status, COUNT(*) as count, SUM(amount) as total FROM orders GROUP BY status. Then in WeWeb, add a new collection pointing to that view.

```
-- Injection point: Supabase Dashboard → SQL Editor
-- Creates a view for dashboard KPI data
CREATE OR REPLACE VIEW public.dashboard_summary AS
SELECT
  status,
  COUNT(*) AS order_count,
  SUM(amount) AS total_revenue,
  AVG(amount) AS avg_order_value,
  DATE_TRUNC('day', created_at) AS day
FROM public.orders
GROUP BY status, DATE_TRUNC('day', created_at)
ORDER BY day DESC;
```

**Expected result:** Two collections visible in the Data panel: 'orders' (full records) and 'dashboardSummary' (aggregated KPIs).

### 2. Add and Configure the DataGrid Component

Click the Add panel (+) in the left sidebar, search for 'DataGrid', and drag it onto your dashboard page. Select the DataGrid and open its Settings panel. Under Data, click the plug icon (🔌) next to 'Row data' and bind it to your orders collection: collections['orders'].data. Switch to the Columns tab in DataGrid settings. Click + Add column for each field you want to display. For each column, set: Field (matches your data key, e.g., 'id', 'status', 'amount', 'created_at'), Header name (display label), and optionally a Value formatter for dates or currency. Enable Sortable and Filterable toggles per column. Under Pagination, set Page size to 25 and enable the pagination bar. Under Advanced settings, enable Row virtualization — this is critical for tables with 1,000+ rows as it only renders visible rows in the DOM, dramatically improving performance.

**Expected result:** DataGrid displays your orders with column headers, sortable columns, and pagination. Scrolling is smooth even with large datasets.

### 3. Install the Charts Plugin and Create a Line Chart

Navigate to Plugins → Extensions → Charts (if not already installed, click Install). After installation, the Charts element appears in the Add panel. Drag a Charts element onto the page. In its Settings panel, you can use Guided mode (simpler, dropdown-based configuration) or Advanced mode (full Chart.js config object). For a line chart of orders over time: select Chart type → Line. In Guided mode, bind the Labels field to a formula extracting dates from your collection: map(collections['dashboardSummary'].data, item => item.day). Bind the Datasets → Data field to: map(collections['dashboardSummary'].data, item => item.order_count). Set a dataset label ('Orders per Day') and choose a line color. In Advanced mode, you can pass a full Chart.js configuration object as a JSON-bound formula — useful for multi-dataset charts or custom tooltips.

```
// Injection point: Charts element → Settings → Advanced mode → Config formula
// Binds a multi-dataset Chart.js config to collection data
// Paste this as a formula in the Advanced Config field (plug icon)
return {
  type: 'line',
  data: {
    labels: map(collections['dashboardSummary'].data, item => formatDate(item.day, 'MMM D')),
    datasets: [
      {
        label: 'Order Count',
        data: map(collections['dashboardSummary'].data, item => item.order_count),
        borderColor: '#6366f1',
        backgroundColor: 'rgba(99, 102, 241, 0.1)',
        tension: 0.4,
        fill: true
      },
      {
        label: 'Revenue ($)',
        data: map(collections['dashboardSummary'].data, item => item.total_revenue),
        borderColor: '#10b981',
        backgroundColor: 'rgba(16, 185, 129, 0.1)',
        tension: 0.4,
        yAxisID: 'y1'
      }
    ]
  },
  options: {
    responsive: true,
    interaction: { mode: 'index', intersect: false },
    scales: {
      y: { type: 'linear', display: true, position: 'left' },
      y1: { type: 'linear', display: true, position: 'right', grid: { drawOnChartArea: false } }
    }
  }
}
```

**Expected result:** A responsive line chart appears showing order trends, with data sourced from your Supabase collection.

### 4. Build KPI Cards with Computed Variables

KPI cards display a single metric with a label and optional trend indicator. Create page-level variables for each KPI: Data panel → Variables → New → Number → name: 'totalRevenue'. Repeat for 'orderCount', 'avgOrderValue'. On page load, compute these from your collection using a workflow: Trigger → On page load → Action → Change variable value → bind the new value to a formula. For total revenue: reduce(collections['orders'].data, (acc, item) => acc + item.amount, 0). For order count: length(collections['orders'].data). Design a KPI card container with a Text element bound to the variable value (formatted with a formula: '$' + round(totalRevenue, 2)), a label below it ('Total Revenue'), and optionally a trend percentage. Duplicate the card three times and update bindings for each metric. Place all four cards in a horizontal flexbox container with wrap enabled so they stack on mobile.

**Expected result:** Four KPI cards display computed values that update when the underlying collection refreshes.

### 5. Enable Supabase Realtime for Live Updates

In the Data panel, select your 'orders' collection and click its settings. Enable the Realtime toggle — this requires Realtime to be enabled for the table in your Supabase project (Supabase Dashboard → Database → Replication → enable for the orders table). Back in WeWeb, go to the workflow editor and add a new workflow with trigger: On realtime - database change. Select your Supabase data source and the orders table. Listen for INSERT, UPDATE, and DELETE events. Add an action: Fetch collection → select 'orders' to refresh the full dataset. Also add Fetch collection for 'dashboardSummary' so your KPI cards and charts update simultaneously. This workflow fires automatically whenever a change is made to the orders table in Supabase, keeping your dashboard live without polling.

**Expected result:** Dashboard data refreshes automatically when records are inserted or updated in Supabase. No manual refresh needed.

### 6. Add Dashboard Filters

Dashboards are more useful with date range and status filters. Add a Date Picker element (Add panel → Date Picker) and a Select element to your dashboard header. Create two page-level variables: 'filterStartDate' (Text) and 'filterStatus' (Text). Bind each input's On change workflow to a 'Change variable value' action that sets the corresponding variable. Then update your 'orders' collection filter: in the collection settings, click the Filter tab → + Add filter → Field: created_at → Condition: is after → Value: bind to filterStartDate variable → enable 'Ignore if empty'. Add another filter for status: Field: status → Condition: equals → Value: filterStatus → Ignore if empty. Add a 'Fetch collection' action to both On change workflows to reload data when filters change.

**Expected result:** Selecting a date range or status filter immediately refetches the collection and updates the DataGrid and Charts.

## Complete code example

File: `dashboard-realtime-workflow.js`

```javascript
// Injection point: Workflow → Custom JavaScript action
// Purpose: Compute KPI values from collection data and return them
// for use in subsequent 'Change variable value' actions
// Trigger: On page load (after collections are fetched)
// OR: On realtime - database change (for live updates)

const orders = variables['ordersCollection']?.data || [];

if (!orders.length) {
  return {
    totalRevenue: 0,
    orderCount: 0,
    avgOrderValue: 0,
    pendingCount: 0,
    completedCount: 0,
    conversionRate: 0
  };
}

const totalRevenue = orders.reduce((sum, o) => sum + (o.amount || 0), 0);
const orderCount = orders.length;
const avgOrderValue = orderCount > 0 ? totalRevenue / orderCount : 0;
const pendingCount = orders.filter(o => o.status === 'pending').length;
const completedCount = orders.filter(o => o.status === 'completed').length;
const conversionRate = orderCount > 0
  ? Math.round((completedCount / orderCount) * 100)
  : 0;

// After this action, add 'Change variable value' actions for each KPI variable
// binding to the returned properties of this workflow step
return {
  totalRevenue: Math.round(totalRevenue * 100) / 100,
  orderCount,
  avgOrderValue: Math.round(avgOrderValue * 100) / 100,
  pendingCount,
  completedCount,
  conversionRate
};
```

## Common mistakes

- **Binding a Chart's data directly to a large un-aggregated collection array** — undefined Fix: Charts perform best with pre-aggregated data. Use a Supabase view, RPC function, or a formula with groupBy() to reduce your collection to summary data before binding to Chart datasets. Passing 10,000 raw rows to a chart causes slow renders and unreadable charts.
- **Forgetting to enable Row virtualization in DataGrid for large tables** — undefined Fix: In the DataGrid Settings panel → Advanced, enable Row virtualization. Without it, AG Grid renders all rows in the DOM at once. With 5,000+ rows, this causes significant memory usage and sluggish scrolling. Virtual scrolling only renders the visible rows (~20-30) plus a small buffer.
- **Enabling Supabase Realtime without enabling replication on the table in Supabase** — undefined Fix: In Supabase Dashboard → Database → Replication (or Table Editor → the table → Realtime toggle), enable Realtime for your table. WeWeb's Realtime trigger will silently fail if the Supabase table does not have replication enabled.
- **Using CSS display:none to hide charts instead of Conditional Rendering when toggling views** — undefined Fix: Chart.js does not correctly resize when its container transitions from display:none to visible. Use Conditional Rendering (element is removed from DOM) instead, or trigger a chart resize via a Custom JavaScript action after making a chart visible.

## Best practices

- Pre-aggregate data in your backend (Supabase views or RPC functions) rather than computing large datasets client-side in formula expressions
- Use DataGrid's server-side pagination for datasets over 10,000 rows — fetch only the current page from Supabase using LIMIT and OFFSET in your collection query
- Set a minimum DataGrid height using vh units (e.g., height: 60vh) so the table has room to display rows without requiring scroll on its container and the page simultaneously
- Give each Chart element a fixed height in px (e.g., 300px) — Chart.js needs a defined height to render correctly and will collapse to 0px in some flex container configurations
- Throttle collection refetches on filter changes using a 300ms Time delay action to avoid firing a Supabase query on every keystroke
- Use the Charts Guided mode for simple single-dataset charts, and Advanced mode (full Chart.js config object) when you need multiple y-axes, custom tooltips, or complex interactions
- Test your dashboard on tablet breakpoint — most dashboard users access via iPad, and DataGrid columns need explicit widths to avoid overflow on 768px screens

## Frequently asked questions

### Can the WeWeb DataGrid export data to CSV?

Yes. The DataGrid component includes a built-in CSV export. In the DataGrid Settings → Toolbar section, enable the Export button. When clicked, it exports all visible (filtered) rows. For programmatic export, use WeWeb's built-in 'Download CSV' workflow action — bind it to a button and pass your collection data array.

### What chart types does the WeWeb Charts plugin support?

The Charts plugin is built on Chart.js and supports: Line, Bar (vertical and horizontal), Pie, Doughnut, Radar, Polar Area, Bubble, and Scatter charts. In Guided mode, you select from a dropdown. In Advanced mode, you can use any Chart.js chart type and configuration option including mixed-type charts (bar + line on the same canvas).

### How do I show a loading skeleton while dashboard data is fetching?

Add a Skeleton Loader element (available in the Add panel) and set its Conditional Rendering condition to: collections['orders'].isFetching. Add the same condition (inverted: NOT isFetching) to your DataGrid and Charts containers. This shows the skeleton while data loads and switches to the real content once fetching completes.

### Can multiple users see the same live dashboard with Supabase Realtime?

Yes. Supabase Realtime broadcasts database changes to all connected clients. Each user's browser maintains its own WebSocket connection to Supabase. When any user or backend process modifies the orders table, all dashboards currently open in any browser receive the change event and can refetch their collections simultaneously.

---

Source: https://www.rapidevelopers.com/weweb-tutorial/weweb-dashboard
© RapidDev — https://www.rapidevelopers.com/weweb-tutorial/weweb-dashboard
