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.
Prerequisites
- 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
Create Dashboard Data Collections
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.
1-- Injection point: Supabase Dashboard β SQL Editor2-- Creates a view for dashboard KPI data3CREATE OR REPLACE VIEW public.dashboard_summary AS4SELECT5 status,6 COUNT(*) AS order_count,7 SUM(amount) AS total_revenue,8 AVG(amount) AS avg_order_value,9 DATE_TRUNC('day', created_at) AS day10FROM public.orders11GROUP BY status, DATE_TRUNC('day', created_at)12ORDER BY day DESC;Expected result: Two collections visible in the Data panel: 'orders' (full records) and 'dashboardSummary' (aggregated KPIs).
Add and Configure the DataGrid Component
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.
Install the Charts Plugin and Create a Line Chart
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.
1// Injection point: Charts element β Settings β Advanced mode β Config formula2// Binds a multi-dataset Chart.js config to collection data3// Paste this as a formula in the Advanced Config field (plug icon)4return {5 type: 'line',6 data: {7 labels: map(collections['dashboardSummary'].data, item => formatDate(item.day, 'MMM D')),8 datasets: [9 {10 label: 'Order Count',11 data: map(collections['dashboardSummary'].data, item => item.order_count),12 borderColor: '#6366f1',13 backgroundColor: 'rgba(99, 102, 241, 0.1)',14 tension: 0.4,15 fill: true16 },17 {18 label: 'Revenue ($)',19 data: map(collections['dashboardSummary'].data, item => item.total_revenue),20 borderColor: '#10b981',21 backgroundColor: 'rgba(16, 185, 129, 0.1)',22 tension: 0.4,23 yAxisID: 'y1'24 }25 ]26 },27 options: {28 responsive: true,29 interaction: { mode: 'index', intersect: false },30 scales: {31 y: { type: 'linear', display: true, position: 'left' },32 y1: { type: 'linear', display: true, position: 'right', grid: { drawOnChartArea: false } }33 }34 }35}Expected result: A responsive line chart appears showing order trends, with data sourced from your Supabase collection.
Build KPI Cards with Computed Variables
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.
Enable Supabase Realtime for Live Updates
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.
Add Dashboard Filters
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 working example
1// Injection point: Workflow β Custom JavaScript action2// Purpose: Compute KPI values from collection data and return them3// for use in subsequent 'Change variable value' actions4// Trigger: On page load (after collections are fetched)5// OR: On realtime - database change (for live updates)67const orders = variables['ordersCollection']?.data || [];89if (!orders.length) {10 return {11 totalRevenue: 0,12 orderCount: 0,13 avgOrderValue: 0,14 pendingCount: 0,15 completedCount: 0,16 conversionRate: 017 };18}1920const totalRevenue = orders.reduce((sum, o) => sum + (o.amount || 0), 0);21const orderCount = orders.length;22const avgOrderValue = orderCount > 0 ? totalRevenue / orderCount : 0;23const pendingCount = orders.filter(o => o.status === 'pending').length;24const completedCount = orders.filter(o => o.status === 'completed').length;25const conversionRate = orderCount > 026 ? Math.round((completedCount / orderCount) * 100)27 : 0;2829// After this action, add 'Change variable value' actions for each KPI variable30// binding to the returned properties of this workflow step31return {32 totalRevenue: Math.round(totalRevenue * 100) / 100,33 orderCount,34 avgOrderValue: Math.round(avgOrderValue * 100) / 100,35 pendingCount,36 completedCount,37 conversionRate38};Common mistakes
Why it's a problem: Binding a Chart's data directly to a large un-aggregated collection array
How to avoid: 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.
Why it's a problem: Forgetting to enable Row virtualization in DataGrid for large tables
How to avoid: 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.
Why it's a problem: Enabling Supabase Realtime without enabling replication on the table in Supabase
How to avoid: 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.
Why it's a problem: Using CSS display:none to hide charts instead of Conditional Rendering when toggling views
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building an analytics dashboard with WeWeb (a no-code Vue.js builder) connected to Supabase. I have an 'orders' table with columns: id, amount, status, created_at, user_id. Write a PostgreSQL view that returns daily aggregated revenue and order count for the last 30 days, suitable for binding to a Chart.js line chart.
In my WeWeb dashboard, I have a Charts element in Advanced mode. Help me write a formula for the Config field that creates a bar chart using collections['monthlySales'].data, where each item has 'month' (string) and 'revenue' (number) fields. The chart should have a blue color scheme and currency-formatted tooltips.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation