OneStream

Building a Budget

With actuals loaded and seeded into the Budget scenario, the next step is building the budget itself. This guide covers the main planning methodologies, the calculation techniques that power them, how users interact with budget data through Cube Views and dashboard components, and the workflow process that moves a budget from draft to approved.

Planning Methods

There is no single way to build a budget. Most implementations combine multiple approaches depending on the line item. Here are the four primary methods, each suited to different parts of the P&L.
MethodHow It WorksAccuracyEffortTypical Use
Driver-BasedInput drivers × rates = planned amountHighMediumSalary (FTE × AvgSalary), Revenue (Volume × Price)
Factor-BasedApply growth/decline % to a base periodMediumLowRevenue trending, OpEx inflation adjustments
Zero-BasedStart from zero, justify every dollarHighestHighestDiscretionary OpEx, new initiatives
Transaction-BasedDetailed item lists aggregated to totalsHighestHighestEmployee-level comp, capital projects
Most FP&A teams use driver-based planning for the core P&L (revenue and salary), factor-based adjustments for less volatile lines, zero-based budgeting for discretionary spend, and transaction-based planning for specialty areas like headcount. OneStream supports all of these — the first three through cube-based calculations, and the fourth through relational tables combined with Business Rules.

Driver-Based Calculations

Driver-based planning is the workhorse of most budgets. Users enter assumptions (drivers) and the system multiplies them to produce planned amounts. The developer's job is writing the Finance Business Rule that performs the multiplication.
The simplest driver calculation uses api.Data.Calculate:
1If api.FunctionType = FinanceFunctionType.Calculate Then
2  ' Salary = Headcount × Average Salary
3  api.Data.Calculate("A#SalaryExpense = RemoveZeros(A#FTE * A#AvgSalary)")
4
5  ' Revenue = Sales Volume × Price Per Unit
6  api.Data.Calculate("A#ProductRevenue = RemoveZeros(A#SalesVolume * A#PricePerUnit)")
7End If
The RemoveZeros function prevents writing zero-amount cells when one of the drivers is empty. Without it, the calculation would create cells with zero values for every dimensional intersection — consuming storage and cluttering reports.
💡Tip
Driver-based calculations operate across all UD dimensions simultaneously. If FTE and AvgSalary are entered by Department (UD1), the Calculate formula automatically processes every UD1 member. You do not need to loop over departments.
These calculations run per Data Unit — once per Entity × Scenario × Time × Consolidation combination. So for ABC's Budget scenario across 12 months and 3 entities, the rule fires 36 times, each time calculating the entire P&L for that specific entity-month.

Factor-Based Calculations

Factor-based planning applies a growth or decline percentage to a baseline — typically prior year actuals or a base scenario. It is useful for line items where detailed drivers are not practical.
1If api.FunctionType = FinanceFunctionType.Calculate Then
2  ' Revenue = Prior year actual × (1 + Growth Rate)
3  ' GrowthRate is stored as a decimal (e.g., 0.05 for 5%)
4  Dim growthBuffer As DataBuffer = api.Data.GetDataBufferUsingFormula("A#GrowthRate")
5  Dim baseBuffer As DataBuffer = api.Data.GetDataBufferUsingFormula("A#Revenue:S#Actual")
6
7  For Each cell As DataBufferCell In baseBuffer.DataBufferCells.Values
8      Dim memberKey As String = cell.GetLocalUniqueKeyString()
9      Dim growth As Decimal = 0D
10      If growthBuffer.DataBufferCells.ContainsKey(memberKey) Then
11          growth = growthBuffer.DataBufferCells(memberKey).CellAmount
12      End If
13      cell.CellAmount = cell.CellAmount * (1D + growth)
14  Next
15
16  api.Data.FormulaVariables.SetDataBufferVariable("factored", baseBuffer, False)
17  api.Data.Calculate("A#Revenue = $factored")
18End If
This uses the Get/Set Data Buffer pattern — reading data into a buffer, manipulating it cell-by-cell, and writing it back. See Writing Calculations for a thorough comparison of api.Data.Calculate vs. the Data Buffer approach.

CustomCalculate

In a planning workflow, users need to see the results of their driver inputs immediately — not wait for a full consolidation cycle. This is where CustomCalculate comes in.
CustomCalculate is a Finance Business Rule function type that runs on demand, triggered by a Data Management step, a dashboard button, or a form save event. It is the standard mechanism for interactive "calculate my budget" operations.
1If api.FunctionType = FinanceFunctionType.CustomCalculate Then
2  If args.CustomCalculateArgs.FunctionName.XFEqualsIgnoreCase("CalcBudgetPL") Then
3      ' Clear previous calculations to avoid stale data
4      api.Data.ClearCalculatedData(True, True, True, True, "A#SalaryExpense,A#ProductRevenue,A#COGSAmount,A#GrossProfit,A#NetIncome")
5
6      ' Driver-based calculations
7      api.Data.Calculate("A#SalaryExpense = RemoveZeros(A#FTE * A#AvgSalary)")
8      api.Data.Calculate("A#ProductRevenue = RemoveZeros(A#SalesVolume * A#PricePerUnit)")
9
10      ' Factor-based calculations
11      api.Data.Calculate("A#COGSAmount = RemoveZeros(A#ProductRevenue * A#COGSPercent)")
12
13      ' Roll-ups
14      api.Data.Calculate("A#GrossProfit = A#Revenue - A#COGSAmount")
15      api.Data.Calculate("A#NetIncome = A#GrossProfit - A#OpEx")
16  End If
17End If
⚠️Warning
Always call api.Data.ClearCalculatedData at the top of a CustomCalculate rule. Unlike standard Calculate (which clears calculated data automatically as part of the consolidation process), CustomCalculate runs outside the normal sequence. Without an explicit clear, you risk accumulating stale calculated values from previous runs.
ℹ️Info
For a detailed comparison of Calculate vs. CustomCalculate — including when to use each and how to configure Data Management steps — see Writing Calculations.

Data Input: Cube Views and Dashboards

Budget calculations run on data that users enter through Cube Views — grid interfaces that read and write cube data directly.
A typical budget Cube View is configured as:
  • Rows — Driver accounts (FTE, AvgSalary, SalesVolume, PricePerUnit) plus calculated results (SalaryExpense, Revenue)
  • Columns — Time periods (2026M1 through 2026M12)
  • POV — Entity, Scenario (Budget), Department (UD1), Product Line (UD2) selected by the user
  • Editable cells — Driver accounts are input-enabled; calculated accounts are read-only
  • Calculate button — Triggers the CustomCalculate rule so users see results immediately after entering drivers
Cube Views are configured in the OneStream application UI — they are not coded in Business Rules. However, Forms Event Handlers can add validation logic that fires when a user saves a Cube View:
  • Validate that FTE is a whole number
  • Check that growth rates are within acceptable bounds (e.g., not above 50%)
  • Prevent saving if required driver fields are empty

The Budget Workflow

A budget is not just a set of numbers — it is a managed process. OneStream's Workflow engine provides structure for who can input data, when calculations run, and how approvals flow.
diagramBudget Workflow Process

Loading diagram...

Each step corresponds to a Workflow state that controls data editability:
  • Not Started / In Progress — Data is editable. Department managers enter drivers and run calculations.
  • Submitted — Data is read-only for the submitter. Reviewers (typically FP&A or senior management) can see the submitted values.
  • Approved — Data is locked. No further changes unless an administrator unlocks the entity.
  • Locked — Hard lock. The budget is final for this entity and period.
Versioning is another common pattern: Budget_V1 (initial draft), Budget_V2 (revised after management review), Budget_Final (approved version). Each version is a separate Scenario member, and the workflow can copy data forward as versions progress.
💡Tip
Workflow configuration is done in the OneStream application UI, not in code. As a developer, you interact with workflow through Event Handlers (reacting to state changes) and by scoping your Business Rules to the correct Scenario members. You rarely need to write code that manipulates workflow state directly.

ABC Example: Building the Annual Budget

ABC Manufacturing's budget is built department by department. Each department manager enters drivers for their area, and the system calculates the full P&L. Here is the complete calculation structure:
Revenue (by Product Line):
  • ProductRevenue = SalesVolume × PricePerUnit (driver-based)
  • ServiceRevenue = prior year ServiceRevenue × (1 + GrowthRate) (factor-based)
Cost of Goods Sold:
  • COGSAmount = ProductRevenue × COGSPercent (factor-based)
Salaries (by Department):
  • SalaryExpense = FTE × AvgSalary (driver-based)
  • BenefitsExpense = SalaryExpense × BenefitsRate (factor-based)
Operating Expenses:
  • OtherOpEx = manually entered by department (zero-based)
Roll-ups:
  • Revenue = ProductRevenue + ServiceRevenue
  • OpEx = SalaryExpense + BenefitsExpense + OtherOpEx
  • GrossProfit = Revenue - COGSAmount
  • NetIncome = GrossProfit - OpEx
The Finance Business Rule that implements this:
1If api.FunctionType = FinanceFunctionType.Calculate Then
2  Dim scenario As String = api.Pov.Scenario.MemberName
3
4  If scenario.XFEqualsIgnoreCase("Budget") Then
5      ' --- Revenue ---
6      api.Data.Calculate("A#ProductRevenue = RemoveZeros(A#SalesVolume * A#PricePerUnit)")
7
8      ' Service revenue: factor-based from prior year actuals
9      Dim svcBase As DataBuffer = api.Data.GetDataBufferUsingFormula("A#ServiceRevenue:S#Actual")
10      Dim growthBuf As DataBuffer = api.Data.GetDataBufferUsingFormula("A#GrowthRate")
11      For Each cell As DataBufferCell In svcBase.DataBufferCells.Values
12          Dim key As String = cell.GetLocalUniqueKeyString()
13          Dim rate As Decimal = 0D
14          If growthBuf.DataBufferCells.ContainsKey(key) Then
15              rate = growthBuf.DataBufferCells(key).CellAmount
16          End If
17          cell.CellAmount = cell.CellAmount * (1D + rate)
18      Next
19      api.Data.FormulaVariables.SetDataBufferVariable("svcCalc", svcBase, False)
20      api.Data.Calculate("A#ServiceRevenue = $svcCalc")
21
22      ' Revenue roll-up
23      api.Data.Calculate("A#Revenue = A#ProductRevenue + A#ServiceRevenue")
24
25      ' --- COGS ---
26      api.Data.Calculate("A#COGSAmount = RemoveZeros(A#Revenue * A#COGSPercent)")
27      api.Data.Calculate("A#GrossProfit = A#Revenue - A#COGSAmount")
28
29      ' --- Salaries ---
30      api.Data.Calculate("A#SalaryExpense = RemoveZeros(A#FTE * A#AvgSalary)")
31      api.Data.Calculate("A#BenefitsExpense = RemoveZeros(A#SalaryExpense * A#BenefitsRate)")
32
33      ' --- Roll-ups ---
34      api.Data.Calculate("A#OpEx = A#SalaryExpense + A#BenefitsExpense + A#OtherOpEx")
35      api.Data.Calculate("A#NetIncome = A#GrossProfit - A#OpEx")
36  End If
37End If
This single rule handles the entire P&L calculation for the Budget scenario. It runs per Data Unit — so for each entity and each month, it calculates the full P&L from the drivers entered by department managers.