Skip to content

Walkthrough: project costs ​

This builds the demo's Project costs data source from scratch. Follow it on your own lists, or import the demo and compare. Allow about 20 minutes.

The goal: one row per project, showing its labour cost, expenses, total spend, budget used and a status, plus portfolio totals for KPI cards.

Step 1: the child data sources ​

Create two This Site data sources: Tasks on FormulasDemoTasks and Expenses on FormulasDemoExpenses.

Name them short

The name becomes the prefix in every formula: {[Tasks_Hours]}. Tasks reads much better than FormulasDemoTasks.

In Tasks, add:

FieldKindTypeFormula
Costdynamic column (+ Dynamic column)Number{[Hours]} * {[Rate]}
Total hoursaggregated field (Σ Aggregated field)NumberSum({[Hours]})

Tasks > Cost

Save & Exit.

Step 2: the parent and the Reference only connections ​

  1. Create a This Site data source Project costs on FormulasDemoProjects.
  2. Click Connect data source (the circled + in the designer's top bar, not the + Dynamic column button under the fields), pick Tasks, click Connect.
  3. In the Tasks card header, open the merge settings and pick Reference only. Save.
  4. Repeat for Expenses.

Reference only

Step 3: per-project totals from the children ​

Add these dynamic columns to Project costs (all type Number):

ColumnFormulaProject 1
Project hoursRows.sum(Rows.filter({[Tasks_MergedData_]}, 'ProjectNo', {[ProjectNo]}), 'Hours')26
Labour costSumIf({[Tasks_<Cost>]}, {[Tasks_ProjectNo]}, {[ProjectNo]})1580
ExpensesRows.sum(Rows.filter({[Expenses_MergedData_]}, 'ProjectNo', {[ProjectNo]}), 'Amount')8400
Unapproved expensesRows.sum(Rows.filter(Rows.filter({[Expenses_MergedData_]}, 'ProjectNo', {[ProjectNo]}), 'Approved', false), 'Amount')2400

Project hours

Labour cost deliberately uses SumIf to show the spreadsheet-style alternative. With Rows it would be Rows.sum(Rows.filter({[Tasks_MergedData_]}, 'ProjectNo', {[ProjectNo]}), '<Cost>').

Step 4: derived columns ​

ColumnTypeFormulaProject 1
Total spentNumber{[Labour cost]} + {[Expenses]}9980
Last invoiceDate and TimeMax(Rows.filter({[Expenses_MergedData_]}, 'ProjectNo', {[ProjectNo]}).map((e) => e.InvoiceDate))2026-09-10
Budget used %NumberMath.round({[Total spent]} / {[Budget]} * 10000) / 10020.79
Budget statusSingle Line of Text{[Budget used %]} > 100 ? 'Over budget' : {[Budget used %]} > 80 ? 'At risk' : 'On track'On track
Project labelSingle Line of Text`${{[ProjectNo]}} - ${{[Title]}}`1 - Harbour Bridge Refit
Share of all task hours %NumberMath.round({[Project hours]} / {[Tasks_<Total hours>]} * 1000) / 1017.8

The last one uses the child's aggregated field Total hours (146).

Step 5: portfolio totals (aggregated fields) ​

Click Σ under Project costs:

FieldTypeFormulaValue
Portfolio budgetNumberSum({[Budget]})92500
Portfolio spentNumberSum({[Total spent]})35275
All expensesNumberSum({[Expenses_Amount]})26500
Pending approvalNumberSumIf({[Expenses_Amount]}, {[Expenses_Approved]}, false)6850
Hardware spendNumberSumIf({[Expenses_Amount]}, {[Expenses_Category]}, 'Hardware')16800
Open task hoursNumberSumIf({[Tasks_Hours]}, {[Tasks_Status]}, (status) => status !== 'Done')85
Projects over budgetNumberCountIf({[Budget status]}, 'Over budget')1
Latest invoiceDate and TimeMax({[Expenses_InvoiceDate]})2026-10-12

Step 6: compare each row with the total ​

One more dynamic column, which becomes a late column automatically:

ColumnTypeFormulaProject 1
Share of portfolio spend %NumberMath.round({[Total spent]} / {[Portfolio spent]} * 10000) / 10028.29

Late dynamic column

Save & Exit. Your designer should now look like this:

The finished Project costs data source

Step 7: the dashboard ​

  1. Create a dashboard, drop a Grid, choose Project costs and add Project label, Budget, Project hours, Labour cost, Expenses, Total spent, Budget used %, Budget status, Last invoice, Share of portfolio spend %.
  2. Drop a Summary Card, choose Project costs, set Value to Portfolio spent and Target to Portfolio budget (both under Aggregated fields).

Finished dashboard

Compare your numbers with the expected values.