Introduction

If you have worked with DAX for a while, you have probably found yourself copying the same piece of logic into several measures. It works at first, but it becomes frustrating when the underlying business rule changes and you have to go back and update every measure individually.

Power BI's named expressions, also referred to as DAX user-defined functions (DAX UDFs), are designed to address this problem. Instead of repeating the same logic across multiple measures, you can define it once, give it a name, and reuse it where needed. Functions can also accept parameters, making them useful when the same calculation needs to work with different inputs.

This article will cover:

  • What named expressions and custom DAX functions are
  • How they compare with measures and calculation groups
  • How to create a reusable DAX function
  • A practical Year-over-Year Growth example
  • How to work with parameters and default values
  • Functions that return tables
  • Best practices and limitations
  • When a function makes more sense than a calculation group

1. The Problem: Repeating the Same DAX Logic

Consider a typical finance report with these three measures:

Revenue YoY % =
VAR CurrentRevenue = [Total Revenue]
VAR PriorYearRevenue =
    CALCULATE([Total Revenue], SAMEPERIODLASTYEAR('Date'[Date]))
RETURN
    DIVIDE(CurrentRevenue - PriorYearRevenue, PriorYearRevenue)

Profit YoY % =
VAR CurrentProfit = [Total Profit]
VAR PriorYearProfit =
    CALCULATE([Total Profit], SAMEPERIODLASTYEAR('Date'))
RETURN
    DIVIDE(CurrentProfit - PriorYearProfit, PriorYearProfit)

Units Sold YoY % =
VAR CurrentUnits = [Total Units Sold]
VAR PriorYearUnits =
    CALCULATE([Total Units Sold], SAMEPERIODLASTYEAR('Date'))
RETURN
    DIVIDE(CurrentUnits - PriorYearUnits, PriorYearUnits)

The three measures are doing essentially the same thing. The only real difference is the measure being passed into the calculation.

That becomes a maintenance problem when the calculation changes.

For example, suppose the business decides to change the way YoY growth is calculated. You would need to locate every measure using that logic and make the same change repeatedly.

This is where reusable DAX functions can help.

2. What Are Named Expressions and Custom Functions?

A named expression is a reusable DAX definition that can be called like a function.

Instead of writing the same calculation repeatedly, you define the logic once and then call it whenever you need it.

A function can:

  • Accept parameters
  • Use default parameter values
  • Return a scalar value
  • Return a table
  • Be reused across multiple calculations

The idea is similar to functions or methods in other programming languages. You define the logic once, then provide different inputs when you call it.

How does this compare with other DAX approaches?

FeatureMeasuresCalculation GroupsNamed Expressions / Functions
ReusableYesYesYes
Accepts parametersNoNoYes
Returns a tableNoNoYes
Main purposeDefine a metricApply transformations across measuresEncapsulate reusable logic
Typical useRevenue, Profit, MarginYoY, MTD, Actual vs BudgetReusable calculations with inputs

The main difference is the ability to pass parameters.

Calculation groups are particularly useful when you want to apply the same transformation to multiple measures dynamically. Functions, on the other hand, are useful when you want to explicitly call a piece of reusable logic and provide it with specific inputs.

3. Creating Your First DAX Function

Step 1: Open DAX Query View

In Power BI Desktop, open the View tab and select DAX query view.

DAX Query View provides an environment for writing and testing DAX queries against your semantic model. It can also be used when working with DAX functions.

Step 2: Define the function

A function can be defined using the DEFINE FUNCTION syntax.

For example:

DEFINE FUNCTION YoYGrowthPercent = (BaseMeasure) =>
    VAR CurrentValue = BaseMeasure
    VAR PriorYearValue =
        CALCULATE(BaseMeasure, SAMEPERIODLASTYEAR('Date'[Date]))
    RETURN
        DIVIDE(CurrentValue - PriorYearValue, PriorYearValue)

EVALUATE
SUMMARIZECOLUMNS(
    'Date'[Year],
    "Revenue YoY", YoYGrowthPercent([Total Revenue])
)

The function accepts a base measure and applies the YoY calculation to it.

Step 3: Test the function

Run the query using the Run button or the appropriate keyboard shortcut in Power BI Desktop.

This allows you to check that the function works correctly with your existing model, including the Date table and [Total Revenue] measure.

Step 4: Add the function to the model

Once you are satisfied with the result, you can promote the function to the semantic model using the available model-update functionality in your Power BI environment.

The exact workflow can vary depending on the Power BI Desktop version and the features currently available in your environment.

Step 5: Reuse the function

Once the function is available to the model, the three original measures can be simplified:

Revenue YoY % = YoYGrowthPercent([Total Revenue])

Profit YoY % = YoYGrowthPercent([Total Profit])

Units Sold YoY % = YoYGrowthPercent([Total Units Sold])

Now the calculation exists in one place.

If the business rule changes later, you have a single function to update rather than several separate measures containing duplicated logic.

4. Using Parameters and Default Values

One of the useful aspects of functions is the ability to accept parameters.

For example, you might want to calculate a moving average over different numbers of months.

DEFINE FUNCTION MovingAverage = (BaseMeasure, NumberOfMonths = 3) =>
    VAR MonthsBack =
        DATESINPERIOD(
            'Date'[Date],
            MAX('Date'[Date]),
            -NumberOfMonths,
            MONTH
        )
    RETURN
        CALCULATE(
            AVERAGEX(VALUES('Date'[Date]), BaseMeasure),
            MonthsBack
        )

The second parameter has a default value of 3.

That means you can call the function without specifying the number of months:

Revenue 3M Moving Avg =
    MovingAverage([Total Revenue])

Or provide a different value when required:

Revenue 6M Moving Avg =
    MovingAverage([Total Revenue], 6)

The same function can therefore support different reporting requirements without having to create a completely new piece of logic each time.

5. Functions That Return Tables

DAX functions do not have to return scalar values. A function can also return a table.

For example, you could create a function that returns the top N customers based on revenue:

DEFINE FUNCTION TopNCustomers = (N) =>
    TOPN(
        N,
        SUMMARIZECOLUMNS(
            'Customer'[Customer Name],
            "Total Sales", [Total Revenue]
        ),
        [Total Sales],
        DESC
    )

The function can then be used wherever a table expression is expected.

For example, you could use the result as part of a calculation that needs to work with a subset of customers.

This can be useful when the same filtering or ranking logic appears in several places within a semantic model.

6. Best Practices

1. Use clear names

Function names should make their purpose obvious.

Names such as:

CalculateYoY
GetTopN
MovingAverage

are easier to understand than generic names that do not tell another developer what the function does.

Consistency is also important. Pick a naming convention and use it throughout the model.

2. Keep functions focused

A function should ideally do one thing well.

If a function starts handling several unrelated calculations and contains a long list of optional parameters, it can become difficult to understand and maintain.

Small, focused functions are generally easier to reuse.

3. Document important parameters

Comments can make reusable functions easier for other developers to work with.

For example:

// NumberOfMonths represents the rolling period used for the calculation.

This becomes particularly useful when the function is part of a shared semantic model.

4. Consider version control

When working with Power BI projects and TMDL, model definitions can be managed alongside source code in version control.

That makes it possible for teams to review changes to model expressions and collaborate on semantic model development more effectively.

5. Know when to use calculation groups instead

Functions are not intended to replace calculation groups in every situation.

Suppose you want users to switch between:

  • Actual
  • Budget
  • Variance
  • Year-over-Year
  • Month-to-Date

across a large number of measures.

A calculation group may be more appropriate because it can apply a transformation across multiple measures dynamically.

A function is more suitable when you want to explicitly call reusable logic and provide specific inputs.

7. Limitations to Keep in Mind

DAX functions are a relatively recent addition to the DAX authoring experience, so it is important to check whether the functionality you need is supported in your current Power BI Desktop and Fabric environment.

There are also a few other considerations:

  • A function created for testing in DAX Query View may not automatically become part of your semantic model.
  • You need to explicitly promote or define it at the model level when you want other model objects to use it.
  • Recursive functions are not supported.
  • Excessive abstraction can make DAX harder to follow. If a measure calls several functions, which in turn call other functions, debugging can become more difficult.
  • Performance should still be monitored, particularly when reusable functions are used inside iterators or complex calculations.

The goal is not to turn every piece of DAX into a function. The goal is to remove meaningful duplication without making the model harder to understand.

Conclusion

Reusable DAX functions provide another way to approach a common problem in Power BI: duplicated calculation logic.

Instead of copying the same VAR...RETURN pattern into multiple measures, you can encapsulate that logic in a function and reuse it with different inputs.

This can be particularly useful for patterns such as:

  • Year-over-Year calculations
  • Moving averages
  • Ranking logic
  • Reusable filtering logic
  • Other calculations that follow the same structure but operate on different inputs

The important thing is to use the feature where it genuinely improves the model.

If you find yourself copying the same block of DAX into a third, fourth, or fifth measure, that is a good point to stop and ask whether the logic belongs in a reusable function instead.