Back to Play 12 Resources
Play 12: Predictive Reporting

Dashboard Setup Guide (Google Sheets / Supabase)

Building a live dashboard from n8n data pulls. Lightweight approach for most firms.

Dashboard Setup Guide (Google Sheets / Supabase)

Most professional services firms drown in spreadsheets but starve for real-time visibility. You need a live dashboard that updates automatically, not another static report that's outdated the moment you export it.

This guide shows you how to build a production-grade dashboard using Google Sheets as your front end and Supabase as your data warehouse. Total monthly cost: $0-25 for most firms under 50 people. Setup time: 2-3 hours.

You'll connect your n8n workflows directly to a Postgres database, then pull that data into Google Sheets with automatic refresh triggers. No Tableau licenses. No Power BI subscriptions. No data engineering team required.

What You Need Before Starting

Active accounts:

  • n8n (cloud or self-hosted)
  • Google Workspace account with Sheets access
  • Supabase account (free tier works for most firms)

Technical requirements:

  • Basic SQL knowledge (SELECT, WHERE, JOIN)
  • Ability to copy/paste JavaScript code
  • 30 minutes of uninterrupted setup time

Data sources already connected in n8n: Your workflows should already be pulling data from your CRM

, billing system, or project management tools. If not, set those up first before building the dashboard.

Step 1: Create Your Supabase Data Warehouse

Supabase gives you a full Postgres database with a REST API

. You'll store all your n8n output here, then query it from Google Sheets.

1. Create a new Supabase project

Log into supabase.com and click "New Project". Choose these settings:

  • Name: [YourFirm]-DataWarehouse
  • Database Password: Generate a strong password and save it in your password manager
  • Region: Select the region closest to your primary office
  • Pricing Plan: Free tier (includes 500MB database, 2GB bandwidth)

Wait 2-3 minutes for provisioning to complete.

2. Create your first data table

Click "Table Editor" in the left sidebar, then "Create a new table". Use this schema for a revenue tracking table:

Table name: monthly_revenue

Columns:

  • id (int8, primary key, auto-increment) - already created by default
  • record_date (date, not null)
  • client_name (text, not null)
  • matter_id (text)
  • revenue_amount (numeric, not null)
  • practice_area (text)
  • partner_name (text)
  • created_at (timestamptz, default now()) - already created by default

Enable Row Level Security: OFF (you'll add authentication later if needed)

Click "Save" to create the table.

3. Get your API credentials

Click "Settings" > "API

" in the left sidebar. Copy these three values:

  • Project URL: https://[your-project-ref].supabase.co
  • anon public key: eyJhbGc... (long string)
  • service_role secret key: eyJhbGc... (different long string)

Store these in a secure note. You'll need them in the next steps.

4. Test your database connection

Click "SQL Editor" in the left sidebar. Run this test query:

SELECT * FROM monthly_revenue LIMIT 10;

You should see an empty result set (no errors). If you get an error, double-check your table name matches exactly.

Step 2: Connect n8n to Supabase

Now you'll modify your existing n8n workflows to write data into Supabase instead of (or in addition to) Google Sheets.

1. Add Supabase credentials to n8n

In n8n, go to "Credentials" > "New" > "Supabase API

". Enter:

  • Credential Name: Supabase Production
  • Host: Your Project URL from Step 1.3 (without https://)
  • Service Role Secret: Your service_role key from Step 1.3

Click "Save".

2. Add a Supabase node to your workflow

Open your existing data collection workflow (for example, your "Daily Revenue Pull" workflow). Add a new node after your data transformation step.

Search for "Supabase" and add the node. Configure it:

  • Credential: Select "Supabase Production"
  • Operation: Insert
  • Table: monthly_revenue
  • Data to Send: All

3. Map your data fields

Click "Add Field" for each column in your Supabase table. Map them to the corresponding output from your previous node:

  • record_date: {{ $json.date }}
  • client_name: {{ $json.client }}
  • matter_id: {{ $json.matter_id }}
  • revenue_amount: {{ $json.amount }}
  • practice_area: {{ $json.practice }}
  • partner_name: {{ $json.partner }}

Adjust these expressions based on your actual data structure.

4. Handle duplicates

Add an "On Conflict" setting to prevent duplicate records. In the Supabase node settings:

  • On Conflict: Ignore
  • Conflict Columns: record_date, client_name, matter_id

This prevents the same record from being inserted twice if your workflow runs multiple times.

5. Test the workflow

Click "Execute Workflow" in n8n. Check the Supabase Table Editor to confirm your data appeared in the monthly_revenue table. You should see new rows with your test data.

6. Schedule automatic runs

Add a "Schedule Trigger" node at the start of your workflow:

  • Trigger Interval: Days
  • Days Between Triggers: 1
  • Trigger at Hour: 6 (6 AM)
  • Trigger at Minute: 0

Activate the workflow. It will now run daily at 6 AM and push fresh data to Supabase.

Step 3: Build Your Google Sheets Dashboard

You'll use Google Apps Script to query Supabase and populate your dashboard automatically.

1. Create your dashboard spreadsheet

Open Google Sheets and create a new spreadsheet named "[YourFirm] Executive Dashboard".

Create three tabs:

  • Dashboard (your main view)
  • Raw Data (where Supabase data loads)
  • Config (stores your API
    credentials)

2. Store your Supabase credentials

In the "Config" tab, add these values:

  • Cell A1: SUPABASE_URL
  • Cell B1: Your Project URL from Step 1.3
  • Cell A2: SUPABASE_KEY
  • Cell B2: Your anon public key from Step 1.3

Right-click the "Config" tab and select "Hide sheet" to keep credentials out of view.

3. Add the Supabase client library

Go to "Extensions" > "Apps Script". Delete any default code. Paste this:

function getSupabaseData(table, selectQuery = '*', filterColumn = null, filterValue = null) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Config');
  const url = sheet.getRange('B1').getValue();
  const key = sheet.getRange('B2').getValue();
  
  let endpoint = `${url}/rest/v1/${table}?select=${selectQuery}`;
  
  if (filterColumn && filterValue) {
    endpoint += `&${filterColumn}=eq.${filterValue}`;
  }
  
  const options = {
    'method': 'get',
    'headers': {
      'apikey': key,
      'Authorization': `Bearer ${key}`,
      'Content-Type': 'application/json'
    },
    'muteHttpExceptions': true
  };
  
  const response = UrlFetchApp.fetch(endpoint, options);
  const data = JSON.parse(response.getContentText());
  
  return data;
}

function refreshDashboardData() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const rawDataSheet = ss.getSheetByName('Raw Data');
  
  // Clear existing data
  rawDataSheet.clear();
  
  // Fetch data from Supabase
  const data = getSupabaseData('monthly_revenue', '*');
  
  if (data.length === 0) {
    rawDataSheet.getRange(1, 1).setValue('No data found');
    return;
  }
  
  // Write headers
  const headers = Object.keys(data[0]);
  rawDataSheet.getRange(1, 1, 1, headers.length).setValues([headers]);
  
  // Write data rows
  const rows = data.map(row => headers.map(header => row[header]));
  rawDataSheet.getRange(2, 1, rows.length, headers.length).setValues(rows);
  
  // Format as table
  rawDataSheet.getRange(1, 1, 1, headers.length).setFontWeight('bold');
  rawDataSheet.setFrozenRows(1);
  
  Logger.log(`Loaded ${data.length} rows from Supabase`);
}

Click "Save" (disk icon). Name your project "Dashboard Automation".

4. Test the data pull

Click "Run" > "refreshDashboardData". You'll be prompted to authorize the script. Click "Review Permissions" > select your Google account > "Allow".

Switch back to your spreadsheet. The "Raw Data" tab should now contain all rows from your Supabase monthly_revenue table.

5. Build your dashboard visualizations

In the "Dashboard" tab, create your key metrics using formulas that reference the "Raw Data" tab:

Total Revenue This Month:

=SUMIFS('Raw Data'!E:E, 'Raw Data'!B:B, ">="&DATE(YEAR(TODAY()),MONTH(TODAY()),1))

Revenue by Practice Area: Create a pivot table:

  • Data range: 'Raw Data'!A:G
  • Rows: practice_area
  • Values: SUM of revenue_amount
  • Insert as new sheet, then copy into Dashboard tab

Top 5 Clients:

=QUERY('Raw Data'!A:G, "SELECT C, SUM(E) WHERE B >= date '"&TEXT(DATE(YEAR(TODAY()),MONTH(TODAY()),1),"yyyy-MM-dd")&"' GROUP BY C ORDER BY SUM(E) DESC LIMIT 5 LABEL C 'Client', SUM(E) 'Revenue'")

Add charts by selecting your data ranges and clicking "Insert" > "Chart". Use column charts for practice area comparison and bar charts for top clients.

6. Set up automatic refresh

Go back to Apps Script ("Extensions" > "Apps Script"). Click "Triggers" (clock icon in left sidebar) > "Add Trigger".

Configure:

  • Function: refreshDashboardData
  • Deployment: Head
  • Event source: Time-driven
  • Type: Day timer
  • Time of day: 7am to 8am

Click "Save". Your dashboard will now refresh every morning after your n8n workflow runs.

Step 4: Add Advanced Dashboard Features

Query specific date ranges

Modify your refreshDashboardData() function to accept parameters:

function refreshDashboardData(daysBack = 90) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const rawDataSheet = ss.getSheetByName('Raw Data');
  
  rawDataSheet.clear();
  
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - daysBack);
  const dateString = cutoffDate.toISOString().split('T')[0];
  
  const data = getSupabaseData('monthly_revenue', '*', 'record_date', `gte.${dateString}`);
  
  // ... rest of function remains the same
}

Add a manual refresh button

In your Dashboard tab, insert a button:

  • "Insert" > "Drawing" > create a rectangle with text "Refresh Data"
  • Click "Save and Close"
  • Click the three dots on the drawing > "Assign script"
  • Enter: refreshDashboardData

Users can now click the button to force a refresh.

Create a summary email

Add this function to Apps Script:

function emailDashboardSummary() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const dashboardSheet = ss.getSheetByName('Dashboard');
  
  const totalRevenue = dashboardSheet.getRange('B2').getValue(); // Adjust cell reference
  const topClient = dashboardSheet.getRange('B10').getValue(); // Adjust cell reference
  
  const emailBody = `Daily Dashboard Summary\n\nTotal Revenue (MTD): ${totalRevenue}\nTop Client: ${topClient}\n\nView full dashboard: ${ss.getUrl()}`;
  
  MailApp.sendEmail({
    to: 'partners@yourfirm.com',
    subject: 'Daily Revenue Dashboard - ' + new Date().toLocaleDateString(),
    body: emailBody
  });
}

Add a daily trigger for this function to send automated summary emails.

Troubleshooting Common Issues

"No data found" in Raw Data tab

Check your Supabase table has data. Run this query in Supabase SQL Editor:

SELECT COUNT(*) FROM monthly_revenue;

If the count is 0, your n8n workflow isn't writing data. Check the Supabase node execution logs in n8n.

"Authorization failed" error

Your API

key is incorrect or expired. Go to Supabase Settings > API
and copy a fresh anon key. Update cell B2 in your Config tab.

Data refreshes but charts don't update

Charts don't auto-refresh when underlying data changes. After running refreshDashboardData(), manually click each chart > three dots > "Refresh" once. Then they'll update automatically on subsequent refreshes.

Script timeout errors

If you're pulling more than 10,000 rows, the script may timeout. Add pagination to your getSupabaseData() function:

endpoint += `&limit=1000&offset=${offset}`;

Call the function multiple times with increasing offset values.

Bottom Line

You now have a live dashboard that costs nothing to run and updates automatically every morning. Your partners can open one Google Sheet and see current revenue, client activity, and practice area performance without waiting for month-end reports.

Next steps: Add more tables to Supabase for utilization tracking, pipeline data, or collections metrics. Each new data source follows the same pattern: n8n workflow > Supabase table > Google Sheets query.

For firms over 50 people or those needing sub-hour refresh rates, consider upgrading to Supabase Pro ($25/month) and switching to a dedicated BI tool like Metabase (open source) or Looker Studio (free for Google Workspace users).

Revenue Institute

Reviewed by Revenue Institute

This guide is actively maintained and reviewed by the implementation experts at Revenue Institute. As the creators of The AI Workforce Playbook, we test and deploy these exact frameworks for professional services firms scaling without new headcount.

Revenue Institute

Need help turning this guide into reality? Revenue Institute builds and implements the AI workforce for professional services firms.

RevenueInstitute.com